📝 TSD3060 Eksamen 2024-2025

Ordinær eksamen - Kodeanalyse og sikkerhet

ℹ️ Om denne eksamenen

Type: Kodeanalyse-eksamen med Docker Compose-system

Varighet: 3-4 timer (anbefalt)

Format: Skriv egne svar, deretter sammenlign med sensorveiledning

Fokus: Systemforståelse, kodeanalyse, sikkerhetsvurdering

📂 Systemoversikt

Eksamenen tar utgangspunkt i et komplett system bestående av følgende filer (klikk for å se innhold):

s.sh Byggeskript

Beskrivelse: Shell-script som bygger og starter systemet

#!/bin/sh
podman-compose up --build
compose.yaml Docker Compose-konfigurasjon

Beskrivelse: Definerer de to containerne og deres konfigurasjon

services:
  web:
    build: web
    ports:
      - "8080:80"
  db:
    build: db
    ports:
      - "8000:8000"
web/Containerfile Frontend container-oppskrift

Beskrivelse: Bygger en nginx-basert webserver for frontend

FROM docker.io/library/nginx:alpine
COPY index.html /usr/share/nginx/html/
web/index.html Brukergrensesnitt (HTML/JS)

Beskrivelse: JavaScript-basert grensesnitt som kommuniserer med REST API

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Personer</title>
  </head>
  <body>
    <h1>Personer</h1>
    <div id="personer"></div>
    <script>
      // JavaScript for å hente og vise personer
      fetch('http://localhost:8000/personer')
        .then(r => r.json())
        .then(data => {
          document.getElementById('personer').innerHTML = 
            data.map(p => `<p>${p.navn}</p>`).join('');
        });
    </script>
  </body>
</html>
db/Containerfile Backend container-oppskrift

Beskrivelse: Bygger container med C-webtjener og shell-basert REST API

FROM docker.io/library/alpine:latest
RUN apk add --no-cache gcc musl-dev sqlite
COPY db.c db.sh /
RUN gcc -o db db.c
EXPOSE 8000
CMD ["/db"]
db/db.sh REST API (Shell-script)

Beskrivelse: Shell-script som håndterer REST API-forespørsler

#!/bin/sh
# REST API håndtering
METHOD=$REQUEST_METHOD
PATH=$REQUEST_URI

case $METHOD in
  GET)
    sqlite3 personer.db "SELECT * FROM personer;"
    ;;
  POST)
    # Håndter POST-forespørsel
    ;;
esac
db/db.c Webtjener (C) - ⭐ Viktigste filen for kodeanalyse

Beskrivelse: C-program som lytter på port 8000 og starter db.sh for hver forespørsel

🎓 Pedagogisk tilnærming: Klikk på hver kodelinje for å se forklaring. Prøv først å analysere selv!

🎯 Øvingsoppgaver - Test din forståelse!

Spørsmål 1: Hva er formålet med fork() i denne koden?

🤔 Tenk på: Hva skjer når flere klienter kobler til samtidig? Kan serveren håndtere dem parallelt?

✅ Fasit:

fork() oppretter en ny prosess (child) for hver klient-tilkobling. Dette gjør at serveren kan håndtere flere klienter samtidig (concurrent handling). Mens barneprosessen kjører db.sh for én klient, kan foreldreprosessen gå tilbake til accept() og ta imot nye tilkoblinger.

Uten fork(): Serveren måtte vente til én klient var ferdig før den kunne ta imot neste.

Spørsmål 2: Hvorfor brukes dup2() to ganger? Hva oppnås?

🤔 Tenk på: Hva er STDIN (0) og STDOUT (1)? Hvor leser/skriver db.sh?

✅ Fasit:

dup2() omdirigerer file descriptors:

dup2(client, 0) → STDIN blir klient-socket (db.sh kan lese HTTP-forespørsel)

dup2(client, 1) → STDOUT blir klient-socket (db.sh kan skrive HTTP-respons)

Resultat: db.sh tror den leser fra tastatur og skriver til terminal, men kommuniserer faktisk med klienten over nettverket!

Spørsmål 3: Hvorfor lukker foreldreprosessen client-socketen med close(client)?

🤔 Tenk på: fork() kopierer alle file descriptors. Hvem trenger client-socketen?

✅ Fasit:

Etter fork() har både forelder og barn en kopi av client file descriptor. Foreldreprosessen trenger ikke denne - den skal bare vente på nye tilkoblinger med accept().

Viktig: Hvis foreldreprosessen ikke lukker sin kopi, vil socketen forbli åpen selv når barneprosessen er ferdig, noe som kan føre til ressurslekkasje (file descriptor exhaustion).

📊 Svar på spørsmålene for å teste din forståelse (0/3 besvart)
Oppgave 1 - Overordnet beskrivelse 5%

Gi en overordnet beskrivelse av systemet.

💡 Tips:

• Identifiser hovedkomponentene (containere, tjenester)

• Beskriv hvordan de samarbeider

• Forklar systemets overordnede formål

✅ Sensorveiledning

Dette er et system som består av to containere:

  • Database-container (db): Gir et REST-API til en SQLite-database med persondata
  • Web-container (web): Gir et webbasert brukergrensesnitt som kommuniserer med REST-APIet

Systemets funksjon: Brukere kan via nettleseren bruke databasen gjennom et JavaScript-basert grensesnitt. Grensesnittet sender HTTP-forespørsler til REST-APIet som håndterer CRUD-operasjoner (Create, Read, Update, Delete) mot databasen.

Arkitektur: Typisk mikrotjeneste-arkitektur hvor frontend og backend er separert i egne containere med definerte kommunikasjonsporter.

Oppgave 2 - Filenes roller 10%

Forklar hvilken rolle/hensikt hver av filene i systemet har. Begrunn hvordan du kommer frem til svarene.

⚖️ Viktig:

Oppgaven er todelt: (1) Forklaring og (2) Begrunnelse

Disse vektes likt (5% + 5%). Du må forklare BÅDE hva filen gjør OG hvorfor du konkluderer med det.

✅ Sensorveiledning

s.sh

Rolle: Systemutviklingsverktøy som stopper systemet, (om)bygger det og starter det opp igjen.

Begrunnelse: Kommandoene `podman-compose down`, `build` og `up` er en typisk utviklingssyklus. Dette er et verktøy for utviklere, ikke en del av det ferdige systemet.

compose.yaml

Rolle: Orkestreringsfile for styring av de to containerne.

Begrunnelse: Filen følger Docker Compose-standarden og definerer services, ports og build-kontekst for begge containere. Den gjør det enkelt å administrere flerkontainer-applikasjonen.

web/Containerfile

Rolle: Oppskrift på bygging av container-bildet for frontend-containeren.

Begrunnelse: Inneholder Dockerfile-instruksjoner (FROM, COPY, EXPOSE, CMD) som definerer hvordan web-containeren skal bygges. Bruker busybox og starter httpd-webserver.

web/index.html

Rolle: Brukergrensesnitt som leveres til nettleseren.

Begrunnelse: HTML-fil med JavaScript som henter data fra input-felt og sender fetch()-requests til REST-APIet. Fungerer som SPA (Single Page Application) for databaseinteraksjon.

db/Containerfile

Rolle: Oppskrift på bygging av container-bildet for backend-containeren med REST-API og database.

Begrunnelse: Installerer nødvendige pakker (sqlite, jq, uuidgen), oppretter database-skjema, setter opp testdata og kopierer inn API-koden.

db/db.sh

Rolle: Implementerer REST-APIet som håndterer HTTP-forespørsler og databaseoperasjoner.

Begrunnelse: Shell-script som leser HTTP-requests fra stdin, parser dem, utfører SQL-queries og returnerer JSON-responses. Håndterer autentisering, sesjoner og CRUD-operasjoner.

db/db.c

Rolle: Enkel webtjener som aksepterer HTTP-tilkoblinger og delegerer til db.sh-prosesser.

Begrunnelse: C-program som lytter på port 80, aksepterer tilkoblinger, forker nye prosesser og bruker dup2() til å koble socket til stdin/stdout før den execer db.sh. Klassisk pre-fork server-pattern.

Oppgave 3 - Detaljert kodebeskrivelse 45%

Gi en detaljert beskrivelse av koden i filene.

Viktig: Selv om det ikke er nødvendig med en forklaring linje-for-linje, bør alle deler av koden være forklart.

🎯 Fokusområder:

• Vis forståelse for sammenhengen koden kjøres i

• Når det leses fra stdin → forklar at det er klientforespørselen

• Når det skrives til stdout → forklar at det havner i HTTP-responsen

• Beskriv effekten av utskrifter hos klienten (f.eks. HTTP-headere)

📄 Kodelistinger (alle filer)

Se eksamen-oppgavesettet for fullstendige kodelistinger. Her er en rask oversikt:

  • s.sh (5 linjer) - Build/deploy script
  • compose.yaml (10 linjer) - Docker Compose config
  • web/Containerfile (5 linjer) - Frontend container
  • web/index.html (80 linjer) - HTML + JavaScript UI
  • db/Containerfile (21 linjer) - Backend container
  • db/db.sh (112 linjer) - REST API implementation
  • db/db.c (28 linjer) - Web server i C

✅ Sensorveiledning

Generelt krav: Forklaringen skal vise forståelse for:

  • Hvordan data flyter gjennom systemet
  • Sammenhengen mellom HTTP-protokollen og koden
  • Hvordan containere kommuniserer
  • Sikkerhetsmessige implikasjoner

Nøkkelpunkter som må dekkes:

db.c (Web Server):

  • Socket-opprettelse og binding til port 80
  • Listen-loop som aksepterer tilkoblinger
  • Fork for å håndtere hver request i egen prosess
  • dup2() for å koble socket til stdin/stdout
  • execlp() for å erstatte prosess med db.sh
  • SIGCHLD-håndtering for zombie-prosesser

db.sh (REST API):

  • Linjer 2-5: Parser første linje av HTTP-request (METHOD, URI)
  • Linjer 7-19: Leser HTTP-headere (Content-Length, Cookie)
  • Linjer 21-26: Sender HTTP-response headers (CORS, Content-Type)
  • Linjer 28-29: Parser URL-path til tabell og rad
  • Linjer 31-45: Leser request body og parser JSON for POST/PUT
  • Linjer 39-44: Henter salt og hasher passord hvis oppgitt
  • Linjer 48-66: Håndterer /login endpoint med autentisering
  • Linjer 74-82: Validerer sesjon via cookie
  • Linjer 84-112: Håndterer CRUD-operasjoner (GET, POST, PUT, DELETE)

index.html (Frontend):

  • Linjer 14-19: inndata() henter verdier fra input-felt
  • Linjer 21-27: login() sender PUT til /login med passord
  • Linjer 29-36: oppdater() sender PUT til /person med fornavn/etternavn
  • Linjer 38-46: ny() sender POST til /person for ny bruker
  • Linjer 48-52: slett() sender DELETE til /person
  • Linjer 54-57: liste() navigerer til GET /person
  • credentials: 'include': Sender cookies med requests

Containerfiler:

  • Bruk av Alpine/busybox for minimale images
  • Database-oppsett med SQL CREATE TABLE og INSERT
  • Passord-hashing med mkpasswd og salt
  • Eksponering av porter og mapping i compose.yaml

⚠️ Viktig: Forklaringen må vise at du forstår at stdin/stdout i db.sh er koblet til socket-tilkoblingen, slik at det som leses er HTTP-requesten fra klienten og det som skrives blir HTTP-responsen tilbake.

Oppgave 4 - Autentisering 20%

Gi en vurdering av autentiseringsmekanismen som brukes i systemet.

Foreslå en forbedring. Begrunn svaret.

🔍 Analyser:

• Hvordan fungerer autentiseringen? (login + cookie-basert sesjon)

• Hva er bra med løsningen?

• Hvilke sikkerhetsproblemer eksisterer?

• Foreslå konkrete forbedringer

✅ Sensorveiledning

Det som er bra:

  • REST-APIet bruker autentisering ved innlogging med navn og passord
  • Påfølgende forespørsler bruker sesjonsidentifikator (cookie) - god praksis!
  • Passord lagres som hash i databasen (med mkpasswd og salt)
  • Sesjon-cookie har SameSite=Strict for CSRF-beskyttelse

Sikkerhetsproblemer:

  • Ukryptert kommunikasjon: Brukernavn, passord og sesjonsidentifikator sendes over HTTP (ikke HTTPS). Dette gjør dem sårbare for avlytting/man-in-the-middle angrep.
  • Ingen tidsutløp: Sesjonsidentifikatoren har ingen utløpstid
  • Ingen HttpOnly-flagg: JavaScript kan lese cookien (XSS-risiko)
  • Manglende input-validering: SQL-injection mulig ved manglende sanering

Forslag til forbedringer:

  1. Innfør HTTPS: All kommunikasjon bør krypteres med TLS/SSL
  2. Session timeout: Sett Max-Age eller Expires på cookie
  3. HttpOnly og Secure flags:
    Set-Cookie: SESJONSID=...; HttpOnly; Secure; SameSite=Strict
  4. Pepper: Legg til en hemmelig pepper i tillegg til salt
  5. CSRF-token: Ekstra beskyttelse mot cross-site request forgery
  6. Input-validering: Saner alle brukerinput før SQL-queries (prepared statements)
  7. Rate limiting: Begrens antall innloggingsforsøk

💡 Viktigste forbedring: HTTPS er kritisk! Uten kryptering er all autentisering verdiløs på usikre nettverk.

Oppgave 5 - Passordoppdatering 20%

Hvordan ville du endret systemet slik at det kun var mulig for autentiserte brukere å endre eget passord?

La svaret ditt være mest mulig konkret og detaljert.

🎯 Viktige observasjoner:

• Oppdateringsmetoden i REST-APIet (linjer 101-103 i db/db.sh) inkluderer innsetting av ny passordhash

• JavaScript-funksjonen oppdater() (linjer 29-36 i web/index.html) inkluderer IKKE passordet

• Systemet har allerede autentisering og sesjoner på plass

✅ Sensorveiledning

Det finnes mange mulige løsninger. Viktig at svaret omtaler endringer i BÅDE web/index.html OG db/db.sh.

Løsningsforslag 1: Separat endrePassord-funksjon

I web/index.html:

// Legg til nytt input-felt:
<input type='password' id='nyttPassord' placeholder='nytt passord'>
<button onclick='endrePassord()'>Endre passord</button>

// Ny JavaScript-funksjon:
function endrePassord() {
    brukerid = document.querySelector('#brukerid').value;
    gammeltPw = document.querySelector('#passord').value;
    nyttPw = document.querySelector('#nyttPassord').value;
    
    fetch(new URL('/endrePw/'+brukerid, base), {
        method: 'put',
        body: JSON.stringify({
            gammeltPassord: gammeltPw,
            nyttPassord: nyttPw
        }),
        credentials: 'include',
        headers: {'Content-Type': 'application/json'}
    });
}

I db/db.sh:

// Legg til ny endpoint (etter linje 66):
elif [ "$TAB" = "endrePw" ] && [ "$REQUEST_METHOD" = "PUT" ]; then
    # Hent gammelt og nytt passord fra JSON
    GAMMELT_PW=$(echo "$KR" | jq -r '.gammeltPassord')
    NYTT_PW=$(echo "$KR" | jq -r '.nyttPassord')
    
    # Verifiser gammelt passord
    LAGRET_HASH=$(echo "SELECT passordhash FROM person WHERE brukerid='$RAD'" | sqlite3 /person.db)
    GAMMELT_PW_HASH=$(echo $GAMMELT_PW | mkpasswd -S $SALT -)
    
    if [ "$GAMMELT_PW_HASH" != "$LAGRET_HASH" ]; then
        printf "\r\n"
        echo '[{"feilmelding":"Feil gammelt passord"}]'
        exit
    fi
    
    # Generer ny hash med nytt salt
    NY_SALT=$(head -c16 /dev/urandom | base64 | tr -d '=+/')
    NY_HASH=$(echo $NYTT_PW | mkpasswd -S $NY_SALT -)
    
    # Oppdater i database
    echo "UPDATE person SET passordhash='$NY_HASH' WHERE brukerid='$RAD'" | sqlite3 /person.db
    printf "\r\n"
    echo '[{"melding":"Passord oppdatert"}]'
    exit
fi

Løsningsforslag 2: Utvid eksisterende oppdater()-funksjon

Alternativt kan oppdater()-funksjonen utvides til å også håndtere passordendring hvis et nytt passord er oppgitt. Dette krever:

  • Legg til nyttPassord-felt i JSON-body fra JavaScript
  • I db.sh: Sjekk om nyttPassord er satt i JSON
  • Hvis ja: Hash og oppdater passordhash i UPDATE-statement
  • Hvis nei: Behold eksisterende oppførsel

Sikkerhetshensyn:

  • ✅ Krev autentisering (bruk eksisterende sesjon-sjekk)
  • ✅ Bekreft gammelt passord før endring
  • ✅ Generer nytt salt for nytt passord
  • ✅ Valider at nyttPassord møter passordkrav (lengde, kompleksitet)
  • ✅ Logg passordendringer for audit trail
  • ✅ Invalider alle eksisterende sesjoner etter passordendring

📋 Kodevisning

Velg en fil fra listen over for å vise koden...