php_bugs代码审计(二)

记录php_bugs代码审计。所有代码均来自:https://github.com/bowu678/php_bugs
这里仅作为本人二次消化、记录。此处为第二部分(4~8),后面待更。

0x04 SQL注入_WITH ROLLUP绕过

这个题来自实验吧因缺思汀的绕过

1
2
3
4
5
6
7
8
$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
// 过滤的字符
$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
// 执行的sql语句
mysql_num_rows($query) == 1
// 返回结果集中行的数目
$key['pwd'] == $_POST['pwd']
// 提交的密码与数据库中的密码相等输出flag

$_POST输入通过定义的AttackFilter()函数过滤导致不能使用常规sql注入,这里的思路是select过程中用group by with rollup方法进行插入查询。

先来看看group by with rollup统计方法有什么作用

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
MariaDB [test]> select * from test;
+-------+--------+
| user | pwd |
+-------+--------+
| admin | mypass |
+-------+--------+
1 row in set (0.010 sec)

MariaDB [test]> select * from test group by pwd with rollup;
+-------+--------+
| user | pwd |
+-------+--------+
| admin | mypass |
| admin | NULL |
+-------+--------+
2 rows in set (0.001 sec)

MariaDB [test]> select * from test group by pwd with rollup limit 1;
+-------+--------+
| user | pwd |
+-------+--------+
| admin | mypass |
+-------+--------+
1 row in set (0.001 sec)

MariaDB [test]> select * from test group by pwd with rollup limit 1 offset 0;
+-------+--------+
| user | pwd |
+-------+--------+
| admin | mypass |
+-------+--------+
1 row in set (0.001 sec)

MariaDB [test]> select * from test group by pwd with rollup limit 1 offset 1;
+-------+-----+
| user | pwd |
+-------+-----+
| admin | NULL |
+-------+-----+
1 row in set (0.001 sec)

limit 1是指只查询一行offset 1指查询某一行的内容,不同的数字出现的是不同行的内容

当用with rollup方法的时候,会在数据库的最后一行生成一个密码NULL的字段,在查询的时候就可以想想办法让pwd为空,而user也是存在的,又有mysql_num_rows($query) == 1,所以可以构造payload

admin' or 1=1 group by pwd with rollup limit 1 offset x #

查询语句就是:

SELECT * FROM interest WHERE uname = 'admin' or 1=1 group by pwd with rollup limit 1 offset x #'

然后一个个试就行了。

参考:

实验吧 因缺思汀的绕过 By Assassin(with rollup统计)

0x05 ereg正则%00截断

直接看关键点审计

1
2
3
4
5
6
ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE
// 要求GET密码只能是大小写字母和数字
strlen($_GET['password']) < 8 && $_GET['password'] > 9999999
// 要求GET密码长度小于8并且值要大于9999999
strpos ($_GET['password'], '*-*') !== FALSE
// strpos():查找字符串首次出现的位置

第二点可以利用科学计数法的方式表示。

第三点 GET密码中要包括*-*,但是前面的ereg()过滤了特殊字符,这时候可以用%00截断,ereg()读到%00的时候就截止了,所以构造payload

1e9%00*-*

0x06 strcmp比较字符串

1
2
3
4
5
6
7
8
9
10
11
<?php
show_source(__FILE__);
$flag = "flag";
if (isset($_GET['a'])) {
if (strcmp($_GET['a'], $flag) == 0) //如果 str1 小于 str2 返回 < 0; 如果 str1大于 str2返回 > 0;如果两者相等,返回 0。
//比较两个字符串(区分大小写)
die('Flag: '.$flag);
else
print 'No';
}
?>

strcmp()期望传入类型是字符串类型,在5.3之前的php版本中若传入其他类型将会报错并返回05.3之后报错不返回任何值,但如果传入数组的话,就会返回NULL,这里的判断是弱等于NULL==0bool(true),所以有构造payload

?a[]=1

0x07 sha()函数比较绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
show_source(__FILE__);
$flag = "flag";
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>';
?>

$_GET['name'] != $_GET['password']同时满足sha1($_GET['name']) === sha1($_GET['password']

sha1()默认的传入类型是字符串类型,若传入数组会返回false,这里的判断是强等,需要构造usernamepassword既不相等,又同样要是数组类型,构造payload:

?name[]=a&password[]=b

0x08 SESSION验证绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
show_source(__FILE__);
$flag = "flag";

session_start();
if (isset ($_GET['password'])) {
if ($_GET['password'] == $_SESSION['password'])
die ('Flag: '.$flag);
else
print '<p>Wrong guess.</p>';
}
mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000));
?>

重点在于$_GET['password'] == $_SESSION['password'],这就很简单了,只需要GET值与SESSION相等,

构造payload

?password=

然后将cookies清空即可。

Thank you very much if you can.

欢迎关注我的其它发布渠道