從一道CTF練習題淺談php原生檔案操作類
前言
記一次CTF練習有感,覺得需要記錄一波…還有就是最近CTF比賽中利用php原生類來進行反序列化的題目比較多,所以就緊跟時代潮流…
一.SPL
顧名思義,SPL就是Standard PHP Library的縮寫。據手冊顯示,SPL是用於解決 典型問題(standard problems)
的一組介面與類的集合。開啟 ofollow,noindex" target="_blank">手冊 ,正如上面的定義一樣,有許多封裝好的類。因為是要解決典型問題,免不了有一些處理檔案的類。
一.可遍歷目錄類
DirectoryIterator
FilesystemIterator
GlobIterator
與上面略不同,該類可以通過模式匹配來尋找檔案路徑。
二.可讀取檔案類
SplFileObject
在此函式中,URL 可作為檔名,不過也要受到 allow_url_fopen
影響。
二.檔案系統相關擴充套件
finfo
該類的建構函式 finfo::__construct
— 別名 finfo_open()
,也可以讀取檔案。
三.例題
題目是websec上面的 第12關
題目可以見原始碼就一句
echo new [class]([first parameter],[second parameter]);
類的名字可控,類的引數可控,個數為二,題目說過濾了(實際可以繞過)上述提到的 splfileobject
, globiterator
, filesystemiterator
,and directoryiterator
等諸多函式,考慮使用 finfo
類,正好開了此拓展。
可以讀到檔案,但是 $key
未知
關鍵程式碼大致如下:
<?php ini_set('display_errors', 'on'); ini_set('error_reporting', E_ALL); .... $key = ini_get ('user_agent'); if ($_SERVER['REMOTE_ADDR'] === '127.0.0.1') { if ($_SERVER['HTTP_USER_AGENT'] !== $key) { die ("Cheating is bad, m'kay?"); } } $i = 0; $flag = ''; foreach (str_split (base64_decode ($text)) as $letter) { $flag .= chr (ord ($key[$i++]) ^ ord ($letter)); }
要想得到flag,則需要知道 php.ini
檔案中的 user_agent
,嘗試讀取 php.ini
檔案,路徑未知,無果。
嘗試使用 SplFileObject
訪問VPS,得到伺服器自身的user_agent。利用 繞過得到
user_agent
四.問題成因分析
除錯一下為什麼 finfo
會將檔案資訊(當然這一些都是建立在程式警告、報錯訊息開啟的情況下
ini_set('display_errors', 'on');ini_set('error_reporting', E_ALL);
)爆出來的原因。
編譯過程就略過了,用的是php-7.0的原始碼。在 ext/fileinfo/fileinfo.c
檔案的285行出下斷點。也即 finfo_open
函式處
/* {{{ proto resource finfo_open([int options [, string arg]]) Create a new fileinfo resource. */ PHP_FUNCTION(finfo_open) { zend_long options = MAGIC_NONE; char *file = NULL;
1.php
內容為:
<?php ini_set('display_errors', 'on'); ini_set('error_reporting', E_ALL); echo new finfo(1,'1.php')
忽略一些過渡的函式呼叫如 magic_load
-> file_apprentice
-> apprentice_1
-> apprentice_load
-> load_1
來到 ext/fileinfo/apprentice.c
檔案1025行處,也即 load_1
函式處
/* * Load and parse one file. */ private void load_1(struct magic_set *ms, int action, const char *fn, int *errs,#flag struct magic_entry_set *mset) { char buffer[BUFSIZ + 1]; char *line = NULL; size_t len; size_t lineno = 0; struct magic_entry me; php_stream *stream; ms->file = fn; stream = php_stream_open_wrapper((char *)fn, "rb", REPORT_ERRORS, NULL); if (stream == NULL) { if (errno != ENOENT) file_error(ms, errno, "cannot read magic file `%s'", fn); (*errs)++; return; } memset(&me, 0, sizeof(me)); /* read and parse this file */ for (ms->line = 1; (line = php_stream_get_line(stream, buffer , BUFSIZ, &len)) != NULL; ms->line++) { if (len == 0) /* null line, garbage, etc */ continue; if (line[len - 1] == 'n') { lineno++; line[len - 1] = ''; /* delete newline */ }
php_stream_get_line
函式將 finfo
要讀取的檔案一行行讀出
隨後將一行行內容進入 parse
函式進行解析,
switch (parse(ms, &me, line, lineno, action)) { case 0: continue; case 1: (void)addentry(ms, &me, mset);
parse
函式解析內容是否有效
/* * parse one line from magic file, put into magic[index++] if valid */ private int parse(struct magic_set *ms, struct magic_entry *me, const char *line, size_t lineno, int action) {
對於不符合magic檔案內容格式的則會發出相應警告,從而一句句報出檔案資訊。
file_magwarn(ms, "offset `%s' invalid", l);#1802行 file_magwarn(ms, "type `%s' invalid", l);#1950行
五.後記:
上訴裡面有一些東西只是簡單提一下,希望能拋磚引玉。在一些情況下,如反序列化和其他某些特定場所,原生檔案操作類也許能發揮不小的作用。