问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
漏洞验证框架的构思与实现(二)
笔者是个基层的"安全研究员",poc 编写算是我最基础的日常工作。本文描述的是我所构思的一款漏洞验证框架以及具体实现。
历史内容: - 第一篇:<https://forum.butian.net/share/160> 本篇主要聊一聊框架核心模块:规则体系。 0x01 写在前面 --------- 在上篇文章提到过,[xray](https://docs.xray.cool/#/guide/poc) 有着完善的规则体系,而且业内同志提交的 poc 积极性很高,所以更新较快,质量也很高,有很多值得借鉴之处。因此我想在 xray 规则的基础上设计一套规则体系,既能兼容 xray 的 poc,又能融入自己的一些想法。 xray poc 的运行原理,简单来说就是根据`自定义的规则`对`原始请求`变形,然后获取变形后的响应,再检查响应是否匹配规则中定义的表达式。 我认为这里有几个关键点: 1. 原始请求来源 2. 兼容 xray 现在规则 3. 扩展 xray:组包时更细致的分类 0x02 原始请求来源 ----------- 原始请求就是根据待检测目标构造 HTTP 请求包,构造原始包时通常有以下来源: 1. url 。根据输入的 url 补充基础请求头,生成基础的 get http 请求,类似于`Burpsuite Repeater`的`Parse URL As Request`。 [![](https://shs3.b.qianxin.com/attack_forum/2021/07/attach-af6c86e46f6f0e5c2c087b93e726f792293950b1.png)](https://shs3.b.qianxin.com/attack_forum/2021/07/attach-af6c86e46f6f0e5c2c087b93e726f792293950b1.png) 核心代码: ```go func GenOriginalReq(url string) (*http.Request, error) { // 生成原始请求,如果没有协议默认使用http if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") { } else { url = "http://" + url } originalReq, err := http.NewRequest("GET", url, nil) if err != nil { log.Error("util/requests.go:GenOriginalReq original request gen error", url, err) return nil, err } originalReq.Header.Set("Host", originalReq.Host) originalReq.Header.Set("Accept-Encoding", "gzip, deflate") originalReq.Header.Set("Accept","*/*") originalReq.Header.Set("User-Agent", conf.GlobalConfig.HttpConfig.Headers.UserAgent) originalReq.Header.Set("Accept-Language","en") originalReq.Header.Set("Connection","close") return originalReq, nil } ``` 2. 直接输入 http 报文文件。解析该文件,生成 http 请求,类似于`Burpsuite Repeater`的`Parse from file`。go 语言可使用原生 http 包的`http.ReadRequest`方法实现。 [![](https://shs3.b.qianxin.com/attack_forum/2021/07/attach-cd06b7f8714a43762cc20422a0204560b7047481.png)](https://shs3.b.qianxin.com/attack_forum/2021/07/attach-cd06b7f8714a43762cc20422a0204560b7047481.png) 核心代码: ```go func GenOriginalReqFromRaw(filePath string) (*http.Request, error) { raw, err := ioutil.ReadFile(filePath) if err != nil { c.JSON(msg.ErrResp("请求报文文件解析失败")) return nil, err } oreq, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(raw))) if err != nil { return nil, err } if !oreq.URL.IsAbs() { scheme := "http" oreq.URL.Scheme = scheme oreq.URL.Host = oreq.Host } return oreq, nil } ``` 3. 通过代理的形式监听获取(如 xray 浏览器代理模式) 0x03 兼容 xray ------------ xray 的规则体系是基于 Common Expression Language ( cel 表达式) 实现的。Google 官方提供了 go 语言的 cel 包 [cel-go](https://github.com/google/cel-go)。通过向 cel 环境注入变量、方法,即可在该环境内动态的执行表达式,并返回 bool 型结果(符合 or 不符合)。 ### cel表达式 运行 cel 表达式有三个过程: 1. 构建 cel 环境。初始化一个 cel.Env 2. 向 cel 环境中注入类型、方法。xray 规则所有的变量、方法,可参考[这里](https://docs.xray.cool/#/guide/poc?id=%E5%86%85%E9%83%A8%E5%8F%98%E9%87%8F%E4%B8%8E%E5%87%BD%E6%95%B0%E9%80%9F%E6%9F%A5)。为兼容 xray ,依照文档实现所有类型、方法(包括文档中未提及的 icontains 方法),[完整实现](https://github.com/jweny/pocassist/blob/master/pkg/cel/cel.go)。 3. 计算表达式。计算表达式的逻辑很简单,传入表达式、cel 环境、以及要检测变量列表即可。对于 xray 来说,要检测的变量列表,除了注入到环境的几个类型(UrlType、Request、Response、Reverse)之外,还要考虑 [Set](https://docs.xray.cool/#/guide/poc?id=poc-%E7%BB%93%E6%9E%84) 中自定义的变量,表达式中使用 `{{}}`的部分需要替换成 Set 的 Value。[完整实现](https://github.com/jweny/pocassist/blob/master/pkg/cel/cel.go#L444)。 值得一提的是,set 中自定义的变量可能引用之前的变量,所以 set 一定是要有序加载的,,因此建议不要使用 go 的map 去保存 set(因为 map 是无序的)。可以使用`gopkg.in/yaml.v2`包的 `MapSlice`保存。 我们可以定一个 `cel controller` 去管理整个 cel 的生命周期,[完整实现](https://github.com/jweny/pocassist/blob/master/poc/rule/cel.go)。 ### 构造请求 上面提到过, poc 的运行阶段,一共有两个请求:一是原始请求,二是根据规则构造的请求。 poc 规则直接决定了对原始请求如何变形。这里以 airflow 未授权访问漏洞的 poc 举例。 ```php name: poc-yaml-airflow-unauth rules: - method: GET path: /admin/ expression: | response.status == 200 && response.body.bcontains(b"<title>Airflow - DAGs</title>") && response.body.bcontains(b"<h2>DAGs</h2>") ``` 检测目标为`testphp.vulnweb.com/aaa/bbb.html` 时,原始请求就应该是 ```php GET /aaa/bbb.html HTTP/1.1 Host: testphp.vulnweb.com User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0 Accept-Encoding: gzip, deflate Connection: close ``` 根据 poc 规则,漏洞所在的 path 为`/admin`,如果airflow部署在二级目录`/aaa/`,那么 poc的请求就应该是`/aaa/admin`。规则中没有配置请求头,那么就使用原始请求的请求头。 xray 是以目录为单位进行扫描,因此 xray 变形后请求为: ```php GET /aaa/admin/ HTTP/1.1 Host: testphp.vulnweb.com User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0 Accept-Encoding: gzip, deflate Connection: close ``` 为了追求性能,尽可能的小内存占用,发起请求时,我使用了更高效的 [fasthttp](https://github.com/valyala/fasthttp) 替换掉原生的 http 包。因此定义了一个`*fasthttp.Request`去保存构造的包。 [完整实现](https://github.com/jweny/pocassist/blob/master/poc/rule/request.go) ### 检测链控制 xray 的单个 [rule](https://docs.xray.cool/#/guide/poc?id=poc-%E7%BB%93%E6%9E%84) 的表达式返回值是一个bool 型结果(符合 or 不符合),这个结果作为整个 rule 的值,也就是对应了一个请求/响应。 通常来说一个 poc 是有多个请求和响应的检测链。xray 中定义了两种检测链格式。 一是 rules 。 rule 组成的有序列表(\[\]rule),值为 true 的 rule ,如果后面还有其他 rule ,则继续执行后续 rule ,如果后续没有其他 rule ,则表示该 poc 的结果是 true 。 二是 groups 。 rules 组成的列表`map[string]rules`,只要有一组 rules 执行成功,就认为该 poc 的结果为 true 。 所以原则上来说 groups 和 rules 只能存在一个。我这里才去的方式是先校验 groups 是否为空,非空才去执行rules。核心代码: ```php func ExecExpressionHandle(ctx controllerContext){ var result bool var err error poc := ctx.GetPoc() if poc == nil { log.Error("[rule/handle.go:ExecExpressionHandle error] ", "poc is nil") return } if poc.Groups != nil { result, err = ctx.Groups(ctx.IsDebug()) } else { result, err = ctx.Rules(poc.Rules,ctx.IsDebug()) } if err != nil { log.Error("[rule/handle.go:ExecExpressionHandle error] ", err) return } if result { ctx.Abort() } return } ``` ### poc运行 基于以上分析,poc 中运行的流程如下: 1. 获取原始请求,根据规则进行变形,构造请求 2. 初始化 cel 环境:注入变量、方法 3. 初始化 cel 变量列表:注入set定义的自定义变量、注入当前请求(构造的请求) 4. 发起构造后的请求,并将响应写入cel变量列表 5. 执行表达式 [![](https://shs3.b.qianxin.com/attack_forum/2021/07/attach-47a446694949a684abdeb4a1f6108bdefa1db7c4.png)](https://shs3.b.qianxin.com/attack_forum/2021/07/attach-47a446694949a684abdeb4a1f6108bdefa1db7c4.png) [ poc 运行过程的完整实现](https://github.com/jweny/pocassist/tree/master/poc/rule) 0x04 扩展xray ----------- xray 以目录为单位进行扫描,足够覆盖绝大多数场景,但我认为还可以再细分下。 1. 目录级的定义可以再完整些。以`testphp.vulnweb.com/aaa/bbb/ccc.html`为例,如果以目录为单位的话,应该扫描三个目录:`/`、`/aaa/`、`bbb`。 2. 某些通用漏洞的组件通常独占端口且部署在一级目录,为减少请求数,只扫描一级目录`/`。 3. 每个请求的响应(包括原始请求和每个变形后的请求)都应该检查一遍响应,例如是否为常见 CMS /框架的报错、绝对路径泄露、IP泄露、数据库报错等等。 4. `参数存在 SSRF`或`参数型的存储型 XSS`等,是需要逐一对原始请求的参数进行处理:直接替换为 payload 或者在原始参数后面拼 payload 。 5. 以上扫描只针对 http web 应用。对于一些复杂的 poc 或者 tcp 扫描的漏洞(如 redis 未授权)等,应该加载支持自定义脚本。 因此我在原有 xray 规则基础上,新加了一个`规则类型`的字段,根据规则所属哪种类型,去执行不同的变形逻辑。 [![](https://shs3.b.qianxin.com/attack_forum/2021/07/attach-ac0b5957d0e1d3ee61785bf059629dde9d2c4382.png)](https://shs3.b.qianxin.com/attack_forum/2021/07/attach-ac0b5957d0e1d3ee61785bf059629dde9d2c4382.png) 核心代码: ```php // 根据原始请求 + rule 生成并发起新的请求 func (controller *PocController) DoSingleRuleRequest(rule *Rule) (*proto.Response, error) { fastReq := controller.Request.Fast // fixReq : 根据规则对原始请求进行变形 fixedFastReq := fasthttp.AcquireRequest() fastReq.CopyTo(fixedFastReq) curPath := string(fixedFastReq.URI().Path()) affects := controller.Plugin.Affects switch affects { // 情况4 参数级 case AffectAppendParameter, AffectReplaceParameter: for k, v := range rule.Headers { fixedFastReq.Header.Set(k, v) } return util.DoFasthttpRequest(fixedFastReq, rule.FollowRedirects) // 情况3 content级 case AffectContent: return util.DoFasthttpRequest(fixedFastReq, rule.FollowRedirects) // 情况1 dir级 case AffectDirectory: // 目录级漏洞检测 判断是否以 "/"结尾 if curPath != "" && strings.HasSuffix(curPath, "/") { // 去掉规则中的的"/" 再拼 curPath = fmt.Sprint(curPath, strings.TrimPrefix(rule.Path, "/")) } else { curPath = fmt.Sprint(curPath, "/" ,strings.TrimPrefix(rule.Path, "/")) } // 情况2 case AffectServer: curPath = rule.Path // url级(直接使用原始请求头,只替换路径和完整post参数) case AffectURL: //curPath = curPath, strings.TrimPrefix(rule.Path, "/")) default: } // 兼容xray: 某些 POC 没有区分path和query curPath = strings.ReplaceAll(curPath, " ", "%20") curPath = strings.ReplaceAll(curPath, "+", "%20") fixedFastReq.URI().DisablePathNormalizing= true fixedFastReq.URI().Update(curPath) for k, v := range rule.Headers { fixedFastReq.Header.Set(k, v) } fixedFastReq.Header.SetMethod(rule.Method) // 处理multipart contentType := string(fixedFastReq.Header.ContentType()) if strings.HasPrefix(strings.ToLower(contentType),"multipart/form-Data") && strings.Contains(rule.Body,"\n\n") { multipartBody, err := util.DealMultipart(contentType, rule.Body) if err != nil { return nil, err } fixedFastReq.SetBody([]byte(multipartBody)) }else { fixedFastReq.SetBody([]byte(rule.Body)) } return util.DoFasthttpRequest(fixedFastReq, rule.FollowRedirects) } ``` `DealMultipart`方法是处理用到`multipart`的 poc (例如验证文件上传),应当按照 RFC 规定要将`multipart`的每个文件头和分隔符的`\n`转成`\r\n`。 核心代码: ```php func DealMultipart(contentType string, ruleBody string) (result string, err error) { errMsg := "" // 处理multipart的/n re := regexp.MustCompile(`(?m)multipart\/form-Data; boundary=(.*)`) match := re.FindStringSubmatch(contentType) if len(match) != 2 { errMsg = "no boundary in content-type" //logging.GlobalLogger.Error("util/requests.go:DealMultipart Err", errMsg) return "", errors.New(errMsg) } boundary := "--" + match[1] multiPartContent := "" // 处理rule multiFile := strings.Split(ruleBody, boundary) if len(multiFile) == 0 { errMsg = "ruleBody.Body multi content format err" //logging.GlobalLogger.Error("util/requests.go:DealMultipart Err", errMsg) return multiPartContent, errors.New(errMsg) } for _, singleFile := range multiFile { // 处理单个文件 // 文件头和文件响应 spliteTmp := strings.Split(singleFile,"\n\n") if len(spliteTmp) == 2 { fileHeader := spliteTmp[0] fileBody := spliteTmp[1] fileHeader = strings.Replace(fileHeader,"\n","\r\n",-1) multiPartContent += boundary + fileHeader + "\r\n\r\n" + strings.TrimRight(fileBody ,"\n") + "\r\n" } } multiPartContent += boundary + "--" + "\r\n" return multiPartContent, nil } ``` [完整实现](https://github.com/jweny/pocassist/blob/master/poc) 0x05 总结 ------- 本文描述的 [pocassist](https://github.com/jweny/pocassist) 的规则体系具体实现。在兼容 xray yaml 规则的基础上,对请求变形设计了更细致的分类。想了解具体实现的师傅可以先自行研究下源码,我也会在后续文档逐一分析具体的实现细节。 如果文章内有描述不清或其他问题,烦请各位师傅斧正。 0x06 参考 ------- - <https://docs.xray.cool/#/guide/poc> - <https://github.com/chaitin/xray/tree/master/pocs> - <https://codelabs.developers.google.com/codelabs/cel-go#0> - <https://github.com/jjf012/gopoc>
发表于 2021-07-16 11:14:14
阅读 ( 6049 )
分类:
WEB安全
1 推荐
收藏
0 条评论
请先
登录
后评论
jweny
16 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!