Skip to main content
CWE-200 Date: 2026-16-09
Severity: Critical (CVSS v3.1 = 9.8) (Full database Permission)
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Finder: M. Enes Aydın https://www.cve.org/CVERecord?id=CVE-2026-92555

TCP/3055 Key → Cipher → Packet Format → Login State Machine

Bu araştırmaya başlarken elimde doğrudan bir credential disclosure bulgusu yoktu. Önce werp.exe ile kontrolpanel.exe arasındaki TCP/3055 trafiğini anlamaya çalıştım. Paketlerin nasıl kodlandığını, login isteğinin hangi alanlardan oluştuğunu ve sunucunun authentication kararını nerede verdiğini çıkardıkça asıl hata görünür hale geldi. Sorun, Kontrol Panel’in kullanıcı doğrulamasını hiç yapmaması değildi. Tam tersine geçersiz kullanıcı adı ve parola doğru şekilde reddediliyordu. Hata, ret kararından sonra başlıyordu. Geçersiz login için SERVER_ERROR hazırlanmasına rağmen akış durmuyor, ortak response builder’a devam ediyor ve sunucunun kendi veritabanı bağlantısında kullandığı SERVER_USERNAME ile SERVER_PASSWORD aynı yanıta ekleniyordu. Yani istemci bir taraftan:
cevabını alırken aynı response içinde aktif veritabanı yönetici kimlik bilgilerini de alabiliyordu. Bu davranışı iki ayrı yetkili ortamda doğruladım:
Birincil sınıflandırma:
Destekleyici sınıflandırmalar:

Test edilen bileşenler

Yerel analiz ortamında: kontrolpanel.exe SHA-256:
İki dosya da native Delphi/Win32 PE. Vendor source code veya PDB olmadığı için analiz string referansları, xref, disassembly ve runtime doğrulama üzerinden ilerledi. İkinci doğrulama ortamında binary alınmadı; çalışan servis kendi yanıtında:
değerlerini bildirdi. Bu nedenle burada yalnızca gerçekten test edilen 26.02.25 binary’si ve s26.02.19 bildiren kurulum hakkında konuşuyorum. Aradaki veya daha eski sürümlerin tamamını etkilendi diye genellemiyorum.

1. Önce hangi binary kiminle konuşuyor onu sabitledim

TCP/3055’i dinleyen taraf:
ERP login sırasında bu porta bağlanan istemci:
Kontrol Panel’in arka tarafta kullandığı database ise kuruluma göre değişebiliyor:
Bu ayrım önemliydi. Çünkü daha sonra protokol içinde gördüğüm SERVER_USERNAME ve SERVER_PASSWORD alanlarının ERP hesabına mı, database hesabına mı ait olduğunu ayrıca kanıtlamam gerekecekti. İlk hedefim şu hattı çıkarmaktı:

2. Protokol anahtarını nasıl buldum?

PoC’de kullanılan şu değer Firebird parolası veya database key’i değil:
Bu, werp.exe ile kontrolpanel.exe arasındaki TCP/3055 mesajlarını dönüştüren sabit Blowfish protokol anahtarı. Her iki binary üzerinde ASCII ve UTF-16LE string taraması yaptım. Özellikle iki tarafta ortak bulunan GUID biçimli sabitleri ayırdım. Aynı değer şu konumlarda bulundu: Anahtar byte dizisi:
Süslü parantezler de değerin parçası; toplam 38 ASCII byte. Burada özellikle bir noktayı ayırmak lazım:
Aynı GUID’nin iki binary’de bulunması, tek başına bunun crypto key olduğunu kanıtlamaz.
Bu aşamada yalnızca güçlü bir aday vardı.

3. ECB sonucuna nasıl gittim?

TCP/3055 wire verisi Base64 görünümlü tek satırlardan oluşuyordu:
Base64 katmanı açıldığında ciphertext uzunlukları sekiz byte’ın katıydı. Bu Blowfish’in 64-bit block size’ıyla uyumluydu ama tek başına yine kanıt değildi. Paketlerde ayrıca şunları göremiyordum:
Aday GUID byte’larını key olarak kullanıp Blowfish ECB denedim. Ardından PKCS#7 padding’i kaldırıp çıktıyı Windows Turkish code page olan cp1254 ile açtım. Ortaya rastgele byte’lar yerine doğrudan protokol alanları çıktı:
Server response tarafında da:
alanları görünüyordu. Benim için somut kanıt üç parçanın birlikte oturmasıydı:
  1. Aynı GUID hem werp.exe hem kontrolpanel.exe içinde bulunuyordu.
  2. Blowfish ECB + PKCS#7 ile çözünce anlamlı protokol alanları çıkıyordu.
  3. Aynı yöntemle yeniden şifrelediğim paket Kontrol Panel tarafından kabul ediliyordu.
Yani “sekiz byte block gördüm, ECB dedim” şeklinde bir çıkarım yapmadım. Sekiz byte yalnızca ilk ipucuydu.

4. Paket formatı

Gönderim sırası:
Alım bunun tersi:
Gönderim tarafının minimal hali:
Çözme tarafı:

5. Neden cp1254?

Burada encoding’i de tahmin ederek bırakmadım. Server’ın Türkçe authentication error mesajı iyi bir doğrulama noktasıydı:
Bu byte dizisi cp1254 ile doğru çözülüyordu. ASCII zaten Türkçe karakterleri temsil edemiyordu. UTF-8 ile aynı byte dizisi düzgün çıkmıyordu. Ayrıca cp1254 kullanarak yeniden ürettiğim paket Kontrol Panel tarafından kabul edildi. Bu nedenle wire plaintext encoding’i için cp1254 sonucuna gittim.

6. Basit protocol sanity check

Zararsız bir PING. mesajıyla codec’i ayrıca test ettim. Amaç vulnerability tetiklemek değil, sadece şu hattı doğrulamaktı:
Aynı yöntemle oluşturulan paket server tarafından kabul edildi. Buradan sonra login packet’ını bağımsız olarak yeniden üretebilecek durumdaydım.

7. ERP login paketini yeniden oluşturmak

ERP login sırasında kullanılan alanları çıkardım:
Burada geçerli bir ERP hesabıyla test yapmak istemedim. Her çalışmada yeni, var olmayan bir username kullandım:
Beklenen davranış çok basit:
İlk runtime testte authentication gerçekten başarısız oldu. Ama aynı response’u çözdüğümde:
alanları birlikte geldi. Bu noktada araştırmanın yönü değişti.

8. Login state machine’i çıkarmak

TCP handler yaklaşık:
civarında başlıyor. Özet state machine: USERNAME alanı tamamen yoksa credential yolu çalışmıyor. Kontrol:
USERNAME bulunmazsa:
adresindeki pre-auth command dispatcher’a geçiliyor. Bu önemli çünkü vulnerability’nin “USERNAME alanı yok” branch’inde olmadığını gösteriyor. Leak için biçimsel olarak login yoluna giren bir USERNAME gerekiyor; ancak bunun geçerli kullanıcı olması gerekmiyor.

9. PERSONEL lookup ve failure branch

Login credential yolu:
Kullanıcı lookup tarafı:
Sorgu mantığı:
Var olmayan username için kayıt dönmüyor. İlgili conditional branch:
buradan authentication error yoluna gidiyor:
IDA’da bu path’i takip ettiğimde akış burada sonlanmıyordu. Devam:
ve sonunda ortak response builder’a ulaşıyordu. Bu, bug’ın merkezindeki control-flow hatası.

10. Breakpoint ile aynı request içinde doğrulama

Static path’i canlıda görmek için şu üç nokta yeterliydi:
Geçersiz login gönderildiğinde üç breakpoint’in de aynı request sırasında tetiklenmesi şu sırayı doğruluyor:
Yani credential alanları başka, başarılı bir session’ın kalıntısı veya bağımsız bir packet değil. Aynı failure response’un parçası.

11. SERVER_USERNAME ve SERVER_PASSWORD nereden geliyor?

Ortak response builder içinde global configuration object:
üzerinden okunuyor. Database username:
Database password:
Özet:
Authentication error ise daha sonra ekleniyor:
Response daha sonra yaklaşık:
üzerinden istemciye gidiyor.

12. [eax+0x3C] değerinin gerçekten DB parolası olduğunu nasıl doğruladım?

Burada string yakınlığına güvenmedim. Şu tek başına yeterli olmazdı:
Bunun aktif database password olduğunu ayrıca runtime’da doğruladım. Aynı object içindeki:
değeri SERVER_USERNAME olarak geliyor. Yanındaki:
değeri SERVER_PASSWORD olarak geliyor. Daha sonra bu ikili gerçek database authentication’ında başarılı oldu. Bu noktadan sonra [eax+0x38] / [eax+0x3C] çiftinin yalnızca isim benzerliğinden değil, producer → response → consumer davranışından database credential olduğunu söyleyebildim.

13. Yerel Firebird doğrulaması

Yerel ortamda response:
şeklindeydi. Dönen password’ü rapor dosyalarına yazmadan yalnız process memory içinde tuttum. Ardından yalnız şu sorguyu çalıştırdım:
Sonuç:
Bu testin amacı privilege abuse yapmak değildi. Tek sorum şuydu:
SERVER_PASSWORD alanındaki değer gerçekten aktif database credential mı?
Cevap evetti.

14. İkinci ortam: SQL Server

Ayrı bir yetkili VDS ortamında aynı akışı tekrar test ettim. Server response:
bildiriyordu. Geçersiz login response’unda:
alanları bulundu. Dönen credential ile TCP/1433 üzerinde yalnız salt-okunur kimlik/rol kontrolü yaptım:
Sonuç:
Böylece aynı disclosure iki farklı database backend üzerinde doğrulandı:
Veri değiştiren veya silen bir SQL komutu çalıştırılmadı.

15. Vulnerability’nin gerçek state transition’ı

Özet tablo: Olması gereken:
Mevcut davranış:
Root cause’u en kısa haliyle böyle tarif ediyorum:
Authentication failure state’i, response serialization sırasında hassas database configuration alanlarının eklenmesini engellemiyor.

16. Sabit Blowfish key neden tek başına vulnerability değil?

Static protocol key kötü bir tasarım. Ancak bu bulgunun root cause’u o değil. Server hiçbir durumda başarısız login response’una database administrator password koymamalı. Transport kusursuz TLS ile korunsaydı bile uygulama logic’i yine yanlış olurdu. Sabit key burada yalnızca bağımsız bir client’ın:
kolaylaştırıyor. Yani:

17. Key’in bir byte’ını değiştirirsem ne olur?

Bu da protokolü anlamak için kullandığım sanity check’lerden biriydi. Key’in bir byte’ı yanlışsa Base64 katmanı hâlâ tamamen geçerli görünebilir. Fakat server aynı key ile decrypt etmediği için plaintext bozulur. İlk güvenilir üst seviye belirti çoğu durumda:
adresindeki USERNAME aramasının başarısız olmasıdır. Padding de bozulduysa işlem daha erken kesilebilir. Protokolde MAC/authentication tag olmadığı için yanlış key için tek ve garantili bir “burada hata verir” noktası yok.

18. Düşük riskli patch ile root cause’u nasıl izole ederdim?

Binary seviyesinde hızlı bir doğrulama yapmak gerekseydi en küçük değişikliklerden biri password value’yu serialization noktasında boşaltmak olurdu. Örneğin:
Bu değişiklik stack düzenini korurken SERVER_PASSWORD= değerini boş bırakır. Bu bir production fix önerisi değil; yalnız binary seviyesinde:
ilişkisini izole etmek için kullanılabilecek düşük riskli bir test. Kalıcı düzeltmede password alanını response schema’dan tamamen kaldırmak gerekir.

19. Etki

Doğrudan doğruladığım şey:
public ortam:
Bu nedenle bulgu yalnızca config string disclosure seviyesinde kalmıyor. Dönen bilgiler gerçek, aktif administrator credential’ları. CVSS v3.1:
Score:
I:H ve A:H seçimleri test sırasında veri silindiği anlamına gelmiyor. Bunlar başarılı şekilde doğrulanan SYSDBA ve sysadmin otoritelerinin yetki kapsamına dayanıyor.

20. Remediation

En doğru çözüm, database administrator credential’ını TCP/3055 client response’undan tamamen çıkarmak. Başarısız login:
ile bitmeli. Şu alanlar authentication failure response’una hiçbir koşulda girmemeli:
Daha da önemlisi, başarılı login durumunda bile ERP client’a tam database administrator password göndermek tasarım olarak yeniden değerlendirilmelidir. Uygulamanın database adına yapması gereken işlemler, mümkünse authenticated ve authorized bir server-side API üzerinden yürütülmeli. Ayrıca:
  • sa ve SYSDBA yerine least-privilege service account kullanılmalı,
  • açığa çıkmış olabilecek DB password’leri rotate edilmeli,
  • TCP/3055 yalnız gerekli client/network segmentlerine sınırlandırılmalı,
  • TCP/3050 ve TCP/1433 güvenilmeyen ağlara açılmamalı,
  • custom static-key transport yerine authenticated modern transport kullanılmalı,
  • invalid username ve wrong-password response’ları regression test’e eklenmeli.

Sonuç

Bu araştırmanın en önemli kısmı Blowfish key’i bulmak değildi. Asıl kritik nokta login state machine’i sonuna kadar takip etmekti. İlk bakışta auth kontrolü vardı ve geçersiz kullanıcı doğru şekilde reddediliyordu. Bu yüzden credential disclosure ihtimali ilk aşamada kolayca false positive sanılabilirdi. Fakat control flow’u hata bloğundan sonra takip edince gerçek sorun ortaya çıktı:
Runtime doğrulama ise response’taki değerlerin gerçekten aktif administrator credential olduğunu kapattı:
Bu noktadan sonra bulgu artık “şüpheli response field” değil, baştan sona doğrulanmış bir unauthenticated database credential disclosure zinciriydi.

Teknik özet