無字母數(shù)字Webshell之提高篇
前幾天有同學(xué)提出了一個(gè)問題,大概代碼如下:
- <?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__);
- }
這個(gè)代碼如果要getshell,怎樣利用?
這題可能來自是我曾寫過的一篇文章,里面介紹了如何構(gòu)造無字母數(shù)字的webshell。其中有兩個(gè)主要的思路:
- 利用位運(yùn)算
- 利用自增運(yùn)算符
當(dāng)然,這道題多了兩個(gè)限制:
- webshell長(zhǎng)度不超過35位
- 除了不包含字母數(shù)字,還不能包含$和_
難點(diǎn)呼之欲出了,我前面文章中給出的所有方法,都用到了PHP中的變量,需要對(duì)變量進(jìn)行變形、異或、取反等操作,最后動(dòng)態(tài)執(zhí)行函數(shù)。但現(xiàn)在,因?yàn)?不能使用了,所以我們無法構(gòu)造PHP中的變量。
所以,如何解決這個(gè)問題?
PHP7 下簡(jiǎn)單解決問題
我們將上述代碼放在index.php中,然后執(zhí)行docker run –rm -p 9090:80 -v pwd:/var/www/html php:7.2-apache,啟動(dòng)一個(gè)php 7.2的服務(wù)器。
php7中修改了表達(dá)式執(zhí)行的順序:http://php.net/manual/zh/migration70.incompatible.php :
PHP7前是不允許用($a)();這樣的方法來執(zhí)行動(dòng)態(tài)函數(shù)的,但PHP7中增加了對(duì)此的支持。所以,我們可以通過(‘phpinfo’)();來執(zhí)行函數(shù),第一個(gè)括號(hào)中可以是任意PHP表達(dá)式。
所以很簡(jiǎn)單了,構(gòu)造一個(gè)可以生成phpinfo這個(gè)字符串的PHP表達(dá)式即可。payload如下(不可見字符用url編碼表示):
- (~%8F%97%8F%96%91%99%90)();
PHP5的思考
我們使用docker run –rm -p 9090:80 -v pwd:/var/www/html php:5.6-apach來運(yùn)行一個(gè)php5.6的web環(huán)境。
此時(shí),我們嘗試用PHP7的payload,將會(huì)得到一個(gè)錯(cuò)誤:
原因就是php5并不支持這種表達(dá)方式。
在我在知識(shí)星球里發(fā)出帖子的時(shí)候,其實(shí)還沒想到如何用PHP5解決問題,但我有自信解決它,所以先發(fā)了這個(gè)小挑戰(zhàn)。后來關(guān)上電腦仔細(xì)想想,發(fā)現(xiàn)當(dāng)思路禁錮在一個(gè)點(diǎn)的時(shí)候,你將會(huì)鉆進(jìn)牛角尖;當(dāng)你用大局觀來看待問題,問題就迎刃而解。
當(dāng)然,我覺得我的方法應(yīng)該不是唯一的,不過一直沒人出來公布答案,我就先拋鉆引玉了。
大部分語言都不會(huì)是單純的邏輯語言,一門全功能的語言必然需要和操作系統(tǒng)進(jìn)行交互。操作系統(tǒng)里包含的最重要的兩個(gè)功能就是“shell(系統(tǒng)命令)”和“文件系統(tǒng)”,很多木馬與遠(yuǎn)控其實(shí)也只實(shí)現(xiàn)了這兩個(gè)功能。
PHP自然也能夠和操作系統(tǒng)進(jìn)行交互,“反引號(hào)”就是PHP中最簡(jiǎn)單的執(zhí)行shell的方法。那么,在使用PHP無法解決問題的情況下,為何不考慮用“反引號(hào)”+“shell”的方式來getshell呢?
PHP5+shell打破禁錮
因?yàn)榉匆?hào)不屬于“字母”、“數(shù)字”,所以我們可以執(zhí)行系統(tǒng)命令,但問題來了:如何利用無字母、數(shù)字、$的系統(tǒng)命令來getshell?
好像問題又回到了原點(diǎn):無字母、數(shù)字、$,在shell中仍然是一個(gè)難題。
此時(shí)我想到了兩個(gè)有趣的Linux shell知識(shí)點(diǎn):
- shell下可以利用.來執(zhí)行任意腳本
- Linux文件名支持用glob通配符代替
第一點(diǎn)曾在在我之前的文章露出過一角,但我沒細(xì)講。.或者叫period,它的作用和source一樣,就是用當(dāng)前的shell執(zhí)行一個(gè)文件中的命令。比如,當(dāng)前運(yùn)行的shell是bash,則. file的意思就是用bash執(zhí)行file文件中的命令。
用. file執(zhí)行文件,是不需要file有x權(quán)限的。那么,如果目標(biāo)服務(wù)器上有一個(gè)我們可控的文件,那不就可以利用.來執(zhí)行它了嗎?
這個(gè)文件也很好得到,我們可以發(fā)送一個(gè)上傳文件的POST包,此時(shí)PHP會(huì)將我們上傳的文件保存在臨時(shí)文件夾下,默認(rèn)的文件名是/tmp/phpXXXXXX,文件名最后6個(gè)字符是隨機(jī)的大小寫字母。
第二個(gè)難題接踵而至,執(zhí)行. /tmp/phpXXXXXX,也是有字母的。此時(shí)就可以用到Linux下的glob通配符:
- *可以代替0個(gè)及以上任意字符
- ?可以代表1個(gè)任意字符
那么,/tmp/phpXXXXXX就可以表示為/*/?????????或/???/?????????。
但我們嘗試執(zhí)行. /???/?????????,卻得到如下錯(cuò)誤:
這是因?yàn)?,能夠匹配????/?????????這個(gè)通配符的文件有很多,我們可以列出來:
可見,我們要執(zhí)行的/tmp/phpcjggLC排在倒數(shù)第二位。然而,在執(zhí)行第一個(gè)匹配上的文件(即/bin/run-parts)的時(shí)候就已經(jīng)出現(xiàn)了錯(cuò)誤,導(dǎo)致整個(gè)流程停止,根本不會(huì)執(zhí)行到我們上傳的文件。
思路又陷入了僵局,雖然方向沒錯(cuò)。
深入理解glob通配符
大部分同學(xué)對(duì)于通配符,可能知道的都只有*和?。但實(shí)際上,閱讀Linux的文檔( http://man7.org/linux/man-pages/man7/glob.7.html ),可以學(xué)到更多有趣的知識(shí)點(diǎn)。
其中,glob支持用[^x]的方法來構(gòu)造“這個(gè)位置不是字符x”。那么,我們用這個(gè)姿勢(shì)干掉/bin/run-parts:
排除了第4個(gè)字符是-的文件,同樣我們可以排除包含.的文件:
現(xiàn)在就剩最后三個(gè)文件了。但我們要執(zhí)行的文件仍然排在最后,但我發(fā)現(xiàn)這三個(gè)文件名中都不包含特殊字符,那么這個(gè)方法似乎行不通了。
繼續(xù)閱讀glob的幫助,我發(fā)現(xiàn)另一個(gè)有趣的用法:
就跟正則表達(dá)式類似,glob支持利用[0-9]來表示一個(gè)范圍。
我們?cè)賮砜纯粗傲谐隹赡芨蓴_我們的文件:
所有文件名都是小寫,只有PHP生成的臨時(shí)文件包含大寫字母。那么答案就呼之欲出了,我們只要找到一個(gè)可以表示“大寫字母”的glob通配符,就能精準(zhǔn)找到我們要執(zhí)行的文件。
翻開ascii碼表,可見大寫字母位于@與[之間:
那么,我們可以利用[@-[]來表示大寫字母:
顯然這一招是管用的。
構(gòu)造POC,執(zhí)行任意命令
當(dāng)然,php生成臨時(shí)文件名是隨機(jī)的,最后一個(gè)字符不一定是大寫字母,不過多嘗試幾次也就行了。
最后,我傳入的code為?>,發(fā)送數(shù)據(jù)包如下:
成功執(zhí)行任意命令。