【安全漏洞】利用CodeQL分析并挖掘Log4j漏洞

在这里插入图片描述

前言

分析漏洞的本质是为了能让我们从中学习漏洞挖掘者的思路以及挖掘到新的漏洞,而CodeQL就是一款可以将我们对漏洞的理解快速转化为可实现的规则并挖掘漏洞的利器。根据网上的传言Log4j2的RCE漏洞就是作者通过CodeQL挖掘出的。虽然如何挖掘的我们不得而知,但我们现在站在事后的角度再去想想,可以推测一下作者如何通过CodeQL挖掘到漏洞的,并尝试基于作者的思路挖掘新漏洞。

分析过程

首先我们要构建Log4j的数据库,由于lgtm.com中构建的是新版本的Log4j数据库,所以只能手动构建数据库了。首先从github获取源码并切换到2.14.1版本。

git clone https://github.com/apache/logging-log4j2.git
git checkout be881e5

【点击查看学习资料】

由于我们这次分析的主要是log4j-core和log4j-api中的内容,所以打开根目录的Pom.xml注释下面的内容。

<modules>
    <module>log4j-api-java9</module>
    <module>log4j-api</module>
    <module>log4j-core-java9</module>
    <module>log4j-core</module>
    <!-- <module>log4j-layout-template-json</module>
    <module>log4j-core-its</module>
    <module>log4j-1.2-api</module>
    <module>log4j-slf4j-impl</module>
    <module>log4j-slf4j18-impl</module>
    <module>log4j-to-slf4j</module>
    <module>log4j-jcl</module>
    <module>log4j-flume-ng</module>
    <module>log4j-taglib</module>
    <module>log4j-jmx-gui</module>
    <module>log4j-samples</module>
    <module>log4j-bom</module>
    <module>log4j-jdbc-dbcp2</module>
    <module>log4j-jpa</module>
    <module>log4j-couchdb</module>
    <module>log4j-mongodb3</module>
    <module>log4j-mongodb4</module>
    <module>log4j-cassandra</module>
    <module>log4j-web</module>
    <module>log4j-perf</module>
    <module>log4j-iostreams</module>
    <module>log4j-jul</module>
    <module>log4j-jpl</module>
    <module>log4j-liquibase</module>
    <module>log4j-appserver</module>
    <module>log4j-osgi</module>
    <module>log4j-docker</module>
    <module>log4j-kubernetes</module>
    <module>log4j-spring-boot</module>
    <module>log4j-spring-cloud-config</module> -->
  </modules>

由于log4j-api-java9和log4j-core- java9需要依赖JDK9,所以要先下载JDK9并且在C:\Users\用户名.m2\toolchains.xml中加上下面的内容。

<toolchains>
<toolchain>  
  <type>jdk</type>  
  <provides>  
    <version>9</version>  
    <vendor>sun</vendor>  
  </provides>  
  <configuration>  
    <jdkHome>C:\Program Files\Java\jdk-9.0.4</jdkHome>  
  </configuration>  
</toolchain>  
</toolchains>

通过下面的命令完成数据库构建

CodeQL database create Log4jDB --language=java --overwrite --command="mvn clean install -Dmaven.test.skip=true"

构建好数据库后,我们要找JNDI注入的漏洞,首先要确定在这套系统中调用了InitialContext#lookup方法。在LookupInterface项目中已经集成了常见的发起JNDI请求的类,只要稍微改一下即可。

首先定义Context类型,这个类中综合了可能发起JNDI请求的类。

class Context extends  RefType{
   
    Context(){
   
        this.hasQualifiedName("javax.naming", "Context")
        or
        this.hasQualifiedName("javax.naming", "InitialContext")
        or
        this.hasQualifiedName("org.springframework.jndi", "JndiCallback")
        or 
        this.hasQualifiedName("org.springframework.jndi", "JndiTemplate")
        or
        this.hasQualifiedName("org.springframework.jndi", "JndiLocatorDelegate")
        or
        this.hasQualifiedName("org.apache.shiro.jndi", "JndiCallback")
        or
        this.getQualifiedName().matches("%JndiCallback")
        or
        this.getQualifiedName().matches("%JndiLocatorDelegate")
        or
        this.getQualifiedName().matches("%JndiTemplate")
    }
}

下面寻找那里调用了Context的lookup方法。

from Call call,Callable parseExpression
where
    call.getCallee() = parseExpression and 
    parseExpression.getDeclaringType() instanceof Context and
    parseExpression.hasName("lookup")
select call

在这里插入图片描述

  • DataSourceConnectionSource#createConnectionSource
@PluginFactory
    public static DataSourceConnectionSource createConnectionSource(@PluginAttribute("jndiName") final String jndiName) {
   
        if (Strings.isEmpty(jndiName)) {
   
            LOGGER.error("No JNDI name provided.");
            return null;
        }
        try {
   
            final InitialContext context = new InitialContext();
            final DataSource dataSource = (DataSource) context.lookup(jndiName);
            if (dataSource == null) {
   
                LOGGER.error("No data source found with JNDI name [" + jndiName + "].");
                return null;
            }
            return new DataSourceConnectionSource(jndiName, dataSource);
        } catch (final NamingException e) {
   
            LOGGER.error(e.getMessage(), e);
            return null;
        }
    }
  • JndiManager#lookup
@SuppressWarnings("unchecked")
    public <T> T lookup(final String name) throws NamingException {
   
        return (T) this.context.lookup(name);
    }

找到sink后我们还需要找到source,虽然Codeql定义了RemoteFlowSource支持多种source,但是我们还是要根据实际的代码业务来分析可能作为source的点。

在Log4j作为日志记录的工具,除了从HTTP请求中获取输入点外,还可以在记录日志请求或者解析配置文件中来获取source。先不看解析配置文件获取source的点了,因为这需要分析Log4j解析配置文件的流程比较复杂。所以目前我们只考虑通过日志记录作为source的情况。稍微了解Log4j的同学都知道,Log4j会通过error/fatal/info/debug/trace等方法对不同级别的日志进行记录。通过分析我们可以看到我们输入的message都调用了logIfEnabled方法并作为第四个参数输入,所以可以将这里定义为source。
在这里插入图片描述
下面使用全局污点追踪分析JNDI漏洞,还是套用LookupInterface项目中的代码,修改source部分即可。

/**
 *@name Tainttrack Context lookup
 *@kind path-problem
 */
import java
import semmle.code.java.dataflow.FlowSources
import DataFlow::PathGraph
class Context extends  RefType{
   
    Context(){
   
        this.hasQualifiedName("javax.naming", "Context")
        or
        this.hasQualifiedName("javax.naming", "InitialContext")
        or
        this.hasQualifiedName("org.springframework.jndi", "JndiCallback")
        or 
        this.hasQualifiedName("org.springframework.jndi", "JndiTemplate")
        or
        this.hasQualifiedName("org.springframework.jndi", "JndiLocatorDelegate")
        or
        this.hasQualifiedName("org.apache.shiro.jndi", "JndiCallback")
        or
        this.getQualifiedName().matches("%JndiCallback")
        or
        this.getQualifiedName().matches("%JndiLocatorDelegate")
        or
        this.getQualifiedName().matches("%JndiTemplate")
    }
}
class Logger extends  RefType{
   
    Logger(){
   
        this.hasQualifiedName("org.apache.logging.log4j.spi", "AbstractLogger")
    }
}
predicate isLookup(Expr arg) {
   
    exists(MethodAccess ma |
        ma.getMethod().getName() = "lookup"
        and
        ma.getMethod().getDeclaringType() instanceof Context
        and
        arg = ma.getArgument(0)
    )
}
predicate isLogging(Expr arg) {
   
    exists
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值