10.7 Security by design

← 10. DevSec – Security alapok

10.7 Security by design – fejlesztési szokások

Áttekintő

Az előző alfejezetekben konkrét sérülékenységekre koncentráltunk: SQL injection, XSS, jelszókezelés. Ez mind fontos. De a valódi védekezés nem az, hogy emlékszel egy listára – hanem az, hogy gondolkodásmódot váltasz.

A security by design azt jelenti: nem a kód végén gondolsz a biztonságra (amikor már késő), hanem beépíted a napi fejlesztési rutinodba. Ez nem bonyolultabb kód – ez más hozzáállás.

Ez az alfejezet nem új sérülékenységeket tanít. Inkább megmutatja, hogyan épülnek be ezek az elvek a mindennapi munkába – és miért nem elég csak "tudni róluk".


Részletes leírás

Least privilege elv – csak annyi jogosultság, amennyi kell

Ez az egyik legegyszerűbb biztonsági elv, és az egyik legritkábban alkalmazott.

Az alapgondolat: minden komponensnek, felhasználónak, és folyamatnak csak annyi hozzáférése legyen, amennyi a feladata elvégzéséhez szükséges – és semmi több.

Adatbázis szinten

A legtöbb alkalmazás egyetlen adatbázis-felhasználót használ mindenre. Ez rossz szokás.

-- Rossz: az app user mindent megcsinálhat
GRANT ALL PRIVILEGES ON mydb.* TO 'appuser'@'localhost';

-- Jobb: az app csak olvasni és írni tud, struktúrát módosítani nem
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'appuser'@'localhost';

-- Ideális: külön user a migrációkhoz (csak deploy alatt fut)
GRANT ALL PRIVILEGES ON mydb.* TO 'migrationuser'@'localhost';

Ha az alkalmazásod adatbázis-felhasználója nem tud DROP TABLE-t végrehajtani, akkor egy SQL injection sem fogja tudni. Ez nem helyettesíti a paraméteres lekérdezéseket – de extra védelmet nyújt.

API kulcsok és jogosultságok

# Rossz: teljes hozzáférésű API kulcs, mert "egyszerűbb"
API_KEY = "sk-fulladmin-key-with-all-permissions"

# Jobb: csak az adott feladathoz szükséges jogosultságú kulcs
# Pl. egy email küldő service-nek csak "send email" jog kell,
# nem kell listázni az összes kontaktot, nem kell törölni, stb.
EMAIL_API_KEY = "sk-send-only-key"

Fájlrendszer szinten

# Az app nem kell hogy írjon a teljes /var/www/-be
# Csak a szükséges könyvtárakhoz adj írási jogot
chmod 755 /var/www/myapp
chmod 775 /var/www/myapp/storage  # csak ide kell írás
chmod 755 /var/www/myapp/public

A kérdés, amit mindig tegyél fel magadnak

"Ez a komponens/felhasználó/szolgáltatás tényleg szüksége van erre a jogosultságra?"

Ha nem vagy biztos benne – add kevesebbet. Mindig könnyebb utólag bővíteni, mint visszaszedni a következményeket.


Dependency auditálás – a kódod amit nem te írtál

Egy modern projekt százszor annyi kódot tartalmaz függőségek formájában, mint amennyit te írtál. Ezek mindegyike potenciális biztonsági kockázat.

A jó hír: az eszközök már elvégzik a munkát helyetted. Csak futtatni kell őket.

# Node.js / npm
npm audit
npm audit fix  # automatikusan javítja, ahol lehetséges

# Python / pip
pip-audit  # telepítés: pip install pip-audit
safety check  # alternatíva

# PHP / Composer
composer audit  # Composer 2.4+ óta beépített

# Ruby
bundle audit  # gem install bundler-audit

Mit csinálj a riporttal?

found 3 vulnerabilities (1 moderate, 2 high)

  high severity vulnerability
  Package: lodash
  Dependency of: mypackage
  Path: mypackage > lodash
  More info: https://npmjs.com/advisories/1523
  1. Olvasd el – nem minden "high" kritikus a te use case-edben
  2. Frissítsd a package-t, ha van javított verzió
  3. Ha nem tudsz frissíteni (breaking change, stb.) – dokumentáld, miért nem, és mikor tervezed
  4. Ne ignoráld – egy "majd egyszer" issue production incidenssé válik

CI/CD integráció

A legjobb, ha ez automatikusan fut:

# GitHub Actions példa
- name: Security audit
  run: npm audit --audit-level=high
  # Sikertelen build, ha high vagy critical sérülékenység van

Ez nem te vagy paranoiás – ez iparági standard.


Security headerek – ingyenes védelem

HTTP válasz headerek segítségével a böngészőt arra utasíthatod, hogy bizonyos biztonsági korlátozásokat alkalmazzon. Ez nem helyettesíti a szerver oldali védelmet, de extra réteget ad – és szinte ingyenes.

A legfontosabb headerek

# Content-Security-Policy – megakadályozza az inline script injekciót
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'

# X-Content-Type-Options – megakadályozza a MIME type sniffinget
X-Content-Type-Options: nosniff

# X-Frame-Options – clickjacking védelem
X-Frame-Options: DENY

# Strict-Transport-Security – HTTPS-t kényszerít ki
Strict-Transport-Security: max-age=31536000; includeSubDomains

# Referrer-Policy – kontrollálja, mi kerül a Referer headerbe
Referrer-Policy: strict-origin-when-cross-origin

Implementáció példák

# Flask (Python)
from flask import Flask
from flask_talisman import Talisman

app = Flask(__name__)
Talisman(app)  # alapértelmezetten beállítja a legfontosabb headereket
// Express (Node.js)
const helmet = require('helmet');
app.use(helmet());  // egy sor, és a legfontosabb headerek be vannak állítva
// Laravel (PHP) – middleware-ben vagy globálisan
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block');

// Vagy Laravel middleware-rel (ajánlott)

Hogyan ellenőrzöd?

Menj a securityheaders.com oldalra, add meg az URL-ed, és azonnal látod, mi hiányzik. Céld az "A" vagy "A+" értékelés.


Informatív hibaüzenetek – a fejlesztő barátja, a hacker segítője

Ez az egyik leggyakoribb hiba, amit juniorok elkövetnek: a hibaüzenet, ami rengeteget segít debuggoláskor, ugyanannyit segít a támadónak is.

Mi a probléma?

# Rossz – részletes stack trace a felhasználónak
try:
    user = db.query(f"SELECT * FROM users WHERE email = '{email}'")
except Exception as e:
    return {"error": str(e)}  # pl: "psycopg2.errors.SyntaxError: syntax error at or near..."
    # Ez elárulja: adatbázis típus, tábla struktúra, hogy SQL injection működött
// Rossz
app.get('/user/:id', async (req, res) => {
    try {
        const user = await db.findById(req.params.id);
        res.json(user);
    } catch (err) {
        res.status(500).json({ error: err.message }); // Stack trace production-ban!
    }
});

A megoldás: két réteg

1. réteg – amit a felhasználó lát: általános, semmitmondó üzenet

{
    "error": "Váratlan hiba történt. Kérjük, próbálja újra később.",
    "error_id": "a3f9c2"
}

2. réteg – amit csak ti láttok: részletes log a szerveren

import logging
import uuid

logger = logging.getLogger(__name__)

try:
    result = process_payment(data)
except Exception as e:
    error_id = str(uuid.uuid4())[:6]
    logger.error(f"Payment error [{error_id}]: {str(e)}", exc_info=True)
    return {"error": "A fizetés feldolgozása sikertelen.", "error_id": error_id}

Az error_id segít: a felhasználó megmondja nektek, és ti megtaláljátok a logban – anélkül, hogy érzékeny infót adnátok ki.

Amit SOHA ne adj vissza API-ban

  • Stack trace
  • Adatbázis hibaüzenet (tábla nevekkel, oszlop nevekkel)
  • Fájlrendszer elérési utak
  • Belső IP-k, hostname-ek
  • Dependency verziókat, framework neveket

Security checklist PR-hoz

Ez az egyik leghasznosabb szokás, amit felvehetsz: mielőtt beküldöd a PR-odat, fuss végig egy rövid listán.

Nem kell órákat tölteni – 5 perc, és sok hibát megelőzhetsz.

Ajánlott PR security checklist

## Security checklist (PR beküldés előtt)

### Input kezelés
- [ ] Minden felhasználói input validálva van (típus, hossz, formátum)
- [ ] Paraméteres lekérdezéseket használok (nem string interpolációt SQL-ben)
- [ ] Output encoding történik HTML-be kerülő adatoknál

### Hozzáférés-kezelés
- [ ] Az endpoint ellenőrzi, hogy a felhasználó be van-e jelentkezve (ha kell)
- [ ] Az endpoint ellenőrzi, hogy a felhasználónak van-e joga az adott művelethez
- [ ] Nincs "admin" funkció ami publikusan elérhető

### Érzékeny adatok
- [ ] Nincs jelszó, API kulcs, token hardcode-olva a kódban
- [ ] Érzékeny adatok nem kerülnek logba (jelszó, teljes hitelkártyaszám, stb.)
- [ ] Environment változókon keresztül jönnek a titkos adatok

### Hibakezelés
- [ ] Hibaüzenetek nem árulnak el belső struktúrát a felhasználónak
- [ ] Minden exception le van kezelve (nincs unhandled exception production-ban)

### Dependency-k
- [ ] Új dependency hozzáadásakor: `npm audit` / `pip-audit` / `composer audit` futtatva
- [ ] Nincs ismert sérülékenységű package hozzáadva

### Általános
- [ ] A feature a "least privilege" elvét követi
- [ ] Security headerek érintve vannak? (ha igen, megfelelően be vannak állítva)

Ez a lista testre szabható – minden csapat hozzáadja, ami releváns számukra.


A security by design gondolkodásmód a gyakorlatban

Összefoglalva: nem arról van szó, hogy minden kódsoron paranoiás legyél. Hanem arról, hogy beépíted ezeket a gondolatokat a normál munkafolyamatodba.

TevékenységSecurity by design hozzáállás
Új endpoint írása"Kell ide authorizáció?"
Új package felvétele"Van ismert sérülékenysége?"
Exception kezelése"Mit lát ebből a felhasználó?"
DB user konfigurálása"Kell ide write jog, vagy csak read?"
PR beküldéseGyors checklist futtatás
Deploy előttAudit eszköz futtatás

Ez nem extra munka – ez egy szokás, ami rövid idő alatt automatikussá válik.


Életszerű példák

Példa 1: A "csak gyorsan csináljuk meg" trap

Bence egy új regisztrációs feature-t kap: a felhasználó megadja az email-t, és kap egy megerősítő linket.

Gyors megoldás (amit sok junior csinál):

@app.post("/register")
def register():
    email = request.json['email']
    # Nincs validáció, nincs rate limiting, nincs input sanitization
    db.execute(f"INSERT INTO users (email) VALUES ('{email}')")
    send_confirmation(email)
    return {"status": "ok"}

Problémák:

  • SQL injection lehetséges
  • Nincs email formátum validáció
  • Nincs rate limiting (bárki 10000 regisztrációt küldhet)
  • Exception esetén stack trace megy ki

Security by design megközelítés:

from email_validator import validate_email, EmailNotValidError
import logging

logger = logging.getLogger(__name__)

@app.post("/register")
@rate_limit("10 per minute")  # rate limiting middleware
def register():
    try:
        email = request.json.get('email', '')
        
        # Validáció
        validated = validate_email(email)
        clean_email = validated.email
        
        # Paraméteres lekérdezés
        db.execute("INSERT INTO users (email) VALUES (?)", [clean_email])
        send_confirmation(clean_email)
        return {"status": "ok"}
        
    except EmailNotValidError:
        return {"error": "Érvénytelen email cím formátum."}, 400
    except Exception as e:
        logger.error(f"Registration error: {str(e)}", exc_info=True)
        return {"error": "Regisztráció sikertelen. Kérjük, próbálja újra."}, 500

Ez nem bonyolultabb – csak tudatosabb.


Példa 2: Az "örököltem ezt a kódot" szituáció

Kata egy régi projektre kerül, és az első npm audit futtatása után ezt látja:

found 47 vulnerabilities (3 low, 28 moderate, 14 high, 2 critical)

Rossz reakció: "Nem én írtam, nem az én gondom."

Helyes reakció:

  1. Megmutatja a senior-nak / tech lead-nek
  2. Prioritizálják: critical és high elsőként
  3. Létrehoznak egy ticketet a technikai adósságra
  4. Fokozatosan oldják meg (nem kell egyszerre mind)

Nem kellett mindent egyedül megoldania – de fel kellett ismernie és eskalálnia.


Példa 3: PR review security szemmel

Ádám reviewolja Zita PR-ját. A kódban látja:

@app.get("/admin/users")
def list_all_users():
    users = db.execute("SELECT * FROM users")
    return jsonify(users)

Ádám megjegyzi: "Hiányzik az authorizáció ellenőrzés. Ez az endpoint publikusan elérhető – bárki listázhatja az összes felhasználót. Kell egy @admin_required decorator."

Ez nem személyes kritika – ez security by design a review folyamatban.


Feladatok és tesztek

Példafeladat: PR security checklist alkalmazása

Szituáció: A csapatod egy új "jelszó megváltoztatása" feature-t fejlesztett. Az alábbi PR leírás alapján alkalmazd a security checklisztet:


PR leírás:

Implementáltam a jelszó megváltoztatása funkciót. A felhasználó megadja a régi jelszavát, majd kétszer az újat (megerősítés). Ha egyeznek, frissítjük az adatbázisban.

Kód (részlet):

@app.post("/change-password")
def change_password():
    old_password = request.json['old_password']
    new_password = request.json['new_password']
    user_id = request.json['user_id']  # kliens küldi
    
    user = db.get_user(user_id)
    if user['password'] == old_password:  # plaintext összehasonlítás
        db.execute(f"UPDATE users SET password='{new_password}' WHERE id={user_id}")
        return {"status": "changed"}
    else:
        return {"error": "Wrong password", "stored_hash": user['password']}  # debug célból

Feladat: Menj végig a security checkliszten, és azonosítsd az összes biztonsági problémát. Legalább 5 különböző problémát kellene találnod. Minden problémánál írd le, hogyan javítanád.


Tesztkérdések

1. kérdés – Least privilege elv

Az alkalmazásod adatbázis-felhasználójának az alábbi jogosultságai vannak: SELECT, INSERT, UPDATE, DELETE, DROP, CREATE, ALTER. Normál üzemelés közben az app csak adatot olvas és ír. Helyes ez a konfiguráció?

  • A) Igen, jobb ha mindent megadunk, nehogy baj legyen
  • B) Nem – a DROP, CREATE, ALTER jogokat el kell venni, ezek csak migráció alatt kellenek
  • C) Részben – a DROP felesleges, de a CREATE és ALTER kell
  • D) Teljesen mindegy, mert SQL injection ellen paraméteres lekérdezéssel védve vagyunk
Helyes válasz

B – Least privilege elvnél fogva az alkalmazás csak azt teheti, amire tényleg szüksége van. A struktúramódosító jogokat (DROP, CREATE, ALTER) érdemes egy külön migration user-nek adni, amit csak deployment alatt használsz.


2. kérdés – Hibaüzenetek

Egy API endpoint az alábbi hibát adja vissza, ha az adatbázis-lekérdezés sikertelen:

{
  "error": "psycopg2.errors.UndefinedColumn: column \"passw\" does not exist\nLINE 1: SELECT id, email, passw FROM users WHERE email = $1"
}

Mi a probléma ezzel?

  • A) Semmi, a fejlesztőknek szükségük van erre az infóra
  • B) A hibaüzenet elárul tábla neveket, oszlop neveket és adatbázis technológiát
  • C) A JSON formátum nem megfelelő
  • D) A hibaüzenet túl rövid
Helyes válasz

B – A részletes stack trace és SQL hibaüzenet elárulja: adatbázis típus (psycopg2 → PostgreSQL), tábla neve (users), oszlop nevek (id, email). Ez rengeteg hasznos információ egy potenciális támadónak. A felhasználónak általános hibaüzenet kell; a részletes log csak szerveren, belső logban legyen.


3. kérdés – Dependency audit

Felveszel egy új npm csomagot a projektbe. Mikor futtatsz npm audit-ot?

  • A) Soha – az npm automatikusan figyelmeztet
  • B) Csak akkor, ha a package ismert és népszerű (pl. lodash, express)
  • C) Minden új dependency felvétele után, és ideálisan CI/CD-ben is
  • D) Évente egyszer, release előtt
Helyes válasz

C – Az audit minden dependency felvétel után ajánlott, mert a sérülékenységek bármikor megjelenhetnek, és a függőségek is hoznak tranzitív dependency-ket. A CI/CD integráció biztosítja, hogy ne kerüljön ismert sérülékenységgel rendelkező package production-ba.


4. kérdés – Security headerek

Melyik HTTP header segít megelőzni a clickjacking támadásokat?

  • A) Content-Security-Policy
  • B) X-Frame-Options
  • C) Strict-Transport-Security
  • D) X-Content-Type-Options
Helyes válasz

B – Az X-Frame-Options: DENY megakadályozza, hogy az oldalad iframe-be ágyazható legyen, ami a clickjacking támadás alapja. A Content-Security-Policy frame-ancestors direktívája is használható ugyanerre (és modernebb megoldás).


5. kérdés – Security by design gondolkodásmód

Egy kollégád azt mondja: "Nem kell a security checklisztet mindig végigcsinálni, csak az érzékeny feature-öknél, pl. autentikáció és fizetés." Helyes ez?

  • A) Igen, teljesen helyes – a többi feature nem kritikus
  • B) Nem – minden PR-nál érdemes legalább egy gyors ellenőrzést végezni, mert a sérülékenységek váratlan helyeken is megjelennek
  • C) Részben – az autentikáció és fizetés mellett elég az admin felületek
  • D) Igen, de hozzáadnám az admin funkciókat is
Helyes válasz

B – A sérülékenységek ritkán ott vannak, ahol várjuk. Egy "egyszerű" listázó oldal is lehet sebezhető (hiányzó authorizáció, érzékeny adat kiszivárgás stb.). A checklist gyors (5 perc), és megakadályoz sok "hogyan kerülhetett ez production-ba?" szituációt.


6. kérdés – Least privilege a kódban

Egy email küldő service API kulcsát tárolod. A service lehetővé teszi, hogy olvasási, küldési, és törlési jogosultságot is adj az API kulcsnak. Melyiket választod?

  • A) Mindhárom jogosultság – hátha kell majd valamire
  • B) Csak küldési – a service-nek csak ez a feladata
  • C) Küldési és olvasási – a logoláshoz hasznos lehet
  • D) Teljesen mindegy, az API kulcs úgyis titkos
Helyes válasz

B – Least privilege elv: ha a service csak emailt küld, csak küldési jog kell. Ha kompromittálódik az API kulcs, a támadó így sem tud olvasni vagy törölni. Az "ami kell majd valamire" gondolkodás a privilege creep forrása.


7. kérdés – Hibakezelés

A következő kód mit csinál jól, és mi a probléma?

app.get('/profile', (req, res) => {
    try {
        const user = getUserFromToken(req.headers.authorization);
        res.json(user);
    } catch (err) {
        console.error(err);
        res.status(500).json({ message: err.message });
    }
});
  • A) Teljesen helyes – logol és visszaadja a hibát
  • B) A logolás jó, de az err.message nem mehet a response-ba
  • C) A console.error nem elég – fájlba is kell logolni
  • D) Nincs baj, a message mező általában nem érzékeny
Helyes válasz

B – A console.error logolás jó irány, de az err.message production-ban nem kerülhet a response-ba. Tartalmazhat stack trace részleteket, belső fájlneveket, vagy más érzékeny infót. A felhasználónak általános hibaüzenet kell, a részletek maradjanak a szerveren.


8. kérdés – Checklist alkalmazása

Melyik szituáció NEM role-based access control probléma, de security szempontból mégis fontos?

  • A) Egy admin endpoint elérhető bejelentkezés nélkül
  • B) Egy npm audit 2 critical sérülékenységet jelez, de nem lett javítva
  • C) A felhasználó más felhasználók adatait is eléri
  • D) Az autentikáció JWT helyett session cookie-t használ
Helyes válasz

B – A dependency sérülékenység nem jogosultság-kezelési probléma, de security szempontból kritikus. A 2 critical sérülékenység aktív kihasználást jelent: ha valaki tudja, melyik verzió fut, pontosan tudja, hogyan kell kihasználni.

Scroll to Top