codeql php,使用codeql 挖掘 ofcms
前言
網上關于codeql的文章并不多,國內現在對codeql的研究相對比較少,可能是因為codeql暫時沒有中文文檔,資料也相對較少,需要比較好的英語功底,但是我認為在隨著代碼量越來越多,傳統的自動化漏洞挖掘工具的瓶頸無法突破的情況下,codeql相當于是一種折中的辦法,通過codeql的輔助,來減少漏洞挖掘人員的工作,更加關注漏洞的發現和利用過程
之所以選ofcms,是因為有p0desta師傅之前的審計經驗,而且使用codeql審計cms尚屬第一次,所以選用了ofcms審計
ql構造
在ql中,漏洞挖掘是根據污點追蹤進行的,所以我們需要知道我們的挖掘的cms的source點在哪里,sink點在哪里,相對來說,source點比較固定,一般就是http的請求參數,請求頭這一類的
但是sink比較難以確定,由于現在的web應用經常使用框架,有些文件讀取,html輸出其實是背后的框架在做,所以這就導致了我們的sink定義不可能是一成不變的,要對整個web應用有一個大致的了解,才能定義對應的sink
source點的ql
source點很清楚,對于一個web應用來說,http請求參數,http請求頭,我們關注ofcms中對請求參數的獲取方式:
ofcms使用了jfinal這個框架,而ofcms繼承了jfinal的controller來獲取參數,在整個ofcms中大體有三種類型來獲取請求參數:
BaseController
Controller(Jfinal提供)
ApiBase
所以我們的source都是根據這幾個類展開的,在觀察這幾個類之后很容易發現,所有的獲取http參數的方法都是getXXX()這樣的命名方式,所以我們可以這樣定義source的ql語法:
class OfCmsSource extends MethodAccess{
OfCmsSource(){
(this.getMethod().getDeclaringType*().hasQualifiedName("com.ofsoft.cms.admin.controller", "BaseController") and
(this.getMethod().getName().substring(0, 3) = "get"))
or
(this.getMethod().getDeclaringType*().hasQualifiedName("com.jfinal.core", "Controller") and
(this.getMethod().getName().substring(0, 3) = "get"))
or
(this.getMethod().getDeclaringType*().hasQualifiedName("javax.servlet.http", "HttpServletRequest") and (this.getMethod().getName().substring(0, 3) = "get"))
or
(this.getMethod().getDeclaringType*().hasQualifiedName("com.ofsoft.cms.api", "ApiBase") and
(this.getMethod().getName().substring(0, 3) = "get"))
}
}
到這一步,我們的source就算定義完了,接下來就是定義對應的sink了
sink點的ql
相對于source的固定,sink就很不固定了,常見的web漏洞一般來說都可以作為sink,而且因為框架的不同,同一種漏洞在不同框架下的ql都是不一樣的,所以我們需要略微分析一下整個web應用在做文件讀取,模版渲染等操作的時候一般都用的是什么方法
模版渲染的問題
Jfinal中對模版渲染有一系列的render方法:
可以看到,所有都是render開頭,所以我們對方法名的判斷很簡單,截取前面6個字符,判斷是否為render,隨便找一個項目使用render的地方,可以發現render其實是在com.jfinal.core.Controller里面定義的方法,所以現在我們唯一確定了模版渲染的方法,所以我們的sink也就呼之欲出了,也就是這些render方法的參數,所以構造ql:
class RenderMethod extends MethodAccess{
RenderMethod(){
(this.getMethod().getDeclaringType*().hasQualifiedName("com.jfinal.core", "Controller") and
this.getMethod().getName().substring(0, 6) = "render") or (this.getMethod().getDeclaringType*().hasQualifiedName("com.ofsoft.cms.core.plugin.freemarker", "TempleteUtile") and this.getMethod().hasName("process"))
}
}
在上面的ql中我添加了TempleteUtile這個類,因為這個類的process第一個參數可控的話也會造成模版的問題,所以我們可以隨時去到ql中添加我們認為可能出現問題的模版渲染方法
文件類的問題
在ofcms中,文件的創建一般都是new File()這種形式創建的,所以我們的sink點應該為new File的參數為我們的sink點,所以構造ql:
class FileContruct extends ClassInstanceExpr{
FileContruct(){
this.getConstructor().getDeclaringType*().hasQualifiedName("java.io", "File")
}
}
污點追蹤
codeql提供了幾種數據流的查詢:
local data flow
local taint data flow
global data flow
global taint data flow
local data flow基本是用在一個方法中的,比如想要知道一個方法的入參是否可以進入到某一個方法,就可以用local data flow
global data flow是用在整個項目的,也是我們做污點追蹤用的最多的
簡單解釋一下taint和非taint有什么區別:taint的dataflow會在數據流分析的基礎上加上污點分析,比如
String a = "evil";
String b = a + a;
在使用taint的dataflow中,b也會被標記為被污染的變量
構造configure
class OfCmsTaint extends TaintTracking::Configuration{
OfCmsTaint(){
this = "OfCmsTaint"
}
override predicate isSource(DataFlow::Node source){
source.asExpr() instanceof OfCmsSource
}
override predicate isSink(DataFlow::Node sink){
exists(
FileContruct rawOutput |
sink.asExpr() = rawOutput.getAnArgument()
)
}
}
當我們需要去做污點分析的時候,我們需要繼承TaintTracking::Configuration這個類,來重寫兩個方法isSource和isSink,在這里,dataflow中的Node節點和我們直接使用的節點是不一樣的,我們需要使用asExpr或者asParamter來將其轉換為語法節點
這里可以看到,我們的source為我們之前定義的http參數的輸入地方,sink為我們之前定義的new File的這種實例化
結果分析
codeql只能給出從source到sink的一條路徑,但是這條路徑中的一些過濾和條件是無法被判斷的,這也就需要一部分的人工成本,讓我們來運行一下我們剛剛寫的ql:
import ofcms
from DataFlow::Node source, DataFlow::Node sink, OfCmsTaint config
where config.hasFlow(source, sink)
select source, sink
最后的查詢結果:
可以看到找到了11個可能存在問題的地方,我們來依次看一看是否有問題:
ReprotAction
第一個在ReprotAction這個類的expReport方法中:
可以很明顯看到,在獲取j參數之后,對jrxmlFileName沒有任何的校驗,導致我們可以穿越到其他目錄,但是文件后綴名必須為jrxml,而且在JasperCompileManager的compileReport函數中,對xml文檔沒有限制實體,導致可以造成XXE漏洞,這里很尷尬的利用點是:
需要一個文件上傳
后綴名必須為jrxml
TemplateController
在TemplateController這個類的getTemplates方法中:
在這里對獲取的參數沒有任何的校驗,導致可以跨越目錄列文件并且修改文件,但是在后面的實現中,我們只能修改和查看特定的文件
假設我們在tmp目錄下有著a.html和a.xml文件,我們可以跨越到tmp目錄下讀取并修改這兩個文件
TemplateController
還有一個地方就是save函數,這個函數在p0desta師傅的博客中也挖掘出了任意文件上傳漏洞:
很明顯的一任意文件上傳,文件名,路徑,文件內容全部可控,直接getshell
剩下的一個并不能造成影響,就不多說了
后記
在render的sink定義中,如果運行可以發現很多地方的前臺的一個小問題,也就是我們可以指定模版文件,ofcms使用了freemarker模版引擎,如果可以包含到我們自定義的模版文件,即可導致RCE,但是并沒有發現有一個文件上傳的點可以上傳文件到模版目錄下(除了上面的一個任意文件上傳),所以不太好前臺RCE
順手測了下發現前臺評論地方有存儲XSS,但是和codeql無關就不多說了
整個ql:
ofcms.qll
import java
import semmle.code.java.dataflow.TaintTracking
class OfCmsSource extends MethodAccess{
OfCmsSource(){
(this.getMethod().getDeclaringType*().hasQualifiedName("com.ofsoft.cms.admin.controller", "BaseController") and
(this.getMethod().getName().substring(0, 3) = "get"))
or
(this.getMethod().getDeclaringType*().hasQualifiedName("com.jfinal.core", "Controller") and
(this.getMethod().getName().substring(0, 3) = "get"))
or
(this.getMethod().getDeclaringType*().hasQualifiedName("javax.servlet.http", "HttpServletRequest") and (this.getMethod().getName().substring(0, 3) = "get"))
or
(this.getMethod().getDeclaringType*().hasQualifiedName("com.ofsoft.cms.api", "ApiBase") and
(this.getMethod().getName().substring(0, 3) = "get"))
}
}
class RenderMethod extends MethodAccess{
RenderMethod(){
(this.getMethod().getDeclaringType*().hasQualifiedName("com.jfinal.core", "Controller") and
this.getMethod().getName().substring(0, 6) = "render") or (this.getMethod().getDeclaringType*().hasQualifiedName("com.ofsoft.cms.core.plugin.freemarker", "TempleteUtile") and this.getMethod().hasName("process"))
}
}
class SqlMethod extends MethodAccess{
SqlMethod(){
this.getMethod().getDeclaringType*().hasQualifiedName("com.jfinal.plugin.activerecord", "Db")
}
}
class FileContruct extends ClassInstanceExpr{
FileContruct(){
this.getConstructor().getDeclaringType*().hasQualifiedName("java.io", "File")
}
}
class ServletOutput extends MethodAccess{
ServletOutput(){
this.getMethod().getDeclaringType*().hasQualifiedName("java.io", "PrintWriter")
}
}
class OfCmsTaint extends TaintTracking::Configuration{
OfCmsTaint(){
this = "OfCmsTaint"
}
override predicate isSource(DataFlow::Node source){
source.asExpr() instanceof OfCmsSource
}
override predicate isSink(DataFlow::Node sink){
exists(
FileContruct rawOutput |
sink.asExpr() = rawOutput.getAnArgument()
)
}
}
test.ql
import ofcms
from DataFlow::Node source, DataFlow::Node sink, OfCmsTaint config
where config.hasFlow(source, sink)
select source, sink
不足
感覺一個很大的問題是sink的定義,因為框架的變換以及一些開發者自己的工具類,以及一些漏洞可能根本不存在,導致sink的定義有時候挖不出來漏洞
像p0desta師傅測的CSRF漏洞,暫時想不到有什么好的辦法來定義sink,人工可能很好去看出來,但是不好用codeql語言定義這種漏洞
太菜了,有個點的任意文件讀取寫不出來ql,2333
師傅們教教我
感覺在定義的時候要盡量找共性,但是也不能找太深
參考文章:
總結
以上是生活随笔為你收集整理的codeql php,使用codeql 挖掘 ofcms的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python图片分类毕业设计成果报告书_
- 下一篇: 动态规划算法php,php算法学习之动态