WSO2 RCE (CVE-2022-29464) 漏洞利用和编写。
2022-11-9 09:43:34 Author: Ots安全(查看原文) 阅读量:48 收藏

WSO2 RCE (CVE-2022-29464) 漏洞利用和编写。

CVE-2022-29464Orange Tsai发现的 WSO2 上的严重漏洞。该漏洞是一种未经身份验证的无限制任意文件上传,允许未经身份验证的攻击者通过上传恶意 JSP 文件在 WSO2 服务器上获得 RCE。

易受攻击的上传路由是/fileuploadFileUploadServletservlet 处理的。indentity.xml正如我们在配置文件中看到的,它是不受 IAM 保护的路由 :

<Resource context="(.*)/fileupload(.*)" secured="false" http-method="all"/>

同样不受默认登录措施保护的handleSecurity()是负责保护 WSO2 服务的不同路由的功能,并提供一种对收到的 HTTP 请求执行安全检查的机制,handleSecurity()将调用CarbonUILoginUtil.handleLoginPageRequest()并根据其返回值决定允许或拒绝访问请求的 URI:

    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

    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_TRUEhandleSecurity()将返回true,然后将授予访问权限而/fileupload无需身份验证。

FileUploadServletservlet 并通过init()一系列方法调用最终从carbon.xml配置文件加载多个上传文件格式/操作以及处理每种格式的对象。

    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构造函数如下:

    public FileUploadExecutorManager(BundleContext bundleContext,                                     ConfigurationContext configCtx,                                     String webContext) throws CarbonException {        this.bundleContext = bundleContext;        this.configContext = configCtx;        this.webContext = webContext;        this.loadExecutorMap();    }

构造函数调用loadExecutorMap()配置加载完成的私有方法:

    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]

文件上传格式配置FileUploadConfig在 XML 配置文件的命名空间内,这是默认配置:

    <FileUploadConfig>        <!--           The total file upload size limit in MB        -->        <TotalFileSizeLimit>100</TotalFileSizeLimit>
<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()方法创建并填充从配置文件中提取的操作和类HashMap<Action, Class>稍后将用于选择使用哪个类来正确处理给定的格式/动作。

稍后当/fileupload路由收到 POST 请求doPost()时,将调用 servlet 的方法。该方法只是将请求和响应对象转发到初始化的execute()方法fileUploadExecutorManagerinit()

    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()方法在字符串之后拆分请求 url fileupload/,这意味着它提取/fileupload/请求 URL 中 之后的任何内容并将其分配给actionString.

    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; }

与and一起actionString传递给CarbonXmlFileUploadExecHandler类构造函数:requestresponse

        private CarbonXmlFileUploadExecHandler(HttpServletRequest request,                                               HttpServletResponse response,                                               String actionString) {            this.request = request;            this.response = response;            this.actionString = actionString;        }

构造函数会将它们保存到其属性中。

之后该carbonXmlExecHandler对象与其他对象将被添加到execHandlerManagerusingaddExecHandler()方法中。

        public void addExecHandler(FileUploadExecutionHandler handler) {            if (prevHandler != null) {                prevHandler.setNext(handler);            } else {                firstHandler = handler;            }            prevHandler = handler;        }然后execHandlerManager.startExec()被称为:        public void startExec() throws IOException {            firstHandler.execute();        }startExec()添加的第一个对象的调用execute()是CarbonXmlFileUploadExecHandler:        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()循环通过之前创建的HashMapof<Action, Class>并找到等于 的 Action(键)actionString,如果找到executeGeneric(),将调用与该 Action 关联的对象的方法。

修改默认配置有 7 个操作,它们是:

  • keystorecertificate,*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

这些对象中的每一个确实以不同的方式处理上传,其中一些接受特定的扩展名。

我发现易受仲裁文件写入影响的第一个是toolsAnyToolsAnyFileUploadExecutor)。 ToolsAnyFileUploadExecutor没有executeGeneric()方法,但它扩展AbstractFileUploadExecutor了它确实有一个executeGeneric()方法:

    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()以请求对象作为参数调用:

    protected void parseRequest(HttpServletRequest request) throws FileUploadFailedException,                                                                 FileSizeLimitExceededException {        fileItemsMap.set(new HashMap<String, ArrayList<FileItemData>>());        formFieldsMap.set(new HashMap<String, ArrayList<String>>());
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 请求是一个多部分的 POST 请求,然后提取上传的文件,确保 POST 请求至少包含一个上传的文件,并根据最大文件大小对其进行验证。

从返回后parseRequest()executeGeneric()现在将调用被以下execute()方法覆盖的方法ToolsAnyFileUploadExecutor

  @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()方法容易受到路径遍历漏洞的影响,因为它信任用户在 POST 请求中提供的文件名。没有路径遍历转义 tmp 目录,文件实际上保存到:

./tmp/work/extra/$uuid/$filename

uuid响应中返回:

该文件可以在以下位置找到:

现在我们只需要转义tmp目录并将我们的 JSP shell 添加到 WSO2 服务的某个位置。

让我们找到tomcatappBase目录:

此目录是部署在 tomcat 上的应用程序的位置,它包含多个已部署的 WAR 应用程序以及它们的原始 WAR 文件:

./repository/deployment/server/webapps

其中一个应用程序是authenticationendpoint//host/authenticationendpoint),它处理对 WSO2 的身份验证,它的位置是:

./repository/deployment/server/webapps/authenticationendpoint

注意:我们也可以利用该漏洞在目录中创建自己的新目录(上下文路径),appBase它会自动部署,但我只携带一个并使用authenticationendpoint.

  • 使用 Burpsuite:

  • 使用exploiy.py:

用法:

python3 exploit.py https://host:9443/ ArbitraryShellName.jsp

原文地址:https://github.com/hakivvi/CVE-2022-29464

点击阅读原文——跳转


文章来源: http://mp.weixin.qq.com/s?__biz=MzAxMjYyMzkwOA==&mid=2247495961&idx=2&sn=49125d4ff2ff5a69d1ec1ad93148c5d4&chksm=9badb852acda3144fd87e9bb3f0f9c892928dc82907b5e15305bd9a4d4d1d675b825851a3242#rd
如有侵权请联系:admin#unsafe.sh