June 9, 2026
İlk CVE’lerim: Traccar’da iki küçük varsayımın peşinden gitmek
Claude Code, SAST skills, biraz sabır ve Traccar’da bulunan iki güvenlik açığı
Emre Koca
7 min read
Birkaç saniye sadece ekrana baktığım bir an oldu.
Traccar advisory sayfası açıktı. Başlık oradaydı, CVE ID oradaydı, ismim oradaydı. Güvenlik araştırmasında çoğu iş sessiz ilerliyor: kod okuyorsun, lab kuruyorsun, aynı endpoint'e tekrar bakıyorsun, yarım çalışan hipotezleri siliyorsun. Sonra bir noktada o notlar public bir GitHub Security Advisory'ye dönüşüyor.
Bu yazı o sürecin kısa hikayesi. Traccar üzerinde iki açık raporladım:
**- CVE-2026–52851 / GHSA-jx5h-fxhc-cc9w: DELETE /api/permissions içinde authenticated blind SQL injection
- CVE-2026–52852 / GHSA-6qh3–234v-r254: group parent cycle üzerinden infinite loop DoS**
İkisi de Traccar <= 6.13.3 üzerinde, kendi local lab ortamımda doğrulandı. Üçüncü parti veya live Traccar instance'larında test yapmadım.
Not: Teknik detaylar GitHub Security Advisory sayfalarında public olduğu için burada root cause seviyesinde anlatıyorum. Yine de yazıyı copy-paste exploit rehberi gibi değil, source review ve doğrulama süreci olarak tutmaya çalıştım.
Neden Traccar?
Traccar açık kaynaklı bir GPS tracking server. Self-host edilebiliyor ve doğal olarak kullanıcılar, cihazlar, gruplar, raporlar ve permission ilişkileri gibi güvenlik açısından ilginç birçok yüzey barındırıyor.
Ben source review yaparken özellikle şu bölgelere bakmayı seviyorum:
- permission ve access-control akışları
- generic storage/helper kodları
- JSON'dan modele, modelden SQL'e giden dönüşümler
- report endpoint'leri
- tree gibi tasarlanmış ama API ile graph'a dönüşebilen veri modelleri
Traccar'daki iki bulgu da tam bu bölgelerden çıktı.
AI destekli source review akışı
Bu araştırmayı tamamen manuel yapmadım. Ama "AI buldu, ben sadece yayınladım" gibi bir hikaye de değil.
Benim kullandığım akış daha pratikti: Claude Code'u bir source review partneri gibi çalıştırdım. Aday yüzeyleri çıkarttırdım, trust boundary'leri takip ettirdim, sonra her iddiayı local lab'da tek tek doğruladım.
Bu süreçte özellikle Utku Şen'in açık kaynak sast-skills projesinden faydalandım. Proje, LLM tabanlı coding assistant'ları SAST scanner gibi çalıştırmaya yarayan agent skill koleksiyonu sunuyor. Claude Code, Codex, OpenCode ve Cursor gibi agent skill destekleyen araçlarla kullanılabiliyor.
Claude Code benim için özellikle şu işleri hızlandırdı:
- Traccar kod tabanındaki entry point'leri ve trust boundary'leri haritalamak
- permission akışlarını dosya dosya takip etmek
- SQLi, missing auth, SSRF, business logic ve DoS gibi aday bug sınıfları için şüpheli yerleri ayırmak
- root cause notlarını ve rapor iskeletini düzenlemek
Ama kritik çizgi şuydu: AI çıktısını hiçbir zaman doğrudan bulgu kabul etmedim.
Her aday için local Traccar instance kuruldu. Request akışı oluşturuldu. Positive ve negative control denendi. SQLi tarafında okunan veriler H2 ground truth ile karşılaştırıldı. DoS tarafında jstack ile worker thread'in gerçekten AttributeUtil.lookup içinde takılı kaldığı doğrulandı.
Bence AI destekli güvenlik araştırmasında sağlıklı nokta burası. Model iyi bir hızlandırıcı olabilir, ama karar verici olmamalı. Candidate üretir, kodu takip etmene yardım eder, raporu toparlar. Exploitability kanıtı hâlâ lab'da, gerçek çıktı ve ölçülebilir evidence ile gelmeli.
Bug 1: Prepared statement vardı, SQL injection yine vardı
İlk açık DELETE /api/permissions endpoint'indeydi.
SQL injection deyince çoğu kişinin aklına önce value concatenation gelir. Kullanıcıdan gelen id doğrudan query string'e eklenmiştir, klasik hikaye. Burada durum biraz daha sinsi.
Traccar value'ları parameterized şekilde bağlıyordu. İlk bakışta "tamam, burada SQLi yok" diyebilirsin.
Problem value tarafında değildi. Problem JSON key tarafındaydı.
Akış kabaca şöyleydi:
- Request body bir JSON object olarak geliyor.
Permissionmodeli ilk iki key'i yorumlayıp permission ilişkisinin owner/property tiplerini çıkarıyor.- Ekstra key'ler reddedilmiyor.
- Storage katmanı daha sonra map içindeki tüm key'leri SQL
WHEREclause içinde column identifier gibi kullanıyor.
Yani değerler güvenli şekilde bind edilirken, key'ler trusted kabul ediliyordu.
İlgili mantık basitleştirilmiş haliyle şöyle:
query.append(entries.stream()
.map(e -> e.getKey() + " = ?")
.collect(Collectors.joining(" AND ")));query.append(entries.stream()
.map(e -> e.getKey() + " = ?")
.collect(Collectors.joining(" AND ")));Burada e.getKey() request'ten gelen JSON key olabiliyor. Eğer üçüncü bir key gönderirsen, ilk iki key permission kontrolünden geçiyor ama üçüncü key SQL'e ham şekilde giriyor.
Ortaya çıkan query mantığı şu hale geliyor:
DELETE FROM tc_user_device
WHERE userId = ? AND deviceId = ? AND <attacker-controlled SQL> = ?DELETE FROM tc_user_device
WHERE userId = ? AND deviceId = ? AND <attacker-controlled SQL> = ?Bunu local H2 lab ortamında düşük yetkili, non-readonly bir kullanıcıyla doğruladım. Önce marker bir key gönderip server'ın oluşturduğu SQL içinde marker'ın göründüğünü kanıtladım. Sonra boolean oracle ile admin kullanıcısına ait bazı alanların okunabildiğini gösterdim.
Buradaki en önemli ders benim için şuydu: prepared statement tek başına sihirli kalkan değil. SQL identifier'ları, column adları veya query parçaları request-controlled bir yerden geliyorsa, parameter binding seni otomatik olarak kurtarmıyor.
Bu bulgu GitHub advisory tarafında High severity ile yayınlandı. CVE ID'si:*CVE-2026–52851.
Permission bug'ında kritik soru
Bu tarz bug'larda en önemli soru şu:
Tamam, SQL'e bir şey sokabiliyorum. Ama düşük yetkili bir kullanıcı bu koda gerçekten erişebiliyor mu?
Cevap evetse bulgu ciddileşiyor. Hayırsa çoğu zaman admin-only hardening konusu olarak kalıyor.
Burada düşük yetkili bir kullanıcının yolu vardı. Authorization kontrolü ilk iki key üzerinden çalışıyordu. Kullanıcı userId=<self> ve erişebildiği bir deviceId ile permission modelini geçerli gösterebiliyordu. Üçüncü key type-check edilmiyordu, authorization-check edilmiyordu, ama storage katmanında SQL'e dahil ediliyordu.
Local validation'da şunları kanıtladım:
- unauthenticated request
401 Unauthorizeddönüyordu - low-privileged authenticated kullanıcı endpoint'e erişebiliyordu
- marker key SQL query içinde görünüyordu
- true/false condition'lar farklı davranış üretiyordu
- blind extraction ile admin kullanıcısına ait alanlar okunabiliyordu
- okunan değerler H2 ground truth ile birebir eşleşiyordu
Son madde önemli. Bir raporda "okunabilir" demekle, local DB ground truth ile birebir doğrulamak aynı şey değil. Ben ikinciyi yapmaya çalışıyorum. Evidence-first ilerlemek hem raporu güçlendiriyor hem de kendini kandırma ihtimalini azaltıyor.
Bug 2: Tree sandığın şey graph'a dönüşünce
İkinci açık DoS tarafındaydı. Root cause daha basit, ama pratik etkisi ilginçti.
Traccar'da cihazlar gruplara bağlanabiliyor. Grupların da parent group ilişkisi var. Kod bazı attribute'ları ararken cihazdan başlayıp group parent chain boyunca yukarı çıkıyor.
Basitleştirilmiş haliyle:
long groupId = device.getGroupId();
while (result == null && groupId > 0) {
Group group = provider.getGroup(groupId);
if (group != null) {
result = group.getAttributes().get(key.getKey());
groupId = group.getGroupId();
} else {
groupId = 0;
}
}long groupId = device.getGroupId();
while (result == null && groupId > 0) {
Group group = provider.getGroup(groupId);
if (group != null) {
result = group.getAttributes().get(key.getKey());
groupId = group.getGroupId();
} else {
groupId = 0;
}
}Burada kodun sessiz varsayımı şu: parent chain bir noktada bitecek.
Ama API tarafı cycle oluşturmaya izin veriyordu:
A.parent = B
B.parent = AA.parent = B
B.parent = ABöyle bir durumda lookup şu döngüye giriyor:
A -> B -> A -> B -> …A -> B -> A -> B -> …Visited-set yok. Depth limit yok. Cycle detection yok.
Bu tek başına teorik bir bug gibi durabilir. Asıl soru yine aynı: reliable trigger var mı?
Vardı.
Trips/stops report path'i bu lookup'u tetikliyordu. Üstelik storage-backed provider kullandığı için group'ları DB'den çözmeye devam ediyor ve döngü bitmiyordu. Local lab'da cyclic group içindeki bir device için trips report istediğimde request dönmedi. Client timeout'a düştü ama server tarafındaki Jetty worker thread çalışmaya devam etti.
Evidence tarafında şunlar vardı:
- normal device için trips report:
HTTP 200, birkaç saniye içinde - cyclic group içindeki device için trips report: client tarafında timeout
- Java process CPU-bound kaldı
jstack, worker thread'inAttributeUtil.lookupiçinde takılı kaldığını gösterdi
Bu bug CVE-2026–52852 / GHSA-6qh3–234v-r254 olarak yayınlandı. Advisory'de severity Moderate, CVSS base score 6.5 olarak görünüyor. Burada güzel bir nüans var: impact availability-only olduğu için skor SQLi kadar yüksek değil, ama pratikte birkaç stuck worker thread web/API tarafını ciddi şekilde etkileyebilir.
DoS bug'ından aldığım ders
Bu bug bana şunu tekrar hatırlattı:
Eğer veri modelin tree olmak zorundaysa, bunu API seviyesinde enforce etmen gerekiyor.
Kod içinde "nasıl olsa parent chain biter" diye düşünmek yetmiyor. Kullanıcı create/update endpoint'leriyle cycle oluşturabiliyorsa, artık elindeki yapı tree değil graph.
Fix tarafı da iki katmanlı olmalı:
- group create/update sırasında cycle oluşturacak parent değişikliklerini reddetmek
- parent chain yürüyen helper'larda visited-set veya conservative depth limit kullanmak
Biri model invariant'ını korur, diğeri defense-in-depth olur.
Lab düzeni
Bu iki bulguyu VDS üzerinde kurduğum local lab akışıyla çalıştım. Claude Code ve sast-skills tarafı aday yüzeyleri ve kod akışlarını çıkarmakta yardımcı oldu. Doğrulama tarafı ise local Traccar instance üzerinde gerçek request'ler, DB ground truth ve stack trace ile yapıldı.
Kısaca:
- Traccar 6.13.3 kaynak koddan build edildi
- H2 database ile temiz test instance kullanıldı
- test kullanıcıları ve device/group fixture'ları local olarak oluşturuldu
- PoC akışları sadece local instance üzerinde koştu
- evidence dosyalarında raw çıktı, ground truth ve stack trace ayrı tutuldu
Benim için bu düzen artık standart hale geldi. İyi bir security report sadece "bence burada bug var" dememeli. Şunları göstermeli:
- kim bu koda erişebiliyor?
- hangi precondition'lar gerekiyor?
- unauthorized kullanıcı ne alıyor?
- low-privileged kullanıcı ne yapabiliyor?
- observed behavior ile expected behavior arasındaki fark ne?
- root cause hangi dosya/satır civarında?
- fix önerisi nerede uygulanmalı?
Bunlar raporu biraz uzatıyor, evet. Ama maintainer tarafında işi kolaylaştırıyor.
Disclosure süreci
Raporları GitHub Security Advisory üzerinden private olarak ilettim. Teknik detayları reproduce edilebilir ama zararsız tutmaya çalıştım. Özellikle SQLi tarafında destructive olmayan oracle ve local-only evidence kullandım.
Advisory'ler yayınlandıktan sonra CVE ID'leri de public oldu:
- CVE-2026–52851: authenticated blind SQL injection
- CVE-2026–52852: infinite loop DoS
İlk CVE heyecanı gerçekten garipmiş. Bir yandan "tamam, bu sadece başlangıç" diyorsun. Bir yandan da o sayfada kendi nick'ini görmek baya motive ediyor.
Kısa teknik özet
SQL injection bug'ı
- Endpoint:
DELETE /api/permissions - Weakness: CWE-89
- Root cause: JSON key'lerinin SQL identifier olarak kullanılması
- Auth: authenticated, non-readonly low-privileged user
- Impact: blind DB read primitive, permission row deletion behavior
- CVE: CVE-2026–52851
DoS bug'ı
- Trigger: cyclic group parent hierarchy + trips/stops report
- Weakness: CWE-674
- Root cause: parent chain traversal içinde cycle detection olmaması
- Auth: group edit + report access yetkisi olan authenticated user
- Impact: stuck Jetty worker thread, repeated requests ile web/API DoS riski
- CVE: CVE-2026–52852
Benim çıkarımlarım
Bu iki bug bana üç şeyi tekrar gösterdi.
Birincisi, source review hâlâ çok güçlü. AI destekli yapınca daha verimli oluyor, ama sadece candidate üretme seviyesinde bırakılırsa gürültüye dönüşebiliyor. Özellikle permission, storage ve report gibi "sıkıcı" görünen yerler çoğu zaman en güzel bulguları saklıyor.
İkincisi, güvenlik sadece input sanitization değil. Bazen mesele veri modelinin invariant'ı. Bir şey tree olmak zorundaysa cycle'a izin vermemelisin. Bir şey permission key olmak zorundaysa request'ten gelen ekstra key'i sessizce taşımamalısın.
Üçüncüsü, evidence kalitesi fark yaratıyor. Stack trace, ground truth, negative control, low-privileged validation… Bunlar raporu uzatıyor ama bulgunun gerçekliğini tartışmasız hale getiriyor.
Benim için bu ilk CVE/GHSA süreciydi. Büyük ihtimalle en çok hatırlayacağım kısmı exploit'in kendisi değil, o ilk doğrulama anı olacak: marker key'in SQL query içinde görünmesi ve "evet, burada gerçekten bir şey var" demek.
Sonrası bildiğimiz iş: daha çok kod okumak, daha temiz lab kurmak, daha iyi rapor yazmak.
Linkler
- GHSA-jx5h-fxhc-cc9w / CVE-2026–52851: https://github.com/traccar/traccar/security/advisories/GHSA-jx5h-fxhc-cc9w
- GHSA-6qh3–234v-r254 / CVE-2026–52852: https://github.com/traccar/traccar/security/advisories/GHSA-6qh3-234v-r254
- sast-skills: https://github.com/utkusen/sast-skills