
Python exploit for CVE-2022-29464, WSO2 서버의 인증되지 않은 임의 파일 업로드 취약점으로, 악성 JSP 파일 업로드를 통해 원격 코드 실행을 가능하게 합니다.
WSO2 RCE (CVE-2022-29464) 익스플로잇 및 분석글.
CVE-2022-29464는 Orange Tsai가 발견한 WSO2의 심각한 취약점입니다. 이 취약점은 인증되지 않은 제한 없는 임의 파일 업로드로, 인증되지 않은 공격자가 악성 JSP 파일을 업로드하여 WSO2 서버에서 RCE를 얻을 수 있게 합니다.
취약한 업로드 경로는 FileUploadServlet 서블릿이 처리하는 /fileupload입니다. 그리고 indentity.xml 설정 파일에서 볼 수 있듯이 IAM에 의해 보호되지 않는 경로입니다:```xml
또한 기본 로그인 조치로 보호되지 않으며, `handleSecurity()`는 WSO2가 제공하는 다양한 경로를 보호하는 역할을 하는 함수로, 수신된 HTTP 요청에 대한 보안 검사를 수행하는 메커니즘을 제공합니다. `handleSecurity()`는 `CarbonUILoginUtil.handleLoginPageRequest()`를 호출하고, 그 반환 값에 따라 요청된 URI에 대한 액세스를 허용하거나 거부할지 결정됩니다:```java
public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response)
throws IOException {
[snipped]
if ((val = CarbonUILoginUtil.handleLoginPageRequest(requestedURI, request, response,
authenticated, context, indexPageURL)) != CarbonUILoginUtil.CONTINUE) {
if (val == CarbonUILoginUtil.RETURN_TRUE) {
return true;
} else {
return false;
}
}
[snipped]
}
CarbonUILoginUtil.handleLoginPageRequest()는 경로가 /fileupload일 때 CarbonUILoginUtil.RETURN_TRUE를 반환합니다:```java
protected static int handleLoginPageRequest(String requestedURI, HttpServletRequest request,
HttpServletResponse response, boolean authenticated, String context, String indexPageURL)
throws IOException {
boolean isTryIt = requestedURI.indexOf("admin/jsp/WSRequestXSSproxy_ajaxprocessor.jsp") > -1;
boolean isFileDownload = requestedURI.endsWith("/filedownload");
if ((requestedURI.indexOf("login.jsp") > -1
|| requestedURI.indexOf("login_ajaxprocessor.jsp") > -1
|| requestedURI.indexOf("admin/layout/template.jsp") > -1
|| isFileDownload
|| requestedURI.endsWith("/fileupload")
|| requestedURI.indexOf("/fileupload/") > -1
|| requestedURI.indexOf("login_action.jsp") > -1
|| isTryIt
|| requestedURI.indexOf("tryit/JAXRSRequestXSSproxy_ajaxprocessor.jsp") > -1)
&& !requestedURI.contains(";")) {
if ((requestedURI.indexOf("login.jsp") > -1
|| requestedURI.indexOf("login_ajaxprocessor.jsp") > -1 || requestedURI
.indexOf("login_action.jsp") > -1) && authenticated) {
[snipped]
} else if ((isTryIt || isFileDownload) && !authenticated) {
[snipped]
} else if (requestedURI.indexOf("login_action.jsp") > -1 && !authenticated) {
[snipped]
} else {
if (log.isDebugEnabled()) {
log.debug("Skipping security checks for " + requestedURI);
}
return RETURN_TRUE;
}
}
return CONTINUE;
}
`CarbonUILoginUtil.handleLoginPageRequest()`가 `CarbonUILoginUtil.RETURN_TRUE`를 반환하면 `handleSecurity()`가 `true`를 반환하여 `/fileupload`에 대한 접근이 인증 없이 허용됩니다.
`FileUploadServlet` 서블릿은 [`init()`](https://github.com/wso2/carbon-kernel/blob/d47232dfb2b26c0ef18a74e2ef4aa503caa59697/core/org.wso2.carbon.ui/src/main/java/org/wso2/carbon/ui/transports/FileUploadServlet.java#L71)을 통해 및 일련의 메서드 호출을 통해 결국 `carbon.xml` 구성 파일에서 여러 업로드 파일 형식/액션과 각 형식을 처리하는 객체를 로드합니다.```java
public void init(ServletConfig servletConfig) throws ServletException {
this.servletConfig = servletConfig;
try {
fileUploadExecutorManager = new FileUploadExecutorManager(bundleContext, configContext, webContext);
//Registering FileUploadExecutor Manager as an OSGi service
bundleContext.registerService(FileUploadExecutorManager.class.getName(), fileUploadExecutorManager, null);
} catch (CarbonException e) {
log.error("Exception occurred while trying to initialize FileUploadServlet", e);
throw new ServletException(e);
}
}
다음은 FileUploadExecutorManager 클래스 생성자입니다:```java
public FileUploadExecutorManager(BundleContext bundleContext,
ConfigurationContext configCtx,
String webContext) throws CarbonException {
this.bundleContext = bundleContext;
this.configContext = configCtx;
this.webContext = webContext;
this.loadExecutorMap();
}
생성자는 구성 로딩이 수행되는 [`loadExecutorMap()`](https://github.com/wso2/carbon-kernel/blob/d47232dfb2b26c0ef18a74e2ef4aa503caa59697/core/org.wso2.carbon.ui/src/main/java/org/wso2/carbon/ui/transports/fileupload/FileUploadExecutorManager.java#L131) 비공개 메서드를 호출합니다.```java
private void loadExecutorMap() throws CarbonException {
[snipped]
try {
documentElement = XMLUtils.toOM(serverConfiguration.getDocumentElement());
} catch (Exception e) {
String msg = "Unable to read Server Configuration.";
log.error(msg);
throw new CarbonException(msg, e);
}
[snipped]
OMElement fileUploadConfigElement =
documentElement.getFirstChildWithName(
new QName(ServerConstants.CARBON_SERVER_XML_NAMESPACE, "FileUploadConfig"));
for (Iterator iterator = fileUploadConfigElement.getChildElements(); iterator.hasNext();) {
OMElement mapppingElement = (OMElement) iterator.next();
if (mapppingElement.getLocalName().equalsIgnoreCase("Mapping")) {
OMElement actionsElement =
mapppingElement.getFirstChildWithName(
new QName(ServerConstants.CARBON_SERVER_XML_NAMESPACE, "Actions"));
String confPath = System.getProperty(CarbonBaseConstants.CARBON_CONFIG_DIR_PATH);
[snipped]
파일 업로드 형식 구성은 XML 구성 파일의 FileUploadConfig 네임스페이스 내에 있으며, 이것이 기본 구성입니다:```xml
100
<Mapping>
<Actions>
<Action>keystore</Action>
<Action>certificate</Action>
<Action>*</Action>
</Actions>
<Class>org.wso2.carbon.ui.transports.fileupload.AnyFileUploadExecutor</Class>
</Mapping>
<Mapping>
<Actions>
<Action>jarZip</Action>
</Actions>
<Class>org.wso2.carbon.ui.transports.fileupload.JarZipUploadExecutor</Class>
</Mapping>
<Mapping>
<Actions>
<Action>dbs</Action>
</Actions>
<Class>org.wso2.carbon.ui.transports.fileupload.DBSFileUploadExecutor</Class>
</Mapping>
<Mapping>
<Actions>
<Action>tools</Action>
</Actions>
<Class>org.wso2.carbon.ui.transports.fileupload.ToolsFileUploadExecutor</Class>
</Mapping>
<Mapping>
<Actions>
<Action>toolsAny</Action>
</Actions>
<Class>org.wso2.carbon.ui.transports.fileupload.ToolsAnyFileUploadExecutor</Class>
</Mapping>
</FileUploadConfig>
`loadExecutorMap()` 메서드는 설정 파일에서 추출한 액션과 클래스로 `<Action, Class>`의 `HashMap`을 생성하고 채웁니다. 이는 나중에 주어진 형식/액션을 적절히 처리하기 위해 어떤 클래스를 사용할지 선택하는 데 사용됩니다.