如何通過Semmle QL找到Apache Struts的遠(yuǎn)程執(zhí)行代碼.,相信很多沒有經(jīng)驗(yàn)的人對(duì)此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個(gè)問題。
創(chuàng)新互聯(lián)公司是一家專業(yè)提供高縣企業(yè)網(wǎng)站建設(shè),專注與網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站、HTML5建站、小程序制作等業(yè)務(wù)。10年已為高縣眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站建設(shè)公司優(yōu)惠進(jìn)行中。
2018年4月,一個(gè)新的Apache Struts遠(yuǎn)程代碼執(zhí)行漏洞被報(bào)告。在Struts特定配置下,訪問特制的URL可以觸發(fā)該漏洞。漏洞編號(hào)為CVE-2018-11776(S2-057)。本文將介紹發(fā)現(xiàn)漏洞的過程。
許多漏洞涉及從不受信任的源(例如,用戶輸入)流向某個(gè)特定位置的數(shù)據(jù),這種情況下數(shù)據(jù)會(huì)以危險(xiǎn)的方式被利用(SQL查詢,反序列化以及一些其他解釋語言等等)。對(duì)于特定項(xiàng)目,開始著手研究此類問題的一種好方法是查看舊版本軟件的已知漏洞。這可以讓你深入了解所想要查找的各種源及接收點(diǎn)。
在本案例中,作者首先查看了RCE漏洞S2-032(CVE-2016-3081),S2-033(CVE-2016-3687)和S2-037(CVE-2016-4438)。與Struts中的許多其他RCE一樣,這些RCE涉及被認(rèn)為是OGNL表達(dá)式的非法輸入,允許攻擊者在 現(xiàn)在已經(jīng)識(shí)別并描述了一些不受信任的來源,下一步是為接收點(diǎn)做同樣的事情。如前所述,許多Struts RCE涉及將遠(yuǎn)程輸入解析為OGNL表達(dá)式。Struts中有許多函數(shù)最終將它們的參數(shù)作為OGNL表達(dá)式進(jìn)行評(píng)估;對(duì)于在本文中開始的三個(gè)漏洞,都使用了OgnlUtil::getValue(),但是在漏洞S2-045(CVE-2017-5638)中,使用了TextParseUtil::translateVariables()。我們可以尋找用于執(zhí)行OGNL表達(dá)式的函數(shù),而不是將每一個(gè)方法描述為QL中的單獨(dú)接收點(diǎn)。而OgnlUtil::compileAndExecute()和OgnlUtl::compileAndExecuteMethod()看起來像有希望的接收點(diǎn)。 作者在一個(gè)QL predicate中描述了它們,如下所示: 現(xiàn)在已經(jīng)在QL中定義了源和接收點(diǎn),所以可以在污點(diǎn)跟蹤查詢中使用這些定義。 這里作者使用DataFlow庫(kù)來定義DataFlow配置: 這里作者使用了之前定義的isActionProxySource和isOgnlSink predicates。 需要注意的是,作者還覆蓋了一個(gè)名為isAdditionalFlowStep的predicate。這個(gè)predicate允許包含可以傳播非法數(shù)據(jù)的額外步驟。例如,這允許將項(xiàng)目特定的信息合并到流配置中。再如,如果有通過某個(gè)網(wǎng)絡(luò)層進(jìn)行通信的組件,則可以在QL中描述那些各種網(wǎng)絡(luò)端點(diǎn)的代碼,允許DataFlow庫(kù)跟蹤非法數(shù)據(jù)。 對(duì)于此特定查詢,作者添加了兩個(gè)額外的流程步驟供DataFlow庫(kù)使用。 第一個(gè): 包含標(biāo)準(zhǔn)QL TaintTracking庫(kù)來跟蹤標(biāo)準(zhǔn)Java庫(kù)調(diào)用,字符串操作等。第二個(gè)是一個(gè)近似值,允許通過字段訪問跟蹤非法數(shù)據(jù): 這表示如果將字段分配給某個(gè)非法值,只要表達(dá)式都由相同類型的方法調(diào)用,那么對(duì)該字段的訪問也將被視為非法。查看以下案例: 如你所見,bar()中this.field的訪問可能并不總是非法。例如,如果在bar()之前未調(diào)用foo()。那么不會(huì)在默認(rèn)的DataFlow::Configuration中包含此流程步驟,因?yàn)槲覀儫o法保證數(shù)據(jù)始終以這種方式流動(dòng)。但是,對(duì)于找到漏洞,將它們包含在DataFlow::Configuration中就很有用。 在最新版本的源代碼上運(yùn)行查詢,并開始檢查結(jié)果,S2-032,S2-033和S2-037仍然被查詢標(biāo)記。在查看其他結(jié)果之前,先調(diào)查為什么即使代碼已修復(fù),這些特定的結(jié)果仍然被標(biāo)記。 雖然最初通過過濾輸入來修復(fù)第一個(gè)漏洞,但是在S2-037之后,Struts團(tuán)隊(duì)決定通過調(diào)用OgnlUtil::getMalue()替換對(duì)OgnlUtil::getMalue()的調(diào)用來修復(fù)漏洞。 方法callMethod()封裝對(duì)compileAndExecuteMethod()的調(diào)用: 并且compileAndExecuteMethod()在執(zhí)行之前對(duì)表達(dá)式執(zhí)行額外檢查: 這意味著我們實(shí)際上可以從接收點(diǎn)中移除compileAndExecuteMethod()。 在重新運(yùn)行查詢后,高亮的getMethod()作為接收點(diǎn)的調(diào)用的結(jié)果消失了。但是,仍然有一些結(jié)果突出顯示了DefaultActionInvocation.java中的代碼,這些代碼被認(rèn)為是已經(jīng)被修復(fù)的,例如對(duì)getActionName()的調(diào)用,并且這里的數(shù)據(jù)路徑并不是很明顯。 為了研究為什么這個(gè)結(jié)果被標(biāo)記,就需要看到DataFlow庫(kù)用來產(chǎn)生這個(gè)結(jié)果的每個(gè)流程步驟。QL允許編寫特殊的路徑問題查詢,這些查詢可生成可逐節(jié)點(diǎn)探索的可變長(zhǎng)度路徑,DataFlow庫(kù)允許編寫輸出此數(shù)據(jù)的查詢。 LGTM本身沒有路徑問題查詢的路徑探索UI,因此需要使用另一個(gè)Semmle應(yīng)用程序:QL for Eclipse。這是一個(gè)Eclipse插件,其中包含一個(gè)可視化工具,允許完成污點(diǎn)跟蹤中的各個(gè)步驟。用戶可以免費(fèi)下載并安裝此Eclipse插件。它不僅可以在LGTM.com上對(duì)開源項(xiàng)目進(jìn)行離線分析,還可以提供更強(qiáng)大的開發(fā)環(huán)境。下文的查詢可以在semmle-security-java目錄下的Semmle/SecurityQueries Git存儲(chǔ)庫(kù)中找到。你可以按照README.md文件中的說明在Eclipse插件中運(yùn)行它們。 首先,在initial.ql中運(yùn)行查詢。 在QL for Eclipse中,從DefaultActionInvocation.java中選擇結(jié)果后,你可以在Path Explorer窗口中看到從源到接收點(diǎn)的詳細(xì)路徑。 在上圖中,你可以看到,經(jīng)過幾個(gè)步驟后,調(diào)用getActionName()返回的值會(huì)流入對(duì)pkg.getActionConfigs()返回的對(duì)象的get()調(diào)用中的參數(shù): actionName來自某個(gè)地方的`getActionName` //chainedTo包含`actionName`并最終出現(xiàn)在`get`方法中 單擊下一步,就到了ValueStackShadowMap::get()方法,如下所示: 事實(shí)證明,因?yàn)閜kg.getActionConfigs()返回一個(gè)Map,而ValueStackShadowMap實(shí)現(xiàn)了Map接口,所以理論上pkg.getActionConfigs()返回的值可能是ValueStackShadowMap的一個(gè)實(shí)例。因此,QL DataFlow庫(kù)顯示了從變量chainedTo到類ValueStackShadowMap中的get()實(shí)現(xiàn)的潛在流程。實(shí)際上,ValueStackShadowMap類屬于jasperreports插件,該類的實(shí)例僅在幾個(gè)地方創(chuàng)建,并都不會(huì)被pkg.getActionConfigs()返回。在發(fā)現(xiàn)ValueStackShadowMap::get()不太可能被命中之后,作者通過在DataFlow::Configuration中添加一個(gè)過濾來刪除依賴它的結(jié)果: 這個(gè)predicate意思是如果非法數(shù)據(jù)流入ValueStackShadowMap的get()或containsKey()方法,那么就不要繼續(xù)跟蹤它。 只有10對(duì)源和接收點(diǎn),就很容易通過手工檢查發(fā)現(xiàn)這些是否真正存在問題。通過一些路徑,作者發(fā)現(xiàn)有些路徑是無效的,因?yàn)樗鼈冊(cè)跍y(cè)試用例中,所以在查詢中添加了一些過濾來過濾掉這些路徑。這就留下了一些非常有趣的結(jié)果。 以ServletActionRedirectResult.java中的源代碼為例: 在第一步中,調(diào)用getNamespace()的源通過變量namespace流入ActionMapping構(gòu)造函數(shù)的參數(shù): 繼續(xù)執(zhí)行這些步驟之后,可以看到getUriFromActionMapping()返回一個(gè)URL字符串,該字符串使用構(gòu)造的ActionMapping中的namespace。然后通過變量tmpLocation流入setLocation()的參數(shù),然后setLocation()在超類StrutsResultSupport中設(shè)置字段位置: 然后代碼在ServletActionResult上調(diào)用execute(): 它將location字段傳遞給對(duì)conditionalParse()的調(diào)用: 然后conditionalParse()將位置傳遞給translateVariables(): 所以當(dāng)在ServletActionRedirectResult中沒有設(shè)置namespace參數(shù)時(shí),代碼從ActionProxy獲取namespace,然后將其作為OGNL表達(dá)式進(jìn)行評(píng)估。為了測(cè)試這個(gè),作者通過以下方法替換了showcase應(yīng)用程序中的一個(gè)配置文件(例如struts-actionchaining.xml)中的struts標(biāo)記: 然后在本地運(yùn)行showcase應(yīng)用程序,并訪問了一個(gè)可以觸發(fā)此漏洞的URL并執(zhí)行shell命令以在計(jì)算機(jī)上打開計(jì)算器應(yīng)用程序。 不僅如此,來自ActionChainResult,PostbackResult和ServletUrlRenderer的不可信來源也同樣有效!PortletActionRedirectResult中的那個(gè)可能也可以,但沒有被測(cè)試。四個(gè)RCE足以證明問題的嚴(yán)重性。 看完上述內(nèi)容,你們掌握如何通過Semmle QL找到Apache Struts的遠(yuǎn)程執(zhí)行代碼.的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!識(shí)別OGNL接收點(diǎn)
predicate isOgnlSink(DataFlow::Node sink) { exists(MethodAccess ma | ma.getMethod().hasName("compileAndExecute") or ma.getMethod().hasName("compileAndExecuteMethod") | ma.getMethod().getDeclaringType().getName().matches("OgnlUtil") and sink.asExpr() = ma.getArgument(0) )}
第一次嘗試污點(diǎn)跟蹤
class OgnlTaintTrackingCfg extends DataFlow::Configuration { OgnlTaintTrackingCfg() { this = "mapping" } override predicate isSource(DataFlow::Node source) { isActionProxySource(source) } override predicate isSink(DataFlow::Node sink) { isOgnlSink(sink) } override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { TaintTracking::localTaintStep(node1, node2) or exists(Field f, RefType t | node1.asExpr() = f.getAnAssignedValue() and node2.asExpr() = f.getAnAccess() and node1.asExpr().getEnclosingCallable().getDeclaringType() = t and node2.asExpr().getEnclosingCallable().getDeclaringType() = t ) }}from OgnlTaintTrackingCfg cfg, DataFlow::Node source, DataFlow::Node sinkwhere cfg.hasFlow(source, sink)select source, sink
TaintTracking::localTaintStep(node1, node2)
exists(Field f, RefType t | node1.asExpr() = f.getAnAssignedValue() and node2.asExpr() = f.getAnAccess() and node1.asExpr().getEnclosingCallable().getDeclaringType() = t and node2.asExpr().getEnclosingCallable().getDeclaringType() = t)
public void foo(String taint) { this.field = taint;}public void bar() { String x = this.field; //x非法,因?yàn)樽侄伪环峙浣o`foo`中的非法值}
初始結(jié)果和查詢細(xì)化
methodResult = ognlUtil.callMethod(methodName + "()", getStack().getContext(), action);
public Object callMethod(final String name, final Map
private
路徑探索和進(jìn)一步查詢細(xì)化
String chainedTo = actionName + nameSeparator + resultCode
ActionConfig chainedToConfig = pkg.getActionConfigs().get(chainedTo)
public Object get(Object key) { Object value = super.get(key); //<--- key gets tainted? if ((value == null) && key instanceof String) { value = valueStack.findValue((String) key); //<--- findValue ended up evaluating `key` } return value;}
override predicate isBarrier(DataFlow::Node node) { exists(Method m | (m.hasName("get") or m.hasName("containsKey")) and m.getDeclaringType().hasName("ValueStackShadowMap") and node.getEnclosingCallable() = m )}
新漏洞:CVE-2018-11776
public void execute(ActionInvocation invocation) throws Exception { actionName = conditionalParse(actionName, invocation); if (namespace == null) { namespace = invocation.getProxy().getNamespace(); //源 } else { namespace = conditionalParse(namespace, invocation); } if (method == null) { method = ""; } else { method = conditionalParse(method, invocation); } String tmpLocation = actionMapper.getUriFromActionMapping(new ActionMapping(actionName, namespace, method, null)); //namespace進(jìn)入ActionMapping的構(gòu)造函數(shù) setLocation(tmpLocation);
public void setLocation(String location) { this.location = location;}
String tmpLocation = actionMapper.getUriFromActionMapping(new ActionMapping(actionName, namespace, method, null));setLocation(tmpLocation);super.execute(invocation);
public void execute(ActionInvocation invocation) throws Exception { lastFinalLocation = conditionalParse(location, invocation); doExecute(lastFinalLocation, invocation);}
protected String conditionalParse(String param, ActionInvocation invocation) { if (parse && param != null && invocation != null) { return TextParseUtil.translateVariables( param, invocation.getStack(), new EncodingParsedValueEvaluator()); } else { return param; }}
本文題目:如何通過SemmleQL找到ApacheStruts的遠(yuǎn)程執(zhí)行代碼.
URL標(biāo)題:http://weahome.cn/article/pcschg.html