Tháng trước, một quỹ nhỏ ở Geneva gửi tôi log giao dịch. Người dùng nạp 1.200 USDT qua XRP Ledger, giao dịch hiện thành công, nhưng số dư chỉ tăng 987. Team vận hành nói không có lỗi. Team kỹ thuật nói giao dịch thành công là đúng. Người dùng thì khẳng định bị mất tiền. Tôi mở metadata và thấy dòng đầu tiên: delivered_amount = 987000000, cùng một flag tfPartialPayment đang bật trong Flags. Giao dịch này không hề sai. Nó là một Partial Payment – một tính năng chính thức của XRP Ledger. Nhưng với hệ thống kế toán của quỹ đó, nó vừa tạo ra một khoản chênh lệch 213 USDT ngay trước mặt họ.
Partial Payment nằm trong transaction type Payment của XRPL. Kể từ năm 2012, giao thức cho phép một payment được đánh dấu để chấp nhận thiếu hụt. Thay vì bắt buộc số tiền khai báo trong Amount phải được giao đủ, flag tfPartialPayment cho phép giao dịch hoàn thành với số tiền thực nhận nhỏ hơn. Tôi từng nghĩ đây là lỗi khi mới đọc tài liệu. Nhưng sau khi xem thiết kế path payment, tôi hiểu nó là một tính năng.
Trong path payment, XRPL chuyển đổi qua nhiều bước: XRP sang USD sang EUR. Nếu thanh khoản ở một bước không đủ, giao dịch sẽ fail toàn bộ – trừ khi bật partial payment. Lúc đó giao dịch sẽ giao phần còn lại và thành công. Với một mạng lưới thanh toán, đây là một lựa chọn hợp lý: một giao dịch đúng một phần vẫn tốt hơn một giao dịch không giao được gì.
Để tránh nhầm lẫn, giao thức trả về delivered_amount trong metadata – con số được validator xác nhận, phản ánh số tiền thực đến tay người nhận. Giao dịch thường có delivered_amount bằng Amount. Giao dịch partial có hai giá trị lệch nhau. Chính sự lệch này là nơi bắt đầu của rắc rối.
Vấn đề không nằm trong giao thức. Nó nằm ở cách các sàn giao dịch và payment gateway đọc dữ liệu. Khi một hệ thống tích hợp XRPL để nhận nạp tiền, có ba điểm phổ biến mà tôi thấy trong audit: đọc Amount trong transaction payload để ghi có; bỏ qua delivered_amount từ metadata; dùng Amount để hiển thị lịch sử giao dịch cho người dùng. Mỗi điểm đều có thể biến một tính năng hợp lệ thành một lỗ hổng.
Kịch bản tấn công rất đơn giản. Kẻ tấn công tạo một payment với Amount bằng 10.000 XRP, nhưng thực tế chỉ chuyển 1 XRP, đồng thời bật flag partial payment. Giao dịch được xác nhận. Nếu hệ thống của nạn nhân đọc Amount và ghi có 10.000, kẻ tấn công vừa tạo ra một khoản tín dụng 9.999 XRP từ một giao dịch có giá trị 1 XRP. Không cần hack mạng, không cần khai thác lỗi consensus. Chỉ cần một webhook đọc sai trường.
Không chỉ sàn giao dịch. Payment gateway trong lĩnh vực thương mại điện tử cũng là nạn nhân tiềm năng. Một cổng thanh toán XRPL thường cung cấp webhook cho merchant. Nếu webhook payload chỉ bao gồm Amount mà thiếu delivered_amount, merchant sẽ giao hàng dựa trên con số sai. Khi merchant kiểm tra số dư thực tế, hàng đã giao, tiền thì không đủ. Loại rủi ro này không cần hacker tinh vi; nó chỉ cần một lập trình viên mệt mỏi và một tài liệu tích hợp dài 300 trang.

Tôi đã gặp phiên bản khác của cùng một vấn đề nhiều lần. Năm 2017, tôi audit một ICO tên ShibeCoin và phát hiện hàm batchTransfer không xác thực đầu vào – nó cho phép đúc token giả. Năm 2020, tôi sửa lỗi trong Uniswap v2 Router khi amountOutMin không được kiểm tra đúng cách ở tầng giao dịch. Năm 2022, tôi tìm ra lỗi trong Arbitrum Nitro bridge khi validateProof không kiểm tra seqNum tăng dần.
Điểm chung của tất cả: không phải giao thức sai, mà là hệ thống tin vào một giá trị không được xác thực tại điểm nhận. Với XRPL, giá trị đó chính là Amount. amount chỉ là một lời hứa, delivered_amount mới là sự thật.
Cần nói rõ: XRPL có cơ chế chống lại sự lạm dụng này. delivered_amount được xác nhận bởi validator và nằm trong metadata. Nhưng không phải SDK nào cũng ép lập trình viên dùng nó. Một số API cũ trả về trường Amount trong transaction, còn delivered_amount nằm sâu trong metadata; dev không đọc tài liệu kỹ sẽ dễ dùng nhầm. Tôi đã thấy các dự án có một năm kinh nghiệm mà vẫn mắc lỗi này.
Khi một giao thức cho phép “ít hơn”, hệ thống của bạn bắt buộc phải hỏi “bao nhiêu?” trước khi ghi sổ. Trong thị trường tăng hiện tại, khi mọi người đang FOMO tích hợp nhanh nhất có thể, lớp kiểm tra này thường bị bỏ qua.

Cộng đồng XRP trả lời “not a bug” về phía giao thức. Về mặt kỹ thuật, tôi đồng ý. Nhưng “not a bug” là một câu trả lời nguy hiểm nếu nó khiến đội ngũ tích hợp ngừng đặt câu hỏi. Hợp lệ ở tầng giao thức không đồng nghĩa với vô hại ở tầng ứng dụng. Một tính năng giao thức có thể tạo ra thiệt hại tài chính nghiêm trọng khi được tích hợp mà thiếu kiểm tra.

Chúng ta đã thấy điều tương tự trong hệ sinh thái Ethereum. EVM luôn chuyển token chính xác, nhưng các hợp đồng clone giả mạo vẫn lừa được hệ thống đọc event log không đúng nguồn. Bug không nằm trong EVM, nằm trong niềm tin mù quáng của người lập trình. Một giao dịch thành công không có nghĩa là một giao dịch chính xác – câu này đúng cho cả Ethereum lẫn XRPL.
Nếu muốn hiểu tại sao chủ đề này quay lại, hãy nhìn lịch sử: đã có những báo cáo bảo mật về việc lợi dụng partial payment để đánh lừa hệ thống tự động. Những bài viết “not a bug” ra đời để dập FUD. Nhưng dập FUD không thể thay thế cho việc dạy tích hợp an toàn.
Câu chuyện này còn tệ hơn vì XRP Ledger đã lớn tuổi. Những tính năng cũ thường bị coi là hiển nhiên, và các nhà phát triển mới không biết chúng tồn tại. Sự thiếu hiểu biết đó tạo ra một lớp tấn công mới: tấn công bằng tính năng hợp lệ, không phải bằng lỗi.
Tôi dự đoán trong vòng 12 tháng tới, sẽ có ít nhất một sàn giao dịch hoặc payment gateway lớn bị khai thác theo kiểu này. Không phải vì XRPL yếu. Mà vì tốc độ tích hợp trong thị trường tăng đang vượt xa tốc độ kiểm soát chất lượng. Bạn có thể chứng minh tôi sai: viết một integration test với một giao dịch partial payment trên testnet, và xem hệ thống của bạn ghi số dư như thế nào. Khi nào một tính năng hợp lệ trở thành lỗ hổng? Khi bạn đối xử với nó như không tồn tại.