https://porterhau5.com/Home | porterhau5.com2018-01-04T20:13:41+00:00A collection of infosec projects & blog posts by @porterhau5porterhau5https://porterhau5.com/Jekyllhttps://porterhau5.com/blog/creating-conditional-statements-with-cypher/Creating Conditional Statements with Cypher2017-04-04T00:00:00+00:00porterhau5https://porterhau5.com/How to hack together Neo4j's Cypher statements to conditionally execute code, along with examples of working with API response metadata.<p>This is another installment in a series of posts regarding modifications to BloodHound and lessons learned while working with Neo4j & Cypher. Other posts can be found here:</p>
<ul>
<li><a href="/blog/extending-bloodhound-track-and-visualize-your-compromise/">Visualizing and Tracking Your Compromise</a></li>
<li><a href="/blog/representing-password-reuse-in-bloodhound/">Representing Password Reuse in BloodHound</a></li>
</ul>
<p>This post will cover some advanced Neo4j concepts and how I hacked Cypher commands together to improve feedback on the <a href="https://github.com/porterhau5/BloodHound-Owned" target="_blank">BloodHound Owned extensions</a> project. I’ll specifically cover how to create conditional statements in Cypher by combining a <code class="highlighter-rouge">CASE</code> expression and <code class="highlighter-rouge">FOREACH</code> clause. Although the examples are in context of BloodHound, I hope Neo4j & Cypher users in general find it helpful.</p>
<p>This post doesn’t cover introductory Cypher concepts. I <em>highly</em> recommend reading Rohan Vazarkar’s <a href="https://blog.cptjesus.com/posts/introtocypher" target="_blank">Intro to Cypher</a> blog post as a primer for Cypher and its usage in BloodHound. One of the concepts covered can also be read about on this <a href="http://www.markhneedham.com/blog/2014/08/22/neo4j-load-csv-handling-empty-columns/" target="_blank">blog</a>.</p>
<ul>
<li><a href="#the-query-objective">The Query Objective</a></li>
<li><a href="#trial-and-error-with-response-metadata">Trial and Error with Response Metadata</a></li>
<li><a href="#creating-conditional-statements-with-cypher">Creating Conditional Statements with Cypher</a></li>
<li><a href="#providing-detailed-feedback">Providing Detailed Feedback</a></li>
</ul>
<h3 id="the-query-objective">The Query Objective</h3>
<p>This came about while I was trying to figure out how to provide granular feedback to Cypher queries issued by <a href="https://github.com/porterhau5/BloodHound-Owned/blob/master/bh-owned.rb" target="_blank">bh-owned.rb</a>. I wanted users to know exactly what happened on the backend after running a command, that way they can tweak their input if needed.</p>
<p>One of the script options, <code class="highlighter-rouge">-a</code>, adds custom properties to nodes in the Neo4j database. Three inputs are used for this:</p>
<ul>
<li><code class="highlighter-rouge">name</code> - name of the node, used to find the node we want to update</li>
<li><code class="highlighter-rouge">owned</code> - method used to compromise the node, a property to add</li>
<li><code class="highlighter-rouge">wave</code> - wave number, a property to add</li>
</ul>
<p>There are three potential outcomes when adding custom properties that I want to track and report back to the user:</p>
<ol>
<li>The node doesn’t exist (maybe because the ‘name’ is misspelled)</li>
<li>The node already has the ‘wave’ property set, no changes are made</li>
<li>The node doesn’t have the ‘wave’ property set, so two custom properties are set</li>
</ol>
<p>I want a single query I can POST to the Neo4j REST endpoint. I also want enough detail in the JSON response so I know which of these three outcomes actually transpired.</p>
<h3 id="trial-and-error-with-response-metadata">Trial and Error with Response Metadata</h3>
<p>Neo4j’s REST API has a parameter called <a href="https://neo4j.com/docs/rest-docs/current/#rest-api-retrieve-query-metadata" target="_blank">includeStats</a> that you can tack on to requests. When added, it will include some metadata about the transaction in the JSON response. This includes statistics like the number of nodes created, relationships deleted, and properties set. We’ll use this to help us determine what happened on the backend after a user POSTs a Cypher query.</p>
<p>For this example, let’s say I want to add User “BLOPER@INTERNAL.LOCAL” to wave “1” via “LLMNR”.</p>
<p>My first query attempt looked like this. The JSON response with ‘includeStats’ is shown below:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">MATCH (n) WHERE (n.name = "BLOPER@INTERNAL.LOCAL")<br data-jekyll-commonmark-ghpages="" />SET n.owned = "LLMNR", n.wave = 1<br data-jekyll-commonmark-ghpages="" />RETURN 'BLOPER@INTERNAL.LOCAL','1','LLMNR'<br data-jekyll-commonmark-ghpages="" />---<br data-jekyll-commonmark-ghpages="" />{<br data-jekyll-commonmark-ghpages="" /> "results": [<br data-jekyll-commonmark-ghpages="" /> {<br data-jekyll-commonmark-ghpages="" /> "columns": [<br data-jekyll-commonmark-ghpages="" /> "'BLOPER@INTERNAL.LOCAL'",<br data-jekyll-commonmark-ghpages="" /> "'1'",<br data-jekyll-commonmark-ghpages="" /> "'LLMNR'"<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "data": [<br data-jekyll-commonmark-ghpages="" /> {<br data-jekyll-commonmark-ghpages="" /> "row": [<br data-jekyll-commonmark-ghpages="" /> "BLOPER@INTERNAL.LOCAL",<br data-jekyll-commonmark-ghpages="" /> "1",<br data-jekyll-commonmark-ghpages="" /> "LLMNR"<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "meta": [<br data-jekyll-commonmark-ghpages="" /> null,<br data-jekyll-commonmark-ghpages="" /> null,<br data-jekyll-commonmark-ghpages="" /> null<br data-jekyll-commonmark-ghpages="" /> ]<br data-jekyll-commonmark-ghpages="" /> }<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "stats": {<br data-jekyll-commonmark-ghpages="" /> "contains_updates": true,<br data-jekyll-commonmark-ghpages="" /> "nodes_created": 0,<br data-jekyll-commonmark-ghpages="" /> "nodes_deleted": 0,<br data-jekyll-commonmark-ghpages="" /> "properties_set": 2,<br data-jekyll-commonmark-ghpages="" /> "relationships_created": 0,<br data-jekyll-commonmark-ghpages="" /> "relationship_deleted": 0,<br data-jekyll-commonmark-ghpages="" /> "labels_added": 0,<br data-jekyll-commonmark-ghpages="" /> "labels_removed": 0,<br data-jekyll-commonmark-ghpages="" /> "indexes_added": 0,<br data-jekyll-commonmark-ghpages="" /> "indexes_removed": 0,<br data-jekyll-commonmark-ghpages="" /> "constraints_added": 0,<br data-jekyll-commonmark-ghpages="" /> "constraints_removed": 0<br data-jekyll-commonmark-ghpages="" /> }<br data-jekyll-commonmark-ghpages="" /> }<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "errors": [<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" /> ]<br data-jekyll-commonmark-ghpages="" />}</code></pre></figure>
<p>Note that “properties_set” is 2. So this query sets the properties that I want set, but what happens if I run it again? It’ll overwrite properties that already exist. I don’t want that. I could fix this by adding an <code class="highlighter-rouge">AND</code> to filter out the nodes that I’d like to skip:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">MATCH (n) WHERE (n.name = "BLOPER@INTERNAL.LOCAL")<br data-jekyll-commonmark-ghpages="" />AND not(exists(n.wave))<br data-jekyll-commonmark-ghpages="" />SET n.owned = "LLMNR", n.wave = 1<br data-jekyll-commonmark-ghpages="" />RETURN 'BLOPER@INTERNAL.LOCAL','1','LLMNR'<br data-jekyll-commonmark-ghpages="" />---<br data-jekyll-commonmark-ghpages="" />{<br data-jekyll-commonmark-ghpages="" /> "results": [<br data-jekyll-commonmark-ghpages="" /> {<br data-jekyll-commonmark-ghpages="" /> "columns": [<br data-jekyll-commonmark-ghpages="" /> "'BLOPER@INTERNAL.LOCAL'",<br data-jekyll-commonmark-ghpages="" /> "'1'",<br data-jekyll-commonmark-ghpages="" /> "'LLMNR'"<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "data": [<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "stats": {<br data-jekyll-commonmark-ghpages="" /> "contains_updates": false,<br data-jekyll-commonmark-ghpages="" /> "nodes_created": 0,<br data-jekyll-commonmark-ghpages="" /> "nodes_deleted": 0,<br data-jekyll-commonmark-ghpages="" /> "properties_set": 0,<br data-jekyll-commonmark-ghpages="" /> "relationships_created": 0,<br data-jekyll-commonmark-ghpages="" /> "relationship_deleted": 0,<br data-jekyll-commonmark-ghpages="" /> "labels_added": 0,<br data-jekyll-commonmark-ghpages="" /> "labels_removed": 0,<br data-jekyll-commonmark-ghpages="" /> "indexes_added": 0,<br data-jekyll-commonmark-ghpages="" /> "indexes_removed": 0,<br data-jekyll-commonmark-ghpages="" /> "constraints_added": 0,<br data-jekyll-commonmark-ghpages="" /> "constraints_removed": 0<br data-jekyll-commonmark-ghpages="" /> }<br data-jekyll-commonmark-ghpages="" /> }<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "errors": [<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" /> ]<br data-jekyll-commonmark-ghpages="" />}</code></pre></figure>
<p>Great, this sets the two properties I want and it doesn’t overwrite existing ones. Notice that “properties_set” was 0 because this node already had the custom properties set.</p>
<p>But what happens if we don’t find the node? For example, if ‘name’ was misspelled or if a node with ‘name’ was never added?</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">{<br data-jekyll-commonmark-ghpages="" /> "results": [<br data-jekyll-commonmark-ghpages="" /> {<br data-jekyll-commonmark-ghpages="" /> "columns": [<br data-jekyll-commonmark-ghpages="" /> "'NLOPER@INTERNAL.LOCAL'",<br data-jekyll-commonmark-ghpages="" /> "'1'",<br data-jekyll-commonmark-ghpages="" /> "'LLMNR'"<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "data": [<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "stats": {<br data-jekyll-commonmark-ghpages="" /> "contains_updates": false,<br data-jekyll-commonmark-ghpages="" /> "nodes_created": 0,<br data-jekyll-commonmark-ghpages="" /> "nodes_deleted": 0,<br data-jekyll-commonmark-ghpages="" /> "properties_set": 0,<br data-jekyll-commonmark-ghpages="" /> "relationships_created": 0,<br data-jekyll-commonmark-ghpages="" /> "relationship_deleted": 0,<br data-jekyll-commonmark-ghpages="" /> "labels_added": 0,<br data-jekyll-commonmark-ghpages="" /> "labels_removed": 0,<br data-jekyll-commonmark-ghpages="" /> "indexes_added": 0,<br data-jekyll-commonmark-ghpages="" /> "indexes_removed": 0,<br data-jekyll-commonmark-ghpages="" /> "constraints_added": 0,<br data-jekyll-commonmark-ghpages="" /> "constraints_removed": 0<br data-jekyll-commonmark-ghpages="" /> }<br data-jekyll-commonmark-ghpages="" /> }<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "errors": [<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" /> ]<br data-jekyll-commonmark-ghpages="" />}</code></pre></figure>
<p>It’s the exact same response (except for the name being misspelled). There’s no way to differentiate between a “node not found” error and a “properties already exists” scenario. That’s not helpful to a user.</p>
<p>I want to conditionally set properties, but I need a way to discern between the three scenarios. Here’s where we get creative.</p>
<h3 id="creating-conditional-statements-with-cypher">Creating Conditional Statements with Cypher</h3>
<p>Cypher doesn’t support full-blown conditional statements. We can’t directly express something like <code class="highlighter-rouge">if a.x > 0, then SET a.y=1, else SET a.y=0, a.z=1</code>. We can get close with the <code class="highlighter-rouge">CASE</code> statement, which acts a lot like it does in the SQL world (example from <a href="https://neo4j.com/docs/developer-manual/current/cypher/syntax/expressions/" target="_blank">here</a>):</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">MATCH (n)<br data-jekyll-commonmark-ghpages="" />RETURN<br data-jekyll-commonmark-ghpages="" />CASE<br data-jekyll-commonmark-ghpages="" /> WHEN n.eyes = 'blue'<br data-jekyll-commonmark-ghpages="" /> THEN 1<br data-jekyll-commonmark-ghpages="" /> WHEN n.age < 40<br data-jekyll-commonmark-ghpages="" /> THEN 2<br data-jekyll-commonmark-ghpages="" /> ELSE 3<br data-jekyll-commonmark-ghpages="" />END AS result</code></pre></figure>
<p>The problem is that <code class="highlighter-rouge">CASE</code> is limited to returning a literal expression. We can’t put clauses like <code class="highlighter-rouge">SET</code> or <code class="highlighter-rouge">MATCH</code> inside a <code class="highlighter-rouge">THEN</code>. That makes it difficult to do something like setting a property conditionally.</p>
<p>What we can do though is nest a <code class="highlighter-rouge">CASE</code> statement inside a <code class="highlighter-rouge">FOREACH</code> clause (<a href="https://neo4j.com/docs/developer-manual/current/cypher/clauses/foreach/" target="_blank">FOREACH documentation here</a>). <code class="highlighter-rouge">FOREACH</code> will loop through a list or a path and pass each matching element to a clause (like <code class="highlighter-rouge">SET</code>):</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">MATCH p =(begin)-[*]->(last)<br data-jekyll-commonmark-ghpages="" />WHERE begin.name = 'A' AND last.name = 'D'<br data-jekyll-commonmark-ghpages="" />FOREACH (n IN nodes(p) | SET n.marked = TRUE)</code></pre></figure>
<p>This is nifty, but we only want to perform clauses under certain conditions, like when a property does or doesn’t exist.</p>
<p><u><b>This is the hack</b></u>: we’ll use a <code class="highlighter-rouge">CASE</code> statement to either return an empty list or a list with one element. That result is passed to a <code class="highlighter-rouge">FOREACH</code> loop. If the result is an empty list, then the <code class="highlighter-rouge">FOREACH</code> clause won’t execute (because there’s nothing to iterate over.) If the result is a list with one element, then the <code class="highlighter-rouge">FOREACH</code> clause will execute once (because it iterated over one element.)</p>
<p>Let’s see it in context. This is how I ultimately ended up crafting the Cypher query (and here it is in <a href="https://github.com/porterhau5/BloodHound-Owned/blob/master/bh-owned.rb#L92" target="_blank">bh-owned.rb</a>):</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">1. MATCH (n) WHERE (n.name = 'BLOPER@INTERNAL.LOCAL')<br data-jekyll-commonmark-ghpages="" />2. FOREACH (ignoreMe in CASE<br data-jekyll-commonmark-ghpages="" />3. WHEN exists(n.wave) THEN [1]<br data-jekyll-commonmark-ghpages="" />4. ELSE [] END | SET n.wave=n.wave)<br data-jekyll-commonmark-ghpages="" />5. FOREACH (ignoreMe in CASE<br data-jekyll-commonmark-ghpages="" />6. WHEN not(exists(n.wave)) THEN [1]<br data-jekyll-commonmark-ghpages="" />7. ELSE [] END | SET n.owned = 'LLMNR', n.wave = 1)<br data-jekyll-commonmark-ghpages="" />8. RETURN 'BLOPER@INTERNAL.LOCAL','1','LLMNR'</code></pre></figure>
<p>A lot going on here, but let’s take it line-by-line:</p>
<p><code class="highlighter-rouge">1. MATCH (n) WHERE (n.name = 'BLOPER@INTERNAL.LOCAL')</code> : Match nodes where the ‘name’ property is equal to ‘BLOPER@INTERNAL.LOCAL’, store node in variable named ‘n’.</p>
<p><code class="highlighter-rouge">2. FOREACH (ignoreMe in CASE</code> : FOREACH will loop through a list or path. We’re not passing in a list or path directly though – we’re passing in the result of a CASE statement. The result will either be a list with one element (if true) or an empty list (if false). As the name suggests, we can ignore the ‘ignoreMe’ variable because we’ll never use it, we just need it there to satisfy syntax.</p>
<p><code class="highlighter-rouge">3. WHEN exists(n.wave) THEN [1]</code> : Here’s where we can specify the “if” conditional. If the ‘wave’ property already exists for our node, then pass a list with one element (<code class="highlighter-rouge">[1]</code>) back to the FOREACH clause. This means we want to execute the clause (<code class="highlighter-rouge">| SET n.wave=n.wave</code>) once. This is our “true” condition.</p>
<p><code class="highlighter-rouge">4. ELSE [] END | SET n.wave=n.wave)</code> : If the ‘wave’ property doesn’t exist for our node, then pass an empty list (<code class="highlighter-rouge">[]</code>) to the FOREACH clause. This is our “false” condition. With an empty list, no iteration happens. That means the clause <code class="highlighter-rouge">| SET n.wave=n.wave</code> won’t execute. However, if the list is not empty (<code class="highlighter-rouge">[1]</code>) then we execute the clause once and set ‘n.wave’ equal to itself (I’ll explain why later, just remember that we only set one property.)</p>
<p><code class="highlighter-rouge">5. FOREACH (ignoreMe in CASE</code> : Again, we’ll loop through the result of a CASE statement (which will return an empty list or list with one element).</p>
<p><code class="highlighter-rouge">6. WHEN not(exists(n.wave)) THEN [1]</code> : If the ‘wave’ property doesn’t exist for our node, then we’ll return a list with one element and ensure we execute the clause.</p>
<p><code class="highlighter-rouge">7. ELSE [] END | SET n.owned = 'LLMNR', n.wave = 1)</code> : If the ‘wave’ property does exist, then return an empty list (<code class="highlighter-rouge">[]</code>) and skip execution of the clause. However if we return a one-element list (<code class="highlighter-rouge">[1]</code>) then the ‘wave’ property doesn’t exist and we’ll execute <code class="highlighter-rouge">| SET n.owned = 'LLMNR', n.wave = 1</code>. In this example, we set two properties.</p>
<p><code class="highlighter-rouge">8. RETURN 'BLOPER@INTERNAL.LOCAL','1','LLMNR'</code> : The primary goal of conditionally setting properties is already done by this point. We RETURN the inputs (name, method, wave) so we can make parsing the API response easier.</p>
<h3 id="providing-detailed-feedback">Providing Detailed Feedback</h3>
<p>With this query, we can now differentiate between the three different potential outcomes by inspecting “properties_set” in the API response:</p>
<ol>
<li>If “properties_set” is 0, then the node with ‘name’ wasn’t found, so the FOREACH statements didn’t execute. This happens when ‘name’ is misspelled.</li>
<li>If “properties_set” is 1, then node was found and the ‘wave’ property already exists, but we didn’t overwrite it. We need something to distinguish this scenario from the third one below, so we set ‘n.wave=n.wave’ which technically means we set one property.</li>
<li>If “properties_set” is 2, then the node was found and the ‘wave’ property didn’t exist, so we created the ‘wave’ and ‘owned’ properties.</li>
</ol>
<p>Here’s what each scenario looks like when using <code class="highlighter-rouge">bh-owned.rb</code>. Take note of the “properties_set” key in the JSON responses.</p>
<p><strong>Scenario 1</strong> – Misspelled node name. “properties_set” is 0. Don’t run the follow-up query to find the spread of compromise for a node:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">$ ruby bh-owned.rb -a example-wave.txt<br data-jekyll-commonmark-ghpages="" />[*] Using default username: neo4j<br data-jekyll-commonmark-ghpages="" />[*] Using default password: BloodHound<br data-jekyll-commonmark-ghpages="" />[*] Using default URL: http://127.0.0.1:7474/<br data-jekyll-commonmark-ghpages="" />[*] No previously owned nodes found, setting wave to 1<br data-jekyll-commonmark-ghpages="" />[-] Properties not added for 'NLOPER@INTERNAL.LOCAL' (node not found, check spelling?)<br data-jekyll-commonmark-ghpages="" />[-] Skipping finding spread of compromise due to "node not found" error<br data-jekyll-commonmark-ghpages="" />{<br data-jekyll-commonmark-ghpages="" /> "results": [<br data-jekyll-commonmark-ghpages="" /> {<br data-jekyll-commonmark-ghpages="" /> "columns": [<br data-jekyll-commonmark-ghpages="" /> "'NLOPER@INTERNAL.LOCAL'",<br data-jekyll-commonmark-ghpages="" /> "'1'",<br data-jekyll-commonmark-ghpages="" /> "'LLMNR'"<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "data": [<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "stats": {<br data-jekyll-commonmark-ghpages="" /> "contains_updates": false,<br data-jekyll-commonmark-ghpages="" /> "nodes_created": 0,<br data-jekyll-commonmark-ghpages="" /> "nodes_deleted": 0,<br data-jekyll-commonmark-ghpages="" /> "properties_set": 0,<br data-jekyll-commonmark-ghpages="" /> "relationships_created": 0,<br data-jekyll-commonmark-ghpages="" /> "relationship_deleted": 0,<br data-jekyll-commonmark-ghpages="" /> "labels_added": 0,<br data-jekyll-commonmark-ghpages="" /> "labels_removed": 0,<br data-jekyll-commonmark-ghpages="" /> "indexes_added": 0,<br data-jekyll-commonmark-ghpages="" /> "indexes_removed": 0,<br data-jekyll-commonmark-ghpages="" /> "constraints_added": 0,<br data-jekyll-commonmark-ghpages="" /> "constraints_removed": 0<br data-jekyll-commonmark-ghpages="" /> }<br data-jekyll-commonmark-ghpages="" /> }<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "errors": [<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" /> ]<br data-jekyll-commonmark-ghpages="" />}</code></pre></figure>
<p><strong>Scenario 2</strong> – Node found and ‘wave’ property already exists. “properties_set” is 1:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">$ ruby bh-owned.rb -a example-wave.txt<br data-jekyll-commonmark-ghpages="" />[*] Using default username: neo4j<br data-jekyll-commonmark-ghpages="" />[*] Using default password: BloodHound<br data-jekyll-commonmark-ghpages="" />[*] Using default URL: http://127.0.0.1:7474/<br data-jekyll-commonmark-ghpages="" />[*] Properties already exist for 'BLOPER@INTERNAL.LOCAL', skipping (overwrite with flag -w <num>)<br data-jekyll-commonmark-ghpages="" />[*] Finding spread of compromise for wave 2<br data-jekyll-commonmark-ghpages="" />[-] No additional nodes found for wave 2<br data-jekyll-commonmark-ghpages="" />{<br data-jekyll-commonmark-ghpages="" /> "results": [<br data-jekyll-commonmark-ghpages="" /> {<br data-jekyll-commonmark-ghpages="" /> "columns": [<br data-jekyll-commonmark-ghpages="" /> "'BLOPER@INTERNAL.LOCAL'",<br data-jekyll-commonmark-ghpages="" /> "'2'",<br data-jekyll-commonmark-ghpages="" /> "'LLMNR'"<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "data": [<br data-jekyll-commonmark-ghpages="" /> {<br data-jekyll-commonmark-ghpages="" /> "row": [<br data-jekyll-commonmark-ghpages="" /> "BLOPER@INTERNAL.LOCAL",<br data-jekyll-commonmark-ghpages="" /> "2",<br data-jekyll-commonmark-ghpages="" /> "LLMNR"<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "meta": [<br data-jekyll-commonmark-ghpages="" /> null,<br data-jekyll-commonmark-ghpages="" /> null,<br data-jekyll-commonmark-ghpages="" /> null<br data-jekyll-commonmark-ghpages="" /> ]<br data-jekyll-commonmark-ghpages="" /> }<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "stats": {<br data-jekyll-commonmark-ghpages="" /> "contains_updates": true,<br data-jekyll-commonmark-ghpages="" /> "nodes_created": 0,<br data-jekyll-commonmark-ghpages="" /> "nodes_deleted": 0,<br data-jekyll-commonmark-ghpages="" /> "properties_set": 1,<br data-jekyll-commonmark-ghpages="" /> "relationships_created": 0,<br data-jekyll-commonmark-ghpages="" /> "relationship_deleted": 0,<br data-jekyll-commonmark-ghpages="" /> "labels_added": 0,<br data-jekyll-commonmark-ghpages="" /> "labels_removed": 0,<br data-jekyll-commonmark-ghpages="" /> "indexes_added": 0,<br data-jekyll-commonmark-ghpages="" /> "indexes_removed": 0,<br data-jekyll-commonmark-ghpages="" /> "constraints_added": 0,<br data-jekyll-commonmark-ghpages="" /> "constraints_removed": 0<br data-jekyll-commonmark-ghpages="" /> }<br data-jekyll-commonmark-ghpages="" /> }<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "errors": [<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" /> ]<br data-jekyll-commonmark-ghpages="" />}</code></pre></figure>
<p><strong>Scenario 3</strong> – Node found and ‘wave’ property doesn’t exist. “properties_set” is 2:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">$ ruby bh-owned.rb -a example-wave.txt<br data-jekyll-commonmark-ghpages="" />[*] Using default username: neo4j<br data-jekyll-commonmark-ghpages="" />[*] Using default password: BloodHound<br data-jekyll-commonmark-ghpages="" />[*] Using default URL: http://127.0.0.1:7474/<br data-jekyll-commonmark-ghpages="" />[*] No previously owned nodes found, setting wave to 1<br data-jekyll-commonmark-ghpages="" />[+] Success, marked 'BLOPER@INTERNAL.LOCAL' as owned in wave '1' via 'LLMNR'<br data-jekyll-commonmark-ghpages="" />[*] Finding spread of compromise for wave 1<br data-jekyll-commonmark-ghpages="" />[+] 2 nodes found:<br data-jekyll-commonmark-ghpages="" />DOMAIN USERS@INTERNAL.LOCAL<br data-jekyll-commonmark-ghpages="" />SYSTEM38.INTERNAL.LOCAL<br data-jekyll-commonmark-ghpages="" />{<br data-jekyll-commonmark-ghpages="" /> "results": [<br data-jekyll-commonmark-ghpages="" /> {<br data-jekyll-commonmark-ghpages="" /> "columns": [<br data-jekyll-commonmark-ghpages="" /> "'BLOPER@INTERNAL.LOCAL'",<br data-jekyll-commonmark-ghpages="" /> "'1'",<br data-jekyll-commonmark-ghpages="" /> "'LLMNR'"<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "data": [<br data-jekyll-commonmark-ghpages="" /> {<br data-jekyll-commonmark-ghpages="" /> "row": [<br data-jekyll-commonmark-ghpages="" /> "BLOPER@INTERNAL.LOCAL",<br data-jekyll-commonmark-ghpages="" /> "1",<br data-jekyll-commonmark-ghpages="" /> "LLMNR"<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "meta": [<br data-jekyll-commonmark-ghpages="" /> null,<br data-jekyll-commonmark-ghpages="" /> null,<br data-jekyll-commonmark-ghpages="" /> null<br data-jekyll-commonmark-ghpages="" /> ]<br data-jekyll-commonmark-ghpages="" /> }<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "stats": {<br data-jekyll-commonmark-ghpages="" /> "contains_updates": true,<br data-jekyll-commonmark-ghpages="" /> "nodes_created": 0,<br data-jekyll-commonmark-ghpages="" /> "nodes_deleted": 0,<br data-jekyll-commonmark-ghpages="" /> "properties_set": 2,<br data-jekyll-commonmark-ghpages="" /> "relationships_created": 0,<br data-jekyll-commonmark-ghpages="" /> "relationship_deleted": 0,<br data-jekyll-commonmark-ghpages="" /> "labels_added": 0,<br data-jekyll-commonmark-ghpages="" /> "labels_removed": 0,<br data-jekyll-commonmark-ghpages="" /> "indexes_added": 0,<br data-jekyll-commonmark-ghpages="" /> "indexes_removed": 0,<br data-jekyll-commonmark-ghpages="" /> "constraints_added": 0,<br data-jekyll-commonmark-ghpages="" /> "constraints_removed": 0<br data-jekyll-commonmark-ghpages="" /> }<br data-jekyll-commonmark-ghpages="" /> }<br data-jekyll-commonmark-ghpages="" /> ],<br data-jekyll-commonmark-ghpages="" /> "errors": [<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" /> ]<br data-jekyll-commonmark-ghpages="" />}</code></pre></figure>
<p>And that’s one way to do conditional statements with Cypher :D</p>
<p><a href="https://porterhau5.com/images/lolcypher.png" target="_blank"><img src="https://porterhau5.com/images/lolcypher.png" /></a></p>
<p>(Sorry. I couldn’t write about hacking, Cypher, and <u>Neo</u>4j without making at least one Matrix reference. <a href="http://www.cracked.com/article_19435_6-movie-plot-holes-you-never-noticed-thanks-to-editing.html" target="_blank">#CypherIsReallyTheOne</a>)</p>
<p>Thank you for reading!</p>
<h4 id="share">Share</h4>
<ul class="share-buttons">
<li><a href="https://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Fporterhau5.com%2Fblog%2Fcreating-conditional-statements-with-cypher%2F&t=Creating%20Conditional%20Statements%20with%20Cypher" title="Share on Facebook" target="_blank"><img alt="Share on Facebook" src="https://porterhau5.com/images/flat_web_icon_set/black/Facebook.png" /></a></li>
<li><a href="https://twitter.com/intent/tweet?source=http%3A%2F%2Fporterhau5.com%2Fblog%2Fcreating-conditional-statements-with-cypher%2F&text=Creating%20Conditional%20Statements%20with%20Cypher:%20http%3A%2F%2Fporterhau5.com%2Fblog%2Fcreating-conditional-statements-with-cypher%2F" target="_blank" title="Tweet"><img alt="Tweet" src="https://porterhau5.com/images/flat_web_icon_set/black/Twitter.png" /></a></li>
<li><a href="https://getpocket.com/save?url=http%3A%2F%2Fporterhau5.com%2Fblog%2Fcreating-conditional-statements-with-cypher%2F&title=Creating%20Conditional%20Statements%20with%20Cypher" target="_blank" title="Add to Pocket"><img alt="Add to Pocket" src="https://porterhau5.com/images/flat_web_icon_set/black/Pocket.png" /></a></li>
<li><a href="http://www.reddit.com/submit?url=http%3A%2F%2Fporterhau5.com%2Fblog%2Fcreating-conditional-statements-with-cypher%2F&title=Creating%20Conditional%20Statements%20with%20Cypher" target="_blank" title="Submit to Reddit"><img alt="Submit to Reddit" src="https://porterhau5.com/images/flat_web_icon_set/black/Reddit.png" /></a></li>
</ul>
2017-04-04T00:00:00+00:00https://porterhau5.com/blog/representing-password-reuse-in-bloodhound/Representing Password Reuse in BloodHound2017-03-30T00:00:00+00:00porterhau5https://porterhau5.com/How to integrate password reuse attacks into BloodHound with the 'SharesPasswordWith' relationship. Includes a new Custom Query, API response logic parsing, detailed query output, and more.<p>This is another installment in a series of posts regarding modifications to BloodHound and lessons learned while working with Neo4j & Cypher. Other posts can be found here:</p>
<ul>
<li><a href="/blog/extending-bloodhound-track-and-visualize-your-compromise/">Visualizing and Tracking Your Compromise</a></li>
<li><a href="/blog/creating-conditional-statements-with-cypher/">Creating Conditional Statements with Cypher</a></li>
</ul>
<p>This post will cover a new relationship added to the BloodHound Owned extensions: <code class="highlighter-rouge">SharesPasswordWith</code>. Check out the <a href="https://github.com/porterhau5/BloodHound-Owned" target="_blank">GitHub BloodHound-Owned</a> project for the latest code update. Here’s what it looks like in action:</p>
<p><a href="https://porterhau5.com/images/spw.gif" target="_blank"><img src="https://porterhau5.com/images/spw.gif" /></a></p>
<ul>
<li><a href="#local-accounts-lazy-users-and-password-reuse">Local Accounts, Lazy Users, and Password Reuse</a></li>
<li><a href="#the-sharespasswordwith-relationship">The ‘SharesPasswordWith’ Relationship</a></li>
<li><a href="#other-new-features">Other New Features</a>
<ul>
<li><a href="#error-checking">Error Checking</a></li>
<li><a href="#detailed-output">Detailed Output</a></li>
<li><a href="#the-reset-flag">The Reset Flag</a></li>
</ul>
</li>
<li><a href="#next-steps">Next Steps</a></li>
</ul>
<h3 id="local-accounts-lazy-users-and-password-reuse">Local Accounts, Lazy Users, and Password Reuse</h3>
<p>My workflow during a penetration test leverages some non-Active Directory techniques, as I’d imagine most pen testers’ would. One of the first actions I take after compromising a system is to dump the hashes of the local accounts. We often find that those accounts exist on other machines in the environment with the exact same password. It’s a common technique for lateral movement and it’s highly effective. <a href="https://twitter.com/simakov_marina" target="_blank">Marina Simakov</a> and <a href="https://twitter.com/TalBeerySec" target="_blank">Tal Be’ery</a> delivered a great presentation at BlueHat IL 2017 that dives deeper into the risks associated with local accounts. I encourage you to <a href="https://youtu.be/HE7X7l-k-A4" target="_blank">check it out here</a>.</p>
<p>However, lateral movement like this is not something that’s guaranteed to work by just inspecting users and attributes – granted you can get a pretty good idea by comparing PwdLastSet dates. Local accounts aren’t necessarily backed by a centralized configuration overlord like Active Directory, so each machine’s local RID 500 account isn’t guaranteed to have the same password or username. Heck, it’s not even guaranteed to be active. Maybe the admins disabled “Administrator” and created “corp_adm” with RID 1000 as the new local admin. Lucky for us red teamers, many Domain Admins haven’t adopted <a href="https://technet.microsoft.com/en-us/mt227395.aspx" target="_blank">LAPS</a> yet, so they instead manually set the password for the local admin account (or even worse, use GPP to set it.) And they set that same password on several machines.</p>
<p><strong>Where else do we see commonly see passwords being reused?</strong> Users with multiple accounts. For example, Bob may have a normal, everyday account (INTERNAL\bob) and a privileged account for administrator-level tasks (INTERNAL\bob_admin). Or perhaps Bob has an account on two different domains (INTERNAL\bob and EXTERNAL\bob). Bob doesn’t like the hassle of remembering two different passwords, so he sets the same password for each account. That’s a bad Bob.</p>
<p>Abusing password reuse between separate accounts is a useful technique for expanding access across the network, but it’s something that BloodHound didn’t have a representation for quite yet. No fault of BloodHound – its data collection script isn’t designed to test for reused passwords, and that’s a sensible approach given the inherit risks of doing so without human oversight. However this knowledge is valuable information for our attack graph, so I created a means to integrate this into BloodHound via a new relationship.</p>
<h3 id="the-sharespasswordwith-relationship">The ‘SharesPasswordWith’ Relationship</h3>
<p>I’ve added a new relationship in BloodHound to represent password reuse – I call it ‘SharesPasswordWith’. It’s meant to be used as a relationship (or an “edge”) between the following types of nodes:</p>
<ul>
<li>(:Computer)-[:SharesPasswordWith]->(:Computer)</li>
<li>(:User)-[:SharesPasswordWith]->(:User)</li>
</ul>
<p>The first point would cover common local administrator passwords on Computers. <br /> The second point would cover Users reusing the same password for multiple accounts.</p>
<p>Adding this relationship for two given nodes via Cypher looks like this:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">MATCH (n {name:$node1}),(m {name:$node2})<br data-jekyll-commonmark-ghpages="" />WITH n,m CREATE UNIQUE (n)-[:SharesPasswordWith]->(m)<br data-jekyll-commonmark-ghpages="" />WITH n,m CREATE UNIQUE (n)<-[:SharesPasswordWith]-(m)</code></pre></figure>
<p>Neo4j doesn’t support bidirectional relationships, however we can make a unidirectional relationship in both directions. We architect it this way to allow paths in either direction for escalation. Don’t worry, Neo4j is smart enough to avoid cycles and to calculate shortest paths despite this configuration.</p>
<p>The use of <code class="highlighter-rouge">CREATE UNIQUE</code> guarantees that we don’t create duplicate relationships between nodes, so there’s no harm in attempting to re-add the same relationship multiple times.</p>
<p>The simplest way to add these relationships is by using the <code class="highlighter-rouge">-s</code> flag in <a href="https://github.com/porterhau5/BloodHound-Owned" target="_blank">bh-owned.rb</a>:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">$ ruby bh-owned.rb<br data-jekyll-commonmark-ghpages="" />Usage: ruby bh-owned.rb [options]<br data-jekyll-commonmark-ghpages="" /> -u, --username <id> Neo4j database username (default: 'neo4j')<br data-jekyll-commonmark-ghpages="" /> -p, --password <pass> Neo4j database password (default: 'BloodHound')<br data-jekyll-commonmark-ghpages="" /> -U, --url <url> URL of Neo4j RESTful host (default: 'http://127.0.0.1:7474/')<br data-jekyll-commonmark-ghpages="" /> -n, --nodes get all node names<br data-jekyll-commonmark-ghpages="" /> -a, --add <file> add 'owned' and 'wave' property to nodes in <file><br data-jekyll-commonmark-ghpages="" /> -s, --spw <file> add 'SharesPasswordWith' relationship between all nodes in <file><br data-jekyll-commonmark-ghpages="" /> -w, --wave <num> value to set 'wave' property (override default behavior)<br data-jekyll-commonmark-ghpages="" /> --reset remove all custom properties and SharesPasswordWith relationships<br data-jekyll-commonmark-ghpages="" /> -e, --examples reference doc of customized Cypher queries for BloodHound</code></pre></figure>
<p>The file passed to <code class="highlighter-rouge">-s</code> should be newline-delimited with one node name per line. All nodes listed in the file will have a ‘SharesPasswordWith’ relationship created between them, essentially creating a small mesh network of relationships.</p>
<p>Let’s say we found four Computers sharing a common local administrator password:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">$ cat common-local-admins.txt<br data-jekyll-commonmark-ghpages="" />MANAGEMENT3.INTERNAL.LOCAL<br data-jekyll-commonmark-ghpages="" />FILESERVER6.INTERNAL.LOCAL<br data-jekyll-commonmark-ghpages="" />SYSTEM38.INTERNAL.LOCAL<br data-jekyll-commonmark-ghpages="" />DESKTOP40.EXTERNAL.LOCAL</code></pre></figure>
<p>For a file containing 4 nodes we’d create a total of <em><code class="highlighter-rouge">P(4,2)=12</code></em> relationships (or, “Choose 2 nodes from group of 4 nodes”.) The output below shows the 6 unique pairs of nodes, and our query is creating the relationship in both directions, so we have a total of 12 relationships created. Yay combinatorics:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">$ ruby bh-owned.rb -s common-local-admins.txt<br data-jekyll-commonmark-ghpages="" />[*] Using default username: neo4j<br data-jekyll-commonmark-ghpages="" />[*] Using default password: BloodHound<br data-jekyll-commonmark-ghpages="" />[*] Using default URL: http://127.0.0.1:7474/<br data-jekyll-commonmark-ghpages="" />[+] Created SharesPasswordWith relationship: 'MANAGEMENT3.INTERNAL.LOCAL' and 'FILESERVER6.INTERNAL.LOCAL'<br data-jekyll-commonmark-ghpages="" />[+] Created SharesPasswordWith relationship: 'MANAGEMENT3.INTERNAL.LOCAL' and 'SYSTEM38.INTERNAL.LOCAL'<br data-jekyll-commonmark-ghpages="" />[+] Created SharesPasswordWith relationship: 'MANAGEMENT3.INTERNAL.LOCAL' and 'DESKTOP40.EXTERNAL.LOCAL'<br data-jekyll-commonmark-ghpages="" />[+] Created SharesPasswordWith relationship: 'FILESERVER6.INTERNAL.LOCAL' and 'SYSTEM38.INTERNAL.LOCAL'<br data-jekyll-commonmark-ghpages="" />[+] Created SharesPasswordWith relationship: 'FILESERVER6.INTERNAL.LOCAL' and 'DESKTOP40.EXTERNAL.LOCAL'<br data-jekyll-commonmark-ghpages="" />[+] Created SharesPasswordWith relationship: 'SYSTEM38.INTERNAL.LOCAL' and 'DESKTOP40.EXTERNAL.LOCAL'</code></pre></figure>
<p>With the relationships added, we can see how the graph in BloodHound changes with our <font color="#C900FF">owned</font> nodes. Here’s the custom query “Find Shortest Paths from owned nodes to Domain Admins”:
<a href="https://porterhau5.com/images/spw-example.png" target="_blank"><img src="https://porterhau5.com/images/spw-example.png" /></a></p>
<p>You can replicate this by adding the first 3 waves in the <code class="highlighter-rouge">example-files</code> directory via <code class="highlighter-rouge">-a</code>, and then adding the <code class="highlighter-rouge">common-local-admins.txt</code> file via <code class="highlighter-rouge">-s</code>.</p>
<p>To supplement this, I also created a Custom Query to highlight all instances of password reuse added to the database – <strong>Find Clusters of Password Reuse</strong> (<a href="https://github.com/porterhau5/BloodHound-Owned/commit/1dca29cd6ec5c297fe19b7e19bc9827246ff9d06" target="_blank">source here</a>):</p>
<p><a href="https://porterhau5.com/images/password-reuse-query.png" target="_blank"><img src="https://porterhau5.com/images/password-reuse-query.png" /></a></p>
<p>Pretty self-explanatory. The Cypher query used to make this graph looks like:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">MATCH p=(n)-[r:SharesPasswordWith]->(m) RETURN p</code></pre></figure>
<p>The graph can become a little hard to interpret when working with a high volume of nodes. I think a future enhancement will be to add a third layout type (joining Directed and Hierarchical) that shows a radial graph output for this query. Doing so will require updating the BloodHound source and learning more about Linkurious and Sigma.</p>
<h3 id="other-new-features">Other New Features</h3>
<p>The latest code pushed to the <a href="https://github.com/porterhau5/BloodHound-Owned" target="_blank">repo</a> contained a couple of smaller additions worth noting.</p>
<h5 id="error-checking">Error Checking</h5>
<p>I’ve focused on the response parsing logic for API requests so that more specific details can be echoed back to the user. For example, here’s what happens when you try to add a node with the name misspelled:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">$ ruby bh-owned.rb -a 1st-wave.txt<br data-jekyll-commonmark-ghpages="" />[*] Using default username: neo4j<br data-jekyll-commonmark-ghpages="" />[*] Using default password: BloodHound<br data-jekyll-commonmark-ghpages="" />[*] Using default URL: http://127.0.0.1:7474/<br data-jekyll-commonmark-ghpages="" />[*] No previously owned nodes found, setting wave to 1<br data-jekyll-commonmark-ghpages="" />[-] Properties not added for 'BLOPER@INT.LOC' (node not found, check spelling?)<br data-jekyll-commonmark-ghpages="" />[+] Success, marked 'JCARNEAL@INTERNAL.LOCAL' as owned in wave '1' via 'NBNS wpad'<br data-jekyll-commonmark-ghpages="" />[-] Skipping finding spread of compromise due to "node not found" error</code></pre></figure>
<p>Since an error was found during the property-adding process (the <code class="highlighter-rouge">MATCH</code> query returned no matches due to a misspelled name), then it won’t run the follow-up query to find the spread of compromise for that wave. Once the typo is fixed, re-run the command and add the <code class="highlighter-rouge">-w</code> flag and the desired wave number:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">$ ruby bh-owned.rb -a 1st-wave.txt -w 1<br data-jekyll-commonmark-ghpages="" />[*] Using default username: neo4j<br data-jekyll-commonmark-ghpages="" />[*] Using default password: BloodHound<br data-jekyll-commonmark-ghpages="" />[*] Using default URL: http://127.0.0.1:7474/<br data-jekyll-commonmark-ghpages="" />[+] Success, marked 'BLOPER@INTERNAL.LOCAL' as owned in wave '1' via 'LLMNR wpad'<br data-jekyll-commonmark-ghpages="" />[+] Success, marked 'JCARNEAL@INTERNAL.LOCAL' as owned in wave '1' via 'NBNS wpad'<br data-jekyll-commonmark-ghpages="" />[*] Finding spread of compromise for wave 1<br data-jekyll-commonmark-ghpages="" />[+] 2 nodes found:<br data-jekyll-commonmark-ghpages="" />DOMAIN USERS@INTERNAL.LOCAL<br data-jekyll-commonmark-ghpages="" />SYSTEM38.INTERNAL.LOCAL</code></pre></figure>
<p>Getting this granularity of detail in the API response required some Cypher expression hacks (<a href="https://github.com/porterhau5/BloodHound-Owned/commit/55b5b763db3332c34a10b8d16b45ba98ce45ce2c#diff-621720f457a37e54436019d9d9b9a6abR92" target="_blank">shown here</a>), probably better suited for a different blog post. I plan to write about it next week.</p>
<h5 id="detailed-output">Detailed Output</h5>
<p>As seen with the above command, the script will now echo back all nodes found in a wave of compromise. Useful for a quick glance at the collateral damage when a node is <font color="#C900FF">owned</font>:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">$ ruby bh-owned.rb -a 2nd-wave.txt<br data-jekyll-commonmark-ghpages="" />[*] Using default username: neo4j<br data-jekyll-commonmark-ghpages="" />[*] Using default password: BloodHound<br data-jekyll-commonmark-ghpages="" />[*] Using default URL: http://127.0.0.1:7474/<br data-jekyll-commonmark-ghpages="" />[+] Success, marked 'ZDEVENS@INTERNAL.LOCAL' as owned in wave '2' via 'Password spray'<br data-jekyll-commonmark-ghpages="" />[+] Success, marked 'BPICKEREL@INTERNAL.LOCAL' as owned in wave '2' via 'Password spray'<br data-jekyll-commonmark-ghpages="" />[*] Finding spread of compromise for wave 2<br data-jekyll-commonmark-ghpages="" />[+] 5 nodes found:<br data-jekyll-commonmark-ghpages="" />BACKUP3@INTERNAL.LOCAL<br data-jekyll-commonmark-ghpages="" />BACKUP_SVC@INTERNAL.LOCAL<br data-jekyll-commonmark-ghpages="" />CONTRACTINGS@INTERNAL.LOCAL<br data-jekyll-commonmark-ghpages="" />DATABASE5.INTERNAL.LOCAL<br data-jekyll-commonmark-ghpages="" />MANAGEMENT3.INTERNAL.LOCAL</code></pre></figure>
<p>We see above that two nodes were marked as <code class="highlighter-rouge">owned</code> for wave 2. After finding the spread of compromise, we found 5 additional nodes in the wave.</p>
<h5 id="the-reset-flag">The Reset Flag</h5>
<p>In the event that you want to remove any custom properties (<code class="highlighter-rouge">owned</code>, <code class="highlighter-rouge">wave</code>) or any custom relationships (<code class="highlighter-rouge">SharesPasswordWith</code>) from the database, you can now use the <code class="highlighter-rouge">--reset</code> flag:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">$ ruby bh-owned.rb --reset<br data-jekyll-commonmark-ghpages="" />[*] Using default username: neo4j<br data-jekyll-commonmark-ghpages="" />[*] Using default password: BloodHound<br data-jekyll-commonmark-ghpages="" />[*] Using default URL: http://127.0.0.1:7474/<br data-jekyll-commonmark-ghpages="" />[*] Removing all custom properties and SharesPasswordWith relationships</code></pre></figure>
<p>Underneath the hood it’s issuing the following Cypher queries:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">MATCH (n) WHERE exists(n.wave) OR exists(n.owned) REMOVE n.wave, n.owned<br data-jekyll-commonmark-ghpages="" />MATCH (n)-[r:SharesPasswordWith]-(m) DELETE r</code></pre></figure>
<h3 id="next-steps">Next Steps</h3>
<p>I’ll post updates here and on <a href="https://twitter.com/porterhau5" target="_blank">Twitter via @porterhau5</a> as I continue to tinker with ideas. Please reach out if you want to explore some of these together! I’d really appreciate some help from those of you who are skilled with front-end development :D Feedback in any form is always welcome.</p>
<ul>
<li>Interrogate Computers for list of local, non-AD admin accounts. Add those accounts to database as User nodes with AdminTo corresponding Computer.</li>
<li>Instead of running custom queries through the Queries tab, have a slider on the main dashboard that can step through waves.</li>
<li>Define a “Critical Path to Compromise” (CPTC): the exact path taken through the network to go from A to Z. Add property to nodes indicating their involvement in the CPTC. Write custom query to show this path specifically and filter out the rest of the noise. Mostly useful for presenting/explaining to client.</li>
<li>Add more options when a node is right-clicked. For example, “Shortest Paths from Here”, “Add to wave X”, or “Created SharesPasswordWith relationship to node X”.</li>
<li>Make “Owned in Wave” and “Owned via Method” values fillable from the UI. Ditch the need for an external script to ingest data.</li>
<li>General query optimizations to help with scalability & speed. The graphs can get a little messy when a DA account is obtained.</li>
</ul>
<p>Thank you for reading!</p>
<h4 id="share">Share</h4>
<ul class="share-buttons">
<li><a href="https://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Fporterhau5.com%2Fblog%2Frepresenting-password-reuse-in-bloodhound%2F&t=Representing%20Password%20Reuse%20in%20BloodHound" title="Share on Facebook" target="_blank"><img alt="Share on Facebook" src="https://porterhau5.com/images/flat_web_icon_set/black/Facebook.png" /></a></li>
<li><a href="https://twitter.com/intent/tweet?source=http%3A%2F%2Fporterhau5.com%2Fblog%2Frepresenting-password-reuse-in-bloodhound%2F&text=Representing%20Password%20Reuse%20in%20BloodHound:%20http%3A%2F%2Fporterhau5.com%2Fblog%2Frepresenting-password-reuse-in-bloodhound%2F" target="_blank" title="Tweet"><img alt="Tweet" src="https://porterhau5.com/images/flat_web_icon_set/black/Twitter.png" /></a></li>
<li><a href="https://getpocket.com/save?url=http%3A%2F%2Fporterhau5.com%2Fblog%2Frepresenting-password-reuse-in-bloodhound%2F&title=Representing%20Password%20Reuse%20in%20BloodHound" target="_blank" title="Add to Pocket"><img alt="Add to Pocket" src="https://porterhau5.com/images/flat_web_icon_set/black/Pocket.png" /></a></li>
<li><a href="http://www.reddit.com/submit?url=http%3A%2F%2Fporterhau5.com%2Fblog%2Frepresenting-password-reuse-in-bloodhound%2F&title=Representing%20Password%20Reuse%20in%20BloodHound" target="_blank" title="Submit to Reddit"><img alt="Submit to Reddit" src="https://porterhau5.com/images/flat_web_icon_set/black/Reddit.png" /></a></li>
</ul>
2017-03-30T00:00:00+00:00https://porterhau5.com/blog/extending-bloodhound-track-and-visualize-your-compromise/Extending BloodHound: Track and Visualize Your Compromise2017-03-22T00:00:00+00:00porterhau5https://porterhau5.com/Customizing BloodHound's UI and taking advantage of Custom Queries to document a compromise, find collateral spread of owned nodes, and visualize deltas in privilege gains.<p>This is the first installment in a series of posts regarding modifications to BloodHound and lessons learned while working with Neo4j & Cypher. Other posts can be found here:</p>
<ul>
<li><a href="/blog/representing-password-reuse-in-bloodhound/">Representing Password Reuse in BloodHound</a></li>
<li><a href="/blog/creating-conditional-statements-with-cypher/">Creating Conditional Statements with Cypher</a></li>
</ul>
<p>If you’re new to BloodHound, I highly recommend checking out <a href="https://wald0.com/?p=68" target="_blank">this blog</a>, <a href="https://www.youtube.com/watch?v=wP8ZCczC1OU" target="_blank">this video</a>, or <a href="https://github.com/BloodHoundAD/BloodHound/wiki" target="_blank">this wiki</a> to familiarize yourself. The work being done by <a href="https://twitter.com/_wald0" target="_blank">_wald0</a>, <a href="https://twitter.com/CptJesus" target="_blank">CptJesus</a>, and <a href="https://twitter.com/harmj0y" target="_blank">harmj0y</a> is changing how red and blue teams approach risk in Active Directory environments.</p>
<p>This post will cover how I’ve adapted BloodHound to enhance my workflow during a penetration test, complete with example usage and a source code release. It’s a bit verbose – I wanted to include why I saw an opportunity for these changes and the process for creating them. If you’d like to skip to the new features then start <a href="#automating-the-workflow">here</a>.</p>
<p>Custom Queries and database updates can be done independent of the BloodHound application. However, any modifications to BloodHound’s display requires changing BloodHound’s source. A quickstart guide for my Custom Queries and database updates can be found in the <a href="https://github.com/porterhau5/BloodHound-Owned" target="_blank">BloodHound-Owned GitHub repo</a>. My BloodHound UI enhancements, including <font color="#C900FF">node highlighting</font> and custom property display, can be found in this <a href="https://github.com/porterhau5/BloodHound" target="_blank">forked BloodHound repo</a>.</p>
<ul>
<li><a href="#the-bloodhound-platform">The BloodHound Platform</a></li>
<li><a href="#opportunities-to-extend-bloodhound">Opportunities to Extend BloodHound</a></li>
<li><a href="#tracking-compromised-users-and-computers">Tracking Compromised Users and Computers</a></li>
<li><a href="#finding-the-collateral-spread-when-a-node-is-compromised">Finding the Collateral Spread When a Node is Compromised</a></li>
<li><a href="#showing-changes-in-privilege-gains">Showing Changes in Privilege Gains</a></li>
<li><a href="#automating-the-workflow">Automating the Workflow</a></li>
<li><a href="#ui-customizations-and-custom-queries">UI Customizations and Custom Queries</a></li>
<li><a href="#next-steps">Next Steps</a></li>
</ul>
<h3 id="the-bloodhound-platform">The BloodHound Platform</h3>
<p>Out of the box, BloodHound works like a champ. The interface is slick, the install is painless enough considering the dependencies, and the pre-built analytics deliver actionable intelligence. What’s important to note though is that BloodHound isn’t necessarily just a tool, it’s a <strong>platform for users to build on</strong>. The foundational elements – a reliable backend, a means for ingesting, querying, and displaying data – are already taken care of. The piping is in place for users to extend the already-great features and tailor it to their specific job function or workflow.</p>
<p>My workflow stems from being a penetration tester. The value derived from BloodHound on an engagement for me is obvious – I can quickly identify those complex attack paths that otherwise would have taken OVER 9000 BILLABLE HOURS to discover. Not only that, BloodHound serves as a tremendous debriefing aide when walking a client through your critical path to compromise (Seriously, clients <em>love</em> it. It’s interactive, it gives a tangible view of the network to less technical people, and it’s not Powerpoint.)</p>
<p>Whenever I obtain a new account or pop a shell on an engagement, one of the first things I do is run over to BloodHound and fire up the Pathfinding feature. I input the User’s/Computer’s name as my source and select Domain Admins as my target. The query only returns data when a path exists – awesome when it happens, but disappointing when it comes back empty. This process is a little tedious, and it lacks showing me the true privilege gains that come along with obtaining a new account/computer. This looked like an opportunity to add in a feature that could make my life easier as a penetration tester.</p>
<h3 id="opportunities-to-extend-bloodhound">Opportunities to Extend BloodHound</h3>
<p>As I contemplated how to add this functionality into BloodHound, I thought more about my workflow and how compromises often unfold. Privilege gains during a penetration test usually happen in waves. For example, an assessment might start out like:</p>
<ol>
<li>Intercept LLMNR requests, collect and crack NTLMv2 hashes. 2 accounts obtained.</li>
<li>Password spray with “Spring2017!” against domain users. 4 more accounts obtained.</li>
<li>Use Mimikatz to dump passwords from SQLSRV01. 3 more accounts obtained.</li>
<li>Pass SQLSRV01 local administrator hash against other hosts. 34 computers obtained.</li>
</ol>
<p>Obviously this process varies depending upon the environment, but the framework holds. We start with no credentials, and gradually our sphere of compromise increases as we control more assets.</p>
<p>Let’s use BloodHound to answer a simple question: When a new set of nodes are <u>owned</u> by some method, <strong>what other nodes can we now collaterally reach?</strong> Let’s call this our <u>wave</u> of compromise. Maybe one of the nodes in that wave has a path to Domain Admins that isn’t yet represented by a BloodHound relationship.</p>
<p>We’ll leverage this notion, that a node can be “owned” during a “wave” of compromise, to build some useful features.</p>
<h3 id="tracking-compromised-users-and-computers">Tracking Compromised Users and Computers</h3>
<p>Going deeper means understanding a little bit about Neo4j and it’s query language, Cypher. I <em>highly</em> recommend reading Rohan Vazarkar’s <a href="https://blog.cptjesus.com/posts/introtocypher" target="_blank">Intro to Cypher</a> blog post, as I’ll skip over some of the introductory concepts. Rohan covers BloodHound’s current node and relationship structure, as well as some of the core analytics currently packed into BloodHound.</p>
<p>In it’s current version, BloodHound stores one property for each node: <code class="highlighter-rouge">name</code>. Depending on the type of node, this will be one of:</p>
<ul>
<li>User’s name (BLOPER@INTERNAL.LOCAL)</li>
<li>Computer’s name (SYSTEM38.INTERNAL.LOCAL)</li>
<li>Group’s name (DOMAIN USERS@INTERNAL.LOCAL)</li>
<li>Domain’s name (INTERNAL.LOCAL)</li>
</ul>
<p>To track owned Users and Computers, we’ll set two new properties on relevant nodes:</p>
<ul>
<li><code class="highlighter-rouge">owned</code> - The method used to compromise this node (ex: LLMNR, Mimikatz, Password reuse, etc.)</li>
<li><code class="highlighter-rouge">wave</code> - The number representing the order in which this node was owned (ex: 1, 2, 3, etc.)</li>
</ul>
<p>These properties can be added to a specified node using Cypher. The two simplest options for issuing ad-hoc Cypher queries are BloodHound’s Raw Query feature (at the bottom of the app), and Neo4j’s web browser (http://localhost:7474/). I recommend using Neo4j’s web browser to tinker with queries since you get some nifty syntax highlighting and debugging tools. Here’s the Cypher syntax for adding the properties to a specified node:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">MATCH (n) WHERE n.name="BLOPER@INTERNAL.LOCAL" SET n.owned="LLMNR wpad", n.wave=1</code></pre></figure>
<p>Now a query for BLOPER@INTERNAL.LOCAL reflects the added properties:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">MATCH (n) WHERE n.name="BLOPER@INTERNAL.LOCAL" RETURN n<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />+-------------------------------+<br data-jekyll-commonmark-ghpages="" />| n |<br data-jekyll-commonmark-ghpages="" />+-------------------------------+<br data-jekyll-commonmark-ghpages="" />| owned | LLMNR wpad |<br data-jekyll-commonmark-ghpages="" />| name | BLOPER@INTERNAL.LOCAL |<br data-jekyll-commonmark-ghpages="" />| wave | 1 |<br data-jekyll-commonmark-ghpages="" />+-------------------------------+</code></pre></figure>
<p>By making a few additions to the source, we can even integrate these properties into the Node Info tab of BloodHound’s UI:
<a href="https://porterhau5.com/images/bloper-node-info.png" target="_blank"><img src="https://porterhau5.com/images/bloper-node-info.png" /></a></p>
<p>It’s a useful way to document your compromise as you go, and a convenient visual aide when explaining your critical path of compromise to a client.</p>
<p>Note that modifications to the UI like this are only possible if you tweak BloodHound’s source. I’m compiling all of my UI enhancements into a <a href="https://github.com/porterhau5/BloodHound" target="_blank">GitHub repo</a> – if you want to try out these additions yourself then you’ll need to install the customized app from the repo.</p>
<p>For those interested, here’s a sample of the changes made to <code class="highlighter-rouge">src/components/SearchContainer/Tabs/UserNodeData.jsx</code> to make this happen (<a href="https://github.com/porterhau5/BloodHound/commit/68e6755902bb32b6552dc43e51a28c53db3e542a#diff-7e83a7fef612c20afca93946a8e057ba">diff here</a>):</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">s8</span> <span class="o">=</span> <span class="nx">driver</span><span class="p">.</span><span class="nx">session</span><span class="p">()</span><br data-jekyll-commonmark-ghpages="" /><span class="kd">var</span> <span class="nx">s9</span> <span class="o">=</span> <span class="nx">driver</span><span class="p">.</span><span class="nx">session</span><span class="p">()</span><br data-jekyll-commonmark-ghpages="" /><span class="p">...</span><br data-jekyll-commonmark-ghpages="" /><span class="nx">s8</span><span class="p">.</span><span class="nx">run</span><span class="p">(</span><span class="s2">"MATCH (n {name:{name}}) RETURN n.wave"</span><span class="p">,</span> <span class="p">{</span><span class="na">name</span><span class="p">:</span><span class="nx">payload</span><span class="p">})</span><br data-jekyll-commonmark-ghpages="" /> <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">result</span><span class="p">){</span><br data-jekyll-commonmark-ghpages="" /> <span class="k">if</span> <span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">records</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">_fields</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span><br data-jekyll-commonmark-ghpages="" /> <span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span><span class="s1">'ownedInWave'</span><span class="p">:</span><span class="nx">result</span><span class="p">.</span><span class="nx">records</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">_fields</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">low</span><span class="p">})</span><br data-jekyll-commonmark-ghpages="" /> <span class="p">}</span><br data-jekyll-commonmark-ghpages="" /> <span class="nx">s8</span><span class="p">.</span><span class="nx">close</span><span class="p">()</span><br data-jekyll-commonmark-ghpages="" /> <span class="p">}.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">))</span><br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" /><span class="nx">s9</span><span class="p">.</span><span class="nx">run</span><span class="p">(</span><span class="s2">"MATCH (n {name:{name}}) RETURN n.owned"</span><span class="p">,</span> <span class="p">{</span><span class="na">name</span><span class="p">:</span><span class="nx">payload</span><span class="p">})</span><br data-jekyll-commonmark-ghpages="" /> <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">result</span><span class="p">){</span><br data-jekyll-commonmark-ghpages="" /> <span class="k">if</span> <span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">records</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">_fields</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span><br data-jekyll-commonmark-ghpages="" /> <span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span><span class="s1">'ownedMethod'</span><span class="p">:</span><span class="nx">result</span><span class="p">.</span><span class="nx">records</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">_fields</span><span class="p">[</span><span class="mi">0</span><span class="p">]})</span><br data-jekyll-commonmark-ghpages="" /> <span class="p">}</span><br data-jekyll-commonmark-ghpages="" /> <span class="nx">s9</span><span class="p">.</span><span class="nx">close</span><span class="p">()</span><br data-jekyll-commonmark-ghpages="" /> <span class="p">}.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">))</span><br data-jekyll-commonmark-ghpages="" /><span class="p">...</span><br data-jekyll-commonmark-ghpages="" /><span class="o"><</span><span class="nx">dt</span><span class="o">></span><br data-jekyll-commonmark-ghpages="" /> <span class="nx">Owned</span> <span class="k">in</span> <span class="nx">Wave</span><br data-jekyll-commonmark-ghpages="" /><span class="o"><</span><span class="sr">/dt</span><span class="err">><br data-jekyll-commonmark-ghpages="" /></span><span class="o"><</span><span class="nx">dd</span><span class="o">></span><br data-jekyll-commonmark-ghpages="" /> <span class="o"><</span><span class="nx">NodeALink</span><br data-jekyll-commonmark-ghpages="" /> <span class="nx">ready</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">ownedInWave</span> <span class="o">!==</span> <span class="o">-</span><span class="mi">1</span><span class="p">}</span><br data-jekyll-commonmark-ghpages="" /> <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">ownedInWave</span><span class="p">}</span><br data-jekyll-commonmark-ghpages="" /> <span class="nx">click</span><span class="o">=</span><span class="p">{</span><span class="kd">function</span><span class="p">(){</span><br data-jekyll-commonmark-ghpages="" /> <span class="nx">emitter</span><span class="p">.</span><span class="nx">emit</span><span class="p">(</span><span class="s1">'query'</span><span class="p">,</span> <span class="s2">"OPTIONAL MATCH (n1:User {wave:{wave}}) WITH collect(distinct n1) as c1 OPTIONAL MATCH (n2:Computer {wave:{wave}}) WITH collect(distinct n2) + c1 as c2 OPTIONAL MATCH (n3:Group {wave:{wave}}) WITH c2, collect(distinct n3) + c2 as c3 UNWIND c2 as n UNWIND c3 as m MATCH (n)-[r]->(m) RETURN n,r,m"</span><span class="p">,</span> <span class="p">{</span><span class="na">wave</span><span class="p">:</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">ownedInWave</span><span class="p">}</span><br data-jekyll-commonmark-ghpages="" /> <span class="p">,</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">label</span><span class="p">)</span><br data-jekyll-commonmark-ghpages="" /> <span class="p">}.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">)}</span> <span class="sr">/</span><span class="err">><br data-jekyll-commonmark-ghpages="" /></span><span class="o"><</span><span class="sr">/dd</span><span class="err">><br data-jekyll-commonmark-ghpages="" /></span><span class="o"><</span><span class="nx">dt</span><span class="o">></span><br data-jekyll-commonmark-ghpages="" /> <span class="nx">Owned</span> <span class="nx">via</span> <span class="nx">Method</span><br data-jekyll-commonmark-ghpages="" /><span class="o"><</span><span class="sr">/dt</span><span class="err">><br data-jekyll-commonmark-ghpages="" /></span><span class="o"><</span><span class="nx">dd</span><span class="o">></span><br data-jekyll-commonmark-ghpages="" /> <span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">ownedMethod</span><span class="p">}</span><br data-jekyll-commonmark-ghpages="" /><span class="o"><</span><span class="sr">/dd></span></code></pre></figure>
<h3 id="finding-the-collateral-spread-when-a-node-is-compromised">Finding the Collateral Spread When a Node is Compromised</h3>
<p>When we designate a node as owned, we want to see the ripple effect across the network. With our <code class="highlighter-rouge">wave</code> property set, we can find those outbound paths like this:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">MATCH (n)-[r*]->(m) WHERE n.wave=1 RETURN n,r,m</code></pre></figure>
<p>This is similar to the “Find Shortest Paths to Here” idea, but we’re now interested in the paths branching out of a node instead of those coming in. For each new node in the paths we find, we’ll set the <code class="highlighter-rouge">wave</code> property equal to the same <code class="highlighter-rouge">wave</code> value of the source node(s).</p>
<p>Let’s see this idea in action with the <a href="https://github.com/BloodHoundAD/BloodHound/tree/master/BloodHoundExampleDB.graphdb" target="_blank">example graphdb</a> included in the BloodHound repo. If you’d like to follow along, I recommend working with a copy of the example graphdb to make starting fresh easier.</p>
<p>We’ll start our theoretical penetration test by firing up <a href="https://github.com/SpiderLabs/Responder" target="_blank">Responder</a> and grabbing some NTLMv2 hashes. Assume we were able to crack the hashes and obtain cleartext passwords for two accounts, BLOPER@INTERNAL.LOCAL and JCARNEAL@INTERNAL.LOCAL. Let’s mark those two accounts as compromised using Cypher:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">// Adding BLOPER@INTERNAL.LOCAL to wave 1 via LLMNR wpad<br data-jekyll-commonmark-ghpages="" />MATCH (n) WHERE n.name="BLOPER@INTERNAL.LOCAL" SET n.owned="LLMNR wpad", n.wave=1<br data-jekyll-commonmark-ghpages="" /> > Set 2 properties, statement completed in 5 ms.<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />// Adding JCARNEAL@INTERNAL.LOCAL to wave 1 via NBNS wpad<br data-jekyll-commonmark-ghpages="" />MATCH (n) WHERE n.name="JCARNEAL@INTERNAL.LOCAL" SET n.owned="NBNS wpad", n.wave=1<br data-jekyll-commonmark-ghpages="" /> > Set 2 properties, statement completed in 6 ms.<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />// Show names of nodes from the first wave<br data-jekyll-commonmark-ghpages="" />MATCH (n) WHERE n.wave=1 RETURN n.name<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />+-------------------------+<br data-jekyll-commonmark-ghpages="" />| n.name |<br data-jekyll-commonmark-ghpages="" />+-------------------------+<br data-jekyll-commonmark-ghpages="" />| BLOPER@INTERNAL.LOCAL |<br data-jekyll-commonmark-ghpages="" />| JCARNEAL@INTERNAL.LOCAL |<br data-jekyll-commonmark-ghpages="" />+-------------------------+</code></pre></figure>
<p>With these two nodes as our source, let’s use BloodHound’s Raw Query feature to find the other nodes collaterally included in this wave of compromise:
<a href="https://porterhau5.com/images/wave1.png" target="_blank"><img src="https://porterhau5.com/images/wave1.png" /></a></p>
<p>We see two additions – both users are a MemberOf “DOMAIN USERS@INTERNAL.LOCAL”, and one user is AdminTo “SYSTEM38.INTERNAL.LOCAL”. Neat. Go ahead and add both of those nodes to <code class="highlighter-rouge">wave</code> 1 as well:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">MATCH (n)-[r*]->(m) WHERE n.wave=1 SET m.wave=1<br data-jekyll-commonmark-ghpages="" /> > Set 3 properties, statement completed in 5 ms.<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />// Show updated names of nodes from the first wave<br data-jekyll-commonmark-ghpages="" />MATCH (n) WHERE n.wave=1 RETURN n.name<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />+-----------------------------+<br data-jekyll-commonmark-ghpages="" />| n.name |<br data-jekyll-commonmark-ghpages="" />+-----------------------------+<br data-jekyll-commonmark-ghpages="" />| BLOPER@INTERNAL.LOCAL |<br data-jekyll-commonmark-ghpages="" />| DOMAIN USERS@INTERNAL.LOCAL |<br data-jekyll-commonmark-ghpages="" />| JCARNEAL@INTERNAL.LOCAL |<br data-jekyll-commonmark-ghpages="" />| SYSTEM38.INTERNAL.LOCAL |<br data-jekyll-commonmark-ghpages="" />+-----------------------------+</code></pre></figure>
<p>Simple enough, right? Let’s build on these queries.</p>
<h3 id="showing-changes-in-privilege-gains">Showing Changes in Privilege Gains</h3>
<p>We have a way for marking nodes as owned, and we can view the ripple effect of a wave. What happens when we compromise a disjoint set of nodes via some new method? What does the delta in our access look like? It would be terrific if we could see what’s available to us now that wasn’t available to us before.</p>
<p>We can use the same queries as before, but we’ll want to be careful not to overwrite data from previous waves. How do we know which nodes we compromised in previous waves? Each compromised node has a <code class="highlighter-rouge">wave</code> property that already exists. If we detect that this property exists, then we know not to include it in the new wave. This can be done in Cypher by negating the <code class="highlighter-rouge">EXISTS</code> function:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">MATCH (n)-[r*]->(m) WHERE n.wave=2 AND not(EXISTS(m.wave)) SET m.wave=2</code></pre></figure>
<p>We’re still looking for the paths branching out (except using the second <code class="highlighter-rouge">wave</code> as our starting point), but we don’t want to circle back to nodes we’ve already compromised. The clause <code class="highlighter-rouge">AND not(EXISTS(m.wave))</code> ensures we don’t include any destination nodes with the <code class="highlighter-rouge">wave</code> property set.</p>
<p>Let’s see it in context by building off of the previous example. Imagine that the next step of our penetration test involved a password spraying attack against domain users. We found two users with “Spring2017!” on the INTERNAL.LOCAL domain, ZDEVENS and BPICKEREL. Start by marking these two new nodes as owned in wave 2:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">// Adding ZDEVENS@INTERNAL.LOCAL to wave 2 via Password spray<br data-jekyll-commonmark-ghpages="" />MATCH (n) WHERE n.name="ZDEVENS@INTERNAL.LOCAL" SET n.owned="Password spray", n.wave=2<br data-jekyll-commonmark-ghpages="" />> Set 2 properties, statement completed in 7 ms.<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />// Adding BPICKEREL@INTERNAL.LOCAL to wave 2 via Password spray<br data-jekyll-commonmark-ghpages="" />MATCH (n) WHERE n.name="BPICKEREL@INTERNAL.LOCAL" SET n.owned="Password spray", n.wave=2<br data-jekyll-commonmark-ghpages="" />> Set 2 properties, statement completed in 5 ms.</code></pre></figure>
<p>Find the spread of compromise, then add those nodes to our second wave:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">MATCH (n)-[r*]->(m) WHERE n.wave=2 AND not(EXISTS(m.wave)) SET m.wave=2<br data-jekyll-commonmark-ghpages="" /> > Set 6 properties, statement completed in 3 ms.</code></pre></figure>
<p>And now the graph showing the second wave. This represents the delta after our password spraying attack:
<a href="https://porterhau5.com/images/wave2.png" target="_blank"><img src="https://porterhau5.com/images/wave2.png" /></a></p>
<p>That’s handy. Now I know which machines I should go plunder for sensitive documents, local hashes, cached passwords, etc.</p>
<h3 id="automating-the-workflow">Automating the Workflow</h3>
<p>It’s tedious to manually run these queries each time a node is compromised. Thankfully, Neo4j’s REST API makes automation possible. With a simple Ruby script, we can leverage the same endpoint used by <code class="highlighter-rouge">Export-BloodHoundData</code> to ingest data directly across the network:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">$ ruby bh-owned.rb<br data-jekyll-commonmark-ghpages="" />Usage: ruby bh-owned.rb [options]<br data-jekyll-commonmark-ghpages="" /> -u, --username <username> Neo4j database username (default: 'neo4j')<br data-jekyll-commonmark-ghpages="" /> -p, --password <password> Neo4j database password (default: 'BloodHound')<br data-jekyll-commonmark-ghpages="" /> -U, --url <url> URL of Neo4j RESTful host (default: 'http://127.0.0.1:7474/')<br data-jekyll-commonmark-ghpages="" /> -n, --nodes get all node names<br data-jekyll-commonmark-ghpages="" /> -a, --add <file> add 'owned' and 'wave' property to nodes in <file><br data-jekyll-commonmark-ghpages="" /> -w, --wave <num> value to set 'wave' property (override default behavior)<br data-jekyll-commonmark-ghpages="" /> -e, --examples reference doc of customized Cypher queries for BloodHound</code></pre></figure>
<p>I usually start by dumping all of the nodes from the database with <code class="highlighter-rouge">-n</code>:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">$ ruby bh-owned.rb -n<br data-jekyll-commonmark-ghpages="" />[*] Using default username: neo4j<br data-jekyll-commonmark-ghpages="" />[*] Using default password: BloodHound<br data-jekyll-commonmark-ghpages="" />[*] Using default URL: http://127.0.0.1:7474/<br data-jekyll-commonmark-ghpages="" />AANSTETT@EXTERNAL.LOCAL<br data-jekyll-commonmark-ghpages="" />ABRENES@INTERNAL.LOCAL<br data-jekyll-commonmark-ghpages="" />ABROOKS@EXTERNAL.LOCAL<br data-jekyll-commonmark-ghpages="" />ABROOKS_A@EXTERNAL.LOCAL<br data-jekyll-commonmark-ghpages="" />ACASTERLINE@INTERNAL.LOCAL<br data-jekyll-commonmark-ghpages="" />ACHAVARIN@EXTERNAL.LOCAL<br data-jekyll-commonmark-ghpages="" />ACLAUSS@INTERNAL.LOCAL<br data-jekyll-commonmark-ghpages="" /><snipped></code></pre></figure>
<p>With <code class="highlighter-rouge">-e</code>, you can see some of the useful Cypher queries used throughout this blog post (some are slightly modified to improve query performance):</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">$ ruby bh-owned.rb -e<br data-jekyll-commonmark-ghpages="" />Find all owned Domain Admins:<br data-jekyll-commonmark-ghpages="" />MATCH (n:Group) WHERE n.name =~ '.*DOMAIN ADMINS.*' WITH n MATCH p=(n)<-[r:MemberOf*1..]-(m) WHERE exists(m.owned) RETURN nodes(p),relationships(p)<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />Find Shortest Path from owned node to Domain Admins:<br data-jekyll-commonmark-ghpages="" />MATCH p=shortestPath((n)-[*1..]->(m)) WHERE exists(n.owned) AND m.name=~ '.*DOMAIN ADMINS.*' RETURN p<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />List all directly owned nodes:<br data-jekyll-commonmark-ghpages="" />MATCH (n) WHERE exists(n.owned) RETURN n<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />Find all nodes in wave $num:<br data-jekyll-commonmark-ghpages="" />MATCH (n)-[r]->(m) WHERE n.wave=$num AND m.wave=$num RETURN n,r,m<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />Show all waves up to and including wave $num:<br data-jekyll-commonmark-ghpages="" />MATCH (n)-[r]->(m) WHERE n.wave<=$num RETURN n,r,m<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />Set owned and wave properties for a node (named $name, compromised via $method in wave $num):<br data-jekyll-commonmark-ghpages="" />MATCH (n) WHERE (n.name = '$name') SET n.owned = '$method', n.wave = $num<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />Find spread of compromise for owned nodes in wave $num:<br data-jekyll-commonmark-ghpages="" />OPTIONAL MATCH (n1:User {wave:$num}) WITH collect(distinct n1) as c1 OPTIONAL MATCH (n2:Computer {wave:$num}) WITH collect(distinct n2) + c1 as c2 UNWIND c2 as n OPTIONAL MATCH p=shortestPath((n)-[*..20]->(m)) WHERE not(exists(m.wave)) WITH DISTINCT(m) SET m.wave=$num</code></pre></figure>
<p>Continuing with our theoretical penetration test, let’s say that we found a juicy Excel spreadsheet which contained credentials for users SMADDUX@INTERNAL.LOCAL and QBULLIS@EXTERNAL.LOCAL. We’ll first create a CSV with the node names and method of compromise like so:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">$ cat 3rd-wave.txt<br data-jekyll-commonmark-ghpages="" />SMADDUX@INTERNAL.LOCAL,Creds in file on DATABASE5<br data-jekyll-commonmark-ghpages="" />QBULLIS@EXTERNAL.LOCAL,Creds in file on DATABASE5</code></pre></figure>
<p>Then we use the <code class="highlighter-rouge">-a</code> flag to ingest:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">$ ruby bh-owned.rb -a 3rd-wave.txt<br data-jekyll-commonmark-ghpages="" />[*] Using default username: neo4j<br data-jekyll-commonmark-ghpages="" />[*] Using default password: BloodHound<br data-jekyll-commonmark-ghpages="" />[*] Using default URL: http://127.0.0.1:7474/<br data-jekyll-commonmark-ghpages="" />[+] Adding SMADDUX@INTERNAL.LOCAL to wave 3 via Creds in file on DATABASE5<br data-jekyll-commonmark-ghpages="" />[+] Adding QBULLIS@EXTERNAL.LOCAL to wave 3 via Creds in file on DATABASE5<br data-jekyll-commonmark-ghpages="" />[+] Querying and updating new owned nodes</code></pre></figure>
<p>The script will first query the database and determine the latest wave added – in this case it was ‘2’. It then increments it by one so that the incoming additions will be in wave ‘3’. You can override this behavior by setting the <code class="highlighter-rouge">-w</code> flag to the preferred wave value.</p>
<p>Once the wave number is determined, the script takes the following steps:</p>
<ul>
<li>Creates the Cypher queries to set properties on the owned nodes</li>
<li>Creates the Cypher query to find the spread of compromise for the new wave</li>
<li>Wraps it all in JSON</li>
<li>POSTs the request to the REST endpoint</li>
</ul>
<p>Let’s look at the result for this third wave in BloodHound:
<a href="https://porterhau5.com/images/wave3.png" target="_blank"><img src="https://porterhau5.com/images/wave3.png" /></a></p>
<p>Turns out this wave wasn’t very exciting ¯\_(ツ)_/¯ And we still have to type in our custom query in order to display the graph in BloodHound. Let’s remove that hassle and take it a step further by tweaking the UI and writing some custom queries.</p>
<h3 id="ui-customizations-and-custom-queries">UI Customizations and Custom Queries</h3>
<p>BloodHound added a feature in v1.2 to allow for custom queries (more info on <a href="https://blog.cptjesus.com/posts/introtocypher#building-on-top" target="_blank">CptJesus’s blog</a>). This has the same effect as adding a pre-built query on the Queries tab, but the configuration file has been decoupled from the project’s source code. I found this file in OS X at <code class="highlighter-rouge">~/Library/Application Support/bloodhound/customqueries.json</code>.</p>
<p>I’ve added four custom queries (<a href="https://github.com/porterhau5/BloodHound-Owned/blob/master/customqueries.json">source here</a>):
<a href="https://porterhau5.com/images/customqueries.png" target="_blank"><img src="https://porterhau5.com/images/customqueries.png" /></a></p>
<ul>
<li><strong>Find all owned Domain Admins</strong>: Same as the “Find all Domain Admins” query, but instead only show Users with <code class="highlighter-rouge">owned</code> property.</li>
<li><strong>Find Shortest Paths from owned node to Domain Admins</strong>: Same as the “Find Shortest Paths to Domain Admins” query, but instead only show paths originating from an <code class="highlighter-rouge">owned</code> node.</li>
<li><strong>Show wave</strong>: Show only the nodes compromised in a selected wave. Useful for focusing in on newly-compromised nodes.</li>
<li><strong>Show delta for wave</strong>: Show all compromised nodes up to a selected wave, and will highlight the nodes gained in that wave. Useful for visualizing privilege gains as access expands.</li>
</ul>
<p>If you’re using the <a href="https://github.com/porterhau5/BloodHound" target="_blank">customized BloodHound app</a>, these queries will highlight <font color="#C900FF">nodes of interest</font> in the graph as well. Let’s see the custom queries and UI enhancements in context of our example penetration test:</p>
<h5 id="find-all-owned-domain-admins">Find all owned Domain Admins</h5>
<p>Let’s add two more nodes to our compromise:</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">$ cat 4th-wave.txt<br data-jekyll-commonmark-ghpages="" />BGRIFFIN@EXTERNAL.LOCAL,Mimikatz on MANAGEMENT3<br data-jekyll-commonmark-ghpages="" />FILESERVER6.INTERNAL.LOCAL,Local Administrator password reuse (dumped from MANAGEMENT3)<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />$ ruby bh-owned.rb -a 4th-wave.txt<br data-jekyll-commonmark-ghpages="" />[*] Using default username: neo4j<br data-jekyll-commonmark-ghpages="" />[*] Using default password: BloodHound<br data-jekyll-commonmark-ghpages="" />[*] Using default URL: http://127.0.0.1:7474/<br data-jekyll-commonmark-ghpages="" />[+] Adding BGRIFFIN@EXTERNAL.LOCAL to wave 4 via Mimikatz on MANAGEMENT3<br data-jekyll-commonmark-ghpages="" />[+] Adding FILESERVER6.INTERNAL.LOCAL to wave 4 via Local Administrator password reuse (dumped from MANAGEMENT3)<br data-jekyll-commonmark-ghpages="" />[+] Querying and updating new owned nodes</code></pre></figure>
<p>And now click on the “Find all owned Domain Admins” custom query:
<a href="https://porterhau5.com/images/find-all-owned-das.png" target="_blank"><img src="https://porterhau5.com/images/find-all-owned-das.png" /></a></p>
<p>Boom, we got one. Notice that a <font color="#C900FF">magenta lightning bolt</font> appears on the top-left of nodes relevant to our custom query. Like the additions to the Node Info tab, this feature is currently only available in the <a href="https://github.com/porterhau5/BloodHound" target="_blank">modified version</a> of BloodHound.</p>
<h5 id="find-shortest-paths-from-owned-node-to-domain-admins">Find Shortest Paths from owned node to Domain Admins</h5>
<p>This is the same as the “Find Shortest Paths to Domain Admins”, but we’re focusing on nodes we’ve owned. You’ll see in this graph that the two starting nodes, FILESERVER6 and BGRIFFIN, are marked with our owned icon:
<a href="https://porterhau5.com/images/find-shortest-path.png" target="_blank"><img src="https://porterhau5.com/images/find-shortest-path.png" /></a></p>
<h5 id="show-wave">Show wave</h5>
<p>This displays a single, isolated wave. It uses <code class="highlighter-rouge">query</code> to present the user with wave values:
<a href="https://porterhau5.com/images/show-wave-picker.png" target="_blank"><img src="https://porterhau5.com/images/show-wave-picker.png" /></a></p>
<p>It passes the choice to <code class="highlighter-rouge">onFinish</code> to display the result. Here’s wave 2 from our example:
<a href="https://porterhau5.com/images/show-wave2.png" target="_blank"><img src="https://porterhau5.com/images/show-wave2.png" /></a></p>
<h5 id="show-delta-for-wave">Show delta for wave</h5>
<p>This displays all waves leading up to the selected wave, and then highlights the nodes from that wave. Like ‘Show wave’, it uses the pop-up picker for wave selection.</p>
<p>If wave 2 is selected, the graph shows waves 1 & 2 then highlights the nodes from wave 2:
<a href="https://porterhau5.com/images/show-delta-wave2.png" target="_blank"><img src="https://porterhau5.com/images/show-delta-wave2.png" /></a></p>
<p>If wave 3 is selected, the graph shows waves 1-3 then highlights the nodes from wave 3:
<a href="https://porterhau5.com/images/show-delta-wave3.png" target="_blank"><img src="https://porterhau5.com/images/show-delta-wave3.png" /></a></p>
<p>I like this graph for visualizing the changes in privilege gains as it pertains to the greater context of the penetration test. Clients like it too for the same reason – it’s an effective visual aide for explaining the collateral risk of each User or Computer you compromise.</p>
<h3 id="next-steps">Next Steps</h3>
<p>Here’s a couple ideas for taking this a little further. Hopefully I’ll have time to tinker with these in the coming weeks. I’ll post updates here and on <a href="https://twitter.com/porterhau5" target="_blank">Twitter via @porterhau5</a>. Please reach out if you want to explore some of these together! I’d really appreciate some help from those of you who are skilled with front-end development :D</p>
<ul>
<li>Create a new relationship (maybe “SharesPasswordWith”?) that can be used between User nodes or Computer nodes to show password reuse. A User might use the same password for their normal account and their DA account. A Computer might use the same local admin password as another Computer’s local admin. Expressing this in the form of a relationship allows us to leverage BloodHound’s pre-built queries.</li>
<li>Instead of running custom queries through the Queries tab, have a slider on the main dashboard that can step through waves.</li>
<li>Define a “Critical Path to Compromise” (CPTC): the exact path taken through the network to go from A to Z. Add property to nodes indicating their involvement in the CPTC. Write custom query to show this path specifically and filter out the rest of the noise. Mostly useful for presenting/explaining to client.</li>
<li>Add more options when a node is right-clicked. For example, “Shortest Paths from Here” or “Add to wave X”.</li>
<li>Make “Owned in Wave” and “Owned via Method” values fillable from the UI. Ditch the need for an external script to ingest data.</li>
<li>General query optimizations to help with scalability & speed. The graphs can get a little messy when a DA account is obtained.</li>
</ul>
<p>Thank you for reading!</p>
<h4 id="share">Share</h4>
<ul class="share-buttons">
<li><a href="https://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Fporterhau5.com%2Fblog%2Fextending-bloodhound-track-and-visualize-your-compromise%2F&t=Extending%20BloodHound%3A%20Track%20and%20Visualize%20Your%20Compromise" title="Share on Facebook" target="_blank"><img alt="Share on Facebook" src="https://porterhau5.com/images/flat_web_icon_set/black/Facebook.png" /></a></li>
<li><a href="https://twitter.com/intent/tweet?source=http%3A%2F%2Fporterhau5.com%2Fblog%2Fextending-bloodhound-track-and-visualize-your-compromise%2F&text=Extending%20BloodHound%3A%20Track%20and%20Visualize%20Your%20Compromise:%20http%3A%2F%2Fporterhau5.com%2Fblog%2Fextending-bloodhound-track-and-visualize-your-compromise%2F" target="_blank" title="Tweet"><img alt="Tweet" src="https://porterhau5.com/images/flat_web_icon_set/black/Twitter.png" /></a></li>
<li><a href="https://getpocket.com/save?url=http%3A%2F%2Fporterhau5.com%2Fblog%2Fextending-bloodhound-track-and-visualize-your-compromise%2F&title=Extending%20BloodHound%3A%20Track%20and%20Visualize%20Your%20Compromise" target="_blank" title="Add to Pocket"><img alt="Add to Pocket" src="https://porterhau5.com/images/flat_web_icon_set/black/Pocket.png" /></a></li>
<li><a href="http://www.reddit.com/submit?url=http%3A%2F%2Fporterhau5.com%2Fblog%2Fextending-bloodhound-track-and-visualize-your-compromise%2F&title=Extending%20BloodHound%3A%20Track%20and%20Visualize%20Your%20Compromise" target="_blank" title="Submit to Reddit"><img alt="Submit to Reddit" src="https://porterhau5.com/images/flat_web_icon_set/black/Reddit.png" /></a></li>
</ul>
2017-03-22T00:00:00+00:00https://porterhau5.com/blog/xkeyscan-parse-linux-keylogger/Using xkeyscan to Parse an X-Based Linux Keylogger2016-07-28T00:00:00+00:00porterhau5https://porterhau5.com/Leverage native X-based tools for real-time keylogging with xkeyscan, a Python script that translates X keycodes into legible keystrokes.<h3 id="background">Background</h3>
<p>A colleague of mine, Sanjiv Kawa, came across a nifty technique for capturing the keystrokes of users on an X-based system. I encourage you to check out <a href="https://popped.io/natively-keylogging-nix-systems/" target="_blank">his post here</a>, where Sanjiv walks through the process of using <code class="highlighter-rouge">xinput</code> to find input devices and then sets up the keylogger for capture. It’s a particularly useful technique against those utilizing GNOME, KDE, or Xfce desktop environments, common on many *nix systems. It has the added bonus of not requiring root privileges to pull off.</p>
<p>For archival’s sake, the process boils down to:</p>
<ol>
<li><code class="highlighter-rouge">xinput list</code> - list potential input devices</li>
<li>Find the XID of the keyboard to monitor</li>
<li><code class="highlighter-rouge">xinput test <XID></code> - display input from the device</li>
</ol>
<p>XID is the “id” number associated with the desired keyboard input device. On my lab machine, this would be id 12:</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html">⎡ Virtual core pointer id=2 [master pointer (3)]<br data-jekyll-commonmark-ghpages="" />⎜ ↳ Virtual core XTEST pointer id=4 [slave pointer (2)]<br data-jekyll-commonmark-ghpages="" />⎜ ↳ Razer Razer Naga 2014 id=8 [slave pointer (2)]<br data-jekyll-commonmark-ghpages="" />⎜ ↳ Razer Razer Naga 2014 id=9 [slave pointer (2)]<br data-jekyll-commonmark-ghpages="" />⎣ Virtual core keyboard id=3 [master keyboard (2)]<br data-jekyll-commonmark-ghpages="" /> ↳ Virtual core XTEST keyboard id=5 [slave keyboard (3)]<br data-jekyll-commonmark-ghpages="" /> ↳ Power Button id=6 [slave keyboard (3)]<br data-jekyll-commonmark-ghpages="" /> ↳ Video Bus id=7 [slave keyboard (3)]<br data-jekyll-commonmark-ghpages="" /> ↳ Razer Razer Naga 2014 id=10 [slave keyboard (3)]<br data-jekyll-commonmark-ghpages="" /> ↳ gpio-keys id=11 [slave keyboard (3)]<br data-jekyll-commonmark-ghpages="" /> ↳ porterhau5’s Keyboard id=12 [slave keyboard (3)]</code></pre></figure>
<p>Now, the output produced by <code class="highlighter-rouge">xinput test <XID></code> isn’t quite the final output we’re looking for. Instead of the characters typed by the user, the result is a series of <strong>keycodes</strong>. This is the output from typing “nano”:</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html">$ script -c "xinput test 12" /dev/null<br data-jekyll-commonmark-ghpages="" />key press 57<br data-jekyll-commonmark-ghpages="" />key release 57<br data-jekyll-commonmark-ghpages="" />key press 38<br data-jekyll-commonmark-ghpages="" />key release 38<br data-jekyll-commonmark-ghpages="" />key press 57<br data-jekyll-commonmark-ghpages="" />key release 57<br data-jekyll-commonmark-ghpages="" />key press 32<br data-jekyll-commonmark-ghpages="" />key release 32</code></pre></figure>
<p>How can these keycodes be translated to something more legible? Well, first we need the system’s device mapping of <strong>keycodes-to-keysyms</strong>, called a <strong>keymap</strong>. This is obtainable via <code class="highlighter-rouge">xmodmap -pke</code>:</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html">$ xmodmap -pke | head -n20<br data-jekyll-commonmark-ghpages="" />keycode 8 =<br data-jekyll-commonmark-ghpages="" />keycode 9 = Escape NoSymbol Escape<br data-jekyll-commonmark-ghpages="" />keycode 10 = 1 exclam 1 exclam exclamdown U2044<br data-jekyll-commonmark-ghpages="" />keycode 11 = 2 at 2 at trademark EuroSign<br data-jekyll-commonmark-ghpages="" />keycode 12 = 3 numbersign 3 numbersign sterling U2039<br data-jekyll-commonmark-ghpages="" />keycode 13 = 4 dollar 4 dollar cent U203A<br data-jekyll-commonmark-ghpages="" />keycode 14 = 5 percent 5 percent infinity UFB01<br data-jekyll-commonmark-ghpages="" />keycode 15 = 6 asciicircum 6 asciicircum section UFB02<br data-jekyll-commonmark-ghpages="" />keycode 16 = 7 ampersand 7 ampersand paragraph doubledagger<br data-jekyll-commonmark-ghpages="" />keycode 17 = 8 asterisk 8 asterisk enfilledcircbullet degree<br data-jekyll-commonmark-ghpages="" />keycode 18 = 9 parenleft 9 parenleft ordfeminine periodcentered<br data-jekyll-commonmark-ghpages="" />keycode 19 = 0 parenright 0 parenright masculine singlelowquotemark<br data-jekyll-commonmark-ghpages="" />keycode 20 = minus underscore minus underscore endash emdash<br data-jekyll-commonmark-ghpages="" />keycode 21 = equal plus equal plus notequal plusminus<br data-jekyll-commonmark-ghpages="" />keycode 22 = BackSpace BackSpace BackSpace BackSpace<br data-jekyll-commonmark-ghpages="" />keycode 23 = Tab ISO_Left_Tab Tab ISO_Left_Tab<br data-jekyll-commonmark-ghpages="" />keycode 24 = q Q q Q oe OE<br data-jekyll-commonmark-ghpages="" />keycode 25 = w W w W U2211 doublelowquotemark<br data-jekyll-commonmark-ghpages="" />keycode 26 = e E e E dead_acute acute<br data-jekyll-commonmark-ghpages="" />keycode 27 = r R r R registered U2030</code></pre></figure>
<p>Notice how the keymap has multiple keysym columns for each keycode - this is due to modifiers (Shift, Alt, etc.) No modifier will return the keysym in the first column. The Shift modifier will return the keysym in the second column, and so on. More details about xmodmap and modifiers can be found <a href="https://wiki.archlinux.org/index.php/Xmodmap" target="_blank">here</a>.</p>
<p>The next logical step is to leverage this keymap to automate the keycode-to-keysym conversion process.</p>
<h3 id="xkeyscan">xkeyscan</h3>
<p>What I ended up creating was a fairly simple Python script called “xkeyscan” which can be found <a href="https://github.com/porterhau5/xkeyscan" target="_blank">here on GitHub</a>.</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html">$ python xkeyscan.py -h<br data-jekyll-commonmark-ghpages="" />Usage: python [-u] xkeyscan.py [LOGFILE]<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />A simple script for converting xinput output to legible keystokes.<br data-jekyll-commonmark-ghpages="" />Can process a log file directly when passed as an argument, or can<br data-jekyll-commonmark-ghpages="" />convert keystrokes in near real-time if tailing a log file.<br data-jekyll-commonmark-ghpages="" />If tailing a log file, use python's -u switch to avoid buffering.<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />Examples:<br data-jekyll-commonmark-ghpages="" /> python xkeyscan.py xkey.log (post-process log file)<br data-jekyll-commonmark-ghpages="" /> cat xkey.log | python xkeyscan.py (accept logs from stdin)<br data-jekyll-commonmark-ghpages="" /> tail -f -n +1 xkey.log | python -u xkeyscan.py (tail log file)<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />Type -h or --help for a full listing of options.</code></pre></figure>
<p>I’ve built in some logic to account for modifier keys, such as Shift or Alt, so the parser will correctly determine things like case-sensitivity and special characters.</p>
<p>The script can be used a handful of ways depending upon how you’d prefer to feed data into it. For the following examples, data is stored to a file called <code class="highlighter-rouge">xkey.log</code> using the following syntax:</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html">$ script -c "xinput test <span class="nt"><XID></span>" /dev/null > xkey.log</code></pre></figure>
<p>xkeyscan can parse the file directly:</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html">$ python xkeyscan.py xkey.log<br data-jekyll-commonmark-ghpages="" />nano tmp<br data-jekyll-commonmark-ghpages="" />Test@ <span class="nt"><Back></span> !</code></pre></figure>
<p>Or read from stdin:</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html">$ cat xkey.log | python xkeyscan.py<br data-jekyll-commonmark-ghpages="" />nano tmp<br data-jekyll-commonmark-ghpages="" />Test@ <span class="nt"><Back></span> !</code></pre></figure>
<p>Or parse in real-time:</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html">$ tail -f -n +1 xkey.log | python -u xkeyscan.py<br data-jekyll-commonmark-ghpages="" />nano tmp<br data-jekyll-commonmark-ghpages="" />Test@ <span class="nt"><Back></span> !</code></pre></figure>
<p>Note the usage of python’s <code class="highlighter-rouge">-u</code> switch on the last command. This disables python’s default buffer, allowing for data to be parsed as it’s streamed in. The switch is necessary for real-time parsing when tailing a log file, otherwise the data doesn’t get parsed until python’s buffer is flushed (for example with a CTL-C or CTL-D.)</p>
<p>Here’s the tool in action:</p>
<ul>
<li>Capturing is done in the right terminal (using <code class="highlighter-rouge">tee</code> to verify output)</li>
<li>Keystrokes being captured were performed in the top-left terminal</li>
<li>xkeyscan is tailing and parsing xkey.log in the bottom-left terminal</li>
</ul>
<p><a href="https://porterhau5.com/images/xkeyscan-ss.png" target="_blank"><img src="https://porterhau5.com/images/xkeyscan-ss.png" /></a></p>
<p>In its current iteration, the xmodmap legend is statically set. If a system’s keymap differs, the codes array near the top of xkeyscan.py will need to be adjusted accordingly. My plan for v2 is for the script to take <code class="highlighter-rouge">xmodmap -pke</code> as input and dynamically generate the appropriate codes. Could even take it a step further and create an all-in-one tool for finding the appropriate device, starting the keylogger, and printing out the parsed result. If you’d like to tackle these changes yourself, feel free to send a <a href="https://github.com/porterhau5/xkeyscan" target="_blank">pull request</a>!</p>
<h4 id="share">Share</h4>
<ul class="share-buttons">
<li><a href="https://twitter.com/intent/tweet?source=http%3A%2F%2Fporterhau5.com%2Fblog%2Fxkeyscan-parse-linux-keylogger%2F&text=Using%20xkeyscan%20to%20Parse%20an%20X-Based%20Linux%20Keylogger:%20http%3A%2F%2Fporterhau5.com%2Fblog%2Fxkeyscan-parse-linux-keylogger%2F&via=porterhau5" target="_blank" title="Tweet"><img alt="Tweet" src="https://porterhau5.com/images/flat_web_icon_set/black/Twitter.png" /></a></li>
<li><a href="http://www.reddit.com/submit?url=http%3A%2F%2Fporterhau5.com%2Fblog%2Fxkeyscan-parse-linux-keylogger%2F&title=Using%20xkeyscan%20to%20Parse%20an%20X-Based%20Linux%20Keylogger" target="_blank" title="Submit to Reddit"><img alt="Submit to Reddit" src="https://porterhau5.com/images/flat_web_icon_set/black/Reddit.png" /></a></li>
<li><a href="https://getpocket.com/save?url=http%3A%2F%2Fporterhau5.com%2Fblog%2Fxkeyscan-parse-linux-keylogger%2F&title=Using%20xkeyscan%20to%20Parse%20an%20X-Based%20Linux%20Keylogger" target="_blank" title="Add to Pocket"><img alt="Add to Pocket" src="https://porterhau5.com/images/flat_web_icon_set/black/Pocket.png" /></a></li>
<li><a href="https://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Fporterhau5.com%2Fblog%2Fxkeyscan-parse-linux-keylogger%2F&t=Using%20xkeyscan%20to%20Parse%20an%20X-Based%20Linux%20Keylogger" title="Share on Facebook" target="_blank"><img alt="Share on Facebook" src="https://porterhau5.com/images/flat_web_icon_set/black/Facebook.png" /></a></li>
</ul>
2016-07-28T00:00:00+00:00https://porterhau5.com/blog/introducing-sleat/Introducing Sleat: Security Logon Event Analysis Tools2016-06-21T00:00:00+00:00porterhau5https://porterhau5.com/Sleat is a toolkit for weaponizing Windows logs, providing a suite of scripts for collecting, parsing, and analyzing Logon Events. Sleat can perform scope validation, identify exploitation targets for pivoting attacks, visualize network logons, and more.<h2 id="quick-overview-of-sleat">Quick Overview of Sleat</h2>
<p>Sleat is a collection of scripts for collecting, parsing, and analyzing logon events from Windows Security logs.</p>
<p>These scripts can be used for:</p>
<ul>
<li>Identifying workstations belonging to privileged users</li>
<li>Identifying workstations/accounts connecting from the CDE</li>
<li>Identifying workstations/accounts connecting from an IP address that wasn’t included (or conveniently forgotten!) in the scoping documents</li>
<li>Visualizing the relationship of logon events across the environment using Graphviz</li>
</ul>
<h2 id="background">Background</h2>
<p>Before I get into Sleat, I’d like to provide context on why I saw a need to create the toolkit. Doing so requires me to explain a little about PCI penetration testing and some common vocabulary used by those in the space.</p>
<p>In the PCI world, there is this notion of a “Cardholder Data Environment”, or a <strong>CDE</strong> – essentially a high-security environment where credit cards are transmitted, processed, or stored. Typical success criteria for this kind of test is to determine if an attacker originating from outside the CDE can find their way in.</p>
<p>The latest iteration of the PCI-DSS, a standard that governs network and configuration baselines for these types of networks, mandates that the CDE be completely segmented from the rest of the corporate network. This means that using a single Active Directory domain to preside over both the corporate network and the CDE <em>is prohibited</em>. If remote access by <strong>privileged users</strong> is needed, then it must be done in a controlled, secure manner with multi-factor authentication and non-corporate domain credentials.</p>
<p>However, I often find that a properly configured, PCI-compliant environment is rare. The majority of my engagements show some degree of connectivity between the two scopes, providing a vector for an attacker to get into the CDE. The key to exploiting this vector is being able to systematically identify it.</p>
<p>This is where Sleat comes in – a toolkit for finding privileged users, validating scope, and visualizing a network of users, all via Windows logs.</p>
<p>By default, Windows will store events pertaining to Logons in the Security log (Security.evtx). These events include different types of logons, such as interactive logons when a user logs in directly at the console, or RemoteInteractive logons when a user logs in via Remote Desktop or Terminal Services. On Windows systems later than 2003/XP, these events get stored under Event ID 4624.</p>
<h2 id="sleats-scripts">Sleat’s Scripts</h2>
<p>Sleat’s collection scripts query for these types of events (Event ID 4624) from the target host, parsing for relevant fields about the authenticating user, such as: IP address, Domain name, Username, and Workstation name. The results are stored in a text file.</p>
<p>Sleat’s analysis scripts correlate these parsed events with user-provided data points, such as a list of networks known to be in the CDE, or a list of privileged users who are known to access the CDE.</p>
<p>The end result is a list of attack vectors in the form of usernames or IP addresses. These are the targets a penetration tester could use for abusing privileged access and pivoting to the CDE.</p>
<p>Sleat’s parse script is for parsing raw EVTX files directly. For example, if a Security.evtx file has been exported from the target host, then the parse script can be used to extract the relevant fields before passing it to the analysis scripts.</p>
<h2 id="more-info">More Info</h2>
<p>To get into the weeds and see how Sleat works, along with example usage and syntax, check out the <a href="/projects/sleat/">Sleat project page</a>. The source code for Sleat is hosted on GitHub <a href="https://github.com/porterhau5/sleat" target="_blank">here</a>.</p>
<h4 id="share">Share</h4>
<ul class="share-buttons">
<li><a href="https://twitter.com/intent/tweet?source=http%3A%2F%2Fporterhau5.com%2Fblog%2Fintroducing-sleat%2F&text=Introducing%20Sleat%3A%20Security%20Logon%20Event%20Analysis%20Tools:%20http%3A%2F%2Fporterhau5.com%2Fblog%2Fintroducing-sleat%2F&via=porterhau5" target="_blank" title="Tweet"><img alt="Tweet" src="https://porterhau5.com/images/flat_web_icon_set/black/Twitter.png" /></a></li>
<li><a href="http://www.reddit.com/submit?url=http%3A%2F%2Fporterhau5.com%2Fblog%2Fintroducing-sleat%2F&title=Introducing%20Sleat%3A%20Security%20Logon%20Event%20Analysis%20Tools" target="_blank" title="Submit to Reddit"><img alt="Submit to Reddit" src="https://porterhau5.com/images/flat_web_icon_set/black/Reddit.png" /></a></li>
<li><a href="https://getpocket.com/save?url=http%3A%2F%2Fporterhau5.com%2Fblog%2Fintroducing-sleat%2F&title=Introducing%20Sleat%3A%20Security%20Logon%20Event%20Analysis%20Tools" target="_blank" title="Add to Pocket"><img alt="Add to Pocket" src="https://porterhau5.com/images/flat_web_icon_set/black/Pocket.png" /></a></li>
<li><a href="https://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Fporterhau5.com%2Fblog%2Fintroducing-sleat%2F&t=Introducing%20Sleat%3A%20Security%20Logon%20Event%20Analysis%20Tools" title="Share on Facebook" target="_blank"><img alt="Share on Facebook" src="https://porterhau5.com/images/flat_web_icon_set/black/Facebook.png" /></a></li>
</ul>
2016-06-21T00:00:00+00:00https://porterhau5.com/blog/native-port-forwarding-windows/Pivoting in Windows Using Native Port Forwarding2016-06-20T00:00:00+00:00porterhau5https://porterhau5.com/Harness the power of netsh interface portproxy to natively pivot on Windows and stay under the AV radar during a penetration test.<p>Ever wanted to route traffic through a compromised Windows host without going through the hassle of setting up a Meterpreter session or uploading files which may get flagged by endpoint protection? <code class="highlighter-rouge">netsh interface portproxy</code> is baked right into Windows and has everything you need to set up a pivot point. It works by directing incoming traffic on a specified host:port to a destination host:port. Very simple, and very effective.</p>
<h3 id="command">Command</h3>
<figure class="highlight"><pre><code class="language-html" data-lang="html">C:\>netsh interface portproxy add v4tov4 listenport=<span class="nt"><lport></span><br data-jekyll-commonmark-ghpages="" />listenaddress=<span class="nt"><lhost></span> connectport=<span class="nt"><rport></span> connectaddress=<span class="nt"><rhost></span><br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" /><span class="nt"><lport></span> - local port to listen on<br data-jekyll-commonmark-ghpages="" /><span class="nt"><lhost></span> - local address to bind to<br data-jekyll-commonmark-ghpages="" /><span class="nt"><rport></span> - remote port<br data-jekyll-commonmark-ghpages="" /><span class="nt"><rhost></span> - remote host</code></pre></figure>
<p>Example:</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html">C:\>netsh interface portproxy add v4tov4 listenport=1194<br data-jekyll-commonmark-ghpages="" />listenaddress=0.0.0.0 connectport=8080 connectaddress=10.10.10.20</code></pre></figure>
<p>This would bind locally to <code class="highlighter-rouge">0.0.0.0</code> (all network interfaces) on <code class="highlighter-rouge">1194/tcp</code> and route incoming connections to remote host <code class="highlighter-rouge">10.10.10.20:8080</code>. This particular example might be used to reach a web application that’s sitting behind a restrictive firewall.</p>
<h3 id="why-this-technique">Why this technique?</h3>
<p>This pivoting technique is an example of living off the land to expand access with the added benefit of stealth. It comes in handy when options are limited or when there’s a genuine concern of being noticed by AV or HIPS sitting on the endpoint. Using <code class="highlighter-rouge">netsh</code> to temporarily modify firewall tables is much less likely to be flagged by one of these products than a malicious or unknown executable, and it requires no uploading of software to the target host.</p>
<p>From a detection standpoint, this technique demonstrates the need for baselining. Comparing network traffic and system behavior against a solid baseline in order to detect anomalous behavior should be part of any proper defense-in-depth strategy. Unfortunately, this is a concept foreign to many security teams. A <a href="https://twitter.com/pfizzell" target="_blank">colleague of mine</a> and I are currently researching and developing a simple tool to help organizations capture these kind of baselines - more info to come as we develop.</p>
<h3 id="scenario">Scenario</h3>
<p>To give this technique a bit more context, here’s how I utilized this feature during a recent engagement:</p>
<p>I had obtained admin creds and had access to a Domain Controller via SMB. An Nmap scan showed that the network firewall allowed me to also get to 88/tcp, 389/tcp, and some of the high-range TCP ports (49152, 49153, etc.) on the DC as well. However, the network firewall didn’t allow me to reach back to my attack host, so reverse connections were out of the question. I couldn’t reach RDP, and all quick attempts to get a Meterpreter session going failed. I tried altering the local firewall, but it got me nowhere due to the restrictive network firewall rules. I really wanted to use this DC as a pivot point to get to some juicy targets that I suspected had RDP exposed.</p>
<p>Within my <a href="https://sourceforge.net/projects/winexe/" target="_blank">winexe</a> session, I ran netstat on the DC (10.10.10.10) to figure out which high-range TCP port would be available for me to bind to:</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html">C:\Windows\system32>netstat -an<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />Active Connections<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" /> Proto Local Address Foreign Address State<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:88 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:111 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:135 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:389 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:445 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:464 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:593 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:610 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:636 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:1688 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:3268 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:3269 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:3389 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:5722 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:6677 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:9389 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:9898 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:47001 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:49152 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:49153 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:49154 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:49155 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:49158 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:49159 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:49160 0.0.0.0:0 LISTENING</code></pre></figure>
<p>I decided to bind to <code class="highlighter-rouge">0.0.0.0:49162</code>, seeing as it was unused and I could most likely reach it through the network firewall. I used the <code class="highlighter-rouge">netsh interface portproxy</code> command to forward my traffic to a database server located at <code class="highlighter-rouge">10.10.10.20:3389</code>:</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html">C:\Windows\system32>netsh interface portproxy add v4tov4 listenport=49162<br data-jekyll-commonmark-ghpages="" />listenaddress=0.0.0.0 connectport=3389 connectaddress=10.10.10.20<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />C:\Windows\system32>netstat -an<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" />Active Connections<br data-jekyll-commonmark-ghpages="" /><br data-jekyll-commonmark-ghpages="" /> Proto Local Address Foreign Address State<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:88 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:111 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:135 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:389 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:445 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:464 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:593 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:610 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:636 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:1688 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:3268 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:3269 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:3389 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:5722 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:6677 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:9389 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:9898 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:47001 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:49152 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:49153 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:49154 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:49155 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:49158 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:49159 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:49160 0.0.0.0:0 LISTENING<br data-jekyll-commonmark-ghpages="" /> TCP 0.0.0.0:49162 0.0.0.0:0 LISTENING <span class="nt"><--</span> <span class="na">pivot</span></code></pre></figure>
<p>The pivot point was set on the DC. I could now connect xfreerdp to my newly-created listener and have my traffic forwarded to the secondary host:</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html">root@kali:~ # xfreerdp /d:DOM /u:admin /p:password /v:10.10.10.10:49162<br data-jekyll-commonmark-ghpages="" />connected to 10.10.10.10:49162<br data-jekyll-commonmark-ghpages="" />@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@<br data-jekyll-commonmark-ghpages="" />@ WARNING: CERTIFICATE NAME MISMATCH! @<br data-jekyll-commonmark-ghpages="" />@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@<br data-jekyll-commonmark-ghpages="" />The hostname used for this connection (10.10.10.10)<br data-jekyll-commonmark-ghpages="" />does not match the name given in the certificate:<br data-jekyll-commonmark-ghpages="" />Common Name (CN):<br data-jekyll-commonmark-ghpages="" /> DB-20.dom.local<br data-jekyll-commonmark-ghpages="" />A valid certificate for the wrong name should NOT be trusted!<br data-jekyll-commonmark-ghpages="" />Certificate details:<br data-jekyll-commonmark-ghpages="" /> Subject: CN = DB-20.dom.local<br data-jekyll-commonmark-ghpages="" /> Issuer: CN = DB-20.dom.local<br data-jekyll-commonmark-ghpages="" /> Thumbprint: 52:da:ec:9e:f6:75:6e:84:12:d2:2f:e9:e3:2c:47:b6:3b:d0:bf:d3<br data-jekyll-commonmark-ghpages="" />The above X.509 certificate could not be verified, possibly because<br data-jekyll-commonmark-ghpages="" />you do not have the CA certificate in your certificate store, or the<br data-jekyll-commonmark-ghpages="" />certificate has expired. Please look at the documentation on how to<br data-jekyll-commonmark-ghpages="" />create local certificate store for a private CA.<br data-jekyll-commonmark-ghpages="" />Do you trust the above certificate? (Y/N) Y</code></pre></figure>
<p>Woo, thanks <code class="highlighter-rouge">netsh</code>!</p>
<h3 id="more-info">More Info</h3>
<p>This is an example of using <code class="highlighter-rouge">netsh interface portproxy</code> to establish pivoting routes over IPv4, but it’s also possible over IPv6, and even IPv4-to-IPv6 (and vice versa). Check out the documentation from Microsoft <a href="https://technet.microsoft.com/en-us/library/cc731068%28v=ws.10%29.aspx" target="_blank">here</a>.</p>
<p>This should work out of the box on Windows 2008 and later. It should also work on Windows 2003, but it may error out if IPv6 isn’t supported on the host. More info <a href="https://support.microsoft.com/en-us/kb/555744" target="_blank">here</a>.</p>
<h4 id="share">Share</h4>
<ul class="share-buttons">
<li><a href="https://twitter.com/intent/tweet?source=http%3A%2F%2Fporterhau5.com%2Fblog%2Fnative-port-forwarding-windows%2F&text=Pivoting%20in%20Windows%20Using%20Native%20Port%20Forwarding:%20http%3A%2F%2Fporterhau5.com%2Fblog%2Fnative-port-forwarding-windows%2F&via=porterhau5" target="_blank" title="Tweet"><img alt="Tweet" src="https://porterhau5.com/images/flat_web_icon_set/black/Twitter.png" /></a></li>
<li><a href="http://www.reddit.com/submit?url=http%3A%2F%2Fporterhau5.com%2Fblog%2Fnative-port-forwarding-windows%2F&title=Pivoting%20in%20Windows%20Using%20Native%20Port%20Forwarding" target="_blank" title="Submit to Reddit"><img alt="Submit to Reddit" src="https://porterhau5.com/images/flat_web_icon_set/black/Reddit.png" /></a></li>
<li><a href="https://getpocket.com/save?url=http%3A%2F%2Fporterhau5.com%2Fblog%2Fnative-port-forwarding-windows%2F&title=Pivoting%20in%20Windows%20Using%20Native%20Port%20Forwarding" target="_blank" title="Add to Pocket"><img alt="Add to Pocket" src="https://porterhau5.com/images/flat_web_icon_set/black/Pocket.png" /></a></li>
<li><a href="https://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Fporterhau5.com%2Fblog%2Fnative-port-forwarding-windows%2F&t=Pivoting%20in%20Windows%20Using%20Native%20Port%20Forwarding" title="Share on Facebook" target="_blank"><img alt="Share on Facebook" src="https://porterhau5.com/images/flat_web_icon_set/black/Facebook.png" /></a></li>
</ul>
2016-06-20T00:00:00+00:00https://porterhau5.com/blog/welcome-to-porterhau5/Welcome to porterhau5.com2016-06-19T00:00:00+00:00porterhau5https://porterhau5.com/Just another guy rambling about InfoSec.<p>I’ve had aspirations of getting a blog going for some time, so this is an attempt at it. I imagine it will mostly serve as a means to <a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging" target="_blank">rubber duck</a> some tool or method I’m working on, but hopefully someone else out there will benefit from a useful command or tidbit found here.</p>
<p>I plan on including general observations from recent penetration tests, scripts that have improved my quality of life, comments on the research my colleagues or I am working on, thoughts about recent events in the InfoSec world, maybe a <a href="http://braves.com" target="_blank">Braves</a> rant or two, and whatever else I find interesting. Find out a little more about me <a href="/about/">here</a>.</p>
<h4 id="share">Share</h4>
<ul class="share-buttons">
<li><a href="https://twitter.com/intent/tweet?source=http%3A%2F%2Fporterhau5.com%2Fblog%2Fwelcome-to-porterhau5%2F&text=Welcome%20to%20porterhau5.com:%20http%3A%2F%2Fporterhau5.com%2Fblog%2Fwelcome-to-porterhau5%2F&via=porterhau5" target="_blank" title="Tweet"><img alt="Tweet" src="https://porterhau5.com/images/flat_web_icon_set/black/Twitter.png" /></a></li>
<li><a href="http://www.reddit.com/submit?url=http%3A%2F%2Fporterhau5.com%2Fblog%2Fwelcome-to-porterhau5%2F&title=Welcome%20to%20porterhau5.com" target="_blank" title="Submit to Reddit"><img alt="Submit to Reddit" src="https://porterhau5.com/images/flat_web_icon_set/black/Reddit.png" /></a></li>
<li><a href="https://getpocket.com/save?url=http%3A%2F%2Fporterhau5.com%2Fblog%2Fwelcome-to-porterhau5%2F&title=Welcome%20to%20porterhau5.com" target="_blank" title="Add to Pocket"><img alt="Add to Pocket" src="https://porterhau5.com/images/flat_web_icon_set/black/Pocket.png" /></a></li>
<li><a href="https://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Fporterhau5.com%2Fblog%2Fwelcome-to-porterhau5%2F&t=Welcome%20to%20porterhau5.com" title="Share on Facebook" target="_blank"><img alt="Share on Facebook" src="https://porterhau5.com/images/flat_web_icon_set/black/Facebook.png" /></a></li>
</ul>
2016-06-19T00:00:00+00:00