Timing attack là gì?
Security
35
White

Cẩm Huỳnh viết ngày 01/11/2018

Như thường lệ, bài gốc được đăng trên Blog Quần Cam


Vài tháng trước tui từng viết về Billion Laugh Attack, một loại hình tấn công lợi dụng entity reference expansion trong XML để làm tràn bộ nhớ hệ thống. Trong bài viết này, tui sẽ giới thiệu Timing Attack, một kiểu tấn công cũng có thể ảnh hưởng đến miếng cơm manh áo của một kĩ sư phần mềm như bạn.

timing-attack

Photo by Bru-nO on Pixabay.

Timing attack là một loại side-channel attack (tấn công kênh kề) trong ngành mật mã học, kiểu tấn công dựa vào những thông tin thu thập được từ cách thực thi của hệ thống. Cụ thể ở đây timing attack phân tích thời gian thực thi của ứng dụng, để từ đó tiến hành các mưu đồ xấu độc. Xấu độc :thinking_face:, nghe y hệt cách chính phủ mô tả mạng xã hội ha :speak_no_evil:.

Bằng cách đo đạc thời gian thực thi của một số thao tác nhất định, hacker có thể sử dụng những thông tin này để truy dần ra dữ liệu đầu vào.

Ô kê phai! Nhưng thân là kĩ sư phần mềm, timing attack thì có ảnh hưởng gì đến chén cơm của tui?

Nhưng đâm ra nó có. Kiểu tấn công này thường dễ bị bỏ hớ, lại hay đến từ cách ta viết code. Những kẻ tấn công lợi dụng bản năng của chúng ta, những kĩ sư luôn muốn code của mình chạy càng nhanh càng tốt, để thực hiện ý đồ tấn công. Đúng nghĩa mất dạy là mẹ thành công mà :cry:.

Ta cùng lướt qua hai ví dụ sau để hiểu rõ thêm.

Password timing attack

API của một website không nổi tiếng lắm sử dụng chữ kí HMAC để verify payload gửi lên server. Đoạn code verify của họ dùng ngôn ngữ Ruby được viết như vầy:

def verify(payload, key, signature)
  hmac(payload) == signature
end

private
def hmac(data)
  # actual implementation
end

Đoạn code nhìn sơ qua không có gì vấn đề gì quá đặc biệt :thinking_face:. Nó chỉ tạo lại chữ ký HMAC cho payload và so trùng với chữ ký được người dùng đưa lên.

Để biết vấn đề là gì, ta hãy nghiên cứu cách Ruby hiện thực hàm so sánh chuỗi của nó.

str_eql(const VALUE str1, const VALUE str2)
{
    const long len = RSTRING_LEN(str1);
    const char *ptr1, *ptr2;

    if (len != RSTRING_LEN(str2)) return Qfalse;
    if (!rb_str_comparable(str1, str2)) return Qfalse;
    if ((ptr1 = RSTRING_PTR(str1)) == (ptr2 = RSTRING_PTR(str2)))
        return Qtrue;
    if (memcmp(ptr1, ptr2, len) == 0)
        return Qtrue;
    return Qfalse;
}

Cách hiện thực của Ruby khá đơn giản: đầu tiên nó kiểm tra độ dài của hai chuỗi là bằng nhau (1), sau đó thử so sánh hai con trỏ (2), rồi dùng memcmp(3) để kiểm tra hai string (3). Vấn đề nằm ở (3). memcmp(3), như khuyến cáo trên Linux man page, không nên được dùng để so sánh cryptographic data. Lý do memcmp(3) là short-circuit, thời gian chạy của nó phụ thuộc vào hai con trỏ đầu vào, chính xác hơn là số byte đầu giống nhau giữa chúng. Như đoạn code so sánh aaabbb sẽ trả về nhanh hơn đoạn so sánh aaaaab.

Đây là cách hiện thực hợp lý và thông minh. Nếu các byte đầu đã là không khớp, việc gì phải kiểm tra thêm các byte sau. Tuy nhiên khi dùng để so sánh hai chuỗi mật mã, cách hiện thực này có thể vô tình làm tiết lộ thông tin của chuỗi thông qua thời gian thực thi của nó.

Giả sử bạn có hai chuỗi bytes 1A 2B 3C 4D 5E 6F1A 00 00 00 00 00, nếu memcmp(3) tốn 1ms để trả về, thì 1A 2B 3C 4D 5E 6F1A 2B 3C 4D 5E 6F sẽ tốn 6ms. Đương nhiên 11 11 11 1100 00 00 00 sẽ trả về ngay lập tức.

Hacker có thể lợi dụng những thông số này để tiến hành mưu đồ của chúng. Đầu tiên chúng tạo ra và thử lần lượt những HMAC signature sau:

0000000000000000000000000000000000000000
0100000000000000000000000000000000000000
0200000000000000000000000000000000000000
... lược bỏ 250 cái ....
FD00000000000000000000000000000000000000
FE00000000000000000000000000000000000000
FF00000000000000000000000000000000000000

Sau đó chúng tìm được A100000000000000000000000000000000000000, signature này tốn lâu hơn một vài mi-li-giây để trả về so với những chuỗi khác. Suy ra chuỗi chính xác bắt đầu bằng A1. Áp dụng thủ thuật tương tự cho 19 đoạn 00 còn lại, chúng có được signature mong muốn :scream:.

Để cho xã hội vững mạnh tiến lên 4.0, chúng ta phải làm cho hàm này chạy chậm 4.0 lần cần loại bỏ yếu tố short-circuit và làm cho thuật toán verify có constant-time. Tức là bắt nó chạy với thời gian cố định mà không phụ thuộc vào input.

def verify(message, signature)
  secure_compare(hmac(payload), signature)
end

# https://github.com/plataformatec/devise/blob/94adec3ceef6a3e9eb6bdc4d3fa7ed4a3fc6b348/lib/devise.rb#L500-L508
def self.secure_compare(a, b)
  return false if a.blank? || b.blank? || a.bytesize != b.bytesize
  l = a.unpack "C#{a.bytesize}"

  res = 0
  b.each_byte { |byte| res |= byte ^ l.shift }
  res == 0
end

Username timing attack

Chắc bạn từng viết login form. Thông thường, khi user nhập sai thông tin login, chúng ta hay trả về những thông báo chung chung như "email or password is invalid", thay vì thông báo chính xác cái nào bị sai. Làm vậy để tránh để lộ dữ liệu nhạy cảm như email của người dùng :bulb: :hugging_face:.

Tuy nhiên, với timing attack, hacker vẫn có thể đạt được ý đồ xấu xa đó. Ví dụ như đoạn code login sau đây:

def login(email, password)
  user = User.find_by(email: email)
  return false if !user
  return encrypt_password(password) == user.encrypted_password
end

Bạn có để ý là: đoạn code trên cũng có short-circuit :face_with_head_bandage:. Cùng là password sai, nhưng login với một email tồn tại sẽ tốn thời gian nhiều hơn đáng kể (để mã hóa password) so với một email không tồn tại.

Những kẻ chống phá có thể lợi dụng lỗ hổng này để dò email. Nếu login bằng a@quan-cam.com tốn 135.34ms nhiều hơn so với b@quan-cam.comc@quan-cam.com, chúng sẽ đoán ra a@quan-cam.com là một email hợp lệ, còn thời gian dôi ra là hệ thống hash password. Cứ thử liên tục như thế, hắn sẽ có được tập email mong muốn từ hệ thống của chúng ta.

Người ta có nghĩ ra một vài cách để chống kiểu tấn công này. Ý tưởng không khác, đó là làm cho thuật toán chạy constant time. Bạn luôn luôn hash password dù cho username có hợp lệ hay không, hoặc cho CPU sleep một khoảng thời gian bằng với thuật toán hash. Nhờ đó, code của bạn sẽ chạy chậm một cách ổn định.

def login(email, password)
  user = User.find_by(email: email)

  if user
    return encrypt_password(password) == user.encrypted_password
  else
    encrypt_password("just-to-waste-some-time")
    return false
  end
end

Thật là cực chẳng đã đúng không bà con? :sweat:

Cơ mà ... ông có dụ tui không?

Đọc tới đây có thể bạn sẽ nghĩ tui đang dụ bạn, vì cách tấn công này nghe có vẻ hơi bất khả thi. Có quá nhiều nhân tố có thể ảnh hưởng đến thời gian thực thi và trả về của một web: như network, sức mạnh của máy, cá mập, cách mạng, 4.0, 2.02.0 v.v. Với lại, sao mà hacker biết được code bạn viết thế nào?

Theo nghiên cứu Opportunities and Limits of Remote Timing Attacks, họ có thể nhận diện được độ khác biệt của remote timing trong mức dao động 20µs, và mạng LAN ở mức 100ns (thậm chí có thể nhỏ hơn) chỉ trên một tập vài trăm hoặc vài ngàn request.

Với kết quả từ nghiên cứu này, đoạn tấn công HMAC ở trên hoàn toàn có thể thực hiện được với khoảng 5,000,000 request (20 x 256 x 1000). Đó là con số có thể thực hiện chỉ trong vòng 3 ngày với tỉ lệ là 20 request/s.

Còn code bạn viết thế nào thì ... tui e rằng nó có đầy trên mạng ấy. Bạn có dùng thư viện mã nguồn mở mà đúng không :wink:?

Bài viết này sẽ giúp tui tăng lương thế nào?

Bài viết này không những không giúp bạn tăng lương, mà còn khuyên bạn làm cho code của bạn chậm đi :cry:.

Túm cái váy là, tui mong bài viết này đã giới thiệu cho bạn thấy:

  • khái niệm về timing attack: tấn công bằng cách phân tích thời gian thực thi của hệ thống.
  • cách phòng chống là thiết kế thuật toán của bạn chạy constant time mà không phụ thuộc vào input.
  • code C nhìn xấu như thế nào :pensive:.

Tham khảo

  1. Timing attack in Google Keyczar library.
  2. A Lesson In Timing Attacks (or, Don’t use MessageDigest.isEquals).
  3. Timing attacks and usernames.
  4. Opportunities and Limits of Remote Timing Attacks
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

Cẩm Huỳnh

47 bài viết.
454 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
66 5
image cover]imgcover] “Make it work, make it right, make it fast.” Bạn vừa viết xong một ứng dụng web :tada:. Mọi thứ chạy ổn. Code cũng đã được...
Cẩm Huỳnh viết 10 tháng trước
66 5
White
47 26
Vừa rồi mình vừa tiết kiệm được $5 mỗi tháng sau khi migrate cái (Link) từ Digital Ocean sang Heroku Free Dyno. (Ảnh) Kết quả thật mĩ mãn vì hầu ...
Cẩm Huỳnh viết 2 năm trước
47 26
White
44 9
(Ảnh) Vì sao lại là Bật Đèn? Ai từng đọc qua Tắt Đèn hẳn đã biết tác phẩm được kết thúc bằng tình huống: Buông tay, chị vội choàng dậy, mở cửa...
Cẩm Huỳnh viết hơn 2 năm trước
44 9
Bài viết liên quan
White
39 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 hơn 3 năm trước
39 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 2 năm trước
20 4
White
27 5
Mỗi khi đăng những bài (Link) về một (Link) được phát hiện trên một trang web nào đó, tôi biết sẽ có những người nhếch mép cười khẩy vì lúc đó tron...
Juno_okyo viết gần 3 năm trước
27 5
{{like_count}}

kipalog

{{ comment_count }}

bình luận

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


White
{{userFollowed ? 'Following' : 'Follow'}}
47 bài viết.
454 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á!