---
title: 'Şablon ve İfade Enjeksiyonu'
source: 'https://academia.sh/tr/kurslar/guvenli-kodlama/sablon-ve-ifade-enjeksiyonu'
course: 'Güvenli Kodlama'
language: tr
updated: '2026-08-17T18:06:42+00:00'
license: 'CC BY-SA 4.0'
---

# Şablon ve İfade Enjeksiyonu

Sunucu tarafı değerlendirme riskinin yüzey olarak ölçülmesi: değerlendirme ortamında çözülebilen ad sayısı ortamın kendisinden üretilen sondalarla sayılır, on iki şablonlu bir envanterde kaçının kullanıcı verisini yalnız değer olarak, kaçının ifade olarak aldığı ayrılır ve gövdesi dışarıdan gelen şablonların toplam değerlendirme yüzeyi üç kurulumda karşılaştırılır.

Önceki ders sorgu metnini sabit kabul etti: iskeleti geliştirici yazar, değer dışarıdan
gelir. Bildirim metinleri, rapor başlıkları ve saha etiketleri bu varsayımı taşımıyor.
Bunların gövdesi bir şablondur ve şablonların bir kısmı çalışma zamanında bir yapılandırma
kaydından ya da bir kullanıcı alanından okunur.

Bu ders şablonları sayar. Ölçülen büyüklük metnin bozulup bozulmadığı değil, **değerlendirme
yüzeyidir**: şablon işlenirken kaç ad çözülebiliyor. Bir şablon kullanıcı verisini yalnız
**değer** olarak alıyorsa yüzey bildirilen alanlarla sınırlıdır; veriyi **ifade** olarak
alıyorsa yüzey ortamın tamamıdır.

## İki Motor, Tek Ortam

Model iki motor içerir. **Değer motoru** yalnız bildirilen adları yerine koyar ve sonucu bir
daha taramaz. **İfade motoru** noktalı bir yolu ortamda çözer; bildirim diye bir şeyi yoktur.
Ortam, şablon işlenirken erişilebilen nesne kümesidir.

**GC9 (varsayım):** ortam ve motorlar modeldir. Yüzey, ortamda noktalı yolla çözülebilen
yaprak ad sayısıdır; adın taşıdığı değerin ne kadar duyarlı olduğu ölçülmez.

```js
// motor.mjs — iki sablon motoru ve degerlendirme ortami (kurgu model)
// Deger motoru: yalniz bildirilen adlari yerine koyar, sonuc bir daha taranmaz.
export const degerMotoru = (sablon, ortam, bildirilen) =>
  sablon.replace(/\{([\w.]+)\}/g, (t, yol) =>
    bildirilen.includes(yol) ? String(yol.split(".").reduce((o, k) => o?.[k], ortam)) : t);

// Ifade motoru: noktali yolu ortamda cozer; bildirim diye bir sey yoktur.
export const ifadeMotoru = (sablon, ortam) =>
  sablon.replace(/\{([\w.]+)\}/g, (t, yol) => {
    const v = yol.split(".").reduce((o, k) => o?.[k], ortam);
    return v === undefined || typeof v === "object" ? t : String(v);
  });

// Erisilebilir ad: ortamda noktali yolla cozulebilen yaprak adlarin listesi.
export const erisilebilir = (o, onek = "") =>
  o === null || typeof o !== "object" ? [onek]
    : Object.entries(o).flatMap(([k, v]) => erisilebilir(v, onek ? `${onek}.${k}` : k));

export const GENIS = {
  abone: { ad: "A. Yilmaz", no: "A-100432", tarife: "mesken" },
  fatura: { donem: "2026-03", tutar: 412, sonGun: "2026-04-15" },
  ayar: { bolge: "kuzey", surum: "3", gunlukSeviye: "bilgi", sunucuAdi: "olcum-1" },
  baglanti: { depoAdi: "olcum", rolAdi: "rapor_okuyucu", zamanAsimi: 30 },
  sistem: { calismaDizini: "/srv/olcum", islemNo: 4120 },
};
export const DAR = { abone: GENIS.abone, fatura: GENIS.fatura };
export const BILDIRILEN = ["abone.ad", "abone.no", "fatura.donem", "fatura.tutar", "fatura.sonGun"];
```

Geniş ortam bir uygulamanın şablon işleyicisine elinin altındaki her şeyi vermesini temsil
eder: abone ve fatura verisinin yanında ayarlar, bağlantı bilgileri ve süreç bilgisi. Dar
ortam yalnız bildirimin gerektirdiği iki nesneyi taşır.

İfade motorunun varlığı bir kusur değil, bir istektir. Bildirim metnini yöneticinin
düzenleyebilmesi, rapor başlığına bölge adının girebilmesi ve saha etiketinin şablondan
üretilmesi işin gerçek gereksinimleridir. Değerlendirme yüzeyi bu gereksinimin sayısal
karşılığıdır: metni dışarıdan düzenlenebilir yapmak, o metni işleyen ortamı da dışarıdan
sorgulanabilir yapar. Ölçülmesi gereken şey bu takasın büyüklüğüdür.

## Değerlendirme Yüzeyi

Yüzey sayılırken sonda kümesi elle yazılmaz; **ortamın kendisinden üretilir**. Her erişilebilir
ad için tek bir yerleştirme denenir ve kaçının çözüldüğü sayılır. Böylece ölçüm, ortam
değiştiğinde kendiliğinden güncellenir.

```js
// yuzey.mjs — degerlendirme yuzeyi: ortamda kac ad cozulebiliyor
import { degerMotoru, ifadeMotoru, erisilebilir, GENIS, DAR, BILDIRILEN } from "./motor.mjs";

// Sonda kumesi ortamin kendisinden uretilir: her erisilebilir ad icin tek yerlestirme.
const sondalar = (ortam) => erisilebilir(ortam).map((yol) => [yol, `{${yol}}`]);

console.log(`bildirilen ad: ${BILDIRILEN.length}\n`);
console.log(`${"ortam / motor".padEnd(22)}${"erisilebilir ad".padStart(16)}${"cozulen".padStart(9)}${"bildirim disi".padStart(15)}`);
for (const [adi, ortam, motor] of [["genis / ifade", GENIS, "ifade"], ["genis / deger", GENIS, "deger"],
    ["dar / ifade", DAR, "ifade"], ["dar / deger", DAR, "deger"]]) {
  const s = sondalar(ortam);
  const uygula = (x) => (motor === "ifade" ? ifadeMotoru(x, ortam) : degerMotoru(x, ortam, BILDIRILEN));
  const cozulen = s.filter(([, x]) => uygula(x) !== x);
  const disi = cozulen.filter(([yol]) => !BILDIRILEN.includes(yol)).length;
  console.log(adi.padEnd(22) + String(s.length).padStart(16) + String(cozulen.length).padStart(9) +
    String(disi).padStart(15));
}
const dis = erisilebilir(GENIS).filter((y) => !BILDIRILEN.includes(y));
console.log(`\ngenis ortamda bildirim disi kalan ad: ${dis.length}`);
console.log(`ust duzey ad kumesi: genis ${Object.keys(GENIS).join(" ")} / dar ${Object.keys(DAR).join(" ")}`);
```

```
bildirilen ad: 5

ortam / motor          erisilebilir ad  cozulen  bildirim disi
genis / ifade                       15       15             10
genis / deger                       15        5              0
dar / ifade                          6        6              1
dar / deger                          6        5              0

genis ortamda bildirim disi kalan ad: 10
ust duzey ad kumesi: genis abone fatura ayar baglanti sistem / dar abone fatura
```

Dört satır iki bağımsız savunmayı ayırıyor. **Ortamı daraltmak** erişilebilir adı on beşten
altıya indiriyor; ifade motoru hâlâ hepsini çözüyor, ama çözülecek ad kalmıyor. **Motoru
değere sabitlemek** ortamdan bağımsız olarak çözülen adı bildirilen kümeye indiriyor: geniş
ortamda bile bildirim dışı çözülen ad sıfır.

Son satır ikisinin birlikte anlamını veriyor. Dar ortamda değer motoru beş ad çözüyor,
ifade motoru altı — aradaki tek ad `abone.tarife`, bildirimde geçmeyen bir alan. Yüzey
sıfırlanmıyor; **daralıyor ve sayılabilir kalıyor**.

## Şablon Envanteri

Yüzey şablon başına ölçülür ve tek bir şey belirler: şablonun **gövdesi** nereden geliyor.
Gövde sabitse çözülecek adları geliştirici yazmıştır ve dışarıdan gelen bir ad yoktur.
Gövde bir yapılandırma kaydından ya da kullanıcı alanından okunuyorsa çözülebilen her ad o
şablonun yüzeyidir.

**GC10 (varsayım):** gövdesi sabit olan şablonun yüzeyi sıfır sayılır. Ölçülen şey şablonun
kim tarafından yazıldığı değil, **nereden okunduğudur**.

```js
// envanter.mjs — sablon envanteri (kurgu model) ve toplam degerlendirme yuzeyi
import { erisilebilir, GENIS, DAR, BILDIRILEN } from "./motor.mjs";

const ORTAM = { genis: GENIS, dar: DAR };
// [servis, sablon, govde kaynagi, motor, ortam]
const SABLON = [
  ["bildirim", "faturaKesildi", "sabit", "deger", "dar"],
  ["bildirim", "sonGunYaklasti", "sabit", "deger", "dar"],
  ["bildirim", "okumaEksik", "sabit", "deger", "dar"],
  ["fatura", "dokumBasligi", "sabit", "deger", "dar"],
  ["fatura", "aciklamaSatiri", "sabit", "deger", "dar"],
  ["is-emri", "gorevMetni", "sabit", "deger", "dar"],
  ["is-emri", "kapanisNotu", "sabit", "deger", "dar"],
  ["bildirim", "kampanyaMetni", "yapilandirma", "ifade", "genis"],
  ["rapor", "ustBilgi", "yapilandirma", "ifade", "genis"],
  ["rapor", "altBilgi", "yapilandirma", "deger", "dar"],
  ["rapor", "ozetBasligi", "kullanici", "ifade", "genis"],
  ["saha", "etiketMetni", "kullanici", "ifade", "genis"],
];
const YENI = [["rapor", "bolgeBasligi", "yapilandirma"], ["bildirim", "hatirlatma", "yapilandirma"],
  ["saha", "formBasligi", "yapilandirma"]];

// Yuzey: govde sabitse cozulecek ad kumesi zaten sablonun icindedir.
const yuzey = ([, , kaynak, motor, o]) =>
  kaynak === "sabit" ? 0 : motor === "ifade" ? erisilebilir(ORTAM[o]).length : BILDIRILEN.length;
const topla = (l) => l.reduce((t, s) => t + yuzey(s), 0);
const say = (l, i, v) => l.filter((s) => s[i] === v).length;

console.log(`${SABLON.length} sablon, hepsi kullanici verisi aliyor`);
console.log(`  motor: deger ${say(SABLON, 3, "deger")}, ifade ${say(SABLON, 3, "ifade")}`);
console.log(`  govde kaynagi: sabit ${say(SABLON, 2, "sabit")}, yapilandirma ` +
  `${say(SABLON, 2, "yapilandirma")}, kullanici ${say(SABLON, 2, "kullanici")}\n`);

const KURULUM = [
  ["mevcut durum", (s) => s],
  ["kalip: deger motoru + dar ortam", ([a, b, k]) => [a, b, k, "deger", "dar"]],
  ["kalip: govde de sabit", ([a, b]) => [a, b, "sabit", "deger", "dar"]],
];
console.log(`${"kurulum".padEnd(34)}${"sablon".padStart(8)}${"disaridan govde".padStart(17)}${"toplam yuzey".padStart(14)}`);
for (const [adi, d] of KURULUM) {
  const l = SABLON.map(d), y = YENI.map(([a, b, k]) => d([a, b, k, "ifade", "genis"]));
  console.log(adi.padEnd(34) + String(l.length).padStart(8) +
    String(l.length - say(l, 2, "sabit")).padStart(17) + String(topla(l)).padStart(14));
  console.log(`  uc yeni sablon eklendikten sonra: ${l.length + y.length} sablon, yuzey ${topla([...l, ...y])}`);
}
```

```
12 sablon, hepsi kullanici verisi aliyor
  motor: deger 8, ifade 4
  govde kaynagi: sabit 7, yapilandirma 3, kullanici 2

kurulum                             sablon  disaridan govde  toplam yuzey
mevcut durum                            12                5            65
  uc yeni sablon eklendikten sonra: 15 sablon, yuzey 110
kalip: deger motoru + dar ortam         12                5            25
  uc yeni sablon eklendikten sonra: 15 sablon, yuzey 40
kalip: govde de sabit                   12                0             0
  uc yeni sablon eklendikten sonra: 15 sablon, yuzey 0
```

On iki şablonun hepsi kullanıcı verisi alıyor; bu tek başına bir sayı taşımıyor. Ayrım
sekizinin veriyi yalnız değer olarak, dördünün ifade olarak almasındadır. Ve asıl ayrım
gövde kaynağındadır: yedi şablonun gövdesi sabit, üçü yapılandırmadan, ikisi doğrudan
kullanıcı alanından okunuyor.

Toplam yüzey altmış beş. Bu sayının tamamı beş şablondan geliyor; kalan yedisi hiç katkı
yapmıyor. **Yüzey şablon sayısıyla değil, gövdesi dışarıdan gelen şablon sayısıyla artıyor.**

Beş şablonun ikisi ayrı bir sınıftır. Rapor özet başlığı ve saha etiket metni gövdelerini
bir yapılandırma kaydından değil, doğrudan bir kullanıcı alanından alıyor: kullanıcının
yazdığı başlık, şablonun gövdesi olarak işleniyor. Yapılandırmadan gelen gövdeyi düzenleyen
kişi sayısı sınırlıdır ve o kayıt bir onay akışından geçebilir; kullanıcı alanından gelen
gövde için böyle bir sınır yoktur. İkisinin yüzeyi aynı — on beşer ad — ama o yüzeye
ulaşabilen kişi sayısı aynı değildir.

İkinci kurulum kalıbın kazancını veriyor: bütün şablonlar değer motoruna ve dar ortama
alındığında yüzey altmış beşten yirmi beşe iniyor. Sıfıra inmiyor, çünkü gövde hâlâ
dışarıdan okunuyor ve bildirilen beş ad her birinde çözülebiliyor. Üçüncü kurulumda gövde de
sabitleniyor ve yüzey sıfırlanıyor — ama bu kurulumda yönetici bildirim metnini artık
düzenleyemez. Bedel budur ve yazılması gerekir.

Alt satırlar üçüncü sütunu veriyor. Üç yeni şablon eklendiğinde mevcut durumda yüzey
altmış beşten yüz ona çıkıyor: **her yeni şablon on beş ad ekliyor**. Kalıp altında aynı üç
şablon yüzeyi yirmi beşten kırka çıkarıyor — beşer ad. Gövdesi sabit kalıpta üç yeni şablon
yüzeyi hiç değiştirmiyor.

Üç kurulumun karşılaştırılması bir şeyi daha söylüyor. İkinci kurulumda yüzey yirmi beşe
inerken şablonların hiçbiri işlevini yitirmiyor: bildirim metni hâlâ yönetici tarafından
düzenlenebiliyor, yalnız düzenlenen metnin çözebileceği ad kümesi daralıyor. Üçüncü kurulumda
kazanılan yirmi beş birim, kaybedilen bir yetenekle ödeniyor. **Yüzeyi sıfırlayan tek yol,
metni dışarıdan düzenlenebilir olmaktan çıkarmaktır** ve bu bir güvenlik kararı değil, bir
ürün kararıdır.

## Gövde Denetimi

Gövdesi sabitlenemeyen şablonlar için üçüncü bir savunma kalır: gövdenin kendisini bir izin
listesinden geçirmek. Bu, gövdeyi okuyup içinde geçen adları çıkarmak ve listede olmayan bir
ad varsa şablonu reddetmek demektir. Kazancı ve bedeli aynı sayıdan okunur.

```js
// govde-denetimi.mjs — disaridan gelen sablon govdesinin izin listesinden gecirilmesi
import { erisilebilir, GENIS, BILDIRILEN } from "./motor.mjs";

// Yapilandirmadan okunan sablon govdeleri (kurgu): hepsi yoneticinin yazdigi mesru metinler.
const GOVDE = [
  "Sayin {abone.ad}, {fatura.donem} donemi faturaniz hazir.",
  "Son odeme gunu {fatura.sonGun}.",
  "{abone.no} numarali abonelik icin {fatura.tutar} birim tahakkuk etti.",
  "Bolge {ayar.bolge} icin aylik dokum.",
  "Sunucu {ayar.sunucuAdi} uzerinden uretildi, surum {ayar.surum}.",
];
const adlar = (s) => [...s.matchAll(/\{([\w.]+)\}/g)].map((m) => m[1]);
const gecer = (s, izin) => adlar(s).every((a) => izin.includes(a));

const IZIN = [["bildirilen ad kumesi", BILDIRILEN],
  ["bildirilen + ayar.bolge", [...BILDIRILEN, "ayar.bolge"]],
  ["govde denetimi yok", erisilebilir(GENIS)]];

console.log(`${GOVDE.length} sablon govdesi, ${new Set(GOVDE.flatMap(adlar)).size} ayri ad kullaniliyor\n`);
console.log(`${"izin listesi".padEnd(24)}${"uzunluk".padStart(8)}${"gecen govde".padStart(12)}${"duran mesru govde".padStart(19)}${"yuzey".padStart(7)}`);
for (const [adi, izin] of IZIN) {
  const gecen = GOVDE.filter((s) => gecer(s, izin)).length;
  console.log(adi.padEnd(24) + String(izin.length).padStart(8) + String(gecen).padStart(12) +
    String(GOVDE.length - gecen).padStart(19) + String(izin.length).padStart(7));
}
```

```
5 sablon govdesi, 8 ayri ad kullaniliyor

izin listesi             uzunluk gecen govde  duran mesru govde  yuzey
bildirilen ad kumesi           5           3                  2      5
bildirilen + ayar.bolge        6           4                  1      6
govde denetimi yok            15           5                  0     15
```

Üç satır tek bir eğriyi veriyor: izin listesi her uzadığında bir meşru gövde kurtuluyor ve
yüzey bir artıyor. Beş adlık listede üç gövde geçiyor, iki meşru gövde duruyor. Listeye
bölge adı eklendiğinde dördü geçiyor ve yüzey altıya çıkıyor. Denetim kaldırıldığında beşi
de geçiyor ve yüzey on beş oluyor.

Denetimin kapsamadığı bir durum kalıyor. Gövdesi doğrudan kullanıcı alanından okunan iki
şablonda süslü parantez taşıyan bir metin meşru olabilir — bir rapor başlığında ölçü birimi
ya da bir saha etiketinde alan kodu yazılabilir. Orada izin listesi meşru başlığı reddeder
ve bedel görünür olur; gövdenin bir kullanıcı alanından okunması kararı, izin listesinin
çözemeyeceği bir karardır.

Bu, ilk dersteki izin listesinin şablon gövdesine uygulanmış hâlidir ve aynı özelliği taşır:
listede olmayan meşru bir ad reddedilir. Farkı ölçünün ne olduğudur — orada dışarıda kalan
nokta sayılıyordu, burada **çözülebilen ad sayısı**. İzin listesinin uzunluğu doğrudan
yüzeyin kendisidir; bu yüzden her yeni ad talebi bir yüzey kararıdır ve bir yerde yazılı
olması gerekir.

## Özet

- Ölçülen büyüklük değerlendirme yüzeyidir: şablon işlenirken ortamda çözülebilen ad sayısı.
  Sonda kümesi elle yazılmaz, ortamın kendisinden üretilir.
- Geniş ortamda ifade motoru 15 adın 15'ini çözdü ve bunların 10'u bildirim dışıydı. Ortamı
  daraltmak erişilebilir adı 6'ya indirdi; motoru değere sabitlemek bildirim dışı çözülen adı
  geniş ortamda bile 0 yaptı. İki savunma bağımsızdır.
- On iki şablonun hepsi kullanıcı verisi alıyor; 8'i yalnız değer olarak, 4'ü ifade olarak.
  Gövde kaynağı ayrımı belirleyici: 7 sabit, 3 yapılandırmadan, 2 kullanıcı alanından.
- Toplam yüzey 65 ve tamamı gövdesi dışarıdan gelen 5 şablondan geliyor. Değer motoru ve dar
  ortam yüzeyi 25'e indiriyor; gövdenin de sabitlenmesi 0'a. Üçüncüsünün bedeli, bildirim
  metninin yönetici tarafından düzenlenememesidir.
- Gövdesi sabitlenemeyen şablonda üçüncü savunma gövdenin izin listesinden geçirilmesidir:
  5 adlık listede 5 gövdenin 3'ü geçti ve 2 meşru gövde durdu; liste 6'ya çıkınca 4'ü geçti
  ve yüzey 6 oldu. İzin listesinin uzunluğu doğrudan yüzeyin kendisidir.
- Üç yeni şablonda yüzey mevcut durumda 65'ten 110'a, kalıp altında 25'ten 40'a çıktı, gövdesi
  sabit kurulumda 0'da kaldı. Yüzey şablon sayısıyla değil, gövdesi dışarıdan gelen şablon
  sayısıyla artar.

## Sonraki Adım

Şablon gövdesi bir metindi ve dışarıdan gelmesi yüzeyi büyütüyordu. Aynı soru metin olmayan
girdiler için de sorulur. Saha uygulaması iş emri kapanışında fotoğraf ve ölçüm dosyası
yüklüyor; abone portalı sayaç okuma dökümü için dosya alıyor. Bu girdinin bir sözleşmesi
yoktur — bir bayt dizisidir ve hakkında söylenen her şey istemcinin beyanıdır. Sonraki ders
yükleme akışını denetim katmanı olarak sayar: tür, boyut, ad ve saklama yeri denetimlerinden
kaçı var, hangi denetim tek başına yetmiyor ve saklama yerinin sunulması kaç nokta açıyor.
