---
title: 'Enjeksiyon Sınıfı Açıklar'
source: 'https://academia.sh/tr/kurslar/backend-uretim/enjeksiyon-sinifi-aciklar'
course: 'Sunucu Güvenliği ve Üretime Alma'
language: tr
updated: '2026-08-17T18:06:49+00:00'
license: 'CC BY-SA 4.0'
---

# Enjeksiyon Sınıfı Açıklar

Sorgu, komut ve şablon enjeksiyonunun tek bir kökten ölçülmesi: on meşru arama girdisinin birleştirilmiş sorguda kaç beklenmeyen sonuç ürettiği, altı rapor etiketinin tek dizge olarak geçirildiğinde kaçının bozulduğu, beş not metninin şablon gövdesine yazıldığında kaçının değiştiği ve on iki çağrı yerinin kaçının birleştirme kullandığı.

Önceki ders sınırın biçimini kurdu: gövdenin alanları, tipleri ve aralıkları sözleşmeye
uyuyor mu. Sözleşmeyi geçen değer artık **doğru biçimdedir**, ama hâlâ bir metindir ve
sistemin içinde bir yere yazılacaktır. Bu ders o yazma anını ölçer.

Üç ayrı sınıf sayılır — sorgu, komut ve şablon — ve üçü de aynı karara iner: **değer, o
bağlamın metnine birleştirildi mi, yoksa ayrı bir işlenen olarak mı geçirildi?** Ölçüm
boyunca hiçbir kötü niyetli girdi kullanılmaz. Girdilerin hepsi kütüphane kataloğunda
gerçekten geçen metinlerdir: kesme işaretli yazar adları, süslü parantez içeren görevli
notları, boşluklu rapor etiketleri. Ölçülen şey **kaç meşru girdinin beklenmeyen sonuç
ürettiğidir**.

**BG4 (varsayım):** iki kurulusun aynı davranması beklenir. Ayrı işlenen yolu ölçüt kabul
edilir; birleşik yolun ondan farklı her sonucu — hata da, farklı satır da — beklenmeyen
sonuç sayılır.

## Sorgu Bağlamı

Katalog arama ucu, ödünç sisteminin en çok çağrılan yeridir. Aynı arama iki kurulusla
yazılır ve on meşru girdiye ayrı ayrı sorulur.

```js
// veri.mjs — odunc katalogu ve gercek kataloglarda gecen mesru arama girdileri
import { DatabaseSync } from "node:sqlite";

export const db = new DatabaseSync(":memory:");
db.exec(`CREATE TABLE kitap (isbn TEXT PRIMARY KEY, baslik TEXT, yazar TEXT, yil INTEGER)`);
const ekle = db.prepare(`INSERT INTO kitap VALUES (?, ?, ?, ?)`);
for (const s of [
  ["9789750718533", "Kayip Zaman", "M. Proust", 1913],
  ["9786050000000", "Rock 'n' Roll Tarihi", "J. O'Neill", 1998],
  ["9789750000001", "100% Pamuk", "A. Saenz", 2004],
  ["9789750000002", "L'Etranger", "A. Camus", 1942],
  ["9789750000003", "Kar_ Deseni", "H. Yilmaz", 2011],
  ["9789750000004", "Tarih -- 20. Yuzyil", "S. Aydin", 2019],
  ["9789750000005", 'Deneme "iki"', "N. Kaya", 2020],
  ["9789750000006", "Kalem ve Kagit", "B. Demir", 1995],
]) ekle.run(...s);

// Hicbiri saldiri degil; hepsi kutuphane katalogunda gecen mesru arama metinleri.
export const ARAMA = ["Kalem", "O'Neill", "100%", "Kar_", "--", 'iki"',
  "Rock 'n' Roll", "Camus", "L'Etranger", "Kaya'nin"];

// Siralama alani: sozlesme bir alan adi bekler.
export const SIRA = ["baslik", "yil", "yazar", "yil desc", "kitap.ic_not"];
```

```js
// sorgu.mjs — ayni arama, iki kurulus: ayri islenen ve dizge birlestirme
import { db, ARAMA, SIRA } from "./veri.mjs";

const dene = (f) => { try { return { ok: 1, v: f() }; } catch (e) { return { ok: 0, v: e.message.slice(0, 34) }; } };
const islenen = (q) => db.prepare(`SELECT isbn FROM kitap WHERE baslik LIKE ? OR yazar LIKE ?`)
  .all(`%${q}%`, `%${q}%`).map((r) => r.isbn);
const birlesik = (q) => db.prepare(
  `SELECT isbn FROM kitap WHERE baslik LIKE '%${q}%' OR yazar LIKE '%${q}%'`).all().map((r) => r.isbn);

console.log(`${ARAMA.length} mesru arama girdisi, iki kurulus\n`);
console.log(`${"girdi".padEnd(16)}${"islenen".padStart(9)}${"birlesik".padStart(10)}  fark`);
let sapan = 0, hatali = 0;
for (const q of ARAMA) {
  const a = dene(() => islenen(q)), b = dene(() => birlesik(q));
  const ayni = a.ok && b.ok && String(a.v) === String(b.v);
  if (!ayni) sapan++;
  if (!b.ok) hatali++;
  console.log(JSON.stringify(q).padEnd(16) + (a.ok ? String(a.v.length) : "hata").padStart(9) +
    (b.ok ? String(b.v.length) : "hata").padStart(10) + "  " + (ayni ? "-" : b.ok ? "farkli satir" : b.v));
}
console.log(`\nbirlesik kurulusta beklenmeyen sonuc: ${sapan}/${ARAMA.length} (${hatali} tanesi hata)`);

// Alan adi islenen olarak gecirilemez; burada birlestirme kacinilmazdir.
const IZIN = { baslik: "baslik", yil: "yil", yazar: "yazar" };
const sirala = (alan, izinli) => {
  if (izinli && !IZIN[alan]) return "ret";
  const s = izinli ? IZIN[alan] : alan;
  return dene(() => db.prepare(`SELECT isbn FROM kitap ORDER BY ${s}`).all()[0].isbn);
};
console.log(`\n${"siralama girdisi".padEnd(18)}${"birlestirme".padStart(18)}${"izin listesi".padStart(18)}`);
for (const alan of SIRA) {
  const b = sirala(alan, false), i = sirala(alan, true);
  const yaz = (r) => (r === "ret" ? "ret" : r.ok ? r.v : "hata");
  console.log(alan.padEnd(18) + yaz(b).padStart(18) + yaz(i).padStart(18));
}
```

```
10 mesru arama girdisi, iki kurulus

girdi             islenen  birlesik  fark
"Kalem"                 1         1  -
"O'Neill"               1      hata  near "Neill": syntax error
"100%"                  1         1  -
"Kar_"                  1         1  -
"--"                    1         1  -
"iki\""                 1         1  -
"Rock 'n' Roll"         1      hata  near "n": syntax error
"Camus"                 1         1  -
"L'Etranger"            1      hata  near "Etranger": syntax error
"Kaya'nin"              0      hata  near "nin": syntax error

birlesik kurulusta beklenmeyen sonuc: 4/10 (4 tanesi hata)

siralama girdisi         birlestirme      izin listesi
baslik                 9789750000001     9789750000001
yil                    9789750718533     9789750718533
yazar                  9789750000002     9789750000002
yil desc               9789750000005               ret
kitap.ic_not                    hata               ret
```

Dört girdi sapıyor ve dördünün de ortak özelliği bir kesme işareti taşımasıdır. Bu dört
okuyucunun araması **çalışmıyor** — ödünç sistemi bir yazar adını arayamıyor. Enjeksiyon
sınıfının en sık görülen üretim etkisi budur: açık değil, kırık işlev.

İkinci tablo asıl noktadır. Sıralama alanı bir işlenen olarak geçirilemez, çünkü sorgu
metninin kendisinin bir parçasıdır; orada birleştirme kaçınılmazdır. `yil desc` girdisi
birleştirmede **hata vermeden** kabul ediliyor ve farklı bir satır dönüyor. `kitap.ic_not`
hata veriyor. İzin listesi ikisini de reddediyor. **Birleştirmenin zorunlu olduğu tek yer,
sapmanın sessiz olduğu tek yerdir** — ve orada tek çare önceki dersin izin listesidir.

## Komut Bağlamı

Rapor servisi, dışa aktarma işini ayrı bir sürece veriyor. Etiket iki yolla geçirilebilir:
ayrı bir argüman olarak ya da komut satırına birleştirilmiş bir dizge içinde.

```js
// yankila.mjs — rapor sureci: aldigi argumanlari oldugu gibi basar
console.log(JSON.stringify(process.argv.slice(2)));
```

```js
// komut.mjs — rapor etiketi alt surece iki yolla gecirilir: ayri arguman ve tek dizge
import { execFileSync, execSync } from "node:child_process";

// Hepsi kutuphane gorevlisinin yazdigi mesru rapor etiketleri.
const ETIKET = ["2026-03", "gunluk rapor", "bolum $raf", "O'Neill listesi", "ikinci  el", "kayip-bulunan"];

const oku = (f) => { try { return JSON.parse(f()); } catch { return null; } };
const islenen = (e) => oku(() => execFileSync("node", ["yankila.mjs", e], { encoding: "utf8" }));
const birlesik = (e) => oku(() => execSync(`node yankila.mjs ${e}`, { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }));

console.log(`${ETIKET.length} mesru rapor etiketi, iki gecirme yolu\n`);
console.log(`${"etiket".padEnd(19)}${"ayri arguman".padStart(22)}${"tek dizge".padStart(26)}`);
let bozuk = 0;
for (const e of ETIKET) {
  const a = islenen(e), b = birlesik(e);
  const ayni = b && b.length === 1 && b[0] === e;
  if (!ayni) bozuk++;
  console.log(e.padEnd(19) + JSON.stringify(a).padStart(22) + (b ? JSON.stringify(b) : "surec hata").padStart(26));
}
console.log(`\nayri arguman: 0/${ETIKET.length} bozuldu, tek dizge: ${bozuk}/${ETIKET.length} bozuldu`);
```

```
6 mesru rapor etiketi, iki gecirme yolu

etiket                       ayri arguman                 tek dizge
2026-03                       ["2026-03"]               ["2026-03"]
gunluk rapor             ["gunluk rapor"]        ["gunluk","rapor"]
bolum $raf                 ["bolum $raf"]                 ["bolum"]
O'Neill listesi       ["O'Neill listesi"]                surec hata
ikinci  el                 ["ikinci  el"]           ["ikinci","el"]
kayip-bulunan           ["kayip-bulunan"]         ["kayip-bulunan"]

ayri arguman: 0/6 bozuldu, tek dizge: 4/6 bozuldu
```

Aynı kök, farklı dil. Boşluklu etiket iki argümana bölünüyor, çift boşluk teke iniyor,
kesme işaretli etiket süreci hiç başlatmıyor. Üçüncü satır bu bölümün sessiz vakasıdır:
`bolum $raf` etiketi hatasız çalışıyor ve alt sürece **`bolum` olarak** varıyor. Rapor
üretilir, dosya yazılır, kimse bir şey fark etmez; yalnız etiketin yarısı yoktur.

## Şablon Bağlamı

Bildirim metni bir şablondan üretiliyor. Görevlinin yazdığı not ya şablona **veri olarak**
verilir ya da şablonun gövdesine yazılıp sonra işlenir.

```js
// sablon.mjs — bildirim metni iki yolla kurulur: veri islenen olarak, veri sablonun icine
const SABLON = "Sayin {ad}, {isbn} son gunu {gun}. Not: {not}";
const YERLESTIR = (metin, veri) => metin.replace(/\{(\w+)\}/g, (_, k) => veri[k] ?? "");

// Hepsi gorevlinin elle yazdigi mesru not metinleri.
const NOTLAR = ["kapak yipranmis", "iade tarihi {gun} olarak yazilmis", "raf {yer} bulunamadi",
  "ucret 5 TL", "{ad} adina kayitli"];
const TEMEL = { ad: "A. Kaya", isbn: "9789750718533", gun: "2026-03-25" };

// Ayri islenen: sablon sabittir, veri bir kez yerine konur ve bir daha taranmaz.
const islenen = (not) => YERLESTIR(SABLON, { ...TEMEL, not });
// Birlestirme: veri once sablonun govdesine yazilir, sonra govde islenir.
const birlesik = (not) => YERLESTIR(SABLON.replace("{not}", not), { ...TEMEL, not });

const notAlani = (m) => m.split("Not: ")[1];
console.log(`${NOTLAR.length} mesru not metni, iki kurulus\n`);
console.log(`${"girilen not".padEnd(36)}${"birlesik kurulusun bastigi not".padEnd(40)}fark`);
let bozuk = 0;
for (const not of NOTLAR) {
  const b = birlesik(not), ayni = b === islenen(not);
  if (!ayni) bozuk++;
  console.log(not.padEnd(36) + notAlani(b).padEnd(40) + (ayni ? "-" : "degisti"));
}
console.log(`\nnot alani girildigi gibi cikmayan bildirim: birlesik ${bozuk}/${NOTLAR.length}, islenen 0/${NOTLAR.length}`);
```

```
5 mesru not metni, iki kurulus

girilen not                         birlesik kurulusun bastigi not          fark
kapak yipranmis                     kapak yipranmis                         -
iade tarihi {gun} olarak yazilmis   iade tarihi 2026-03-25 olarak yazilmis  degisti
raf {yer} bulunamadi                raf  bulunamadi                         degisti
ucret 5 TL                          ucret 5 TL                              -
{ad} adina kayitli                  A. Kaya adina kayitli                   degisti

not alani girildigi gibi cikmayan bildirim: birlesik 3/5, islenen 0/5
```

Beş nottan üçü, okuyucuya giden bildirimde başka bir metne dönüştü. Hiçbiri hata vermedi.
Bir tanesi bilinmeyen bir anahtar taşıdığı için **boşaldı**; kayıt duruyor, bildirimde yok.
Ayrı işlenen yolunda şablon sabittir ve veri bir kez yerine konup bir daha taranmaz — bu
yüzden beşi de girildiği gibi çıkıyor.

## Birleştirmenin Sayımı

Karar tek bir yerde verilmez; her çağrı yerinde ayrı ayrı verilir. Bölünmüş sistemin
çağrı yerleri sayılıyor.

```js
// cagri.mjs — bolunmus sistemdeki cagri yerlerinin sayimi ve tek unutulmus yerin olcusu
import { db, ARAMA } from "./veri.mjs";

// [servis, cagri yeri, baglam, kurulus]
const CAGRI = [
  ["katalog", "ara", "sorgu", "islenen"],
  ["katalog", "sirala", "sorgu", "birlestirme + izin listesi"],
  ["katalog", "isbnGetir", "sorgu", "islenen"],
  ["odunc", "ver", "sorgu", "islenen"],
  ["odunc", "gecikmis", "sorgu", "islenen"],
  ["odunc", "arsivAra", "sorgu", "birlestirme"],          // gozden kacan tek yer
  ["uye", "notEkle", "sorgu", "islenen"],
  ["uye", "gecmis", "sorgu", "islenen"],
  ["bildirim", "bicimle", "sablon", "islenen"],
  ["bildirim", "topluGonder", "sablon", "islenen"],
  ["rapor", "disaAktar", "komut", "islenen"],
  ["rapor", "arsivle", "komut", "islenen"],
];

const say = (k) => CAGRI.filter(k).length;
console.log(`${CAGRI.length} cagri yeri, ${new Set(CAGRI.map((c) => c[0])).size} servis`);
for (const b of ["sorgu", "komut", "sablon"])
  console.log(`  ${b.padEnd(7)} ${say((c) => c[2] === b)} yer, ` +
    `${say((c) => c[2] === b && c[3].startsWith("birlestirme"))} tanesi birlestirme`);
console.log(`  korumasiz birlestirme: ${say((c) => c[3] === "birlestirme")} ` +
  `(${CAGRI.filter((c) => c[3] === "birlestirme").map((c) => c[0] + "." + c[1]).join(", ")})`);

// Gozden kacan yer gercekten kosuluyor: ayni arama girdileri, tek fark kurulus.
const arsivAra = (q) => db.prepare(`SELECT isbn FROM kitap WHERE baslik LIKE '%${q}%'`).all();
let sapan = 0;
const sapanlar = [];
for (const q of ARAMA) {
  try { arsivAra(q); } catch { sapan++; sapanlar.push(q); }
}
console.log(`\ntek unutulmus yerde ${ARAMA.length} girdiden ${sapan} tanesi beklenmeyen sonuc verdi`);
console.log(`sapanlarin ortak ozelligi: hepsinde kesme isareti var -> ${sapanlar.every((q) => q.includes("'"))}`);
console.log(`kesme isareti tasimayan ${ARAMA.filter((q) => !q.includes("'")).length} girdi ile kurulan bir sinama kumesi bu yeri hic gormez`);
```

```
12 cagri yeri, 5 servis
  sorgu   8 yer, 2 tanesi birlestirme
  komut   2 yer, 0 tanesi birlestirme
  sablon  2 yer, 0 tanesi birlestirme
  korumasiz birlestirme: 1 (odunc.arsivAra)

tek unutulmus yerde 10 girdiden 4 tanesi beklenmeyen sonuc verdi
sapanlarin ortak ozelligi: hepsinde kesme isareti var -> true
kesme isareti tasimayan 6 girdi ile kurulan bir sinama kumesi bu yeri hic gormez
```

On iki çağrı yerinden ikisi birleştirme kullanıyor; biri sıralama alanıdır ve izin listesiyle
korunmuştur, biri gözden kaçmıştır. Sistem çalışıyor, bütün uçlar yanıt veriyor, hiçbir
sınama kırmızı değil. Son satır nedenini söylüyor: sapma yalnız kesme işareti taşıyan
girdilerde ortaya çıkıyor ve sade adlarla kurulmuş bir sınama kümesi o yeri hiç görmez.

**Ayarın yeri ve tekrarı:** bu karar bir yapılandırma dosyasında durmaz; on iki çağrı
yerinin her birinde ayrı ayrı verilir. Tek yerden değiştirilebilir hâle gelmesi için sorgu,
komut ve şablon çağrılarının tek bir sarmalayıcıdan geçmesi gerekir — o zaman karar 12
noktadan 3 noktaya iner.

## Özet

- Üç sınıf tek bir karara iniyor: değer bağlamın metnine mi birleştirildi, yoksa ayrı bir
  işlenen olarak mı geçirildi. Ölçüm boyunca hiçbir kötü niyetli girdi kullanılmadı.
- On meşru arama girdisinin 4'ü birleştirilmiş sorguda beklenmeyen sonuç verdi; dördü de
  kesme işareti taşıyordu ve dördünde de sonuç bir hataydı — açık değil, kırık arama.
- Sıralama alanı işlenen olarak geçirilemez; birleştirmenin zorunlu olduğu bu tek yerde
  sapma sessizdir: `yil desc` hatasız kabul edildi ve farklı bir satır döndü. İzin listesi
  reddetti.
- Altı rapor etiketinin 4'ü tek dizge olarak geçirildiğinde bozuldu; `bolum $raf` hatasız
  çalışıp alt sürece yarım vardı. Beş nottan 3'ü şablon gövdesine yazıldığında değişti,
  biri boşaldı. Ayrı işlenen yolunda üç bağlamda da bozulma sıfır.
- On iki çağrı yerinden 2'si birleştirme kullanıyor; biri izin listesiyle korunmuş, biri
  gözden kaçmış. Kaçan yer 10 girdiden 4'ünde sapıyor, kalan 6 girdiyle kurulmuş bir sınama
  kümesi onu hiç görmüyor. Karar 12 noktada tekrarlanıyor.

## Sonraki Adım

Buraya kadarki iki ders isteğin **gövdesine** baktı: alanlar doğru biçimde mi, değerler
metne mi birleştiriliyor. İkisi de isteğin nereden geldiğini sormadı. Oysa ödünç sisteminin
uçlarına yalnız kendi arayüzü değil, tarayıcıda açık başka sayfalar da istek gönderebilir ve
bu isteklerin bir kısmı okuyucunun oturum bilgisini yanında taşır. Sonraki ders sunucunun bu
konudaki tek sözünü — hangi kökenden gelen isteğe ne kadar izin verdiğini — ölçer: ayarın
hangi katmanda durduğunu, joker bir değerin kaç kökene kapı açtığını ve kimlik bilgisi
taşıyan bir istekte yanlış ayarın neden hata vermeden sessizce başarısız olduğunu.
