Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
Tools/GitHubGitHub/diegslva/cve-2010-4221-lab
Exploit FrameworksVulnerability AnalysisExploitationReverse EngineeringPenetration TestingLearning & EducationBinary ExploitationLabs & Practice
GitHubdiegslva/cve-2010-4221-lab

cve-2010-4221-lab

From patch to RCE: hand-built exploit for CVE-2010-4221 (ProFTPD TELNET IAC stack overflow), with the full failure-driven journey documented

View Repository
12h 14m agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2010-4221 — ProFTPD TELNET IAC Stack Overflow: From Patch to RCE

A fully reproducible lab and a hand-written, raw-socket exploit for CVE-2010-4221 — the pre-authentication stack buffer overflow in ProFTPD's pr_netio_telnet_gets() — built as a learning exercise in vulnerability research and exploit development.

Every failure is documented. The happy path is a lie; the detours are the lesson.


Legal and ethical notice — read this first

This repository is an educational artifact. It exists so that people who cannot afford a mentor or a training can learn how a memory-corruption exploit is actually born: from a patch, through failures, to a working proof of concept inside a laboratory you own.

Red Team work — real, professional offensive security — is defined by one word: authorization. Everything a professional does happens inside a written agreement: a signed Rules of Engagement document that names the scope, the targets, the techniques allowed, the time window, and the people who approved it. Without that paper, the exact same keystrokes are not a profession — they are a crime in essentially every jurisdiction on Earth.

So here is the contract for this repo, non-negotiable:

  • Run this only against the enclosed Docker lab or systems you own.
  • Never against anything without explicit, written authorization.
  • If you are learning: welcome, this was built for you.
  • If you are looking for a weapon to use on others: close this tab. This bug is from 2010; it will get you nothing but a criminal record.

The craft is worth learning. The craft is only worth something with the discipline that comes with it.


The bug

ProFTPD speaks TELNET escape sequences on the FTP control channel. In TELNET, 0xFF (IAC, "Interpret As Command") is the escape byte; a literal 0xFF is sent as 0xFF 0xFF.

pr_netio_telnet_gets() copies client bytes into a stack buffer (pr_cmd_read's char buf[PR_DEFAULT_CMD_BUFSZ+1], 4104 bytes with glibc's MAXPATHLEN=4096), tracking remaining space in buflen — a size_t, unsigned.

The vulnerable path (1.3.3a, netio.c):

root@kitploit:~
case TELNET_IAC:
  switch (cp) {
    ...
    default:
      *bp++ = TELNET_IAC;   // write #1
      buflen--;             // decrement #1
      telnet_mode = 0;
      break;
  }
  break;
...
*bp++ = cp;                 // write #2
buflen--;                   // decrement #2  <-- no check in between

Two writes, two decrements, no zero check between them. When buflen is exactly 1, the pair decrements it to 0, then underflows to SIZE_MAX (18 quintillion). The loop now believes the buffer is infinite and keeps writing attacker-controlled bytes up the stack — over saved registers, saved RBP, and the return address.

Pre-auth. The function runs before USER/PASS are ever processed.

The patch

The fix (commit 3cc69b8388, "Bug#3521 - Telnet IAC processing stack overflow", released in 1.3.3c) is twelve lines. The entire security boundary is:

root@kitploit:~
if (buflen == 0) {
  break;
}

See patch.diff. Reading the patch tells you where the wound was — that is the skill.

The lab

The Dockerfile compiles ProFTPD 1.3.3a from the historical Debian snapshot source, deliberately insecure (this is how 2010 looked):

  • -fno-stack-protector — no canary
  • -z execstack — executable stack (no NX)
  • -no-pie — fixed binary addresses
  • run under gdb, which disables ASLR by default → deterministic stack
root@kitploit:~
docker build -t proftpd-133a .
docker rm -f lab133 2>/dev/null
docker run -d --name lab133 --cap-add SYS_PTRACE \
  --security-opt seccomp=unconfined -p 127.0.0.1:2122:21 \
  proftpd-133a sh -c 'gdb -batch -ex "set follow-fork-mode child" \
  -ex "run" -ex "continue" --args /usr/local/sbin/proftpd -n -d1 \
  > /tmp/gdb.txt 2>&1; sleep 600'
python3 exploit.py

Expected output:

root@kitploit:~
[S] 220 ProFTPD 1.3.3a Server (lab-iac) ...
[S] THE SERVER SAID: b'PWNED!!PWNED!!'

(The shellcode writes to fds 0, 1 and 2 because we did not want to depend on knowing which one carries the control channel — two of them answer.)

The exploit architecture

root@kitploit:~
"SITE " + NOP sled + shellcode + [\xff\xff flood] + padding + [ret] + "\n"
 ^^^^^^^^^^^^^^^^^^^^                             ^^^^
 shellcode lives INSIDE the command               the overflow tail only
 buffer — the region nobody touches               delivers ONE address
  1. "SITE " keeps the FTP parser alive — the command processes cleanly.
  2. The shellcode is the command's content. The buffer is the safest place on the stack: after the read, only buf[4102] is touched (truncation NUL). Everything below the frame's live locals is calm.
  3. The IAC flood drives buflen to the underflow (see "The journey" for the parity problem).
  4. The ret slot (buf + 4152) receives the address of the middle of the NOP sled. When pr_cmd_read hits return 0 after parsing, the CPU lands in the sled and slides into the shellcode.

This inverted architecture — payload first, flood second, address last — was validated against the canonical Metasploit module (proftp_telnet_iac), which uses the same layout. Their targets had NX, so they needed a ROP chain with a "quadruple deref" of the res pointer; our lab has an executable stack, so a single direct return suffices.

The journey (the actual point of this repo)

The final exploit is 60 lines. What it cost:

  1. Blind \xff flood → nothing. The server politely closed the session. Root cause: buflen starts at 4102 (EVEN) and each IAC pair decrements by 2 — it lands on 0 cleanly, never on 1. The underflow needs parity broken. Lesson: reading the state machine beats spraying.

  2. Wrong buffer size. First calibrated attempt assumed a 1024-byte buffer. The real one is MAXPATHLEN+8 = 4104 on Linux/glibc. The flood stopped 3KB short of the target. Lesson: measure the target, don't assume the target.

  3. First SIGSEGV. Cyclic de Bruijn pattern (Aa0Aa1...) placed the return slot at buf+4152, cross-validated twice (frame math + pattern offset). Lesson: the cyclic pattern is a measuring tape, not an exploit.

  4. RIP control. Setting the slot to 0x4141414141414141 crashed the ret instruction itself — x86-64 refuses non-canonical addresses, and the fault lands on ret, with our value waiting in the backtrace. Lesson: a crash on ret with your value in the frame = control.

  5. Shellcode above the ret slot → clobbered. 8 bytes overwritten by a heap pointer (0x4d7838 — later identified as the cmd_rec pool allocation). The stack frames above the slot belong to functions still working between landing and hijack. Lesson: the overflow is not the last write; the program keeps living on the stack you just vandalized.

  6. "Dead zone" below the slot → also scribbled. pr_cmd_read's own locals (cmd, buflen, cp) live right there and keep being stored during parsing.

  7. Hardware watchpoint forensics. watch *(long*)ADDR in gdb turned the mystery into a camera: every write to the clobbered address, with backtrace, in order. Lesson: when the question is "who wrote this memory?", the answer is one gdb command away.

  8. Read the reference, then understand it. The canonical module confirmed the inverted architecture. Reading another exploit after building your own mental model is study; before, it is copying.

Lessons learned

  • size_t never goes negative — it goes gigantic. Integer underflow in a remaining-space counter is a stack overflow with extra steps.
  • Parity is a weapon. When a primitive decrements 2-by-2, you control the underflow by controlling odd/even, not just size.
  • Bad chars are a protocol question. Our shellcode avoids \x0a (ends the read) and survives \xff (TELNET escape) by design.
  • Prefork servers forgive crashes. The child dies, the parent keeps accepting: infinite attempts. Reliability engineering is part of the exploit.
  • Measure and exploit the same artifact. A 11-byte difference in argv[0] (build-tree binary vs installed binary) shifted the whole stack by 0x40 and silently invalidated a perfect exploit.
  • The signal handler confesses. ProFTPD catches SIGSEGV and logs "terminating (signal 11)" — the target tells you it died, even when the kernel stays quiet.

References

  • Fix commit: https://github.com/proftpd/proftpd/commit/3cc69b8388
  • CVE: https://nvd.nist.gov/vuln/detail/CVE-2010-4221
  • Canonical module: modules/exploits/linux/ftp/proftp_telnet_iac.rb (rapid7/metasploit-framework)
  • Sibling lab (logic bug, same daemon): CVE-2015-3306 mod_copy

Author

Built by diegslva, learning in public — from "never wrote an exploit" to pre-auth RCE with hand-rolled shellcode, in one documented day. If this repo taught you something, pay it forward.

Download Tool