
CVE-2022-29464 के लिए Python एक्सप्लॉइट, WSO2 सर्वरों में एक बिना प्रमाणीकरण वाली मनमानी फ़ाइल अपलोड भेद्यता, जो दुर्भावनापूर्ण JSP फ़ाइल अपलोड के माध्यम से रिमोट कोड निष्पादन सक्षम बनाती है।
WSO2 RCE (CVE-2022-29464) एक्सप्लॉइट और राइटअप।
CVE-2022-29464 WSO2 पर एक महत्वपूर्ण कमजोरी है जिसे Orange Tsai ने खोजा था। यह कमजोरी एक अप्रमाणित, अप्रतिबंधित मनमानी फ़ाइल अपलोड है जो अप्रमाणित हमलावरों को दुर्भावनापूर्ण JSP फ़ाइलें अपलोड करके WSO2 सर्वरों पर RCE प्राप्त करने की अनुमति देती है।
कमजोर अपलोड रूट /fileupload है जिसे FileUploadServlet सर्वलेट द्वारा संभाला जाता है। और यह IAM द्वारा असुरक्षित रूट है, जैसा कि हम indentity.xml कॉन्फ़िगरेशन फ़ाइल में देख सकते हैं:```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() CarbonUILoginUtil.RETURN_TRUE वापस करता है जब रूट /fileupload है:```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()` विधि कॉन्फ़िग फ़ाइल से निकाले गए Actions और Classes के साथ `<Action, Class>` का एक `HashMap` बनाती और भरती है। जिसका उपयोग बाद में यह चुनने के लिए किया जाएगा कि किसी दिए गए फ़ॉर्मेट/कार्रवाई को ठीक से संभालने के लिए किस वर्ग का उपयोग किया जाए।
बाद में जब `/fileupload` रूट पर POST अनुरोध प्राप्त होता है, तो सर्वलेट की [`doPost()`](https://github.com/wso2/carbon-kernel/blob/d47232dfb2b26c0ef18a74e2ef4aa503caa59697/core/org.wso2.carbon.ui/src/main/java/org/wso2/carbon/ui/transports/FileUploadServlet.java#L53) विधि को कॉल किया जाएगा। यह विधि केवल अनुरोध और प्रतिक्रिया ऑब्जेक्ट को `fileUploadExecutorManager` की `execute()` विधि को अग्रेषित करती है, जिसे `init()` पर प्रारंभ किया गया था।```java
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
try {
fileUploadExecutorManager.execute(request, response);
} catch (Exception e) {
String msg = "File upload failed ";
log.error(msg, e);
throw new ServletException(e);
}
}
execute() विधि, fileupload/ स्ट्रिंग के ठीक बाद अनुरोध URL को विभाजित करती है, जिसका अर्थ है कि यह अनुरोध URL में /fileupload/ के बाद जो कुछ भी है उसे निकालती है और इसे actionString को असाइन करती है।```java
public boolean execute(HttpServletRequest request,
HttpServletResponse response) throws IOException {
HttpSession session = request.getSession();
String cookie = (String) session.getAttribute(ServerConstants.ADMIN_SERVICE_COOKIE);
request.setAttribute(CarbonConstants.ADMIN_SERVICE_COOKIE, cookie);
request.setAttribute(CarbonConstants.WEB_CONTEXT, webContext);
request.setAttribute(CarbonConstants.SERVER_URL,
CarbonUIUtil.getServerURL(request.getSession().getServletContext(),
request.getSession()));
String requestURI = request.getRequestURI();
//TODO - fileupload is hardcoded
int indexToSplit = requestURI.indexOf("fileupload/") + "fileupload/".length();
String actionString = requestURI.substring(indexToSplit);
// Register execution handlers
FileUploadExecutionHandlerManager execHandlerManager =
new FileUploadExecutionHandlerManager();
CarbonXmlFileUploadExecHandler carbonXmlExecHandler =
new CarbonXmlFileUploadExecHandler(request, response, actionString);
execHandlerManager.addExecHandler(carbonXmlExecHandler);
OSGiFileUploadExecHandler osgiExecHandler =
new OSGiFileUploadExecHandler(request, response);
execHandlerManager.addExecHandler(osgiExecHandler);
AnyFileUploadExecHandler anyFileExecHandler =
new AnyFileUploadExecHandler(request, response);
execHandlerManager.addExecHandler(anyFileExecHandler);
execHandlerManager.startExec();
return true;
}
`actionString` को `CarbonXmlFileUploadExecHandler` वर्ग के कंस्ट्रक्टर को `request` और `response` के साथ पारित किया जाता है:```java
private CarbonXmlFileUploadExecHandler(HttpServletRequest request,
HttpServletResponse response,
String actionString) {
this.request = request;
this.response = response;
this.actionString = actionString;
}
कंस्ट्रक्टर उन्हें अपनी प्रॉपर्टीज़ में सेव करेगा। उसके बाद carbonXmlExecHandler ऑब्जेक्ट को अन्य ऑब्जेक्ट्स के साथ execHandlerManager में addExecHandler() मेथड का उपयोग करके जोड़ा जाएगा।```java
public void addExecHandler(FileUploadExecutionHandler handler) {
if (prevHandler != null) {
prevHandler.setNext(handler);
} else {
firstHandler = handler;
}
prevHandler = handler;
}
फिर `execHandlerManager.startExec()` को कॉल किया जाता है:```java
public void startExec() throws IOException {
firstHandler.execute();
}
startExec() पहले जोड़े गए ऑब्जेक्ट के execute() को कॉल करता है जो CarbonXmlFileUploadExecHandler है:```java
public void execute() throws IOException {
boolean foundExecutor = false;
for (String key : executorMap.keySet()) {
if (key.equals(actionString)) {
AbstractFileUploadExecutor obj = executorMap.get(key);
foundExecutor = true;
obj.executeGeneric(request, response, configContext);
break;
}
}
if (!foundExecutor) {
next();
}
}
[`execute()`](https://github.com/wso2/carbon-kernel/blob/d47232dfb2b26c0ef18a74e2ef4aa503caa59697/core/org.wso2.carbon.ui/src/main/java/org/wso2/carbon/ui/transports/fileupload/FileUploadExecutorManager.java#L430) पहले बनाए गए `<Action, Class>` के `HashMap` में पुनरावृत्त करता है और Action (key) को ढूंढता है जो `actionString` के बराबर है, यदि मिल जाता है तो उस Action से संबद्ध ऑब्जेक्ट की `executeGeneric()` विधि कॉल की जाएगी।
डिफ़ॉल्ट कॉन्फ़िगरेशन में 7 क्रियाएँ हैं:
* `keystore`, `certificate`, `*` को `org.wso2.carbon.ui.transports.fileupload.AnyFileUploadExecutor` द्वारा हैंडल किया जाता है
* `jarZip` को `org.wso2.carbon.ui.transports.fileupload.JarZipUploadExecutor` द्वारा हैंडल किया जाता है
* `dbs` को `org.wso2.carbon.ui.transports.fileupload.DBSFileUploadExecutor` द्वारा हैंडल किया जाता है
* `tools` को `org.wso2.carbon.ui.transports.fileupload.ToolsFileUploadExecutor` द्वारा हैंडल किया जाता है
* `toolsAny` को `org.wso2.carbon.ui.transports.fileupload.ToolsAnyFileUploadExecutor` द्वारा हैंडल किया जाता है
इनमें से प्रत्येक ऑब्जेक्ट अपलोड को अलग-अलग तरीके से हैंडल करता है, उनमें से कुछ विशिष्ट एक्सटेंशन स्वीकार करते हैं।
मैंने जो पहला arbitrary file write के लिए कमजोर पाया वह `toolsAny` ([`ToolsAnyFileUploadExecutor`](https://github.com/wso2/carbon-kernel/blob/4.4.x/core/org.wso2.carbon.ui/src/main/java/org/wso2/carbon/ui/transports/fileupload/ToolsAnyFileUploadExecutor.java)) था।
`ToolsAnyFileUploadExecutor` में `executeGeneric()` विधि नहीं है, लेकिन यह [`AbstractFileUploadExecutor`](https://github.com/wso2/carbon-kernel/blob/d47232dfb2b26c0ef18a74e2ef4aa503caa59697/core/org.wso2.carbon.ui/src/main/java/org/wso2/carbon/ui/transports/fileupload/AbstractFileUploadExecutor.java#L61) का विस्तार करता है, जिसमें एक [`executeGeneric()`](https://github.com/wso2/carbon-kernel/blob/d47232dfb2b26c0ef18a74e2ef4aa503caa59697/core/org.wso2.carbon.ui/src/main/java/org/wso2/carbon/ui/transports/fileupload/AbstractFileUploadExecutor.java#L97) विधि मौजूद है:```java
boolean executeGeneric(HttpServletRequest request,
HttpServletResponse response,
ConfigurationContext configurationContext) throws IOException {//,
// CarbonException {
this.configurationContext = configurationContext;
try {
parseRequest(request);
return execute(request, response);
} catch (FileUploadFailedException e) {
sendErrorRedirect(request, response, e);
} catch (FileSizeLimitExceededException e) {
sendErrorRedirect(request, response, e);
} catch (CarbonException e) {
sendErrorRedirect(request, response, e);
}
return false;
}
executeGeneric() पहले parseRequest() को अनुरोध ऑब्जेक्ट को एक पैरामीटर के रूप में कॉल करता है:```java
protected void parseRequest(HttpServletRequest request) throws FileUploadFailedException,
FileSizeLimitExceededException {
fileItemsMap.set(new HashMap<String, ArrayList>());
formFieldsMap.set(new HashMap<String, ArrayList>());
ServletRequestContext servletRequestContext = new ServletRequestContext(request);
boolean isMultipart = ServletFileUpload.isMultipartContent(servletRequestContext);
Long totalFileSize = 0L;
if (isMultipart) {
List items;
try {
items = parseRequest(servletRequestContext);
} catch (FileUploadException e) {
String msg = "File upload failed";
log.error(msg, e);
throw new FileUploadFailedException(msg, e);
}
boolean multiItems = false;
if (items.size() > 1) {
multiItems = true;
}
// Add the uploaded items to the corresponding maps.
for (Iterator iter = items.iterator(); iter.hasNext();) {
FileItem item = (FileItem) iter.next();
String fieldName = item.getFieldName().trim();
if (item.isFormField()) {
if (formFieldsMap.get().get(fieldName) == null) {
formFieldsMap.get().put(fieldName, new ArrayList<String>());
}
try {
formFieldsMap.get().get(fieldName).add(new String(item.get(), "UTF-8"));
} catch (UnsupportedEncodingException ignore) {
}
} else {
String fileName = item.getName();
if ((fileName == null || fileName.length() == 0) && multiItems) {
continue;
}
if (fileItemsMap.get().get(fieldName) == null) {
fileItemsMap.get().put(fieldName, new ArrayList<FileItemData>());
}
totalFileSize += item.getSize();
if (totalFileSize < totalFileUploadSizeLimit) {
fileItemsMap.get().get(fieldName).add(new FileItemData(item));
} else {
throw new FileSizeLimitExceededException(getFileSizeLimit() / 1024 / 1024);
}
}
}
}
}
पहले यह सुनिश्चित करता है कि POST अनुरोध एक multipart POST अनुरोध है, और फिर अपलोड की गई फ़ाइलों को निकालता है, सुनिश्चित करता है कि POST अनुरोध में कम से कम एक अपलोड की गई फ़ाइल है और इसे अधिकतम फ़ाइल आकार के विरुद्ध मान्य करता है।
`parseRequest()` से लौटने के बाद, `executeGeneric()` अब `execute()` विधि को कॉल करेगा जो `ToolsAnyFileUploadExecutor` द्वारा [overrode](https://github.com/wso2/carbon-kernel/blob/d47232dfb2b26c0ef18a74e2ef4aa503caa59697/core/org.wso2.carbon.ui/src/main/java/org/wso2/carbon/ui/transports/fileupload/ToolsAnyFileUploadExecutor.java#L36) किया गया है:```java
@Override
public boolean execute(HttpServletRequest request,
HttpServletResponse response) throws CarbonException, IOException {
PrintWriter out = response.getWriter();
try {
Map fileResourceMap =
(Map) configurationContext
.getProperty(ServerConstants.FILE_RESOURCE_MAP);
if (fileResourceMap == null) {
fileResourceMap = new TreeBidiMap();
configurationContext.setProperty(ServerConstants.FILE_RESOURCE_MAP,
fileResourceMap);
}
List<FileItemData> fileItems = getAllFileItems();
//String filePaths = "";
for (FileItemData fileItem : fileItems) {
String uuid = String.valueOf(
System.currentTimeMillis() + Math.random());
String serviceUploadDir =
configurationContext
.getProperty(ServerConstants.WORK_DIR) +
File.separator +
"extra" + File
.separator +
uuid + File.separator;
File dir = new File(serviceUploadDir);
if (!dir.exists()) {
dir.mkdirs();
}
File uploadedFile = new File(dir, fileItem.getFileItem().getFieldName());
try (FileOutputStream fileOutStream = new FileOutputStream(uploadedFile)) {
fileItem.getDataHandler().writeTo(fileOutStream);
fileOutStream.flush();
}
response.setContentType("text/plain; charset=utf-8");
//filePaths = filePaths + uploadedFile.getAbsolutePath() + ",";
fileResourceMap.put(uuid, uploadedFile.getAbsolutePath());
out.write(uuid);
}
//filePaths = filePaths.substring(0, filePaths.length() - 1);
//out.write(filePaths);
out.flush();
} catch (Exception e) {
log.error("File upload FAILED", e);
out.write("<script type=\"text/javascript\">" +
"top.wso2.wsf.Util.alertWarning('File upload FAILED. File may be non-existent or invalid.');" +
"</script>");
} finally {
out.close();
}
return true;
}
यहाँ बग है, execute() method path traversal भेद्यता के लिए असुरक्षित है क्योंकि यह POST अनुरोध में उपयोगकर्ता द्वारा दिए गए फ़ाइलनाम पर भरोसा करता है। path traversal द्वारा tmp निर्देशिका से बाहर निकले बिना फ़ाइल वास्तव में सहेजी जाती है:```
./tmp/work/extra/$uuid/$filename
प्रतिक्रिया में `uuid` लौटाए जाने के साथ:

फ़ाइल निम्न में मिल सकती है:

अब हमें बस `tmp` निर्देशिका से बाहर निकलना है और अपने JSP शेल को WSO2 द्वारा सेवा किए जा रहे किसी स्थान पर जोड़ना है।
आइए tomcat `appBase` निर्देशिका खोजें:

यह निर्देशिका tomcat पर तैनात अनुप्रयोगों का स्थान है, इसमें कई पहले से तैनात WAR अनुप्रयोग और उनकी कच्ची WAR फ़ाइलें भी शामिल हैं:```
./repository/deployment/server/webapps

उन अनुप्रयोगों में से एक authenticationendpoint (//host/authenticationendpoint) है, जो WSO2 में प्रमाणीकरण को संभालता है और इसका स्थान है:```
./repository/deployment/server/webapps/authenticationendpoint

**नोट:** हम इस कमजोरी का उपयोग करके `appBase` निर्देशिका में अपनी खुद की नई निर्देशिका (कॉन्टेक्स्ट पथ) बना सकते हैं और यह स्वचालित रूप से तैनात हो जाएगी, लेकिन मैं सिर्फ एक का उपयोग करूंगा और `authenticationendpoint` का उपयोग करूंगा।
# PoC
* Burpsuite का उपयोग करते हुए:



* exploiy.py का उपयोग करते हुए:
> उपयोग:
> ```
> python3 exploit.py https://host:9443/ ArbitraryShellName.jsp
> ```
