序
今天我們利用codeql分析下“cookie未啟用httponly“這類的安全問題,由此加深自己對(duì)codeql的使用。如果效果良好,可以考慮修復(fù)vulnerability-goapp的其他漏洞。
分析go程序時(shí)必須額外下載codeql-go
說明
審計(jì)對(duì)象
Vulnerability-goapp:Vulnerable golang Web application for education。
修改
由于在該項(xiàng)目中所有的 Cookie 都沒有設(shè)置 http-only 屬性,因此我們需要先對(duì)其進(jìn)行修改才能進(jìn)行比較。以下是對(duì)原句的重寫: 記錄修改:在某些cookie設(shè)置中增加http-only選項(xiàng)。
pkgadminadmin.go修改如下。
pkgloginlogin.go修改如下。
pkgregisterregister.go修改如下。
修改后記得重新生成一次database(如果需要覆蓋舊的DATabase的話,則需要先刪除舊的再生成新的。
目的
就是通過codeql腳本來發(fā)現(xiàn)其中未設(shè)置httponly和設(shè)置了httponly的但httponly的值為false(一般不會(huì)這樣,但保不齊有)的這樣存在漏洞的點(diǎn)。
確定Source和Sink
Sink定義
Sink很簡(jiǎn)單,設(shè)置Cookie時(shí),需要用到http.SetCookie方法,而需要設(shè)置的Cookie值是這個(gè)函數(shù)的第二個(gè)參數(shù),然后我們可以寫出找到類似這樣Sink的查詢語句。
import go from DataFlow::Node sink where exists(DataFlow::CallNode c | c.getTarget().hasQualifiedName("net/http", "SetCookie") and c.getArgument(1) = sink ) select sink
運(yùn)行后可獲得以下結(jié)果,點(diǎn)擊任意條目都會(huì)跳轉(zhuǎn)到復(fù)合要求的代碼段下。
我們將其轉(zhuǎn)換成一個(gè)Sink類,如下。
private class Sink extends DataFlow::Node { Sink() { exists(DataFlow::CallNode c | c.getTarget().hasQualifiedName("net/http", "SetCookie") and c.getArgument(1) = this ) } }
這樣之后我們通過將一個(gè)變量定義成Sink的話,就是指符合條件的所有代碼片段,例如:
import go private class Sink extends DataFlow::Node { Sink() { exists(DataFlow::CallNode c | c.getTarget().hasQualifiedName("net/http", "SetCookie") and c.getArgument(1) = this ) } } from Sink s select s
運(yùn)行后會(huì)獲得同樣的結(jié)果。
Source定義
然后我們?cè)賮泶_定Source,從http.SetCookie方法接收的參數(shù)來看,實(shí)際第二個(gè)參數(shù)是接收一個(gè)Cookie的結(jié)構(gòu)體的指針。
所以我們先要找到這樣一個(gè)結(jié)構(gòu)體,我們可以先把項(xiàng)目中所有的結(jié)構(gòu)體列出來。
codeql-go中關(guān)于結(jié)構(gòu)體的定義如下。
所以我們的查詢腳本例如。
import go from StructLit source select source
也如我們預(yù)期的一樣列出了所有的結(jié)構(gòu)體。
然后接下來就是剔除其他不相干的內(nèi)容,對(duì)類型做限制。
關(guān)于hasQualifiedName方法,在各種Codeql-go中的各種類型都有相同的方法,定義如下,標(biāo)記對(duì)象的是在屬于哪個(gè)包,叫什么名。
如果不確定的話,可以通過,getPackage和getName打印相關(guān)字段,例如。
import go from StructLit source // where source.getType().hasQualifiedName("net/http", "Cookie") select source.getType().getPackage(), source.getType().getName()
結(jié)果如下。
我們可以找到source定義,例如。
import go from StructLit source where source.getType().hasQualifiedName("net/http", "Cookie") select source
同樣轉(zhuǎn)換成DataFlow::Node的子類。
private class Source extends DataFlow::Node { Source() { exists(StructLit s | s.getType().hasQualifiedName("net/http", "Cookie") and this.asExpr() = s) } }
TaintConfig定義
簡(jiǎn)單的數(shù)據(jù)流
有了Source和Sink,簡(jiǎn)單定義TaintConfig,就能獲得所有從Source到Sink的數(shù)據(jù)流。
import go private class Source extends DataFlow::Node { Source() { exists(StructLit s | s.getType().hasQualifiedName("net/http", "Cookie") and this.asExpr() = s) } } private class Sink extends DataFlow::Node { Sink() { exists(DataFlow::CallNode c | c.getTarget().hasQualifiedName("net/http", "SetCookie") and c.getArgument(1) = this ) } } class Configuration extends TaintTracking::Configuration { Configuration() { this = "HttpOnly" } override predicate isSource(DataFlow::Node source) { source instanceof Source } override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } } from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select source, sink
結(jié)果如下:
剔除
然而,我們?nèi)晕匆瞥O(shè)置了httponly=true的部分。因此需要加入限定條件,即將設(shè)有HttpOnly屬性為true的數(shù)據(jù)流從結(jié)果中排除。
我們可以 CodeQL 提供的 TaintTracking::isSanitizer,來過濾無害節(jié)點(diǎn):
override predicate isSanitizer(DataFlow::Node node) { exists(Write w, Field f, DataFlow::Node rhs | f.hasQualifiedName("net/http", "Cookie", "HttpOnly") and w.writesField(node, f, rhs) and rhs.getBoolValue() = true ) }
運(yùn)行結(jié)果如下,但有一處地方需要注意。紅框中實(shí)際有對(duì)HttpOnly進(jìn)行設(shè)置,但我們的腳本并不能識(shí)別這樣的一個(gè)數(shù)據(jù)流。后面試了各種方法,最終找到一種解決方式,將isSanitizer修改成以下內(nèi)容。
override predicate isSanitizer(DataFlow::Node node) { exists(Write w, Field f, DataFlow::Node n, DataFlow::Node rhs | f.hasQualifiedName("net/http", "Cookie", "HttpOnly") and w.writesField(n, f, rhs) and rhs.getBoolValue() = true and node = n.getAPredecessor*()n ) }
其中node=n.getAPredecessor*()是說node是n的前置數(shù)據(jù)流節(jié)點(diǎn),數(shù)據(jù)可以在0個(gè)或多個(gè)步驟中從node流到n。
最終腳本
加上一些信息,模仿官方的示例,最終腳本如下。
/** * @name Cookie未設(shè)置httponly * @description Cookies包含一個(gè)HTTPOnly的設(shè)置選項(xiàng),可以使此cookie不能被js讀取,而只能用于HTTP請(qǐng)求。 * @kind path-problem * @problem.severity error * @precision low * @id go/Cookie-not-set-httponly * @tags security */ import go import DataFlow::PathGraph private class Source extends DataFlow::Node { Source() { exists(StructLit s | s.getType().hasQualifiedName("net/http", "Cookie") and this.asExpr() = s) } } private class Sink extends DataFlow::Node { Sink() { exists(DataFlow::CallNode c | c.getTarget().hasQualifiedName("net/http", "SetCookie") and c.getArgument(1) = this ) } } class Configuration extends TaintTracking::Configuration { Configuration() { this = "HttpOnly" } override predicate isSource(DataFlow::Node source) { source instanceof Source } override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } override predicate isSanitizer(DataFlow::Node node) { exists(Write w, Field f, DataFlow::Node n, DataFlow::Node rhs | f.hasQualifiedName("net/http", "Cookie", "HttpOnly") and w.writesField(n, f, rhs) and rhs.getBoolValue() = true and node = n.getAPredecessor*() ) } } from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, "Cookie-not-set-httponly in $@.", source.getNode(), "here"
最終篩選出存在問題的內(nèi)容。