OWASP Top 10, injeksjon, XSS og inputvalidering.
Sikker programmering handlar om å skrive kode som er resistent mot angrep. Mange tryggleikshol oppstår fordi utviklarar stolar for mykje på brukarinput eller gløymer å implementere grunnleggjande tryggleikstiltak.
I dette kapittelet skal vi sjå på dei vanlegaste tryggleikssårbarheitene i webapplikasjonar og korleis du som utviklar kan verne deg mot dei. Vi fokuserer spesielt på input-validering, SQL injection, XSS og CSRF.
Det viktigaste prinsippet i sikker programmering er: Aldri stol på brukarinput.
All data som kjem frå brukarar - anten det er frå skjema, URL-parameter, cookies eller filer - må validerast og sanerast før det blir brukt.
Typar validering:
1. Klientside-validering (JavaScript)
- Rask feedback til brukaren
- Kan enkelt omgåast
- Aldri stol på denne åleine!
2. Tenarside-validering (Python/Node.js)
- Obligatorisk tryggleikstiltak
- Kan ikkje omgåast av brukaren
- Siste forsvarslinje
3. Database-validering
- Ekstra lag med tryggleik
- Constraints og foreign keys
Valideringsteknikkar:
Whitelist (tillatne verdiar):
# 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"Lengdegrense:
# 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 ei av dei mest alvorlege sårbarheitene (A03 i OWASP Top 10). Ho oppstår når angriparar kan injisere SQL-kode gjennom brukarinput.
Usikker kode:
# ALDRI gjør dette!
brukernavn = request.form.get('brukernavn')
query = f"SELECT * FROM brukere WHERE brukernavn = '{brukernavn}'"
cursor.execute(query)Angrep:
Hvis ein angripar skriv inn: ' OR '1'='1
Blir spørjinga: SELECT * FROM brukere WHERE brukernavn = '' OR '1'='1'
Dette returnerer alle brukarar!
Enda verre: '; DROP TABLE brukere; --
Dette kan slette heile tabellen.
Sikker kode med parameteriserte spørjingar:
# 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,)
)Kvifor dette er sikkert:
- Databasen skil mellom SQL-kode og data
- Input blir alltid handsama som data, aldri som kode
- Spesialteikn blir escapa automatisk
ORM (Object-Relational Mapping):
Bruk av ORM som SQLAlchemy gjer det lettare å 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 ein angripar kan injisere skadeleg JavaScript i ei webside som andre brukarar ser.
Typar XSS:
1. Stored XSS (lagra)
Vond kode blir lagra i database og vist til alle brukarar.
Eksempel: Kommentarfelt på blogg
2. Reflected XSS (reflektert)
Vond kode blir sendt som parameter og reflektert tilbake til brukaren.
Eksempel: Søkjeresultat-side
3. DOM-based XSS
Sårbarheita ligg 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 køyre i nettlesaren til alle som ser sida.
Angripar kan:
- Stele cookies og sesjonstokens
- Omdirigere til phishing-sider
- Endre innhald på sida
- Køyre keyloggers
Vern 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 gjer:
- < 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 hindrar nettlesaren i å køyre inline scripts og scripts frå andre domene.
3. Sanering av HTML:
Hvis du MÅ tillate HTML-input (t.d. 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 (uttalt "sea-surf") er eit angrep der ei vond side lurer nettlesaren til å sende førespurnader til ei anna side der brukaren er innlogga.
Eksempel på angrep:
Du er innlogga på bank.no. Du vitjar ondsinnet.com som inneheld:
<img src="https://bank.no/overforing?til=angriper&belop=10000">Nettlesaren sender automatisk di innlogga sesjon med førespurnaden!
Vern: 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>Korleis CSRF-token fungerer:
1. Tenar genererer unikt, hemmeleg token for kvar sesjon
2. Token blir inkludert i alle skjema som hidden field
3. Ved POST-førespurnad sjekkar tenar at token matchar
4. Vonde sider kan ikkje kjenne til tokenet
Andre CSRF-vern:
- SameSite cookies: Hindrar sending av cookies på cross-site førespurnader
- Double Submit Cookie: Send token både som cookie og i form
- Custom headers: Krev X-Requested-With header for API-kall
Input-handtering:
- [ ] All brukarinput blir validert på tenarside
- [ ] Whitelist blir brukt der mogleg
- [ ] Type og lengde blir sjekka
- [ ] Feilmeldingar avslører ikkje sensitiv info
Database:
- [ ] Parameteriserte spørjingar blir brukte konsekvent
- [ ] Prinsippet om minste privilegium (database-brukar har berre nødvendige rettar)
- [ ] Sensitive data blir krypterte
Autentisering og autorisasjon:
- [ ] Passord blir hasha (aldri lagra i klartekst)
- [ ] Sesjonar har timeout
- [ ] Autorisasjon blir sjekka for kvar handling
- [ ] Brute force-vern implementert
Output:
- [ ] All output blir escapa/encoda
- [ ] CSP-header sett
- [ ] Ingen sensitive data i HTML-kommentarar eller JavaScript
CSRF-vern:
- [ ] CSRF-tokens på alle state-changing operasjonar
- [ ] SameSite cookies blir brukte
HTTPS og transport:
- [ ] All kommunikasjon over HTTPS
- [ ] Secure og HttpOnly flags på cookies
- [ ] HSTS-header aktivert
Dependencies:
- [ ] Alle avhengigheiter er oppdaterte
- [ ] Ingen kjende sårbarheiter (bruk npm audit eller pip-audit)
Logging og overvaking:
- [ ] Tryggleikshendingar blir logga
- [ ] Ingen passord eller tokens i loggar
- [ ] Varsling ved mistenkeleg aktivitet
Lat oss implementere ein sikker funksjon for å leggje til bloggkommentarar:
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')Tryggleikstiltak implementerte:
1. CSRF-vern: Flask-WTF validerer automatisk token
2. Input-validering: WTForms validators sjekkar lengde og innhald
3. XSS-vern: Bleach sanerer HTML
4. SQL injection-vern: Parameteriserte spørjingar
5. Autentisering: Sjekkar at brukaren er innlogga
6. Error handling: Skjuler tekniske detaljar for brukaren
7. Logging: Loggar hendingar for overvaking
8. HTTPS: SSL-context i produksjon
Oppsummering
I dette kapittelet har du lært:
- Sikker programmering: validere og sanere input.
- SQL injection: blir hindra med parameteriserte spørjingar.
- XSS: blir hindra ved å escape brukarinput (autoescaping).
- Input-validering: sjekk type, lengde og format.
- CSRF-vern: t.d. Flask-WTF.
Nøkkelomgrep
| Omgrep | Forklaring |
|---|---|
| Input-validering | Sjekke at brukarinput er gyldig |
| SQL injection | Angrep via uvalidert input i SQL |
| XSS | Innsetjing av skadeleg 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.