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
- Olvasd el – nem minden "high" kritikus a te use case-edben
- Frissítsd a package-t, ha van javított verzió
- Ha nem tudsz frissíteni (breaking change, stb.) – dokumentáld, miért nem, és mikor tervezed
- 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ég | Security 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ése | Gyors checklist futtatás |
| Deploy előtt | Audit 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ó:
- Megmutatja a senior-nak / tech lead-nek
- Prioritizálják: critical és high elsőként
- Létrehoznak egy ticketet a technikai adósságra
- 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, ALTERjogokat el kell venni, ezek csak migráció alatt kellenek - C) Részben – a
DROPfelesleges, de aCREATEésALTERkell - 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.messagenem mehet a response-ba - C) A
console.errornem elég – fájlba is kell logolni - D) Nincs baj, a
messagemező á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 audit2 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.