codeql php,使用codeql 挖掘 ofcms
前言
網(wǎng)上關(guān)于codeql的文章并不多,國內(nèi)現(xiàn)在對(duì)codeql的研究相對(duì)比較少,可能是因?yàn)閏odeql暫時(shí)沒有中文文檔,資料也相對(duì)較少,需要比較好的英語功底,但是我認(rèn)為在隨著代碼量越來越多,傳統(tǒng)的自動(dòng)化漏洞挖掘工具的瓶頸無法突破的情況下,codeql相當(dāng)于是一種折中的辦法,通過codeql的輔助,來減少漏洞挖掘人員的工作,更加關(guān)注漏洞的發(fā)現(xiàn)和利用過程
之所以選ofcms,是因?yàn)橛衟0desta師傅之前的審計(jì)經(jīng)驗(yàn),而且使用codeql審計(jì)cms尚屬第一次,所以選用了ofcms審計(jì)
ql構(gòu)造
在ql中,漏洞挖掘是根據(jù)污點(diǎn)追蹤進(jìn)行的,所以我們需要知道我們的挖掘的cms的source點(diǎn)在哪里,sink點(diǎn)在哪里,相對(duì)來說,source點(diǎn)比較固定,一般就是http的請(qǐng)求參數(shù),請(qǐng)求頭這一類的
但是sink比較難以確定,由于現(xiàn)在的web應(yīng)用經(jīng)常使用框架,有些文件讀取,html輸出其實(shí)是背后的框架在做,所以這就導(dǎo)致了我們的sink定義不可能是一成不變的,要對(duì)整個(gè)web應(yīng)用有一個(gè)大致的了解,才能定義對(duì)應(yīng)的sink
source點(diǎn)的ql
source點(diǎn)很清楚,對(duì)于一個(gè)web應(yīng)用來說,http請(qǐng)求參數(shù),http請(qǐng)求頭,我們關(guān)注ofcms中對(duì)請(qǐng)求參數(shù)的獲取方式:
ofcms使用了jfinal這個(gè)框架,而ofcms繼承了jfinal的controller來獲取參數(shù),在整個(gè)ofcms中大體有三種類型來獲取請(qǐng)求參數(shù):
BaseController
Controller(Jfinal提供)
ApiBase
所以我們的source都是根據(jù)這幾個(gè)類展開的,在觀察這幾個(gè)類之后很容易發(fā)現(xiàn),所有的獲取http參數(shù)的方法都是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就算定義完了,接下來就是定義對(duì)應(yīng)的sink了
sink點(diǎn)的ql
相對(duì)于source的固定,sink就很不固定了,常見的web漏洞一般來說都可以作為sink,而且因?yàn)榭蚣艿牟煌?#xff0c;同一種漏洞在不同框架下的ql都是不一樣的,所以我們需要略微分析一下整個(gè)web應(yīng)用在做文件讀取,模版渲染等操作的時(shí)候一般都用的是什么方法
模版渲染的問題
Jfinal中對(duì)模版渲染有一系列的render方法:
可以看到,所有都是render開頭,所以我們對(duì)方法名的判斷很簡單,截取前面6個(gè)字符,判斷是否為render,隨便找一個(gè)項(xiàng)目使用render的地方,可以發(fā)現(xiàn)render其實(shí)是在com.jfinal.core.Controller里面定義的方法,所以現(xiàn)在我們唯一確定了模版渲染的方法,所以我們的sink也就呼之欲出了,也就是這些render方法的參數(shù),所以構(gòu)造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這個(gè)類,因?yàn)檫@個(gè)類的process第一個(gè)參數(shù)可控的話也會(huì)造成模版的問題,所以我們可以隨時(shí)去到ql中添加我們認(rèn)為可能出現(xiàn)問題的模版渲染方法
文件類的問題
在ofcms中,文件的創(chuàng)建一般都是new File()這種形式創(chuàng)建的,所以我們的sink點(diǎn)應(yīng)該為new File的參數(shù)為我們的sink點(diǎn),所以構(gòu)造ql:
class FileContruct extends ClassInstanceExpr{
FileContruct(){
this.getConstructor().getDeclaringType*().hasQualifiedName("java.io", "File")
}
}
污點(diǎn)追蹤
codeql提供了幾種數(shù)據(jù)流的查詢:
local data flow
local taint data flow
global data flow
global taint data flow
local data flow基本是用在一個(gè)方法中的,比如想要知道一個(gè)方法的入?yún)⑹欠窨梢赃M(jìn)入到某一個(gè)方法,就可以用local data flow
global data flow是用在整個(gè)項(xiàng)目的,也是我們做污點(diǎn)追蹤用的最多的
簡單解釋一下taint和非taint有什么區(qū)別:taint的dataflow會(huì)在數(shù)據(jù)流分析的基礎(chǔ)上加上污點(diǎn)分析,比如
String a = "evil";
String b = a + a;
在使用taint的dataflow中,b也會(huì)被標(biāo)記為被污染的變量
構(gòu)造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()
)
}
}
當(dāng)我們需要去做污點(diǎn)分析的時(shí)候,我們需要繼承TaintTracking::Configuration這個(gè)類,來重寫兩個(gè)方法isSource和isSink,在這里,dataflow中的Node節(jié)點(diǎn)和我們直接使用的節(jié)點(diǎn)是不一樣的,我們需要使用asExpr或者asParamter來將其轉(zhuǎn)換為語法節(jié)點(diǎn)
這里可以看到,我們的source為我們之前定義的http參數(shù)的輸入地方,sink為我們之前定義的new File的這種實(shí)例化
結(jié)果分析
codeql只能給出從source到sink的一條路徑,但是這條路徑中的一些過濾和條件是無法被判斷的,這也就需要一部分的人工成本,讓我們來運(yùn)行一下我們剛剛寫的ql:
import ofcms
from DataFlow::Node source, DataFlow::Node sink, OfCmsTaint config
where config.hasFlow(source, sink)
select source, sink
最后的查詢結(jié)果:
可以看到找到了11個(gè)可能存在問題的地方,我們來依次看一看是否有問題:
ReprotAction
第一個(gè)在ReprotAction這個(gè)類的expReport方法中:
可以很明顯看到,在獲取j參數(shù)之后,對(duì)jrxmlFileName沒有任何的校驗(yàn),導(dǎo)致我們可以穿越到其他目錄,但是文件后綴名必須為jrxml,而且在JasperCompileManager的compileReport函數(shù)中,對(duì)xml文檔沒有限制實(shí)體,導(dǎo)致可以造成XXE漏洞,這里很尷尬的利用點(diǎn)是:
需要一個(gè)文件上傳
后綴名必須為jrxml
TemplateController
在TemplateController這個(gè)類的getTemplates方法中:
在這里對(duì)獲取的參數(shù)沒有任何的校驗(yàn),導(dǎo)致可以跨越目錄列文件并且修改文件,但是在后面的實(shí)現(xiàn)中,我們只能修改和查看特定的文件
假設(shè)我們?cè)趖mp目錄下有著a.html和a.xml文件,我們可以跨越到tmp目錄下讀取并修改這兩個(gè)文件
TemplateController
還有一個(gè)地方就是save函數(shù),這個(gè)函數(shù)在p0desta師傅的博客中也挖掘出了任意文件上傳漏洞:
很明顯的一任意文件上傳,文件名,路徑,文件內(nèi)容全部可控,直接getshell
剩下的一個(gè)并不能造成影響,就不多說了
后記
在render的sink定義中,如果運(yùn)行可以發(fā)現(xiàn)很多地方的前臺(tái)的一個(gè)小問題,也就是我們可以指定模版文件,ofcms使用了freemarker模版引擎,如果可以包含到我們自定義的模版文件,即可導(dǎo)致RCE,但是并沒有發(fā)現(xiàn)有一個(gè)文件上傳的點(diǎn)可以上傳文件到模版目錄下(除了上面的一個(gè)任意文件上傳),所以不太好前臺(tái)RCE
順手測了下發(fā)現(xiàn)前臺(tái)評(píng)論地方有存儲(chǔ)XSS,但是和codeql無關(guān)就不多說了
整個(gè)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
不足
感覺一個(gè)很大的問題是sink的定義,因?yàn)榭蚣艿淖儞Q以及一些開發(fā)者自己的工具類,以及一些漏洞可能根本不存在,導(dǎo)致sink的定義有時(shí)候挖不出來漏洞
像p0desta師傅測的CSRF漏洞,暫時(shí)想不到有什么好的辦法來定義sink,人工可能很好去看出來,但是不好用codeql語言定義這種漏洞
太菜了,有個(gè)點(diǎn)的任意文件讀取寫不出來ql,2333
師傅們教教我
感覺在定義的時(shí)候要盡量找共性,但是也不能找太深
參考文章:
總結(jié)
以上是生活随笔為你收集整理的codeql php,使用codeql 挖掘 ofcms的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python图片分类毕业设计成果报告书_
- 下一篇: 4. DICOM图像层级分类-DCMTK