İçeriğe geç
academia.sh

Ders 05 / 17

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.

İçindekiler

Ö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.

// 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.

// 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 };
}
// 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.

// 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ış.

// 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.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat