溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何通過Semmle QL找到Apache Struts的遠程執(zhí)行代碼.

發(fā)布時間:2021-12-22 22:03:23 來源:億速云 閱讀:135 作者:柒染 欄目:安全技術(shù)

如何通過Semmle QL找到Apache Struts的遠程執(zhí)行代碼.,相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

前言

2018年4月,一個新的Apache Struts遠程代碼執(zhí)行漏洞被報告。在Struts特定配置下,訪問特制的URL可以觸發(fā)該漏洞。漏洞編號為CVE-2018-11776(S2-057)。本文將介紹發(fā)現(xiàn)漏洞的過程。    

映射攻擊面

許多漏洞涉及從不受信任的源(例如,用戶輸入)流向某個特定位置的數(shù)據(jù),這種情況下數(shù)據(jù)會以危險的方式被利用(SQL查詢,反序列化以及一些其他解釋語言等等)。對于特定項目,開始著手研究此類問題的一種好方法是查看舊版本軟件的已知漏洞。這可以讓你深入了解所想要查找的各種源及接收點。

在本案例中,作者首先查看了RCE漏洞S2-032(CVE-2016-3081),S2-033(CVE-2016-3687)和S2-037(CVE-2016-4438)。與Struts中的許多其他RCE一樣,這些RCE涉及被認為是OGNL表達式的非法輸入,允許攻擊者在服務(wù)器上運行任意代碼。這三個漏洞特別有趣,不僅因為它們讓我們對Struts的內(nèi)部工作有了一些了解,而且還因為同樣的問題需要三次才能修復(fù)!

所有這三個問題都是遠程輸入通過變量methodName作為參數(shù)傳遞給方法OgnlUtil::getValue()的結(jié)果。

String methodName = proxy.getMethod();    //<--- untrusted source, but where from?LOG.debug("Executing action method = {}", methodName);String timerKey = "invokeAction: " + proxy.getActionName();try {    UtilTimerStack.push(timerKey);    Object methodResult;    try {        methodResult = ognlUtil.getValue(methodName + "()", getStack().getContext(), action); //<--- RCE

這里的proxy字段是ActionProxy類型,它是一個接口。在查看它的定義時,作者發(fā)現(xiàn)除了方法getMethod()(在上面的代碼中用于賦值的變量methodName)之外,還有各種其他的方法,如getActionName()和getNamespace()。這些方法貌似會從URL返回信息。所以作者開始假設(shè)所有這些方法都可能返回不受信任的輸入。

現(xiàn)在可以使用QL開始對這些不受信任的來源進行建模:

class ActionProxyGetMethod extends Method {  ActionProxyGetMethod() {    getDeclaringType().getASupertype*().hasQualifiedName("com.opensymphony.xwork2", "ActionProxy") and    (      hasName("getMethod") or      hasName("getNamespace") or      hasName("getActionName")    )  }}predicate isActionProxySource(DataFlow::Node source) {   source.asExpr().(MethodAccess).getMethod() instanceof ActionProxyGetMethod}

識別OGNL接收點

現(xiàn)在已經(jīng)識別并描述了一些不受信任的來源,下一步是為接收點做同樣的事情。如前所述,許多Struts RCE涉及將遠程輸入解析為OGNL表達式。Struts中有許多函數(shù)最終將它們的參數(shù)作為OGNL表達式進行評估;對于在本文中開始的三個漏洞,都使用了OgnlUtil::getValue(),但是在漏洞S2-045(CVE-2017-5638)中,使用了TextParseUtil::translateVariables()。我們可以尋找用于執(zhí)行OGNL表達式的函數(shù),而不是將每一個方法描述為QL中的單獨接收點。而OgnlUtil::compileAndExecute()和OgnlUtl::compileAndExecuteMethod()看起來像有希望的接收點。

作者在一個QL predicate中描述了它們,如下所示:

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)  )}

第一次嘗試污點跟蹤

現(xiàn)在已經(jīng)在QL中定義了源和接收點,所以可以在污點跟蹤查詢中使用這些定義。 這里作者使用DataFlow庫來定義DataFlow配置:

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

這里作者使用了之前定義的isActionProxySource和isOgnlSink predicates。

需要注意的是,作者還覆蓋了一個名為isAdditionalFlowStep的predicate。這個predicate允許包含可以傳播非法數(shù)據(jù)的額外步驟。例如,這允許將項目特定的信息合并到流配置中。再如,如果有通過某個網(wǎng)絡(luò)層進行通信的組件,則可以在QL中描述那些各種網(wǎng)絡(luò)端點的代碼,允許DataFlow庫跟蹤非法數(shù)據(jù)。

對于此特定查詢,作者添加了兩個額外的流程步驟供DataFlow庫使用。 第一個:

TaintTracking::localTaintStep(node1, node2)

包含標(biāo)準(zhǔn)QL TaintTracking庫來跟蹤標(biāo)準(zhǔn)Java庫調(diào)用,字符串操作等。第二個是一個近似值,允許通過字段訪問跟蹤非法數(shù)據(jù):

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)

這表示如果將字段分配給某個非法值,只要表達式都由相同類型的方法調(diào)用,那么對該字段的訪問也將被視為非法。查看以下案例:

public void foo(String taint) {  this.field = taint;}public void bar() {  String x = this.field; //x非法,因為字段被分配給`foo`中的非法值}

如你所見,bar()中this.field的訪問可能并不總是非法。例如,如果在bar()之前未調(diào)用foo()。那么不會在默認的DataFlow::Configuration中包含此流程步驟,因為我們無法保證數(shù)據(jù)始終以這種方式流動。但是,對于找到漏洞,將它們包含在DataFlow::Configuration中就很有用。

初始結(jié)果和查詢細化

在最新版本的源代碼上運行查詢,并開始檢查結(jié)果,S2-032,S2-033和S2-037仍然被查詢標(biāo)記。在查看其他結(jié)果之前,先調(diào)查為什么即使代碼已修復(fù),這些特定的結(jié)果仍然被標(biāo)記。

雖然最初通過過濾輸入來修復(fù)第一個漏洞,但是在S2-037之后,Struts團隊決定通過調(diào)用OgnlUtil::getMalue()替換對OgnlUtil::getMalue()的調(diào)用來修復(fù)漏洞。

methodResult = ognlUtil.callMethod(methodName + "()", getStack().getContext(), action);

方法callMethod()封裝對compileAndExecuteMethod()的調(diào)用:

public Object callMethod(final String name, final Map<String, Object> context, final Object root) throws OgnlException {  return compileAndExecuteMethod(name, context, new OgnlTask<Object>() {    public Object execute(Object tree) throws OgnlException {      return Ognl.getValue(tree, context, root);    }  });}

并且compileAndExecuteMethod()在執(zhí)行之前對表達式執(zhí)行額外檢查:

private <T> Object compileAndExecuteMethod(String expression, Map<String, Object> context, OgnlTask<T> task) throws OgnlException {  Object tree;  if (enableExpressionCache) {    tree = expressions.get(expression);    if (tree == null) {      tree = Ognl.parseExpression(expression);      checkSimpleMethod(tree, context); //額外檢查    }

這意味著我們實際上可以從接收點中移除compileAndExecuteMethod()。

在重新運行查詢后,高亮的getMethod()作為接收點的調(diào)用的結(jié)果消失了。但是,仍然有一些結(jié)果突出顯示了DefaultActionInvocation.java中的代碼,這些代碼被認為是已經(jīng)被修復(fù)的,例如對getActionName()的調(diào)用,并且這里的數(shù)據(jù)路徑并不是很明顯。

路徑探索和進一步查詢細化

為了研究為什么這個結(jié)果被標(biāo)記,就需要看到DataFlow庫用來產(chǎn)生這個結(jié)果的每個流程步驟。QL允許編寫特殊的路徑問題查詢,這些查詢可生成可逐節(jié)點探索的可變長度路徑,DataFlow庫允許編寫輸出此數(shù)據(jù)的查詢。

LGTM本身沒有路徑問題查詢的路徑探索UI,因此需要使用另一個Semmle應(yīng)用程序:QL for Eclipse。這是一個Eclipse插件,其中包含一個可視化工具,允許完成污點跟蹤中的各個步驟。用戶可以免費下載并安裝此Eclipse插件。它不僅可以在LGTM.com上對開源項目進行離線分析,還可以提供更強大的開發(fā)環(huán)境。下文的查詢可以在semmle-security-java目錄下的Semmle/SecurityQueries Git存儲庫中找到。你可以按照README.md文件中的說明在Eclipse插件中運行它們。

首先,在initial.ql中運行查詢。 在QL for Eclipse中,從DefaultActionInvocation.java中選擇結(jié)果后,你可以在Path Explorer窗口中看到從源到接收點的詳細路徑。

如何通過Semmle QL找到Apache Struts的遠程執(zhí)行代碼.

在上圖中,你可以看到,經(jīng)過幾個步驟后,調(diào)用getActionName()返回的值會流入對pkg.getActionConfigs()返回的對象的get()調(diào)用中的參數(shù):

String chainedTo = actionName + nameSeparator + resultCode

actionName來自某個地方的`getActionName`

ActionConfig chainedToConfig = pkg.getActionConfigs().get(chainedTo)

//chainedTo包含`actionName`并最終出現(xiàn)在`get`方法中

單擊下一步,就到了ValueStackShadowMap::get()方法,如下所示:

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;}

事實證明,因為pkg.getActionConfigs()返回一個Map,而ValueStackShadowMap實現(xiàn)了Map接口,所以理論上pkg.getActionConfigs()返回的值可能是ValueStackShadowMap的一個實例。因此,QL DataFlow庫顯示了從變量chainedTo到類ValueStackShadowMap中的get()實現(xiàn)的潛在流程。實際上,ValueStackShadowMap類屬于jasperreports插件,該類的實例僅在幾個地方創(chuàng)建,并都不會被pkg.getActionConfigs()返回。在發(fā)現(xiàn)ValueStackShadowMap::get()不太可能被命中之后,作者通過在DataFlow::Configuration中添加一個過濾來刪除依賴它的結(jié)果:

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  )}

這個predicate意思是如果非法數(shù)據(jù)流入ValueStackShadowMap的get()或containsKey()方法,那么就不要繼續(xù)跟蹤它。

新漏洞:CVE-2018-11776

只有10對源和接收點,就很容易通過手工檢查發(fā)現(xiàn)這些是否真正存在問題。通過一些路徑,作者發(fā)現(xiàn)有些路徑是無效的,因為它們在測試用例中,所以在查詢中添加了一些過濾來過濾掉這些路徑。這就留下了一些非常有趣的結(jié)果。

以ServletActionRedirectResult.java中的源代碼為例:

如何通過Semmle QL找到Apache Struts的遠程執(zhí)行代碼.

在第一步中,調(diào)用getNamespace()的源通過變量namespace流入ActionMapping構(gòu)造函數(shù)的參數(shù):

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進入ActionMapping的構(gòu)造函數(shù)  setLocation(tmpLocation);

繼續(xù)執(zhí)行這些步驟之后,可以看到getUriFromActionMapping()返回一個URL字符串,該字符串使用構(gòu)造的ActionMapping中的namespace。然后通過變量tmpLocation流入setLocation()的參數(shù),然后setLocation()在超類StrutsResultSupport中設(shè)置字段位置:

public void setLocation(String location) {    this.location = location;}

然后代碼在ServletActionResult上調(diào)用execute():

String tmpLocation = actionMapper.getUriFromActionMapping(new ActionMapping(actionName, namespace, method, null));setLocation(tmpLocation);super.execute(invocation);

它將location字段傳遞給對conditionalParse()的調(diào)用:

public void execute(ActionInvocation invocation) throws Exception {    lastFinalLocation = conditionalParse(location, invocation);    doExecute(lastFinalLocation, invocation);}

然后conditionalParse()將位置傳遞給translateVariables():

protected String conditionalParse(String param, ActionInvocation invocation) {    if (parse && param != null && invocation != null) {        return TextParseUtil.translateVariables(            param,            invocation.getStack(),            new EncodingParsedValueEvaluator());    } else {        return param;    }}

所以當(dāng)在ServletActionRedirectResult中沒有設(shè)置namespace參數(shù)時,代碼從ActionProxy獲取namespace,然后將其作為OGNL表達式進行評估。為了測試這個,作者通過以下方法替換了showcase應(yīng)用程序中的一個配置文件(例如struts-actionchaining.xml)中的struts標(biāo)記:

<struts>    <package name="actionchaining" extends="struts-default">        <action name="actionChain1" class="org.apache.struts2.showcase.actionchaining.ActionChain1">           <result type="redirectAction">             <param name = "actionName">register2</param>           </result>        </action>    </package></struts>

然后在本地運行showcase應(yīng)用程序,并訪問了一個可以觸發(fā)此漏洞的URL并執(zhí)行shell命令以在計算機上打開計算器應(yīng)用程序。

不僅如此,來自ActionChainResult,PostbackResult和ServletUrlRenderer的不可信來源也同樣有效!PortletActionRedirectResult中的那個可能也可以,但沒有被測試。四個RCE足以證明問題的嚴(yán)重性。

看完上述內(nèi)容,你們掌握如何通過Semmle QL找到Apache Struts的遠程執(zhí)行代碼.的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI