4. Tấn công “Heartbleed ngược”: đọc bộ nhớ của các máy tính client.
Trong quá trình update máy chủ để khắc phục điểm yếu Heartbleed, công ty Meldium đã phát hiện ra một điều đặc biệt: đối với một số website (trong số đó có cả những website rất nổi tiếng) vẫn còn điểm yếu kể cả sau khi update. Đó chính là một cách tấn công đặc biệt – tấn công “Heartbleed ngược”.
Theo cách tấn công thông thường: đối tượng xấu sử dụng các máy client để tấn công máy chủ HTTPS, sao chép cookies, mật khẩu và các Chứng thư từ bộ nhớ động (RAM). Còn một kịch bản tấn công ngược cũng được tiến hành theo đúng logic khai thác điểm yếu Heartbleed như vậy khi các máy chủ cấu hình gửi các gói heartbeat theo TLS và đọc nội dung RAM của máy khách (clients). Điều này là thực tế bởi vì TLS Heartbeat là giao thức đối xứng cho phép sinh gói từ cả hai phía.
Các điểm yếu của máy khách có thể kể đến: các web browser thông thường và các ứng dụng sử dụng HTTP API (tất cả từ Dropbox cho đến Microsoft Office) cũng như các ứng dụng di động trên nền tảng Android, IOS và các nền tảng khác. Để tấn công, cách đơn giản chỉ cần điều hướng ngược các yêu cầu từ máy client tới máy chủ và như thế có thể sử dụng phương pháp MiTM đối với Hotspot công khai, mạng cục bộ hoặc bộ định tuyến.
5. Tính chất nguy hiểm của lỗi Heartbleed
Phân tích lỗi Heartbleed trong OpenSSL
Lỗi bắt đầu trong ssl/d1_both.c:
int
dtls1_process_heartbeat(SSL *s)
{
unsigned char *p = &s->s3->rrec.data[0], *pl;
unsigned short hbtype;
unsigned int payload;
unsigned int padding = 16; /* Use minimum padding */
Như vậy có thể thấy đầu tiên sẽ nhận được con trỏ tới dữ liệu trong bản ghi SSLv3 như sau:
typedef struct ssl3_record_st
{
int type; /* type of record */
unsigned int length; /* How many bytes available */
unsigned int off; /* read/write offset into 'buf' */
unsigned char *data; /* pointer to the record data */
unsigned char *input; /* where the decode bytes are */
unsigned char *comp; /* only used with decompression - malloc()ed */
unsigned long epoch; /* epoch number, needed by DTLS1 */
unsigned char seq_num[8]; /* sequence number, needed by DTLS1 */
} SSL3_RECORD;
Cấu trúc mô tả bản ghi này có chứa loại, độ dài và dữ liệu. Quay lại với hàm dtls1_process_heartbeat:
/* Read type and payload length first */
hbtype = *p++;
n2s(p, payload);
pl = p;
Chú thích:
#define n2s(c,s) ((s=(((unsigned int)(c[0]))<< 8)| \
(((unsigned int)(c[1])) )),c+=2)
Byte đầu tiên của bản ghi SSLv3 – thể hiện loại Heartbeats. Macro n2s lấy 2 byte từ p và chuyển chúng vào payload. Thực tế đây chính là độ dài (length) của dữ liệu. Để ý có thể thấy rằng độ dài thực tế trong bản ghi SSLv3 không được kiểm tra.Sau đó biến pl nhận dữ liệu heartbeat chuyển cho bên hỏi.
Tiếp theo:
unsigned char *buffer, *bp;
int r;
/* Allocate memory for the response, size is 1 byte
* message type, plus 2 bytes payload length, plus
* payload, plus padding
*/
buffer = OPENSSL_malloc(1 + 2 + payload + padding);
bp = buffer;
Lấy số lượng bộ nhớ cần thiết cho bên hỏi: tới 65535+1+2+16. Biến bp – con trỏ sử dụng để truy cập tới bộ nhớ này.
Tiếp theo:
/* Enter response type, length and copy payload */
*bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
memcpy(bp, pl, payload);
Macro s2n sẽ thực hiện ngược với macro n2s: lấy giá trị 16 bit và chuyển nó vào 2 byte. Sau đó thiết lập chính độ dài được yêu cầu cho phần payload.
Sau đó chép payload byte từ pl vào trong mảng bp. Sau đó tất cả sẽ gửi ngược cho người dùng.
Như vậy lỗi ở chỗ nào?
Người dùng điều khiển payload và pl
Chuyện gì sẽ xảy ra nếu thực tế phía yêu cầu không gửi lượng payload byte? Nếu pl trong thực tế chỉ chứa 1 byte? Khi đó memcpy sẽ đọc từ bộ nhớ tất cả những gì ở gần với bản ghi SSLv3.
Tồn tại hai phương pháp mà bộ nhớ được phân chia động qua lệnh malloc (ít nhất là trong Linux): lệnh sbrk() và lệnh mmap(). Nếu bộ nhớ được phân chia theo sbrk thì các qui tắc cữ heap-grows-up được sử dụng và do đó sẽ hạn chế những gì có thể tìm được thông qua lệnh tuy nhiên có thể dùng nhiều yêu cầu (request) (đặc biệt là nhiều yêu cầu đồng thời) vẫn có thể tìm được những giá trị cần thiết.
Nếu như sử dụng mmap thì đối với mmap có thể được phân chia bất kì phần bộ nhớ chưa sử dụng nào và đây là mục đích chính của tấn công. Và quan trọng hơn cả càng nhiều yêu cầu thì xác suất rằng các yêu cầu đó được phục vụ bởi mmap càng cao chứ không phải sbrk.
Các hệ thống không sử dụng mmap để thực hiện malloc thì có vẻ như bớt nguy hiểm hơn.
Vị trí của bp nói chung không có ý nghĩa thực tế nhiều còn vị trí của pl ngược lại có ý nghĩa giá trị lớn. Phần bộ nhớ dưới nó hầu như được phân bố thông qua sbrk() vì giới hạn của mmap trong malloc(). Tuy nhiên phần bộ nhớ với thông tin hữu ích (ví dụ như các thông tin người dùng, …) với xác suất cao lại được phân chia bởi mmap() và có thể truy cập từ pl. Một vài yêu cầu đồng thời sẽ cho phép truy cập được các dữ liệu cần thiết.
Từ đó có thể nói rằng mô hình phân chia bộ nhớ cho pl cho phép chúng ta có thể đọc được các dữ liệu. Một trong những người phát hiện ra điều này đã nói: Các mô hình phân chia bộ nhớ trong cache cho phép tấn công khóa bí mật với xác suất thấp.
6. Cách khắc phục
Một trong những điểm chính cần khắc phục như sau:
/* Read type and payload length first */
if (1 + 2 + 16 > s->s3->rrec.length)
return 0; /* silently discard */
hbtype = *p++;
n2s(p, payload);
if (1 + 2 + payload + 16 > s->s3->rrec.length)
return 0; /* silently discard per RFC 6520 sec. 4 */
pl = p;
Đoạn mã này sẽ làm hai việc: kiểm tra thứ nhất ngăn chặn các heartbeats độ dài bằng 0. Kiểm tra thứ hai (if) thực hiện cảnh báo và loại bỏ nếu độ dài thực tế của bản ghi lớn.
Chính vì vậy, các chuyên gia đưa ra một vài nhận xét như sau:
- Cần quan tâm tới audit an toàn các thành phần của cơ sở hạ tầng an toàn quan trọng như OpenSSL.
- Viết nhiều unit và các test tích hợp cho các thư viện này.
- Có thể xây dựng các phương án thực thi khác trên các ngôn ngữ khác ngoài C.