XSS 启发式检测基础设施

admin 2022年6月11日02:57:42安全文章评论11 views7777字阅读25分55秒阅读模式

XSS 启发式检测基础设施

背景:

XSS 启发式检测基础设施
XSS 漏洞检测是所有漏洞检测算法很难绕开的一个难点,传统的漏洞检测算法采用的方案其实很容易想到:
1.“构造很多 Payload”,把响应放到浏览器中,检测浏览器中的 “alert / prompt 是否被调用”
2.喷洒各种各样的 Payload,在 XSS 平台上看 “是否有目标上线”。
但是实际上,如果仅仅为了检测漏洞,或者提示 “潜在 XSS 风险”,一些发送大量数据包的破坏性的检测手段并不是一个好的办法。
在 Yaklang 中,我们要解决这个问题,并不应该直接提供给大家一个 “XSS” 检测算法,而是提供可以辅助编写算法的 “基础设施”。
有没有一种可能,全自动扫描漏洞其实并不重要,能提出高级的风险点,用户可以自己去挖掘一些非常 “深入” 的 XSS 更合适?

为此我们实现了两个很有意思的内置库以及函数:
1.yak.xhtml 提供HTML 相关的解析与定位辅助函数加速 XSS 漏洞检测
2.yak.js.ASTWalk 提供针对 javascript 的词法解析与 AST 遍历服务,把遍历到的字面量 / 符号 / 错误信息作为结果返回
XSS 启发式检测基础设施

核心基础设施函数

XSS 启发式检测基础设施
yak.xhtml

XSS 启发式检测基础设施


yak.js.ASTWalk


XSS 启发式检测基础设施

*javascript.ASTWalkerResult 描述

这个结构是 ASTWalk 的结果,会把解析的 JS 的内容遍历 AST,汇总出 JS 代码整体的一些资料,用来判断用户构造的 Payload 在其中生效与否。
type palm/common/javascript.(ASTWalkerResult) struct {  Fields(可用字段):       // 所有字符串字面量:所有的字符串字面量和字符串定义都会在这里被找到并总结      StringLiteral: []string        // 整数字面量      Int64Literal: []int64        // 浮点数字面量      Float64Literal: []float64  
// ID: 被认为是符号的内容(被当作调用对象,或者 field) Identifies: []string // 出现语法错误的位置 BadSyntax: []string }




XSS 启发式检测基础设施

如何编写启发式检测 XSS?



XSS 启发式检测基础设施


0. 思路总集:Mind in a Graph

XSS 启发式检测基础设施
其中几个关键技术可以有效减少无效payload数量
1.通过参数回显位置生成相应payload:例如在div标签内、在script标签内、在标签属性中等等,不同属性对应生成 Payload 方法也不一样,例如 href 与 onerror / onclick 等。
2.根据位置探测过滤内容:把所有payload中的可疑字符拼接发送,根据响应包分析字符被过滤、转义、被转义为了什么,再进一步对受影响的payload进行变形或舍弃。
下面在分别对流程图中每一步进行分析介绍。


1.参数检测


参数位置

在页面回显的参数可能出现在:
1.post请求(form、json或xml)
2.get请求
3.Cookie
4.url path(例如form的action使用当前页面的url)
yak的fuzz库提供了方法可以轻松获取上述所有参数。
req = "<raw request>"freq, err = fuzz.HTTPRequest(req)if err!= nil{  println("new HTTPRequest error: %v",err)  return}params = freq.GetCommonParams() // 包含post json、post form、get参数、cookie参数(会自动过滤PHPSESSID、_ga、_gid等参数)for _, param = range params {   printf("key: %s, value: %s, postion: %sn", param.Name(),param.Value(),param.PositionVerbose())}

多参数如何测试

同时对多个参数做测试可靠度低,没必要。只分别针对每个参数做测试(需要过滤一些没必要的参数,如form请求的submit或cookie参数中的PHPSESSID、JSESSIONID等)。

XSS 启发式检测基础设施



2.检测回显位置

通过生成一个随机的,不会被过滤的安全字符串测试请求,检测字符串的回显位置。
回显位置无非是:
1.标签内文本
2.属性
3.注释
4.script标签内(也属于标签内文本)
str = str.RandStr(5) // RandStr生成的随机字符集是大小写字母resp, err = param.Fuzz(randStr).Exec() // param 是freq.GetCommonParams()获取的参数对象,可以直接对某个参数fuzzif err != nil {   println("Fuzz param %s error: %v",param.Name(), err)   return}rspo = <-respbody, err = str.ExtractBodyFromHTTPResponseRaw(rspo.ResponseRaw)if err != nil {   println("Get response body error: %v", err)   return}matchNodes = xhtml.FindNodeFromHtml(body, randStr)
xhtml.FindNodeFromHtml 方法可以从 body 中查找回显出现的位置信息。

XSS 启发式检测基础设施



3.生成 Paylaod

接着上一节,通过 xhtml.FindNodeFromHtml 查找出回显位置,生成相应 Payload,如:
1.标签内文本:构造标签闭合
2.属性:构造标签闭合或伪协议
3.注释:构造标签闭合
4.script 标签内:构造标签闭合或dom型
for _, matchNode = range matchNodes {   printf("Echo xpath postion: %s", matchNode.Xpath)   if matchNode.IsText() {      if matchNode.TagName == "script" {         //例:<script>a = '<参数>';</script>         payload = sprintf("';alert('Hello');'", matchNode.TagName)         payloads = append(payloads, payload)      } else {         //例:<div><参数></div>         payload = sprintf("</%s>Hello<%s>", matchNode.TagName)         payloads = append(payloads, payload)      }   } else if matchNode.IsAttr() {      //例:<div id="<参数>"></div>      payload = sprintf(""></%s>Hello<%s %s="%s", matchNode.TagName, matchNode.TagName, matchNode.Key, matchNode.Value)      payloads = append(payloads, payload)   } else if matchNode.IsCOMMENT() {      //例:<!-- <参数> -->      payload = sprintf("-->Hello<!--")      payloads = append(payloads, payload)   }}


XSS 启发式检测基础设施


4.如何检测过滤字符?

根据回显的位置,生成相应payload,提取payload中存在可能被过滤的字符
生成一个随机字符串,用随机字符串作为分隔符,拼接这些可能被过滤的字符串
例:随机字符串agsAKdhjkTUI,可疑字符:<,',",/
生成:

agsAKdhjkTUI<agsAKdhjkTUI'agsAKdhjkTUI"agsAKdhjkTUI/agsAKdhjkTUI

对比响应包,可以找出哪些字符被过滤,或被转义为什么字符,
再根据过滤情况,对payload做一遍过滤,可以有效减少无效payload数量。

提取出payload中所有的可疑字符:
dangerousChars = ["<", ">", "/", "\", "'", """]detectChars = []for _, payload = range payloads {   for _, dangerousChar = range dangerousChars {      if utils.MatchAllOfGlob(payload, sprintf("*%s*", dangerousChar)) {         detectChars = append(detectChars, dangerousChar)      }   }}
检测被过滤的字符:
detectStr = randStr + strings.Join(detectChars, randStr) + randStrresp, err = param.Fuzz(detectStr).Exec()if err != nil {   println("Fuzz param %s error: %v", param.Name(), err)   return}rspo = <-respbody, err = str.ExtractBodyFromHTTPResponseRaw(rspo.ResponseRaw)if err != nil {   println("Get response body error: %v", err)   return}randStrFromIndex = bodypassChars = []i = 0for {   n, btChar = xhtml.MatchBetween(randStrFromIndex, randStr, randStr, 50)   if n == -1 {      break   }   if i >= len(dangerousChars) {      break   }   if dangerousChars[i] == btChar {      passChars = append(passChars, btChar)   } else {      printf("Found characters to be filtered: %s->%s", dangerousChars[i], btChar)   }   randStrFromIndex = randStrFromIndex[n+len(randStr):]   i += 1}
再通过筛选出不含有过滤字符的payload
newPayloads = []for _, payload = range payloads {   for _, filterChar = range passChars {      if str.MatchAllOfGlob(payload, sprintf("*%s*", filterChar)) {         newPayloads = append(newPayloads, payload)      }   }}


XSS 启发式检测基础设施


5.判断成功

根据payload类型判断,大概有以下四种类型:
1.标签内文本:构造标签闭合
2.属性:构造标签闭合或伪协议
3.注释:构造标签闭合
4.script标签内:构造标签闭合或dom型
对于构造标签闭合的payload,可以通过对比dom树的方式检测。

手工判断

可以在payload中加入随机字符串做定位,再通过xhtml.Find查询回显位置,对比原请求的回显位置,针对每种payload对html的影响做相应检测。
例如:
// <div><参数></div>payload = "<script>asgdhFFASDljl</script>"resp,err = param.Fuzz(sprintf("</div>%s<div>",payload)).Exec()if err != nil {   println("Fuzz param %s error: %v", param.Name(), err)   return}rspo = <-respbody, err = str.ExtractBodyFromHTTPResponseRaw(rspo.ResponseRaw)if err != nil {   println("Get response body error: %v", err)   return}matchNodes = FindNodeFromHtml(body, "asgdhFFASDljl")for _, matchNode = range matchNodes {   if matchNode.MatchText == "" && matchNode.TagName == "script"{       println("Found xss, payload: %s",payload)   }}
或者在属性中
// <div id="参数"></div>resp,err = param.Fuzz(sprintf(""></div>Hello<div id="asgdhFFASDljl")).Exec()if err != nil {   println("Fuzz param %s error: %v", param.Name(), err)   return}rspo = <-respbody, err = str.ExtractBodyFromHTTPResponseRaw(rspo.ResponseRaw)if err != nil {   println("Get response body error: %v", err)   return}matchNodes = FindNodeFromHtml(body, "asgdhFFASDljl")for _, matchNode = range matchNodes {   if matchNode.IsAttr() && matchNode.Key == "id"{       println("Found xss")   }}

DOM树判断

深度遍历整棵树,比较标签名、属性key和value、文本、注释
检测标志:
标签名出现不同(可能有些页面广告是后端渲染的,每次请求生成的广告数量不同会影响检测结果)、属性key和数量(两次相同请求可能会有key不同的情况,例如是样式属性,也会影响检测结果)。
例如:
rawHtml = "<raw html>"resp,err = param.Fuzz(sprintf(""></div>Hello<div id="asgdhFFASDljl")).Exec()if err != nil {   println("Fuzz param %s error: %v", param.Name(), err)   return}rspo = <-respbody, err = str.ExtractBodyFromHTTPResponseRaw(rspo.ResponseRaw)if err != nil {   println("Get response body error: %v", err)   return}diffs,err = xhtml.CompareHtml(rawHtml,body)if err != nil {   println("CompareHtml func error: %v", err)   return}for _, diff = range diffs {   if diff.Type == Tag {      printf("Found xss, XpathPos: %s, Reason: %s", diff.XpathPos, diff.Reason)   }}

JavaScript 的 AST 抽象语法树判断

除了闭合标签或属性,目标站点还可能通过后端动态生成javascript代码,如
<script>var name = "<参数>";</script>,如果payload不会产生新标签,那就需要对script标签内的代码进行分析,yak提供了js.ASTWalk方法,可以获取所有变量、Identifies,语法错误
如果变量数量变化、函数名和数量变化或原请求的AST无语法错误,恶意请求导致AST有语法错误,就可以判断payload对页面“产生了影响”。
例:
originJs = "console.log('<参数>');"fuzzJs = "console.log(''); 2022-6-10; console.log('');"result,err = js.ASTWalk(fuzzJs)// result有五个数组类型成员:StringLiteral、Int64Literal、Float64Literal、Identifies、BadSyntax
// originJs的<参数>本应该是String类型,fuzzJs的payload是'); 2022-6-10 console.log(',isContain = falsefor _,s = range result.StringLiteral{ if str.StringContainsAnyOfSubString(s, "2022-6-10"){ isContain = true break }}if !isContain{ println("Found xss")}
通过语法错误判断
originJs = "console.log('<参数>');"fuzzJs = "console.log(''');"result1,err = js.ASTWalk(originJs)result2,err = js.ASTWalk(fuzzJs)if len(result1.BadSyntax) == 0 && len(result2.BadSyntax) != 0{    println("可能存在xss漏洞,但payload不对")}
XSS 启发式检测基础设施

小总结

XSS 启发式检测基础设施
虽然我们并没有给出一个针对 XSS 检测的最佳实践,但是在启发式检测中,我们已经完善了启发式检测全流程的 “思路”。在用户掌握上述步骤中使用到的基础设施的时候,我相信每个人都是有关于 “漏洞检测” 的想法,恰好这一套辅助函数可以尝试让自己的想法无障碍落地。

XSS 启发式检测基础设施

更新通知

XSS 启发式检测基础设施

Yakit 1.0.15-sp7 Feature

1. 优化 HTTP History 上滚动交互:会在恰当的时候自动加载数据,不需要每次都手动线上滚动~

2. 修复了某些时候 “上 / 下” 方向键切换不了 HTTPFlow 的 BUG

3. MITM 过滤器自动保存

4. MITM 交互界面的表格新增 “ID / Body Size” 字段

5. 右键 HTTP History 新增 “删除” 按钮

6. 数据包编辑器 / monaco editor 新增 “自动解码”,可恢复 (u0000) 格式的中文字符 @南风

7. Web Fuzzer 新增简易版本的 “导出” 功能,支持 CSV / JSON 两种格式。


yaklang 引擎 v1.0.15-sp13

1. 增加 gzip 支持:支持快捷解压/压缩接口

2. 增加 fuzz.UrlToHTTPRequest,可以使用 method + url 快速构建 fuzz.HTTPRequest

3. 增加 js.ASTWalk 遍历 javascript 中的符号和字面量

4. 增加通过 poc.UrlToHTTPRequest 快速构建一个原始数据包

5. 增加 codec.AutoDecode 以及 grpc autoDecode 支持,以便 yakit 今后提供智能解析的规则

6. 支持 fuzz {{int(1-9990|4)}} 语法来支持 fuzz zero padding


VSCode 插件:

1. 支持最新 v1.0.15-sp13


如果你也喜欢 Yakit,欢迎关注我们公众号,或者加入小小的技术讨论组(进讨论组请添加微信),关注公众号,立即获取最新的技术姿势。

官网教程:

https://www.yaklang.io/products/intro

视频教程:

https://space.bilibili.com/437503777

下载地址:

https://github.com/yaklang/yakit

XSS 启发式检测基础设施



原文始发于微信公众号(Yak Project):XSS 启发式检测基础设施

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月11日02:57:42
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  XSS 启发式检测基础设施 http://cn-sec.com/archives/1106783.html

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: