mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
[Java] Query for Log4j JNDI Injection
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
package com.example.restservice;
|
||||
|
||||
import org.apache.commons.logging.log4j.Logger;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class Log4jJndiInjection {
|
||||
|
||||
private final Logger logger = LogManager.getLogger();
|
||||
|
||||
@GetMapping("/bad")
|
||||
public String bad(@RequestParam(value = "username", defaultValue = "name") String username) {
|
||||
logger.warn("User:'{}'", username);
|
||||
return username;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Log4j versions prior to 2.15.0 are subject to a remote code execution vulnerability via the ldap JNDI parser.
|
||||
</p>
|
||||
<p>
|
||||
As per Apache's Log4j security guide: Apache Log4j2 <=2.14.1 JNDI features used in configuration, log messages, and parameters
|
||||
do not protect against attacker controlled LDAP and other JNDI related endpoints. An attacker who can control log messages or
|
||||
log message parameters can execute arbitrary code loaded from LDAP servers when message lookup substitution is enabled.
|
||||
From log4j 2.15.0, this behavior has been disabled by default.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
This issue was remediated in Log4J v2.15.0. The Apache Logging Services team provides the following mitigation advice:
|
||||
</p>
|
||||
<p>
|
||||
In previous releases (>=2.10) this behavior can be mitigated by setting system property "log4j2.formatMsgNoLookups" to “true”
|
||||
or by removing the JndiLookup class from the classpath (example: zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class).
|
||||
Java 8u121 protects against RCE by defaulting "com.sun.jndi.rmi.object.trustURLCodebase" and "com.sun.jndi.cosnaming.object.trustURLCodebase" to "false".
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
Where possible, upgrade to Log4J version 2.15.0. If you are using Log4J v1 there is a migration guide available.
|
||||
</p>
|
||||
<p>
|
||||
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
|
||||
recommend you migrate to Log4J 2.15.0 where possible.
|
||||
</p>
|
||||
<p>
|
||||
If upgrading is not possible, then ensure the -Dlog4j2.formatMsgNoLookups=true system property is set on both client- and server-side components.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<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>).
|
||||
If a malicious user provides <code>${jndi:ldap://127.0.0.1:1389/a}</code> as a username parameter,
|
||||
Log4j will make a JNDI lookup on the specified LDAP server and potentially load arbitrary code.
|
||||
</p>
|
||||
<sample src="Log4jJndiInjection.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>GitHub Advisory Database: <a href="https://github.com/advisories/GHSA-jfh8-c2jp-5v3q">Remote code injection in Log4j</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* @name Log4j JNDI Injection
|
||||
* @description Building Log4j log entries from user-controlled data may allow
|
||||
* attackers to inject malicious code through JNDI lookups.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/log4j-jndi-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-020
|
||||
* external/cwe/cwe-074
|
||||
* external/cwe/cwe-400
|
||||
* external/cwe/cwe-502
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.dataflow.ExternalFlow
|
||||
import DataFlow::PathGraph
|
||||
|
||||
private class LoggingSummaryModels extends SummaryModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
"org.apache.logging.log4j;Logger;true;traceEntry;(Message);;Argument[0];ReturnValue;taint",
|
||||
"org.apache.logging.log4j;Logger;true;traceEntry;(String,Object[]);;Argument[0..1];ReturnValue;taint",
|
||||
"org.apache.logging.log4j;Logger;true;traceEntry;(String,Supplier[]);;Argument[0..1];ReturnValue;taint",
|
||||
"org.apache.logging.log4j;Logger;true;traceEntry;(Supplier[]);;Argument[0];ReturnValue;taint",
|
||||
"org.apache.logging.log4j;Logger;true;traceExit;(EntryMessage,Object);;Argument[1];ReturnValue;value",
|
||||
"org.apache.logging.log4j;Logger;true;traceExit;(Message,Object);;Argument[1];ReturnValue;value",
|
||||
"org.apache.logging.log4j;Logger;true;traceExit;(Object);;Argument[0];ReturnValue;value",
|
||||
"org.apache.logging.log4j;Logger;true;traceExit;(String,Object);;Argument[1];ReturnValue;value",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
private class LoggingSinkModels extends SinkModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
// org.apache.logging.log4j.Logger
|
||||
"org.apache.logging.log4j;Logger;true;" +
|
||||
["debug", "error", "fatal", "info", "trace", "warn"] +
|
||||
[
|
||||
";(CharSequence);;Argument[0];logging",
|
||||
";(CharSequence,Throwable);;Argument[0];logging",
|
||||
";(Marker,CharSequence);;Argument[1];logging",
|
||||
";(Marker,CharSequence,Throwable);;Argument[1];logging",
|
||||
";(Marker,Message);;Argument[1];logging",
|
||||
";(Marker,MessageSupplier);;Argument[1];logging",
|
||||
";(Marker,MessageSupplier);;Argument[1];logging",
|
||||
";(Marker,MessageSupplier,Throwable);;Argument[1];logging",
|
||||
";(Marker,Object);;Argument[1];logging",
|
||||
";(Marker,Object,Throwable);;Argument[1];logging",
|
||||
";(Marker,String);;Argument[1];logging",
|
||||
";(Marker,String,Object[]);;Argument[1..2];logging",
|
||||
";(Marker,String,Object);;Argument[1..2];logging",
|
||||
";(Marker,String,Object,Object);;Argument[1..3];logging",
|
||||
";(Marker,String,Object,Object,Object);;Argument[1..4];logging",
|
||||
";(Marker,String,Object,Object,Object,Object);;Argument[1..5];logging",
|
||||
";(Marker,String,Object,Object,Object,Object,Object);;Argument[1..6];logging",
|
||||
";(Marker,String,Object,Object,Object,Object,Object,Object);;Argument[1..7];logging",
|
||||
";(Marker,String,Object,Object,Object,Object,Object,Object,Object);;Argument[1..8];logging",
|
||||
";(Marker,String,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..9];logging",
|
||||
";(Marker,String,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..10];logging",
|
||||
";(Marker,String,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..11];logging",
|
||||
";(Marker,String,Supplier);;Argument[1..2];logging",
|
||||
";(Marker,String,Throwable);;Argument[1];logging",
|
||||
";(Marker,Supplier);;Argument[1];logging",
|
||||
";(Marker,Supplier,Throwable);;Argument[1];logging",
|
||||
";(MessageSupplier);;Argument[0];logging",
|
||||
";(MessageSupplier,Throwable);;Argument[0];logging", ";(Message);;Argument[0];logging",
|
||||
";(Message,Throwable);;Argument[0];logging", ";(Object);;Argument[0];logging",
|
||||
";(Object,Throwable);;Argument[0];logging", ";(String);;Argument[0];logging",
|
||||
";(String,Object[]);;Argument[0..1];logging",
|
||||
";(String,Object);;Argument[0..1];logging",
|
||||
";(String,Object,Object);;Argument[0..2];logging",
|
||||
";(String,Object,Object,Object);;Argument[0..3];logging",
|
||||
";(String,Object,Object,Object,Object);;Argument[0..4];logging",
|
||||
";(String,Object,Object,Object,Object,Object);;Argument[0..5];logging",
|
||||
";(String,Object,Object,Object,Object,Object,Object);;Argument[0..6];logging",
|
||||
";(String,Object,Object,Object,Object,Object,Object,Object);;Argument[0..7];logging",
|
||||
";(String,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..8];logging",
|
||||
";(String,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..9];logging",
|
||||
";(String,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..10];logging",
|
||||
";(String,Supplier);;Argument[0..1];logging",
|
||||
";(String,Throwable);;Argument[0];logging", ";(Supplier);;Argument[0];logging",
|
||||
";(Supplier,Throwable);;Argument[0];logging"
|
||||
],
|
||||
"org.apache.logging.log4j;Logger;true;log" +
|
||||
[
|
||||
";(Level,CharSequence);;Argument[1];logging",
|
||||
";(Level,CharSequence,Throwable);;Argument[1];logging",
|
||||
";(Level,Marker,CharSequence);;Argument[2];logging",
|
||||
";(Level,Marker,CharSequence,Throwable);;Argument[2];logging",
|
||||
";(Level,Marker,Message);;Argument[2];logging",
|
||||
";(Level,Marker,MessageSupplier);;Argument[2];logging",
|
||||
";(Level,Marker,MessageSupplier);;Argument[2];logging",
|
||||
";(Level,Marker,MessageSupplier,Throwable);;Argument[2];logging",
|
||||
";(Level,Marker,Object);;Argument[2];logging",
|
||||
";(Level,Marker,Object,Throwable);;Argument[2];logging",
|
||||
";(Level,Marker,String);;Argument[2];logging",
|
||||
";(Level,Marker,String,Object[]);;Argument[2..3];logging",
|
||||
";(Level,Marker,String,Object);;Argument[2..3];logging",
|
||||
";(Level,Marker,String,Object,Object);;Argument[2..4];logging",
|
||||
";(Level,Marker,String,Object,Object,Object);;Argument[2..5];logging",
|
||||
";(Level,Marker,String,Object,Object,Object,Object);;Argument[2..6];logging",
|
||||
";(Level,Marker,String,Object,Object,Object,Object,Object);;Argument[2..7];logging",
|
||||
";(Level,Marker,String,Object,Object,Object,Object,Object,Object);;Argument[2..8];logging",
|
||||
";(Level,Marker,String,Object,Object,Object,Object,Object,Object,Object);;Argument[2..9];logging",
|
||||
";(Level,Marker,String,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2..10];logging",
|
||||
";(Level,Marker,String,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2..11];logging",
|
||||
";(Level,Marker,String,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[2..12];logging",
|
||||
";(Level,Marker,String,Supplier);;Argument[2..3];logging",
|
||||
";(Level,Marker,String,Throwable);;Argument[2];logging",
|
||||
";(Level,Marker,Supplier);;Argument[2];logging",
|
||||
";(Level,Marker,Supplier,Throwable);;Argument[2];logging",
|
||||
";(Level,Message);;Argument[1];logging",
|
||||
";(Level,MessageSupplier);;Argument[1];logging",
|
||||
";(Level,MessageSupplier,Throwable);;Argument[1];logging",
|
||||
";(Level,Message);;Argument[1];logging",
|
||||
";(Level,Message,Throwable);;Argument[1];logging",
|
||||
";(Level,Object);;Argument[1];logging", ";(Level,Object);;Argument[1];logging",
|
||||
";(Level,String);;Argument[1];logging",
|
||||
";(Level,Object,Throwable);;Argument[1];logging",
|
||||
";(Level,String);;Argument[1];logging",
|
||||
";(Level,String,Object[]);;Argument[1..2];logging",
|
||||
";(Level,String,Object);;Argument[1..2];logging",
|
||||
";(Level,String,Object,Object);;Argument[1..3];logging",
|
||||
";(Level,String,Object,Object,Object);;Argument[1..4];logging",
|
||||
";(Level,String,Object,Object,Object,Object);;Argument[1..5];logging",
|
||||
";(Level,String,Object,Object,Object,Object,Object);;Argument[1..6];logging",
|
||||
";(Level,String,Object,Object,Object,Object,Object,Object);;Argument[1..7];logging",
|
||||
";(Level,String,Object,Object,Object,Object,Object,Object,Object);;Argument[1..8];logging",
|
||||
";(Level,String,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..9];logging",
|
||||
";(Level,String,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..10];logging",
|
||||
";(Level,String,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[1..11];logging",
|
||||
";(Level,String,Supplier);;Argument[1..2];logging",
|
||||
";(Level,String,Throwable);;Argument[1];logging",
|
||||
";(Level,Supplier);;Argument[1];logging",
|
||||
";(Level,Supplier,Throwable);;Argument[1];logging"
|
||||
], "org.apache.logging.log4j;Logger;true;entry;(Object[]);;Argument[0];logging",
|
||||
"org.apache.logging.log4j;Logger;true;logMessage;(Level,Marker,String,StackTraceElement,Message,Throwable);;Argument[4];logging",
|
||||
"org.apache.logging.log4j;Logger;true;printf;(Level,Marker,String,Object[]);;Argument[2..3];logging",
|
||||
"org.apache.logging.log4j;Logger;true;printf;(Level,String,Object[]);;Argument[1..2];logging",
|
||||
"org.apache.logging.log4j;Logger;true;traceEntry;(Message);;Argument[0];logging",
|
||||
"org.apache.logging.log4j;Logger;true;traceEntry;(String,Object[]);;Argument[0..1];logging",
|
||||
"org.apache.logging.log4j;Logger;true;traceEntry;(String,Supplier[]);;Argument[0..1];logging",
|
||||
"org.apache.logging.log4j;Logger;true;traceEntry;(Supplier[]);;Argument[0];logging",
|
||||
"org.apache.logging.log4j;Logger;true;traceExit;(EntryMessage);;Argument[0];logging",
|
||||
"org.apache.logging.log4j;Logger;true;traceExit;(EntryMessage,Object);;Argument[0..1];logging",
|
||||
"org.apache.logging.log4j;Logger;true;traceExit;(Message,Object);;Argument[0..1];logging",
|
||||
"org.apache.logging.log4j;Logger;true;traceExit;(Object);;Argument[0];logging",
|
||||
"org.apache.logging.log4j;Logger;true;traceExit;(String,Object);;Argument[0..1];logging",
|
||||
// org.apache.logging.log4j.LogBuilder
|
||||
"org.apache.logging.log4j;LogBuilder;true;log;(CharSequence);;Argument[0];logging",
|
||||
"org.apache.logging.log4j;LogBuilder;true;log;(Message);;Argument[0];logging",
|
||||
"org.apache.logging.log4j;LogBuilder;true;log;(Object);;Argument[0];logging",
|
||||
"org.apache.logging.log4j;LogBuilder;true;log;(String);;Argument[0];logging",
|
||||
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object[]);;Argument[0..1];logging",
|
||||
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object);;Argument[0..1];logging",
|
||||
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object);;Argument[0..2];logging",
|
||||
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object);;Argument[0..3];logging",
|
||||
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object);;Argument[0..4];logging",
|
||||
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object);;Argument[0..5];logging",
|
||||
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object,Object);;Argument[0..6];logging",
|
||||
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object,Object,Object);;Argument[0..7];logging",
|
||||
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..8];logging",
|
||||
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..9];logging",
|
||||
"org.apache.logging.log4j;LogBuilder;true;log;(String,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object);;Argument[0..10];logging",
|
||||
"org.apache.logging.log4j;LogBuilder;true;log;(String,Supplier);;Argument[0..1];logging",
|
||||
"org.apache.logging.log4j;LogBuilder;true;log;(Supplier);;Argument[0];logging"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/** A data flow sink for unvalidated user input that is used to log messages. */
|
||||
class Log4jInjectionSink extends DataFlow::Node {
|
||||
Log4jInjectionSink() { sinkNode(this, "logging") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A node that sanitizes a message before logging to avoid log injection.
|
||||
*/
|
||||
class Log4jInjectionSanitizer extends DataFlow::Node {
|
||||
Log4jInjectionSanitizer() {
|
||||
this.getType() instanceof BoxedType or this.getType() instanceof PrimitiveType
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for tracking untrusted user input used in log entries.
|
||||
*/
|
||||
class Log4jInjectionConfiguration extends TaintTracking::Configuration {
|
||||
Log4jInjectionConfiguration() { this = "Log4jInjectionConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Log4jInjectionSink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Log4jInjectionSanitizer }
|
||||
}
|
||||
|
||||
from Log4jInjectionConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This $@ flows to a Log4j log entry.", source.getNode(),
|
||||
"user-provided value"
|
||||
Reference in New Issue
Block a user