CodeQL 学习小记 Log4j
关于代码审计、数据流与安全研究的笔记。
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。
QL
这里可以借鉴 CWE-074 中的内容,直接使用其定义的 sink,其已经将常见的利用点标记出来
private class DefaultJndiInjectionSinkModel extends SinkModelCsv { override predicate row(string row) { row = [ "javax.naming;Context;true;lookup;;;Argument[0];jndi-injection", "javax.naming;Context;true;lookupLink;;;Argument[0];jndi-injection", "javax.naming;Context;true;rename;;;Argument[0];jndi-injection", "javax.naming;Context;true;list;;;Argument[0];jndi-injection", "javax.naming;Context;true;listBindings;;;Argument[0];jndi-injection", "javax.naming;InitialContext;true;doLookup;;;Argument[0];jndi-injection", "javax.management.remote;JMXConnector;true;connect;;;Argument[-1];jndi-injection", "javax.management.remote;JMXConnectorFactory;false;connect;;;Argument[0];jndi-injection", // Spring "org.springframework.jndi;JndiTemplate;false;lookup;;;Argument[0];jndi-injection", // spring-ldap 1.2.x and newer "org.springframework.ldap.core;LdapOperations;true;lookup;;;Argument[0];jndi-injection", "org.springframework.ldap.core;LdapOperations;true;lookupContext;;;Argument[0];jndi-injection", "org.springframework.ldap.core;LdapOperations;true;findByDn;;;Argument[0];jndi-injection", "org.springframework.ldap.core;LdapOperations;true;rename;;;Argument[0];jndi-injection", "org.springframework.ldap.core;LdapOperations;true;list;;;Argument[0];jndi-injection", "org.springframework.ldap.core;LdapOperations;true;listBindings;;;Argument[0];jndi-injection", "org.springframework.ldap.core;LdapOperations;true;search;(Name,String,ContextMapper);;Argument[0];jndi-injection", "org.springframework.ldap.core;LdapOperations;true;search;(Name,String,int,ContextMapper);;Argument[0];jndi-injection", "org.springframework.ldap.core;LdapOperations;true;search;(Name,String,int,String[],ContextMapper);;Argument[0];jndi-injection", "org.springframework.ldap.core;LdapOperations;true;search;(String,String,ContextMapper);;Argument[0];jndi-injection", "org.springframework.ldap.core;LdapOperations;true;search;(String,String,int,ContextMapper);;Argument[0];jndi-injection", "org.springframework.ldap.core;LdapOperations;true;search;(String,String,int,String[],ContextMapper);;Argument[0];jndi-injection", "org.springframework.ldap.core;LdapOperations;true;searchForObject;(Name,String,ContextMapper);;Argument[0];jndi-injection", "org.springframework.ldap.core;LdapOperations;true;searchForObject;(String,String,ContextMapper);;Argument[0];jndi-injection", // spring-ldap 1.1.x "org.springframework.ldap;LdapOperations;true;lookup;;;Argument[0];jndi-injection", "org.springframework.ldap;LdapOperations;true;lookupContext;;;Argument[0];jndi-injection", "org.springframework.ldap;LdapOperations;true;findByDn;;;Argument[0];jndi-injection", "org.springframework.ldap;LdapOperations;true;rename;;;Argument[0];jndi-injection", "org.springframework.ldap;LdapOperations;true;list;;;Argument[0];jndi-injection", "org.springframework.ldap;LdapOperations;true;listBindings;;;Argument[0];jndi-injection", "org.springframework.ldap;LdapOperations;true;search;(Name,String,ContextMapper);;Argument[0];jndi-injection", "org.springframework.ldap;LdapOperations;true;search;(Name,String,int,ContextMapper);;Argument[0];jndi-injection", "org.springframework.ldap;LdapOperations;true;search;(Name,String,int,String[],ContextMapper);;Argument[0];jndi-injection", "org.springframework.ldap;LdapOperations;true;search;(String,String,ContextMapper);;Argument[0];jndi-injection", "org.springframework.ldap;LdapOperations;true;search;(String,String,int,ContextMapper);;Argument[0];jndi-injection", "org.springframework.ldap;LdapOperations;true;search;(String,String,int,String[],ContextMapper);;Argument[0];jndi-injection", "org.springframework.ldap;LdapOperations;true;searchForObject;(Name,String,ContextMapper);;Argument[0];jndi-injection", "org.springframework.ldap;LdapOperations;true;searchForObject;(String,String,ContextMapper);;Argument[0];jndi-injection", // Shiro "org.apache.shiro.jndi;JndiTemplate;false;lookup;;;Argument[0];jndi-injection" ] }}所以我们只要关注到 source 的定义即可,而追溯 source 不管怎么向上,终归应该是有个函数中的某个参数是源头,所以如下定义 source
/** * @kind path-problem */
import javaimport semmle.code.java.security.JndiInjectionimport semmle.code.java.dataflow.FlowSourcesimport DataFlow::PathGraph
class Config extends TaintTracking::Configuration { Config() { this = "Config" }
override predicate isSource(DataFlow::Node source) { exists(Argument a | a = source.asExpr()) }
override predicate isSink(DataFlow::Node sink) { sink instanceof JndiInjectionSink }
}
from DataFlow::PathNode source, DataFlow::PathNode sink, Config confwhere conf.hasFlowPath(source, sink)select sink.getNode(), source, sink, "JNDI lookup might include name from $@.", source.getNode(), "this user input"这个结果太多以至于就不放上来占篇幅了,不过有很多有意思的东西可以自己看看
限定 AbstractLogger
限定为AbstractLogger的主要考虑是为了重现 CVE-2021-44228,由于此类下方法太多,没必要过于细化,我们只要考虑这个类里,某个公开函数的某个参数应是 source 即可
override predicate isSource(DataFlow::Node source) { exists(MethodAccess mda | mda.getMethod().getDeclaringType().hasQualifiedName("org.apache.logging.log4j.spi", "AbstractLogger") and mda.getAnArgument() = source.asExpr() and mda.getMethod().isPublic() )}基本有的没的就全出来了

关于 DataSourceConnectionSource
这个发现不用什么高大上的方法,甚至根本不该称为漏洞(很模糊的界限),注释里面写的就是对 JNDI 形式的支持(我觉得官方现在是草木皆兵
只需要对上述的 isSink 使用CodeQL: Quick Evaluation快速查询即可

如果上面的都能称为漏洞,那么如org.apache.logging.log4j.jmx.gui.ClientGui#main里也明显有 JNDI 注入,只是需要传输指定类名为 jmxrmi

直接利用
import org.apache.logging.log4j.jmx.gui.ClientGui;
public class Test { public static void main(String[] args) throws Exception { System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true"); ClientGui.main(new String[]{"127.0.0.1"}); }}终归就是 