OWASP Top 10, injeksjon, XSS og inputvalidering.
Sikker programmering handler om å skrive kode som er resistent mot angrep. Mange sikkerhetshull oppstår fordi utviklere stoler for mye på brukerinput eller glemmer å implementere grunnleggende sikkerhetstiltak.
I dette kapittelet skal vi se på de vanligste sikkerhetssårbarhetene i webapplikasjoner og hvordan du som utvikler kan beskytte deg mot dem. Vi fokuserer spesielt på input-validering, SQL injection, XSS og CSRF.
Det viktigste prinsippet i sikker programmering er: Aldri stol på brukerinput.
All data som kommer fra brukere - enten det er fra skjemaer, URL-parametere, cookies eller filer - må valideres og saniteres før det brukes.
Typer validering:
1. Klientside-validering (JavaScript)
- Rask feedback til brukeren
- Kan enkelt omgås
- Aldri stol på denne alene!
2. Serverside-validering (Python/Node.js)
- Obligatorisk sikkerhetstiltak
- Kan ikke omgås av brukeren
- Siste forsvarslinje
3. Database-validering
- Ekstra lag med sikkerhet
- Constraints og foreign keys
Valideringsteknikker:
Whitelist (tillatte verdier):
# Kun tillat spesifikke verdier
gyldig_sort = ['navn', 'dato', 'pris']
if sortering in gyldig_sort:
# Sikker å bruke
sorter_etter(sortering)Type-sjekk:
# Sjekk at input er riktig datatype
alder = request.form.get('alder')
try:
alder = int(alder)
if 0 < alder < 120:
# Gyldig alder
lagre_alder(alder)
except ValueError:
return "Ugyldig alder"Lengdebegrensning:
# Begrens lengde på input
navn = request.form.get('navn', '')
if len(navn) > 100:
return "Navn for langt"Regex-validering:
import re
# Valider e-postformat
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if re.match(email_pattern, email):
# Gyldig e-post
send_email(email)SQL injection er en av de mest alvorlige sårbarhetene (A03 i OWASP Top 10). Den oppstår når angripere kan injisere SQL-kode gjennom brukerinput.
Usikker kode:
# ALDRI gjør dette!
brukernavn = request.form.get('brukernavn')
query = f"SELECT * FROM brukere WHERE brukernavn = '{brukernavn}'"
cursor.execute(query)Angrep:
Hvis en angriper skriver inn: ' OR '1'='1
Blir spørringen: SELECT * FROM brukere WHERE brukernavn = '' OR '1'='1'
Dette returnerer alle brukere!
Enda verre: '; DROP TABLE brukere; --
Dette kan slette hele tabellen.
Sikker kode med parameteriserte spørringer:
# SQLite (Python)
brukernavn = request.form.get('brukernavn')
cursor.execute(
"SELECT * FROM brukere WHERE brukernavn = ?",
(brukernavn,)
)
# PostgreSQL med psycopg2
cursor.execute(
"SELECT * FROM brukere WHERE brukernavn = %s",
(brukernavn,)
)Hvorfor dette er sikkert:
- Databasen skiller mellom SQL-kode og data
- Input behandles alltid som data, aldri som kode
- Spesialtegn escapes automatisk
ORM (Object-Relational Mapping):
Bruk av ORM som SQLAlchemy gjør det lettere å unngå SQL injection:
from sqlalchemy.orm import Session
from models import Bruker
# Sikkert med ORM
bruker = session.query(Bruker).filter(
Bruker.brukernavn == brukernavn
).first()XSS oppstår når en angriper kan injisere skadelig JavaScript i en webside som andre brukere ser.
Typer XSS:
1. Stored XSS (lagret)
Ondsinnet kode lagres i database og vises til alle brukere.
Eksempel: Kommentarfelt på blogg
2. Reflected XSS (reflektert)
Ondsinnet kode sendes som parameter og reflekteres tilbake til brukeren.
Eksempel: Søkeresultat-side
3. DOM-based XSS
Sårbarheten ligger i klientside JavaScript-kode.
Eksempel på angrep:
<!-- Usikker visning av brukerinput -->
<p>Velkommen, {{ brukernavn }}</p>Hvis brukernavn er: <script>alert(document.cookie)</script>
Vil scriptet kjøres i nettleseren til alle som ser siden.
Angriper kan:
- Stjele cookies og sesjonstokens
- Omdirigere til phishing-sider
- Endre innhold på siden
- Kjøre keyloggers
Beskyttelse mot XSS:
1. Output encoding/escaping:
# Flask escaper automatisk i Jinja2-templates
# Men sørg for at autoescaping er på:
{{ brukernavn }} # Sikkert
# Ikke bruk |safe uten grunn:
{{ bruker_html|safe }} # Farlig!HTML-escaping gjør:
- < blir til <
- > blir til >
- " blir til "
- ' blir til '
- & blir til &
2. Content Security Policy (CSP):
# Flask
@app.after_request
def set_csp(response):
response.headers['Content-Security-Policy'] = \
"default-src 'self'; script-src 'self'"
return responseDette hindrer nettleseren i å kjøre inline scripts og scripts fra andre domener.
3. Sanitering av HTML:
Hvis du MÅ tillate HTML-input (f.eks. rich text editor):
import bleach
# Tillat bare sikre tags
allowed_tags = ['p', 'b', 'i', 'u', 'a']
allowed_attrs = {'a': ['href', 'title']}
ren_html = bleach.clean(
bruker_html,
tags=allowed_tags,
attributes=allowed_attrs,
strip=True
)CSRF (uttales "sea-surf") er et angrep der en ondsinnet side lurer nettleseren til å sende forespørsler til en annen side der brukeren er innlogget.
Eksempel på angrep:
Du er innlogget på bank.no. Du besøker ondsinnet.com som inneholder:
<img src="https://bank.no/overforing?til=angriper&belop=10000">Nettleseren sender automatisk din innloggede sesjon med forespørselen!
Beskyttelse: CSRF-tokens
# Flask med Flask-WTF
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
class OverforingForm(FlaskForm):
til_konto = StringField('Til konto')
belop = StringField('Beløp')
submit = SubmitField('Overfør')
@app.route('/overforing', methods=['GET', 'POST'])
def overforing():
form = OverforingForm()
if form.validate_on_submit():
# CSRF-token er validert
# Trygt å utføre overføring
utfor_overforing(form.til_konto.data, form.belop.data)
return render_template('overforing.html', form=form)I template:
<form method="POST">
{{ form.csrf_token }} <!-- Automatisk CSRF-token -->
{{ form.til_konto.label }} {{ form.til_konto }}
{{ form.belop.label }} {{ form.belop }}
{{ form.submit }}
</form>Hvordan CSRF-token fungerer:
1. Server genererer unikt, hemmelig token for hver sesjon
2. Token inkluderes i alle skjemaer som hidden field
3. Ved POST-forespørsel sjekker server at token matcher
4. Ondsinne sider kan ikke kjenne til tokenet
Andre CSRF-beskyttelser:
- SameSite cookies: Forhindrer sending av cookies på cross-site forespørsler
- Double Submit Cookie: Send token både som cookie og i form
- Custom headers: Krever X-Requested-With header for API-kall
Input-håndtering:
- [ ] All brukerinput valideres på server-side
- [ ] Whitelist brukes der mulig
- [ ] Type og lengde sjekkes
- [ ] Feilmeldinger avslører ikke sensitiv info
Database:
- [ ] Parameteriserte spørringer brukes konsekvent
- [ ] Minste privilegium-prinsippet (database-bruker har kun nødvendige rettigheter)
- [ ] Sensitive data krypteres
Autentisering og autorisasjon:
- [ ] Passord hashes (aldri lagret i klartekst)
- [ ] Sesjoner har timeout
- [ ] Autorisasjon sjekkes for hver handling
- [ ] Brute force-beskyttelse implementert
Output:
- [ ] All output escapes/encodes
- [ ] CSP-header satt
- [ ] Ingen sensitive data i HTML-kommentarer eller JavaScript
CSRF-beskyttelse:
- [ ] CSRF-tokens på alle state-changing operasjoner
- [ ] SameSite cookies brukes
HTTPS og transport:
- [ ] All kommunikasjon over HTTPS
- [ ] Secure og HttpOnly flags på cookies
- [ ] HSTS-header aktivert
Dependencies:
- [ ] Alle avhengigheter er oppdaterte
- [ ] Ingen kjente sårbarheter (bruk npm audit eller pip-audit)
Logging og overvåkning:
- [ ] Sikkerhetshendelser logges
- [ ] Ingen passord eller tokens i logger
- [ ] Varsling ved mistenkelig aktivitet
La oss implementere en sikker funksjon for å legge til bloggkommentarer:
from flask import Flask, request, render_template, redirect, session
from flask_wtf import FlaskForm
from wtforms import TextAreaField, SubmitField
from wtforms.validators import DataRequired, Length
import sqlite3
import bleach
from datetime import datetime
app = Flask(__name__)
app.config['SECRET_KEY'] = 'din-hemmelige-nøkkel-her'
class KommentarForm(FlaskForm):
innhold = TextAreaField(
'Kommentar',
validators=[
DataRequired(message='Kommentar kan ikke være tom'),
Length(min=1, max=1000, message='Kommentar må være mellom 1-1000 tegn')
]
)
submit = SubmitField('Legg til kommentar')
def get_db():
conn = sqlite3.connect('blogg.db')
conn.row_factory = sqlite3.Row
return conn
@app.route('/innlegg/<int:innlegg_id>', methods=['GET', 'POST'])
def vis_innlegg(innlegg_id):
# Sjekk at bruker er innlogget
if 'bruker_id' not in session:
return redirect('/login')
form = KommentarForm()
if form.validate_on_submit():
# CSRF-token er allerede validert av Flask-WTF
# Hent og valider input
innhold = form.innhold.data
# Sanitér HTML (tillat kun sikre tags)
allowed_tags = ['p', 'b', 'i', 'u', 'br']
ren_innhold = bleach.clean(
innhold,
tags=allowed_tags,
strip=True
)
# Bruk parameterisert spørring (SQL injection-beskyttelse)
db = get_db()
try:
db.execute(
"""
INSERT INTO kommentarer (innlegg_id, bruker_id, innhold, opprettet)
VALUES (?, ?, ?, ?)
""",
(innlegg_id, session['bruker_id'], ren_innhold, datetime.now())
)
db.commit()
# Logg hendelsen (for overvåkning)
app.logger.info(
f"Kommentar lagt til av bruker {session['bruker_id']} "
f"på innlegg {innlegg_id}"
)
except sqlite3.Error as e:
# Ikke avslør database-detaljer til bruker
app.logger.error(f"Database-feil: {e}")
return "En feil oppstod. Prøv igjen senere.", 500
finally:
db.close()
return redirect(f'/innlegg/{innlegg_id}')
# Hent innlegg og kommentarer
db = get_db()
innlegg = db.execute(
"SELECT * FROM innlegg WHERE id = ?",
(innlegg_id,)
).fetchone()
if not innlegg:
return "Innlegg ikke funnet", 404
kommentarer = db.execute(
"""
SELECT k.*, b.brukernavn
FROM kommentarer k
JOIN brukere b ON k.bruker_id = b.id
WHERE k.innlegg_id = ?
ORDER BY k.opprettet DESC
""",
(innlegg_id,)
).fetchall()
db.close()
return render_template(
'innlegg.html',
innlegg=innlegg,
kommentarer=kommentarer,
form=form
)
if __name__ == '__main__':
# I produksjon: Ikke bruk debug=True, bruk HTTPS
app.run(debug=False, ssl_context='adhoc')Sikkerhetstiltak implementert:
1. CSRF-beskyttelse: Flask-WTF validerer automatisk token
2. Input-validering: WTForms validators sjekker lengde og innhold
3. XSS-beskyttelse: Bleach saniterer HTML
4. SQL injection-beskyttelse: Parameteriserte spørringer
5. Autentisering: Sjekker at bruker er innlogget
6. Error handling: Skjuler tekniske detaljer for bruker
7. Logging: Logger hendelser for overvåkning
8. HTTPS: SSL-context i produksjon
Oppsummering
I dette kapittelet har du lært:
- Sikker programmering: validere og sanere input.
- SQL injection: forhindres med parameteriserte spørringer.
- XSS: forhindres ved å escape brukerinput (autoescaping).
- Input-validering: sjekk type, lengde og format.
- CSRF-beskyttelse: f.eks. Flask-WTF.
Noekkelbegreper
| Begrep | Forklaring |
|---|---|
| Input-validering | Sjekke at brukerinput er gyldig |
| SQL injection | Angrep via uvalidert input i SQL |
| XSS | Innsetting av skadelig skript i nettsider |
Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.