<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Camo on bramp.net</title>
    <link>https://blog.bramp.net/</link>
    <description>Recent content in Camo on bramp.net</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-GB</language>
    <lastBuildDate>Tue, 27 Nov 2012 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://blog.bramp.net/tags/camo/" rel="self" type="application/rss+xml" />
    
    <item>
      <title>Invalid IP range checking defeated by DNS</title>
      <link>https://blog.bramp.net/post/2012/11/27/invalid-ip-range-checking-defeated-by-dns/</link>
      <pubDate>Tue, 27 Nov 2012 00:00:00 +0000</pubDate>
      
      <guid>https://blog.bramp.net/post/2012/11/27/invalid-ip-range-checking-defeated-by-dns/</guid>
      <description><p>I’ve seen a particular kind of vulnerability in a few different applications but I’m not sure of an appropriate name for it. So I thought I’d write about it, and informally call it the “DNS defeated IP address check”. Basically, if you have an application that can be used as a proxy, or can be instructed to make web request, you don’t want it fetching files from internal services.</p>
<p>For example, there is a simple proxy called <a href="https://github.com/atmos/camo">Camo</a>, which is used to fetch third party images when you need to display them on a SSL secure site. (Read more about Camo on the <a href="https://github.com/blog/743-sidejack-prevention-phase-3-ssl-proxied-assets">GitHub blog</a>).</p>
<p>This kind of application can be incorrectly setup such that the application has access to internal servers and resources that wouldn’t normally be exposed to the Internet. This make the proxy application a good way a hacker could gain information about a private network. However Camo tries to address this issue by forbidding URLs that contain private IP addresses. It does a check like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-coffeescript" data-lang="coffeescript"><span class="line"><span class="cl"><span class="nv">RESTRICTED_IPS = </span><span class="sr">/^((10\.)|(127\.)|(169\.254)|(192\.168)|(172\.((1[6-9])|(2[0-9])|(3[0-1]))))/</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nx">url</span><span class="p">.</span><span class="nx">host</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="nx">RESTRICTED_IPS</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">four_oh_four</span><span class="p">(</span><span class="nx">resp</span><span class="p">,</span> <span class="s">&#34;Hitting excluded hostnames&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>This code (written for <a href="http://nodejs.org/">Node.js</a> in <a href="http://coffeescript.org/">CoffeeScript</a>) is taking a <a href="http://nodejs.org/api/url.html">url object</a> and checking the hostname doesn’t match a restricted address. This works great against URLs such as <a href="http://127.0.0.1/">http://127.0.0.1/</a>, or <a href="http://10.0.0.1/">http://10.0.0.1/</a>, however this check can easily be defeated. If you create a domain name, such as localhost.bramp.net, which resolves to 127.0.0.1, and ask the proxy to fetch <a href="http://localhost.bramp.net/">http://localhost.bramp.net/</a>, then it won’t be caught by that check. Now the proxy will continue to try and fetch a resource from 127.0.0.1.</p>
<p>The solution to this problem is to do that IP address check <strong>after</strong> the DNS name has been resolved. This can also be problematic if you use a standard library for making web requests, as they will do the DNS lookup for you, and don’t give you the fine grain control you need. For example, I’ve seen this be a problem for a Java application using the <a href="http://hc.apache.org/httpclient-3.x/">Apache HTTP Client</a>.</p>
<p>One might naively assume they could do a DNS check, and then hand the processing to a HTTP library to make the actual request. The issue here is that the DNS record the HTTP library uses might not be the same as the one you checked against with the DNS check. For example, many domains have multiple A records, and some DNS servers can be configured to round robin DNS records. If you can’t be sure the HTTP library will do another DNS requests, then you’d be vulnerable.</p>
<p>Luckily, in Camo’s case the fix was relatively easy (see my <a href="https://github.com/atmos/camo/pull/19">pull request</a>).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-coffeescript" data-lang="coffeescript"><span class="line"><span class="cl"><span class="c1"># We do DNS lookup ourselves
</span></span></span><span class="line"><span class="cl"><span class="nx">Dns</span><span class="p">.</span><span class="nx">lookup</span> <span class="nx">url</span><span class="p">.</span><span class="nx">host</span><span class="p">,</span> <span class="nf">(err, address, family) -&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="nx">address</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="nx">RESTRICTED_IPS</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">four_oh_four</span><span class="p">(</span><span class="nx">resp</span><span class="p">,</span> <span class="s">&#34;Hitting excluded hostnames&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># We connect to the IP address, not hostname
</span></span></span><span class="line"><span class="cl">  <span class="nv">src = </span><span class="nx">Http</span><span class="p">.</span><span class="nx">createClient</span> <span class="nx">url</span><span class="p">.</span><span class="nx">port</span> <span class="o">||</span> <span class="mi">80</span><span class="p">,</span> <span class="nx">address</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># We add a host header, so the request will work
</span></span></span><span class="line"><span class="cl">  <span class="nv">headers = </span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Host&#39; : url.host
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">  # Boom, we make the request
</span></span></span><span class="line"><span class="cl"><span class="s">  srcReq = src.request &#39;GET&#39;, query_path, headers
</span></span></span></code></pre></div><p>The above code was simplified a little from the real code, but basically we do the DNS lookup, check the returned address is good, and then make a HTTP request to that IP address with a <code>Host:</code> header to ensure the request will work.</p>
<p>Really though, the correct solution to this is to configure a suitably paranoid firewall to stop requests from the proxy machine to anything internal. However, as with all security, the more <a href="http://en.wikipedia.org/wiki/Swiss_cheese_model">layers of protection</a> you have the better, and you should never depend on just one.</p></description>
    </item>
    
  </channel>
</rss>
