Lỗ hổng bảo mật mới và nghiêm trọng của hầu hết phiên bản PHP (5/2015)
Security
32
PHP
76
White

Vu Nhat Minh viết ngày 02/11/2015

Lỗ hổng này nguy hiểm đến mức nào ?

Lỗ hổng với mã CVE-2015-4024 được coi là nghiêm trọng khi cho phép attacker thực hiện tấn công từ chối dịch vụ (DoS) gây ra tổn thất lớn cho máy chủ chỉ bằng một đoạn mã nhỏ. Nếu một hệ thống botnet được trang bị sử dụng chiến lược này thì thiệt hại gây ra sẽ là khủng khiếp.

CVE-2015-4024 được báo cáo bởi một một nhà nghiên cứu an ninh tại Baidu, Trung Quốc, dựa vào lỗi trong đoạn logic phân tích HTTP header của PHP, qua đó nếu tạo ra một đoạn HTTP header đặc biệt thì có thể đẩy được CPU của server lên 100% khiến toàn bộ các chức năng khác trở nên tê liệt.

Server của tôi có bị ảnh hưởng hay không ?

Nếu nhìn vào CHANGELOG của PHP gần đây và search CVE-2015-4024 thì sẽ thấy các dòng

Fixed bug #69364 (PHP Multipart/form-data remote dos Vulnerability). (CVE-2015-4024)

vào ngày 14/5/2015 ở các phiên bản

  • PHP 5.6.9
  • PHP 5.5.25
  • PHP 5.4.41

Điều này có nghĩa là PHP 5.6.9+, 5.5.25+, 5.4.41+ đã được vá, đồng nghĩa với chuyện toàn bộ các version

  • PHP 5.6.(<9)
  • PHP 5.5.(<25)
  • 5.4.(<41)
  • 5.3.*
  • 5.2.*

đều đang nằm trong vùng nguy hiểm. Nếu bạn đang chạy dịch vụ với dịch vụ sử dụng PHP, bạn nên kiểm tra version và vá lỗ hổng ngay bây giờ.

Vá lỗ hổng này như thế nào ?

Hiện tại có 2 cách vá lỗ hổng tại thời điểm này

  • Nâng cấp version PHP lên các phiên bản đã được vá lỗi (đã liệt kê ở trên). Tuy nhiên nâng cấp version thường không đơn giản đối với các hệ thống lớn và đã xây dựng từ lâu do độ phức tạp của xử lý và tính lệ thuộc của source code.
  • Sử dụng bản patch. Cách này có vẻ thực tế hơn. Mình sẽ hướng dẫn cách dùng bản patch ở phần tiếp.

Sử dụng bản patch để vá lỗ hổng

HIện nay đã có bản patch cho PHP 5.4, 5.5. và 5.6 ở ngay trong link báo cáo CVE-2015-4024. Cụ thể hơn, bạn có thể xem ở link trực tiếp của bản patch. Cách sử dụng bản patch là như sau

  • Download bản patch về (patch-5.4.patch.txt)
  • Đổi tên thành patch-5.4.patch
  • Vào thư mục chứa source PHP của máy chủ
  • patch -p2 < /path/to/patch-5.4.patch
  • Recompile lại bản PHP vừa mới patch
  • Khởi động lại server máy chủ (apache/nginx/v.v...)

Nếu mảy chủ của bạn đang sử dụng PHP 5.3 hoặc nhỏ hơn thì sẽ không thể dùng được bản patch theo cách trên, do source code của file sửa đã thay đổi cả những phần khác, vì thế số dòng đã thay đổi, khiến cho câu lệnh patch không còn nhận được. Cách làm sẽ hơi phức tạp hơn một chút

  • Vào thư mục chứa source PHP của máy chủ
  • Tìm đến file sửa của bản vá lần này (main/rfc1867.c)
  • Thay đổi bằng tay giống như các chỉnh sửa trong patch (cái này hơi mất thời gian)
  • Recompile lại bản PHP vừa mới patch
  • Khởi động lại server máy chủ (apache/nginx/v.v...)

Cách kiểm tra sau khi vá

Cách kiểm tra tốt nhất là ... tự viết script tấn công để tấn công chính server của mình. Hiện giờ mình có script mô phỏng lại quá trình trên, tuy nhiên vì lỗ hổng này mới xuất hiện nên sẽ không công khai. Mặc dù vậy để tự viết thì khá dễ, bạn chỉ cần đọc hiểu nội dung báo cáo phân tích về code lỗi (ở đây là ngôn ngữ C) là có thể làm được ngay.

Lược dịch mô tả bug

Mình sẽ lược dịch phần mô tả bug sang tiếng Việt để các bạn tiện theo dõi.

Khi một HTTP request được gửi lên server và "đến lượt" PHP xử lý, PHP sẽ đọc tuần tự từng dòng (line by line) của phần header trong HTTP request đó. Giả sử với một đoạn header như là:

Content-Disposition: form-data; name="file"; filename="test.txt" 
Content-Type: application/octet-stream 

thì PHP sẽ đọc lần lượt 2 dòng và chạy xử lý từ trên xuống dưới.

Content-Disposition: form-data; name="file"; filename="test.txt" 

Content-Type: application/octet-stream 

Xử lý khi đọc tuần tự từng dòng là đoạn code C như sau:

/* parse headers */ 
static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header TSRMLS_DC) 
{ 
char *line; 
mime_header_entry prev_entry = {0}, entry; 
int prev_len, cur_len; 

/* didn't find boundary, abort */ 
if (!find_boundary(self, self->boundary TSRMLS_CC)) { 
return 0; 
} 

/* get lines of text, or CRLF_CRLF */ 

while( (line = get_line(self TSRMLS_CC)) && line[0] != '\0' ) 
{ 
/* add header to table */ 
char *key = line; 
char *value = NULL; 

if (php_rfc1867_encoding_translation(TSRMLS_C)) { 
self->input_encoding = zend_multibyte_encoding_detector(line, strlen(line), self->detect_order, self->detect_order_size TSRMLS_CC); 
} 

/* space in the beginning means same header */ 
if (!isspace(line[0])) { 
value = strchr(line, ':'); 
} 

if (value) { 
*value = 0; 
do { value++; } while(isspace(*value)); 

entry.value = estrdup(value); 
entry.key = estrdup(key); 

} else if (zend_llist_count(header)) { /* If no ':' on the line, add to previous line */ 

prev_len = strlen(prev_entry.value); 
cur_len = strlen(line); 

entry.value = emalloc(prev_len + cur_len + 1); 
memcpy(entry.value, prev_entry.value, prev_len); 
memcpy(entry.value + prev_len, line, cur_len); 
entry.value[cur_len + prev_len] = '\0'; 

entry.key = estrdup(prev_entry.key); 

zend_llist_remove_tail(header); 
} else { 
continue; 
} 

zend_llist_add_element(header, &entry); 
prev_entry = entry; 
} 

return 1; 
} 

Ở đây nếu như thỏa mãn 2 điều kiện:

  • Dòng đang đọc không bắt đầu bởi kí tự space
  • Dòng đang đọc không chứa dấu :

thì xử lý sẽ nhảy vào 2 điều kiện

if (!isspace(line[0])) {
  value = strchr(line, ':');
} 

if (zend_llist_count(header)) {
  ... 
}

thì khi đó đoạn code tương ứng sẽ được thực thi, mà cụ thể là

prev_len = strlen(prev_entry.value); 
cur_len = strlen(line); 

entry.value = emalloc(prev_len + cur_len + 1); //allocate (prev_len + cur_len) bytes memory. 
memcpy(entry.value, prev_entry.value, prev_len); //copy prev_len bytes. 
memcpy(entry.value + prev_len, line, cur_len); // cope (prev_len + cur_len) bytes memory. 
entry.value[cur_len + prev_len] = '\0'; 

entry.key = estrdup(prev_entry.key); 

zend_llist_remove_tail(header); // free memory 

Đoạn code này có một vấn đề: nếu được thực thi nhiều lần liên tiếp liên tục nhau thì hàm memcpy sẽ gây ra hiện tượng thiếu hụt tài nguyên, đẩy CPU lên 100% và tê liệt hệ thống. Vì vậy attacker sẽ làm thế nào để tạo ra một vòng loop mà PHP luôn luôn nhảy vào đoạn xử lý này.

Vậy làm thế nào để tạo ra vòng loop nói trên ? Câu trả lời là tạo ra HTTP header mà nhiều dòng liên tiếp không bắt đầu bằng kí tự space và trong dòng không chưa dấu ":" . VÍ dụ:

Content-Disposition: form-data; name="file"; filename="s 
a 
a 
a 
a" 
Content-Type: application/octet-stream 

<?php phpinfo();?> 

HTTP header có max size là 2097152 bytes (2M), với size này và cách làm như trên thì có thể tạo ra request đủ sức đánh gục CPU của server :)

Tham khảo

XIn nhắc lại các link quan trọng trong bài viết này.

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Vu Nhat Minh

54 bài viết.
818 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
126 30
Nếu bạn thường vào trang mua sắm của amazon, chắc sẽ chẳng lạ gì với menu Shop by Department. Tốc độ hiển thị nội dung của menu là tức thì so với d...
Vu Nhat Minh viết 3 năm trước
126 30
White
100 4
Lời người dịch Người dịch là một developer , sau khi tìm đọc được bài viết này bằng bản gốc tiếng Anh đã cảm thấy như được "khai sáng" về khả năng...
Vu Nhat Minh viết hơn 3 năm trước
100 4
White
68 7
Form là thành phần quan trọng nhất khi design flow đăng ký của 1 web hay 1 app, dù là view gồm nhiều bước hay chỉ là một màn hình đơn điệu. Bài này...
Vu Nhat Minh viết gần 2 năm trước
68 7
Bài viết liên quan
White
37 11
Có 1 kiểu tấn công vào website mà chúng ta không thể nào chống được, dù có làm thế nào đi nữa: DDOS. Đây cũng là một từ rất hay dc nhắc đến và rất...
quocnguyen viết gần 3 năm trước
37 11
White
20 4
(Ảnh) Nếu máy tính của bạn đã bị lây nhiễm, mã độc có thể lây lan tới trang web của bạn thông qua trình soạn thảo văn bản và (Link). Dùng các mật ...
Juno_okyo viết hơn 1 năm trước
20 4
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
54 bài viết.
818 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!