---
title: 'Dosya Yükleme Güvenliği'
source: 'https://academia.sh/tr/kurslar/guvenli-kodlama/dosya-yukleme-guvenligi'
course: 'Güvenli Kodlama'
language: tr
updated: '2026-08-17T18:06:42+00:00'
license: 'CC BY-SA 4.0'
---

# Dosya Yükleme Güvenliği

Yükleme akışının denetim katmanı olarak sayılması: on yükleme kalemi beş denetim katmanından tek tek ve katman katman geçirilir, hangi denetimin tek başına yetmediği ve hangisi eksik kaldığında kaç beklenmedik kalemin geçtiği ölçülür, saklama yerinin doğrudan adreslenebilir olmasının açtığı nokta sayılır ve yükleme noktası sayısı arttığında elle yazılan denetim sayısı karşılaştırılır.

Önceki ders şablon gövdesinin dışarıdan gelmesinin yüzeyi nasıl büyüttüğünü ölçtü. Orada
gelen şey en azından bir metindi ve içindeki adlar sayılabiliyordu. Yükleme akışında gelen
şey bir bayt dizisidir ve hakkında söylenen her şey — türü, adı, boyutu — istemcinin
beyanıdır.

Bu ders yükleme akışını **denetim katmanı** olarak sayar. Ölçülen şey tek bir dosyanın
zararlı olup olmadığı değil, katmanların kaçını geçtiğidir: hangi denetim tek başına
yetmiyor, hangisi eksik kaldığında kaç kalem geçiyor ve saklama yerinin seçimi kaç nokta
açıyor.

## Yükleme Kalemleri

Saha uygulaması iş emri kapanışında ölçüm fotoğrafı ve döküm belgesi yüklüyor. Kalem
kümesi kurgudur; her kalemin **baş baytları gerçektir**, geri kalanı modellenmiş bir
boyuttur.

**GC11 (varsayım):** her kalemin beklenen kararı elle etiketlenmiştir ve doğrudur. Sayılar
bu etiket kümesine görelidir; kümede olmayan bir yükleme biçimi hakkında bir şey söylemez.

```js
// kalem.mjs — saha uygulamasindan gelen yukleme kalemleri (kurgu model)
// Her kalemin bas baytlari gercektir; kalemin geri kalani modellenmis boyuttur.
export const IMZA = { "resim-png": [0x89, 0x50, 0x4e, 0x47], "resim-jpeg": [0xff, 0xd8, 0xff],
  "belge-pdf": [0x25, 0x50, 0x44, 0x46], "sikistirilmis": [0x50, 0x4b, 0x03, 0x04] };
const bas = (t) => Uint8Array.from(IMZA[t] ?? [0x6f, 0x6b, 0x0a]);
export const turBul = (b) =>
  Object.entries(IMZA).find(([, s]) => s.every((x, i) => b[i] === x))?.[0] ?? "bilinmeyen";

// [ad (istemcinin verdigi), beyan edilen tur, gercek icerik turu, boyut, beklenen karar]
export const KALEM = [
  ["olcum-kuzey.png", "resim-png", "resim-png", 120_000, "kabul"],
  ["olcum-guney.jpg", "resim-jpeg", "resim-jpeg", 240_000, "kabul"],
  ["dokum-2026-03.pdf", "belge-pdf", "belge-pdf", 380_000, "kabul"],
  ["sayac.png", "resim-png", "resim-jpeg", 90_000, "ret"],
  ["arsiv.png", "resim-png", "sikistirilmis", 150_000, "ret"],
  ["panorama.png", "resim-png", "resim-png", 12_000_000, "ret"],
  ["not.txt", "metin-duz", "bilinmeyen", 2_000, "ret"],
  ["saha/olcum.png", "resim-png", "resim-png", 110_000, "ret"],
  ["olcum-kuzey.png", "resim-png", "resim-png", 130_000, "ret"],
  ["kapak.jpeg", "resim-png", "resim-jpeg", 95_000, "ret"],
].map(([ad, beyan, gercek, boyut, bekleniyor], i) =>
  ({ no: i + 1, ad, beyan, boyut, bekleniyor, veri: bas(gercek) }));

export const IZINLI = ["resim-png", "resim-jpeg", "belge-pdf"];
export const UZANTI = { png: "resim-png", jpg: "resim-jpeg", jpeg: "resim-jpeg", pdf: "belge-pdf" };
export const EN_COK = 5_000_000;
```

Yedi kalem beklenmedik olarak etiketlendi ve hiçbiri zararlı içerik taşımıyor. Bir fotoğraf
yanlış uzantıyla adlandırılmış, bir sıkıştırılmış dosyanın adı resim uzantısı taşıyor, bir
panorama sınırı aşıyor, bir ad yol ayırıcı içeriyor, bir ad daha önce yüklenmiş bir dosyayla
çakışıyor. Bunlar saha kullanımının olağan kazalarıdır; ölçüm de bunları sayar.

## Beş Denetim Katmanı

Her katman tek bir soru sorar ve tek bir şeye bakar. Beyan istemcinin bildirdiği türe,
uzantı dosya adının sonuna, imza gerçek baytlara, boyut sınıra, ad ise saklama adının
belirlenme biçimine bakar.

```js
// denetim.mjs — bes denetim katmani. Her katman tek bir soru sorar.
import { KALEM, IZINLI, UZANTI, EN_COK, turBul } from "./kalem.mjs";

export const KATMAN = {
  beyan:  (k) => IZINLI.includes(k.beyan),
  uzanti: (k) => UZANTI[k.ad.split(".").pop().toLowerCase()] === k.beyan,
  imza:   (k) => turBul(k.veri) === k.beyan,
  boyut:  (k) => k.boyut <= EN_COK,
  ad:     (k, gorulen) => !k.ad.includes("/") && !gorulen.has(k.ad),
};

// Bir denetim kumesini butun kalemlere uygular.
export function kos(kumeAdlari) {
  const gorulen = new Set(), kabuller = [];
  let gecenBeklenmedik = 0, duranMesru = 0;
  for (const k of KALEM) {
    const gecti = kumeAdlari.every((a) => KATMAN[a](k, gorulen));
    if (gecti) { kabuller.push(k); gorulen.add(k.ad); }
    if (gecti && k.bekleniyor === "ret") gecenBeklenmedik++;
    if (!gecti && k.bekleniyor === "kabul") duranMesru++;
  }
  return { kabul: kabuller.length, kabuller, gecenBeklenmedik, duranMesru };
}
```

```js
// kapsama.mjs — tek basina her katman, sonra katman katman ekleme
import { KALEM } from "./kalem.mjs";
import { KATMAN, kos } from "./denetim.mjs";

const AD = Object.keys(KATMAN);
const beklenmedik = KALEM.filter((k) => k.bekleniyor === "ret").length;
console.log(`${KALEM.length} yukleme kalemi: ${KALEM.length - beklenmedik} beklenen, ${beklenmedik} beklenmedik\n`);

console.log(`${"tek basina katman".padEnd(20)}${"kabul".padStart(7)}${"gecen beklenmedik".padStart(19)}${"duran beklenen".padStart(16)}`);
for (const a of AD) {
  const r = kos([a]);
  console.log(a.padEnd(20) + String(r.kabul).padStart(7) + String(r.gecenBeklenmedik).padStart(19) +
    String(r.duranMesru).padStart(16));
}

console.log(`\n${"katman katman".padEnd(34)}${"kabul".padStart(7)}${"gecen beklenmedik".padStart(19)}${"duran beklenen".padStart(16)}`);
for (let i = 1; i <= AD.length; i++) {
  const kume = AD.slice(0, i), r = kos(kume);
  console.log(kume.join(" + ").padEnd(34) + String(r.kabul).padStart(7) +
    String(r.gecenBeklenmedik).padStart(19) + String(r.duranMesru).padStart(16));
}
```

```
10 yukleme kalemi: 3 beklenen, 7 beklenmedik

tek basina katman     kabul  gecen beklenmedik  duran beklenen
beyan                     9                  6               0
uzanti                    8                  5               0
imza                      6                  3               0
boyut                     9                  6               0
ad                        8                  5               0

katman katman                       kabul  gecen beklenmedik  duran beklenen
beyan                                   9                  6               0
beyan + uzanti                          8                  5               0
beyan + uzanti + imza                   6                  3               0
beyan + uzanti + imza + boyut           5                  2               0
beyan + uzanti + imza + boyut + ad      3                  0               0
```

Üst tablo tek başına hiçbir katmanın yetmediğini gösteriyor. En güçlü katman imzadır ve o da
yedi beklenmedik kalemin üçünü durdurup dördünü geçiriyor. Beyan ve boyut altışar kalem
geçiriyor. **Tür denetimi tek başına yetmez** cümlesinin sayısı budur: beyan tek başına
geçen altı kalemden birinde bile içeriğe bakmıyor.

Alt tablo katmanların birbirini nasıl tamamladığını veriyor. Beş katmanla geçen beklenmedik
kalem sıfıra iniyor ve duran beklenen kalem sütunu baştan sona sıfır kalıyor — bu kümede
katmanlar meşru yüklemeleri durdurmuyor. Bedelin sıfır çıkması kümenin dar olmasındandır ve
GC11 bunu söylüyor.

## Eksik Kalan Katman ve Saklama Yeri

Katmanların hangisinin gerçekten yük taşıdığı, onu çıkarınca görülür. Aynı blok saklama
yerini de ölçüyor: kabul edilen kalemler nereye yazılıyor ve o yer dışarıdan adreslenebiliyor
mu.

**GC12 (varsayım):** doğrudan adreslenebilir her kabul edilmiş kalem bir nokta sayılır.
Noktanın açtığı sonuç değil, **sayısı** ölçülür.

```js
// saklama.mjs — eksik katmanin bedeli ve saklama yerinin actigi nokta
import { KALEM } from "./kalem.mjs";
import { KATMAN, kos } from "./denetim.mjs";

const AD = Object.keys(KATMAN), TAM = kos(AD);
console.log(`${"eksik katman".padEnd(16)}${"kabul".padStart(7)}${"gecen beklenmedik".padStart(19)}`);
for (const a of AD) {
  const r = kos(AD.filter((x) => x !== a));
  console.log(a.padEnd(16) + String(r.kabul).padStart(7) + String(r.gecenBeklenmedik).padStart(19));
}
console.log(`${"eksik yok".padEnd(16)}${String(TAM.kabul).padStart(7)}${String(TAM.gecenBeklenmedik).padStart(19)}`);

// Saklama yeri: ayni kabul kumesi, uc saklama bicimi. Saklama adi kalemden turetilir.
const uretilen = (k) => `y-${String(k.no).padStart(4, "0")}.bin`;
const SAKLAMA = [["sunulan dizin + istemci adi", true, (k) => k.ad],
  ["sunulan dizin + uretilen ad", true, uretilen],
  ["sunulmayan depo + araci uc", false, uretilen]];
console.log(`\n${"saklama bicimi".padEnd(30)}${"adreslenebilir".padStart(15)}${"istemci adiyla".padStart(15)}${"yetki noktasi".padStart(14)}`);
for (const [adi, sunulan, adUret] of SAKLAMA) {
  const adreslenebilir = sunulan ? TAM.kabuller.length : 0;
  const istemciAdi = sunulan ? TAM.kabuller.filter((k) => adUret(k) === k.ad).length : 0;
  console.log(adi.padEnd(30) + String(adreslenebilir).padStart(15) +
    String(istemciAdi).padStart(15) + String(sunulan ? 0 : 1).padStart(14));
}
```

```
eksik katman      kabul  gecen beklenmedik
beyan                 3                  0
uzanti                3                  0
imza                  5                  2
boyut                 4                  1
ad                    5                  2
eksik yok             3                  0

saklama bicimi                 adreslenebilir istemci adiyla yetki noktasi
sunulan dizin + istemci adi                 3              3             0
sunulan dizin + uretilen ad                 3              0             0
sunulmayan depo + araci uc                  0              0             1
```

Üst tablo katmanları ağırlıklarına göre ayırıyor. Beyan ve uzantı çıkarıldığında geçen
beklenmedik kalem yine sıfır: bu iki katmanın durdurduğu her şeyi imza da durduruyor. İmza,
ad ve boyut çıkarıldığında ise sırasıyla iki, iki ve bir kalem geçiyor. **Beş katmanın
üçü taşıyıcı, ikisi yedek.** Yedek olmaları gereksiz oldukları anlamına gelmez — ucuzdurlar
ve daha erken reddederler — ama kapsamayı onlar sağlamıyor.

İmzanın taşıyıcı olması, onun yeterli olduğu anlamına gelmez. İmza yalnız ilk birkaç bayta
bakar ve dosyanın geri kalanı hakkında hiçbir şey söylemez; ölçtüğü tek şey beyanın
içerikle tutarlı olup olmadığıdır. Bu ölçüm kapsamının sınırıdır ve yazılması gerekir:
tabloda sıfırlanan sayı **beyanla içerik arasındaki tutarsızlıktır**, dosyanın güvenli
olduğu değil.

Orta tablo saklama kararını ölçüyor. Üç kalem de kabul edildi; değişen tek şey nereye
yazıldıkları. Sunulan bir dizine yazıldıklarında üçü de doğrudan adreslenebilir üç nokta
açıyor. Ad istemciden geliyorsa bu üç adresin üçü de yükleyenin bildiği adreslerdir.
Sunucunun ürettiği ad bu sayıyı sıfıra indiriyor ama nokta sayısını değiştirmiyor.
Sunulmayan depo ve aracı uç kurulumunda adreslenebilir nokta sıfır, buna karşılık bir yetki
denetimi noktası doğuyor — dosya artık bir uçtan geçerek veriliyor ve o uçta kimin neyi
alabileceği sorulmak zorunda.

Saklama kararının kabul kararından bağımsız olması bu ölçümün asıl bulgusudur. Beş katman da
doğru çalışsa ve geçen beklenmedik kalem sıfır olsa bile saklama yeri hâlâ üç nokta açar.
İki karar iki ayrı yerde verilir ve biri ötekini kapatmaz; yükleme akışının kapsaması ancak
ikisi birlikte sayıldığında bilinir. Denetim katmanlarını sayıp saklama yerini saymayan bir
inceleme, sayının yarısını çıkarmış olur.

## Yükleme Noktası Envanteri

Buraya kadarki bütün ölçüm tek bir yükleme noktası üzerindeydi. Bölgesel ölçüm ağında beş
yükleme noktası var ve her birinde katmanlar ayrı ayrı yazılmış.

```js
// noktalar.mjs — yukleme noktasi envanteri: hangi noktada hangi katman elle yazilmis
import { KALEM } from "./kalem.mjs";
import { KATMAN, kos } from "./denetim.mjs";

const AD = Object.keys(KATMAN);
// [servis, yukleme noktasi, o noktada elle yazilmis katmanlar]
const NOKTA = [
  ["saha", "is emri eki", ["beyan", "uzanti", "imza", "boyut", "ad"]],
  ["saha", "fotograf", ["beyan", "uzanti", "imza"]],
  ["abone portali", "belge", ["beyan", "boyut"]],
  ["abone portali", "okuma dokumu", ["beyan", "uzanti", "boyut", "ad"]],
  ["fatura", "itiraz eki", ["beyan"]],
];

console.log(`${NOKTA.length} yukleme noktasi, ${AD.length} katman, ${KALEM.length} kalem\n`);
console.log(`${"yukleme noktasi".padEnd(16)}${"yazilan katman".padStart(15)}${"eksik".padStart(7)}${"gecen beklenmedik".padStart(19)}`);
let elle = 0, gecen = 0, tam = 0;
for (const [, n, k] of NOKTA) {
  const r = kos(k);
  elle += k.length; gecen += r.gecenBeklenmedik; tam += k.length === AD.length ? 1 : 0;
  console.log(n.padEnd(16) + String(k.length).padStart(15) + String(AD.length - k.length).padStart(7) +
    String(r.gecenBeklenmedik).padStart(19));
}
console.log(`\n${"kurulum".padEnd(30)}${"yazilan denetim".padStart(16)}${"tam kapsanan nokta".padStart(20)}${"gecen beklenmedik".padStart(19)}`);
console.log("nokta basina elle yazim".padEnd(30) + String(elle).padStart(16) +
  String(tam).padStart(20) + String(gecen).padStart(19));
const m = kos(AD).gecenBeklenmedik * NOKTA.length;
console.log("merkezi yukleme kalibi".padEnd(30) + String(AD.length).padStart(16) +
  String(NOKTA.length).padStart(20) + String(m).padStart(19));
console.log(`\nyeni yukleme noktasi eklendiginde yazilacak denetim: elle ${AD.length}, merkezi kalipta 0`);
```

```
5 yukleme noktasi, 5 katman, 10 kalem

yukleme noktasi  yazilan katman  eksik  gecen beklenmedik
is emri eki                   5      0                  0
fotograf                      3      2                  3
belge                         2      3                  5
okuma dokumu                  4      1                  2
itiraz eki                    1      4                  6

kurulum                        yazilan denetim  tam kapsanan nokta  gecen beklenmedik
nokta basina elle yazim                     15                   1                 16
merkezi yukleme kalibi                       5                   5                  0

yeni yukleme noktasi eklendiginde yazilacak denetim: elle 5, merkezi kalipta 0
```

Beş noktanın yalnız biri beş katmanın hepsini taşıyor. Kalan dördünde toplam on eksik katman
var ve aynı on kalem kümesi bu noktalardan geçtiğinde on altı beklenmedik kabul doğuyor. En
zayıf nokta olan itiraz eki tek bir katman taşıyor ve yedi beklenmedik kalemin altısını
kabul ediyor.

İki kurulum arasındaki fark yazılan denetim sayısında da görünüyor. Nokta başına elle yazımda
on beş denetim yazılmış ve tam kapsanan nokta bir; merkezî kalıpta beş denetim tek yerde
yazılıyor ve beş noktanın beşi de kapsanıyor. **Aynı kapsama üç kat daha az kodla elde
ediliyor** ve yeni bir yükleme noktası eklendiğinde yazılacak denetim sayısı sıfır kalıyor.

## Özet

- On yükleme kaleminin 7'si beklenmedik olarak etiketlendi ve hiçbiri zararlı içerik
  taşımıyor; yanlış uzantı, yanlış beyan, sınır aşımı, yol ayırıcılı ad ve ad çakışması var.
- Tek başına hiçbir katman yetmedi: en güçlüsü olan imza 7 beklenmedik kalemin 3'ünü durdurup
  4'ünü geçirdi, beyan ve boyut 6'şar kalem geçirdi. Beş katman birlikte geçen beklenmedik
  kalemi 0'a indirdi ve hiçbir aşamada meşru kalem durmadı.
- Katmanlar eşit ağırlıkta değil: beyan ve uzantı çıkarıldığında geçen kalem 0 kalıyor, imza
  ve ad çıkarıldığında 2'şer, boyut çıkarıldığında 1 kalem geçiyor. Üç katman taşıyıcı, ikisi
  yedek.
- Saklama yeri kabul kararından bağımsız bir noktadır: sunulan dizinde 3 kabul edilmiş kalem
  3 adreslenebilir nokta açıyor. Sunucunun ürettiği ad tahmin edilebilir adresi 0'a indiriyor;
  sunulmayan depo adreslenebilir noktayı 0'a indirip 1 yetki denetimi noktası ekliyor.
- Beş yükleme noktasının yalnız 1'i beş katmanı da taşıyor; kalan dörtte 10 eksik katman var
  ve aynı kalem kümesi 16 beklenmedik kabul üretiyor. Nokta başına elle yazımda 15 denetim
  yazılmış, merkezî kalıpta 5 denetim beş noktayı da kapsıyor ve yeni nokta için yazılacak
  denetim 0'dır.

## Sonraki Adım

Yükleme akışında dışarıdan gelen şey bir bayt dizisiydi ve sunucu onu yalnız saklıyordu.
Bazı akışlarda sunucu daha ileri gider: dışarıdan gelen bir adrese kendisi istek yapar.
Rapor üretici abone portalının verdiği bağlantıdan belge çekiyor, saha uygulaması harita
karosu için dış bir uca gidiyor. O istekleri yapan taraf sunucudur ve sunucunun ağdaki yeri
istemcininkinden başkadır. Sonraki ders giden istek hedeflerini bir kısıt kümesinden geçirir
ve tek soruyu ölçer: kaç hedef iç ağa ulaşabiliyor, kısıt hangi noktada uygulandığında
kapsama düşüyor.
