
WSO2 서버에서 인증되지 않은 임의 파일 업로드가 가능한 CVE-2022-29464 취약점에 대한 익스플로잇으로, 악성 JSP 업로드를 통해 원격 코드 실행을 가능하게 합니다.
WSO2 RCE(CVE-2022-29464) 익스플로잇 및 분석 문서.
CVE-2022-29464 WSO2의 심각한 취약점입니다. 이 취약점은 인증되지 않은, 제약 없는 임의 파일 업로드로, 인증되지 않은 사용자가 WSO2 서버에 악성 JSP 파일을 업로드하여 원격 코드 실행(RCE)을 달성할 수 있게 합니다.
취약한 업로드 경로는 /fileupload이며, FileUploadServlet 서블릿이 처리합니다. 그리고 indentity.xml 구성 파일에서 볼 수 있듯이 이 경로는 IAM으로 보호되지 않는 경로입니다:```xml
The function 'handleSecurity()' is in charge of safeguarding the various routes provided by WSO2 and offers a mechanism for performing security checks on the received HTTP requests. 'handleSecurity()' will call 'CarbonUILoginUtil.handleLoginPageRequest(),' and based on its return value, it will be decided whether to grant or deny access to the requested 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();
}
생성자는 구성 로딩이 수행되는 private 메서드인 [`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()` 메서드는 config 파일에서 추출한 Action과 Class를 사용해 `<Action, Class>`의 `HashMap`을 생성하고 채웁니다. 이 HashMap은 나중에 주어진 형식/작업을 적절히 처리하기 위해 어떤 클래스를 사용할지 선택하는 데 사용됩니다.