Bybit gần đây đã phải chịu một cuộc tấn công của tin tặc liên quan đến 1,5 tỷ đô la, thu hút sự chú ý rộng rãi từ cộng đồng bảo mật blockchain . Tin tặc đã xâm nhập thành công vào ví lạnh đa chữ ký của Bybit và chuyển một lượng lớn tài sản đến các địa chỉ không xác định bằng cách can thiệp hoặc khai thác các cơ chế bảo mật hiện có theo một cách nào đó.
Cuộc tấn công này không chỉ gây ra tổn thất tài chính lớn cho Bybit mà còn đặt ra những thách thức mới đối với tính bảo mật của ví đa chữ ký. Bài viết này, được viết bởi một nhà nghiên cứu bảo mật blockchain từ BSOS Labs, sẽ phân tích sâu các chi tiết kỹ thuật của cuộc tấn công, bao gồm kiến trúc đa chữ ký được Bybit sử dụng, phương pháp của kẻ tấn công và những tác động về bảo mật của sự cố lần.
Bối cảnh
Ví đa chữ ký
Trong thiết kế blockchain của Ethereum , có hai loại tài khoản, một là tài khoản EOA (Tài khoản sở hữu bên ngoài) và loại còn lại là Tài khoản hợp đồng. Ví lạnh đa chữ ký Bybit sử dụng để quản lý tài sản trong sự cố này thuộc về loại sau. Ví này thực chất là một hợp đồng thông minh tùy chỉnh một số logic kinh doanh thông qua lập trình. Trong thiết kế của một tài khoản đa chữ ký, khi ví nhận tài sản từ bên ngoài, nếu muốn rút hoặc thực hiện bất kỳ hoạt động nào khác, ví phải vượt qua một số ngưỡng nhất định do ví đa chữ ký đặt ra trước khi có thể thực sự thực hiện.
Nhìn chung, người dùng có thể coi Safe Wallet như một kho bạc của công ty, được quản lý bởi các giám đốc công ty. Khi các giám đốc công ty muốn rút tài sản kho bạc hoặc sử dụng tài sản này để đầu tư bên ngoài, trước tiên họ phải trải qua quy trình sau:
- Hội đồng quản trị đầu tiên Đề án quyết định số tiền sẽ sử dụng trong tương lai, các mục tiêu và hoạt động sẽ thực hiện.
- Các thành viên hội đồng quản trị ký vào Đề án. Khi số lượng chữ ký vượt quá ngưỡng thì có thể thực hiện được.
Ví dụ, ví đa chữ ký 3 trong số 5 sẽ có tổng cộng 5 giám đốc, những người mà chúng tôi gọi là Người ký. Những Người ký này có thể khởi xướng Đề án trước. Danh từ Đề án với ví đa chữ ký là Giao dịch. Một số người cũng nói rằng đó là Đề xuất để tránh nhầm lẫn với giao dịch của blockchain.
Đề án này có thể là một giao dịch chuyển ETH/ERC-20 đơn giản sang các tài khoản khác hoặc có thể là một hoạt động DeFi phức tạp hơn. Tiếp theo, những người ký tên sẽ ký vào các đề xuất này để thể hiện sự đồng ý của họ với Đề án và một chữ ký sẽ được tạo ra làm bằng chứng trong quá trình này. Khi số lượng Chữ ký hợp lệ lớn hơn 3, người dùng có thể gọi thao tác executeTransaction của Safe Wallet và gửi các Chữ ký này. Sau khi xác minh rằng các Chữ ký này thực sự được ký bởi Người ký được chứng nhận, tức là giám đốc của kho lưu trữ, nội dung của Đề xuất ban đầu sẽ thực sự được thực hiện.

Quy trình đơn giản được thể hiện trong hình trên. Đầu tiên, Người ký 0 gửi đề xuất, sau đó Người ký 2 và 3 xác nhận rằng đề xuất không có vấn đề gì và sau đó ký. Thông thường, người gửi đề xuất sẽ ký cùng lúc, vì vậy tại thời điểm này, đã có 3 chữ ký, đáp ứng ngưỡng do ví đa chữ ký này đặt ra. Lúc này, bạn có thể thực hiện nội dung của Đề xuất này. Trong thiết kế của Safe front-end, nếu bạn đạt ngưỡng khi ký, hệ thống sẽ hỏi bạn có muốn thực hiện sau khi ký không. Bạn có thể chọn Có hoặc chọn Không và để người khác giúp bạn thực hiện. Sự khác biệt nằm ở người trả phí gas .
Mẫu Proxy
Trên đây là hiểu biết sơ bộ về hoạt động của ví đa chữ ký thông qua việc giới thiệu Safe Wallet. Chúng ta hãy tiếp tục xem xét thiết kế hợp đồng của Safe . Phần lớn logic được đề cập ở trên được viết trong hợp đồng thông minh. Nếu bạn quan tâm, bạn có thể tham khảo triển khai hợp đồng chính của Safe Wallet: https://etherscan.io/address/0x34cfac646f301356faa8b21e94227e3583fe3f5f#code
Hợp đồng này không phức tạp và lượng code không lớn, nhưng nếu bạn phải triển khai một hợp đồng như vậy lần lần tạo ví Safe thì sẽ tốn rất nhiều Gas , do đó Safe sử dụng Proxy Pattern để giảm chi phí. Cần lưu ý rằng điều này đề cập đến Proxy Pattern chứ không phải Upgradeable Proxy. Không giống như Transparent Proxy và UUPS Proxy thông thường, không có hành vi nâng cấp được cài đặt sẵn nào ở đây. Nguyên lý hoạt động cụ thể tương tự như Minimal Proxy trong EIP-1167 .

Lần lần người dùng tạo ví an Safe , một Proxy sẽ được tạo. Chương trình của Proxy này rất đơn giản và về cơ bản chỉ sử dụng Delegatecall để gọi hợp đồng chính. Dữ liệu được gọi là dữ liệu khi người dùng tương tác với Proxy. Mọi logic kinh doanh đều nằm trong hợp đồng chính 0x34Cf…3F5F, nhưng do đặc điểm của Delegatecall, trạng thái của mỗi Proxy (như số dư mã thông báo, v.v.) được tách biệt, như được hiển thị ở trên. Thiết kế này sẽ ngăn chặn tình trạng tài sản của mọi người bị trộn lẫn với nhau vì chỉ có một hợp đồng chính duy nhất.
Nói một cách đơn giản, Proxy này gọi hợp đồng chính thông qua Delegatecall và sử dụng logic kinh doanh của hợp đồng chính để sửa đổi trạng thái của chính Proxy, chẳng hạn như chuyển mã thông báo và các hoạt động khác.
Trong sự cố Bybit , cũng có hai địa chỉ, Proxy và hợp đồng chính, cả hai đều nằm trên blockchain mạng chủ Ethereum .
- Máy chủ ủy nhiệm: 0x1Db92e2EeBC8E0c075a02BeA49a2935BcD2dFCF4
- Ví an Safe : 0x34CfAC646f301356fAa8B21e94227e3583Fe3F5F
Hãy cùng xem chương trình Proxy thực tế. Bạn sẽ tìm thấy một biến địa chỉ masterCopy
(L#14). Giá trị này được thiết lập một lần khi hợp đồng được tạo (xem Constructor
của hợp đồng, L#18). Giá trị ban đầu của giá trị này là hợp đồng chính 0x34Cf…3F5F. function()
(L#26) sẽ gửi tất cả dữ liệu được người dùng truyền đến hợp đồng chính thông qua delegatecall
(L#39) trong khối assembly.
LƯU Ý: Người đọc quen thuộc với Solidity có thể không quen với function()
, nhưng đây thực chất là fallback
của Solidity phiên bản 0.5.0.
Tiếp theo, chúng ta hãy xem xét việc triển khai hợp đồng chính execTransaction
. Hàm này truyền vào nhiều tham số, nhưng những tham số quan trọng nhất là:
-
to
: Địa chỉ hợp đồng mà chữ ký đa năng muốn tương tác với -
value
: số lượng ETH cần chuyển -
data
:data
được gửi khi gọito
địa chỉ -
operation
: các chế độ hoạt động khác nhau, hỗ trợcall
vàdelegatecall
trongSafe
-
signature
: Chữ ký sau mỗi lầnSigner
Sau đây là mô tả ngắn gọn về quy trình vận hành:
- L#25–32 Khôi phục Bản tóm tắt được Người ký ký thông qua
encodeTransactionData
vàkeccak256
- L#33 Kiểm tra xem Chữ ký có được Người ký nhiều chữ ký ký hay không và xác nhận xem nó có vượt quá Threshold hay không
- L#40 Dữ liệu thực sự thực hiện giao dịch, lớp cơ bản sử dụng lệnh gọi cấp thấp hoặc lệnh gọi đại biểu
Nếu Operation là 0, logic của chương trình sẽ gần giống như sau:
( bool success, ) = to.call{gas: gas}(dữ liệu); require (thành công, "thực hiện giao dịch không thành công" );
Giao dịch tấn công
Vậy mối liên hệ giữa cuộc tấn công này và những điều trên là gì? Theo CEO Bybit , khi họ ký đề xuất ví đa chữ ký, họ hy vọng sẽ chuyển một số tài sản được lưu trữ trong ví lạnh đa chữ ký sang ví nóng. Ban đầu, đây chỉ là một giao dịch chuyển tài sản thông thường. Trong quá trình ký, họ đã kiểm tra xem trang web ví an Safe có chính xác không và địa chỉ chuyển tiền cũng như các thông tin khác có chính xác không. Nói cách khác, họ đã xem xét toàn diện đề xuất này trước khi ký và thực hiện.
Tuy nhiên, khi thực sự kiểm tra Giao dịch đã thực hiện, bạn sẽ thấy một số điều kỳ lạ. Sau đây là một đoạn trích các tham số khi Giao dịch này được thực hiện. Bạn có thể thấy giá trị là 0, nhưng dữ liệu không phải là giá trị null. Theo thảo luận ở trên, giá trị phải là giá trị số được dùng để xác định số lượng ETH được chuyển, nhưng ở đây là 0. Việc chuyển ETH thông thường không yêu cầu bất kỳ dữ liệu nào.
Còn nếu không phải là chuyển ETH mà là chuyển ERC20 như WETH hoặc stETH thì sao? Điều này có ý nghĩa khi giá trị là 0 và dữ liệu không phải là null. Bằng cách phân tích bộ chọn hàm được biểu diễn bởi 4 byte dữ liệu đầu tiên, chúng ta có thể sử dụng thông tin này để khôi phục chữ ký hàm và cố gắng hiểu dữ liệu muốn thực hiện thao tác gì.

Bằng cách thả 0xa9059cbb
vào cơ sở dữ liệu chữ ký hàm , bạn có thể tìm thấy thông tin sau. Hóa ra ID bộ chọn hàm 145 là hoạt động chuyển giao của ERC-20.

Và phần dữ liệu còn lại được giải mã. Các tham số giống như ERC-20 Transfer, bao gồm to (địa chỉ) và amount (uint256). Vì vậy, sự ngụy trang ở đây rất tốt và nếu bạn không kiểm tra dữ liệu chính xác, bạn có thể bị bộ chọn hàm đánh lừa.

Nhưng điều kỳ lạ ở đây là nếu chúng ta chỉ muốn chuyển ETH/ERC-20, chúng ta có thể chỉ cần sử dụng lệnh call (Operation = 0) để thực hiện, nhưng giá trị Operation ở đây là 1 (xem Hình 3), điều đó có nghĩa là lệnh delegatecall đang được sử dụng. Dựa trên hiểu biết trước đây của chúng ta về Mẫu Proxy, hoạt động này tương đương với hợp đồng Proxy sử dụng logic của hợp đồng (0x9622…C7242) để sửa đổi trạng thái của chính Proxy.
Bằng cách quan sát hợp đồng 0x9622…C7242 thông qua các công cụ kỹ thuật đảo ngược như Dedaub, chúng ta có thể thấy rằng hợp đồng này có một hoạt động chuyển giao (L#15) nhưng nó không liên quan gì đến Chuyển giao ERC-20. Nó chỉ gán tham số người nhận đến cho biến _transfer (L#07).

Để thuận tiện, chúng ta gọi địa chỉ 0x9622…C7242 tại thời điểm này là Hợp đồng độc hại. Vậy tại sao chúng ta cần chỉ định giá trị của _transfer
làm tham số recipient
ở đây? Nếu chúng ta triển khai toàn bộ logic hoạt động, quá trình sẽ như sau:
- Sau khi Người ký ký, cần thực hiện thao tác. Lúc này, Safe sẽ gọi execTransaction đến hợp đồng Proxy
- Hoạt động này sẽ được truyền đến hợp đồng chính của Safe thông qua Delegatecall để thực hiện logic của execTransaction
- Trong logic của execTransaction, địa chỉ đến được gọi bằng Delegatecall để kích hoạt hoạt động chuyển giao, hoạt động này sẽ sửa đổi giá trị của _transfer.
Sau khi đơn giản hóa chuỗi Delegatecall này, hoạt động hiện tại là Proxy sử dụng logic của Hợp đồng độc hại để sửa đổi trạng thái của chính Proxy. Vào thời điểm này, biến _transfer được sửa đổi trong Malicious Contract nằm ở Slot 0 của Storage Layout, do đó thay đổi tương ứng là ở Slot 0 của Proxy Contract Storage Layout. Theo Hình 3, giá trị này là giá trị của MasterCopy.

Biểu đồ luồng ở trên thực sự có thể phản ánh luồng lệnh được trình bày trong Phalcon.

Khi MasterCopy được sửa đổi, khi Signer gọi hợp đồng Proxy trong tương lai, đối tượng thực thi Delegatecall sẽ không còn là hợp đồng chính của Safe nữa mà là địa chỉ 0xbDd0…9516 được đặt sau đó, đây là địa chỉ của người nhận đến.

Tiếp tục đảo ngược hợp đồng 0xbDd0…9516, chúng ta có thể thấy hai hoạt động, một trong số đó là hàm sweepETH. Trong L#18, thực hiện lệnh gọi để chuyển ETH đến địa chỉ của người nhận.

Đối với SweepERC20, có thể thấy trong L#35 rằng Chuyển tiền ERC-20 đang được thực hiện tại đây, chuyển tất cả Số dư trong hợp đồng cùng một lúc.

Do đó, sau khi Bybit vô tình cập nhật địa chỉ của MasterCopy trong execTransaction, các hoạt động sweepERC20 và sweepETH tiếp theo được thực hiện theo cách này. Các giao dịch này là các hoạt động thực sự chuyển giao tài sản.

Ký tên mù
Vậy, nguyên nhân gốc rễ của toàn bộ sự cố là do Bybit không kiểm tra giá trị Safe khi ký ví đa chữ ký? Nếu giá trị hoạt động và thông tin địa chỉ quan trọng khác trong các tham số được xác minh tại thời điểm đó, liệu có thể xác định sớm các hoạt động bất thường và ngăn chặn cuộc tấn công xảy ra không?
Vẫn còn nhiều nghi vấn trong trường hợp này. Trước hết, ai là người khởi xướng Đề án đa chữ ký này? Những Người ký khác đã kiểm tra cẩn thận nội dung Đề án, bao gồm thông tin quan trọng như URL của giao diện Safe chưa? Có khả năng private key của Người ký đã bị rò rỉ, khiến toàn bộ hệ thống phải đối mặt với rủi ro bảo mật nghiêm trọng không?
Theo Bybit , nhà đồng sáng lập kiêm giám đốc điều hành Ben đã kiểm tra trang web của Safe và thông tin tham số thực tế trong quá trình ký kết. Tuy nhiên, một chuỗi ký tự bị bóp méo đã được hiển thị khi ví lạnh được ký. Điều này có nghĩa là trang web front-end của Safe đã bị tin tặc can thiệp không? Hiện tại, chính thức Safe đã tạm thời đóng dịch vụ phía trước để làm rõ thêm sự việc.
Một khả năng khác là máy tính của Bybit đã bị hack, khiến màn hình hiển thị không phải là giao diện Safe chính thức mà là một trang được tin tặc ngụy tạo một cách cẩn thận. Tình huống này không phải là không thể. Ngay từ ngày 16 tháng 10 năm 2024, trong sự kiện Radiant Capital , đã xảy ra một phương pháp tấn công tương tự: Radiant Post-Mortem .
Radiant cũng sử dụng ví đa chữ ký của Safe . Vào thời điểm đó, ba kỹ sư cao cấp đã ký thông qua ví lạnh phần cứng và sử dụng Tenderly để mô phỏng kết quả giao dịch trước khi ký và kiểm tra chúng theo SOP nội bộ nghiêm ngặt. Tuy nhiên, trong quá trình ký và thực thi, MetaMask hiển thị thông báo lỗi và yêu cầu ký lại.
Tình huống này không phải là hiếm. Trong quá trình thực hiện giao dịch, lỗi do các vấn đề liên quan đến Nonce hoặc Gas thường yêu cầu thực hiện lại. Tuy nhiên, vì các hoạt động như vậy quá phổ biến nên khi ví hiện lên yêu cầu ký lại, kỹ sư đã không xác minh lại nội dung cụ thể của giao dịch và cuối cùng đã rơi vào bẫy của tin tặc.
Vậy chúng ta nên ngăn ngừa nó như thế nào? Hay những kiểu tấn công này khó có thể phòng thủ được?
Trên thực tế, trong những vụ việc này, tin tặc Triều Tiên chủ yếu vẫn khai thác điểm yếu của bản chất con người. Chúng ta có thể vô thức cho rằng mình đã rất thành thạo trong các hoạt động này, vì vậy chúng ta dễ rơi vào bẫy.
Để giảm thiểu rủi ro, chúng ta có thể cô lập hoàn cảnh hoạt động của ví đa chữ ký khỏi các thiết bị khác và đảm bảo Payload được xác minh cẩn thận lần khi được ký. Nếu phát hiện bất kỳ bất thường nào trong thông tin giữa Safe và Ledger, hoạt động phải được dừng ngay lập tức và private key phải được lưu trữ an toàn.
Việc thiết lập một quy trình SOP nhất định và tuân thủ nghiêm ngặt lần để đảm bảo mỗi giai đoạn đều tuân thủ đúng quy định có thể ngăn ngừa hiệu quả việc xảy ra những sự cố này.
Phần kết luận
Mặc dù Web3 đã bị lu mờ bởi sự cố Bybit và ấn tượng về bảo mật không đủ và rủi ro cao dường như vẫn còn dai dẳng, tôi tin rằng đằng sau sự cố thường xuyên xảy ra này, mọi người sẽ chú ý nhiều hơn đến Bảo mật hoạt động và việc giáo dục và hiểu biết về quản lý private key và chữ ký sẽ được đào sâu hơn nữa.
Nhìn lại, có những vụ tấn công thường xuyên xảy ra trong khoảng thời gian từ năm 2021 đến năm 2023, và các lỗ hổng hợp đồng thông minh với hàng triệu tổn thất sẽ xuất hiện trong vài tuần. Tuy nhiên, điều này cũng khiến ngành công nghiệp dần chú ý đến tầm quan trọng của việc phát triển và kiểm toán an toàn, và hệ thống Bug Bounty trở nên rõ ràng hơn. Sau khi triển khai thực tế, hệ thống giám sát đã trở nên hoàn thiện hơn.

Do đó, ở phần cuối bài viết, chúng ta có thể ứng xử sự phát triển trong tương lai của ngành với thái độ tương đối lạc quan. Những sự cố có vẻ đơn giản nhưng gây ra tổn thất này sẽ dần giảm khi ngành công nghiệp phát triển và tổn thất cũng sẽ giảm theo.
Bài viết này được viết bởi một nhà nghiên cứu bảo mật blockchain tại BSOS Labs. Tác giả có bằng thạc sĩ khoa học máy tính của Đại học Quốc gia Đài Loan. Ông đã tham gia nghiên cứu bảo mật và đóng góp tại DeFiHackLabs và phát triển hợp đồng thông minh tại Ocean Finance. Ông cũng là giảng viên bootcamp trong lĩnh vực bảo mật blockchain.