Ordinær eksamen - C-webtjener/HTML/JS/Sikkerhet
Type: Blandet kodeanalyse - C-programmering, chroot, namespaces, HTML/JS, cookies, serviceworkers
Fokus: Sockets, sikkerhetsteknologi, web-autentisering
Metode: Skriv egne svar først, sammenlign deretter med sensorveiledning
Del 1 (10%): Gi en overordnet beskrivelse (to–tre setninger) av hva programmet gjør.
Del 2 (10%): Gi en detaljert forklaring av koden f.o.m. linje 13. t.o.m. linje 19.
Del 3 (10%): Gi en detaljert forklaring av koden f.o.m. linje 21. t.o.m. linje 39.
1: #include <arpa/inet.h>
2: #include <unistd.h>
3: #include <stdlib.h>
4: #include <stdio.h>
5: #define P 80
6: #define L 128
7:
8: int main ()
9: {
10: struct sockaddr_in adr;
11: int s1, s2; ant; char buf[BUFSIZ];
12:
13: s1 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
14: adr.sin_family = AF_INET;
15: adr.sin_port = htons((u_short)P);
16: adr.sin_addr.s_addr = htonl(INADDR_ANY);
17:
18: bind(s1, (struct sockaddr *)&adr, sizeof(adr));
19: listen(s1, L);
20:
21: while(1){
22:
23: s2 = accept(s1, NULL, NULL);
24: if(0==fork()) {
25:
26: dup2(s2, 1);
27: printf("HTTP/1.1 200 OK\n"
28: "Content-Type: text/plain\n"
29: "\n"
30: );
31: fflush(stdout);
32: ant=read(s2,buf,BUFSIZ);
33: write(s2, buf, ant);
34: shutdown(s2, SHUT_RDWR);
35: exit(0);
36: }
37:
38: else { close(s2); }
39: }
40:
41: return 0;
Dette er en web-tjener som mottar HTTP-forespørsler og returnerer dem som et ekko. Hele HTTP-forespørselen (inkludert både hode og kropp) sendes i HTTP-responsens kropp.
/* Linje 13: Oppretter TCP-socket for IPv4. Returverdi er fildeskriptor. */ s1 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); /* Linje 14-16: Initierer en adressestruktur slik at: - Socketen skal være en IPv4-socket (AF_INET) - Knyttes til lokal port nummer 80 (standard for HTTP) - Knyttes til alle vertens ip-adresser (INADDR_ANY = 0.0.0.0) */ adr.sin_family = AF_INET; adr.sin_port = htons((u_short)P); adr.sin_addr.s_addr = htonl(INADDR_ANY); /* Linje 18: Knyttes adresse-strukturen sammen med socketen */ bind(s1, (struct sockaddr *)&adr, sizeof(adr)); /* Linje 19: Setter socket s1 som passiv -- beregnet for mottak av innkommende forespørsler. Maksimal kølengde for innkommende forespørsler settes til L (128) */ listen(s1, L);
/* Linje 21: En evig løkke, for kontinuerlig betjening av klientene */
while(1){
/* Linje 23: Henter innkommende forespørsel fra kø av innkommende
forespørsler mot s1, setter opp en ny socket-struktur koblet til
forespørselens avsender og returnerer en referanse til denne
strukturen. Dersom køen er tom vil prosessen blokkeres inntil
forespørsel finnes i køen. */
s2 = accept(s1, NULL, NULL);
/* Linje 24: En ny prosess opprettes. Den nye prosessen kjører
koden i kodeblokken under */
if(0==fork()) {
/* Linje 26: Kopierer den nye socketens fildeskriptor til posisjon
1 i prosessens fildeskriptortabell, slik at standard utgang
(STDOUT) kobles til klienten. */
dup2(s2, 1);
/* Linje 27-30: Begynnelsen på en HTTP-respons skrives til STDOUT
(og dermed klienten). Hodet og en tom linje (som angir skillet
mellom HTTP-hodet og HTTP-kroppen) */
printf("HTTP/1.1 200 OK\n"
"Content-Type: text/plain\n"
"\n");
/* Linje 31: Minnelageret (bufferet) som printf-utskriften
midlertidig blir lagret i, tømmes. For å sikre at utskriften
av hodet blir sendt før kroppen. */
fflush(stdout);
/* Linje 32: Data leses maksimalt et antall byte fra klienten via
den nye socketen. BUFSIZ er et systemoptimalisert heltall
beregnet for i/o-operasjoner. */
ant=read(s2,buf,BUFSIZ);
/* Linje 33: Det som ble lest fra klienten, skrives nå til STDOUT,
slik at det sendes til klienten. Det vil utgjøre kroppen av
HTTP-responsen. */
write(s2, buf, ant);
/* Linje 34: Socketen stenges for både lesing og skriving */
shutdown(s2, SHUT_RDWR);
/* Linje 35: Prosessen, som ble opprettet for å kommunisere med
klienten, avsluttes. Tallet null indikerer normal avslutning
uten feil. */
exit(0);
}
/* Linje 38: Den opprinnelige tjener-prosessen skal ikke kommunisere
med klienten, og lukker derfor socketen som er forbundet med
klienten. Deretter startes umiddelbart en ny runde i den evige
tjener-løkka. */
else { close(s2); }
}
Det er mulig å øke sikkerheten ved hjelp av chroot.
Hva er chroot?
chroot() er et systemkall som endrer den kallende prosessens rot-katalog.
Hvordan bidrar det til sikkerhet?
Ved å sette en ny rot, begrenses prosessens tilgang til filer. Dette vil kunne bidra til å hindre uautorisert tilgang til filer – som vil kunne gjøre systemet mer robust (tilgjengelighets-fremmende) og hindre data-lekasjer (konfidensialitet-fremmende).
Implementering i koden:
C-koden for å sette ny rotkatalog til f.eks. /var/www, er:
chroot("/var/www");
Jo tidligere dette gjøres, desto bedre/sikrere er det. Siden koden ikke åpner noen filer, er det ingen grunn til å vente med å gjøre dette. Det kan derfor gjøres helt i starten (rett etter linje 11).
Forklar/vis og begrunn hvordan kjøring av koden, kan få økt sikkerhet ved hjelp av Linux-navnerom (Linux namespaces).
Prinsipp:
Ved å kjøre prosessen i avgrensede navnerom, vil en begrense prosessens tilgang til å gjelde et bestemt utvalg av systemets ressurser. Dette bidra til å hindre uautorisert tilgang, og gjør dermed systemet mer robust mot forstyrrelser, lekasjer og manipulasjon.
Typer navnerom (fra manual namespaces(7)):
Namespace Constant Isolates Cgroup CLONE_NEWCGROUP Cgroup root directory IPC CLONE_NEWIPC System V IPC, POSIX message queues Network CLONE_NEWNET Network devices, stacks, ports, etc. Mount CLONE_NEWNS Mount points PID CLONE_NEWPID Process IDs User CLONE_NEWUSER User and group IDs UTS CLONE_NEWUTS Hostname and NIS domain name
Implementering:
Vi har i emnet sett på hvordan vi kan bruke programmene unshare og docker, for å kjøre kode i avgrensede navnerom.
Eksempel på kommando med unshare:
sudo unshare --user --map-root-user --fork --pid \\
--mount --cgroup --ipc --uts --net ./tjener
Eksempel med Docker:
Når en Docker-container startes med f.eks. docker run en_eller_annen_konteiner, vil prosessene den består av kjøres i samme navnerom, slik at de alle får samme begrensninger/tilganger.
Del 1 (05%): Gi en kortfattet og overordnet beskrivelse av hva dette er.
Del 2 (10%): Gi en detaljert forklaring av xyz.html.
Del 3 (10%): Gi en detaljert forklaring av xyz.js.
Del 4 (05%): Koden inneholder to trykk-knapper. Bruken av dem gir ulike resultat. Beskriv forskjellen.
Kode: xyz.html og xyz.js
1: <!doctype html>
2: <html>
3: <head>
4: <meta charset="utf-8">
5: <title>XYZ</title>
6: <link rel="stylesheet" type="text/css" href="xyz.css" />
7: <script src="xyz.js"> </script>
8: </head>
9: <body>
10: <form action='xyz.cgi' method='post'>
11: <input name='X' id='x' type='text'>
12: <input type='submit'>
13: </form>
14: <button type="button" onclick='xyz()'> fetch </button>
15: <div id='r'></div>
16: </body>
17: </html>
function xyz(){
let b = 'https://usn.no/';
let u = new URL('xyz.cgi', b);
let t = document.querySelector('#x').value;
let r = document.querySelector('#r');
fetch( u, { method: 'post', body: t } )
.then( respons => respons.text() )
.then( k => r.innerHTML += k )
}
Dette er kode som skal tolkes og kjøres i en nettleser (web browser). Det gir brukeren et felt for innskriving av tekst (tekstfelt) og to trykk-knapper.
Et trykk/klikk på en hvilken som helst av de to knappene utløser innsending av den innskrevne teksten til et CGI-skript. Det mottagende skriptet får den innsendte teksten på standard inngang (STDIN). Etter en evt. behandling av innlest tekst, sender skriptet tilbake til klienten. Hva som skjer i nettleseren ved mottak, avhenger av hvilken av knappene brukeren trykket på.
<!doctype html> <!-- Angir at dokumentet er et HTML-dokument (HTML5) -->
<html>
<head>
<meta charset="utf-8">
<title>XYZ</title>
<!-- Referanse til en fil (xyz.css) som inneholder informasjon om
hvordan denne websiden skal stilsettes. Stilinformasjonen i filen
skal følge standarden Cascading Style Sheets (CSS) -->
<link rel="stylesheet" type="text/css" href="xyz.css" />
<!-- Referanse til en fil (xyz.js) som inneholder et skript som
skal lastes, tolkes og kjøres. Koden i skriptet skal være kodet
JavaScript, som er standard-skriptspråk -->
<script src="xyz.js"> </script>
</head>
<body>
<!-- Et skjema med et tekstfelt og en
innsendingsknapp. Form-attributtene angir hvor skjemaet skal
sendes (xyz.cgi) og hvilken HTTP-metode som skal brukes ved
innsendingen (post). -->
<form action='xyz.cgi' method='post'>
<input name='X' id='x' type='text'>
<input type='submit'>
</form>
<!-- En ekstra trykk-knapp som utløser kjøring av
JavaScript-funksjonen xyz(). Denne funksjonen er definert i filen
xyz.js -->
<button type="button" onclick='xyz()'> fetch </button>
<!-- En tom beholder beregnet for resultatet fra fetch-kallet i
xyz() -->
<div id='r'></div>
</body>
</html>
// funksjonen xyz() defineres
function xyz(){
// Lager et URL-objekt til bruk i fetch-metoden
let b = 'https://usn.no/';
let u = new URL('xyz.cgi', b);
// Lager referanser til elementer i html-dokumentet
let t = document.querySelector('#x').value;
let r = document.querySelector('#r');
// Sender HTTP-post-forespørsel. Responsens lagres i
// html-elementet r som er definert i xyz.html.
fetch( u, { method: 'post', body: t } )
.then( respons => respons.text() )
.then( k => r.innerHTML += k )
}
Dersom brukeren trykker på "button"-knappen (som er utenfor html-skjemaet), vil responsen fra tjeneren bli satt inn i den websiden som allerede eksisterer og vises i nettleseren.
Ved trykk på submit-knappen (inni skjemaet/"formen"), vil derimot websiden erstattes av responsen, slik at responsen danner en hel ny web-side som vises.
Forklar og vis med et eksempel hvordan informasjonskapsler (cookies) kan brukes i en autentiseringsmekanisme i et web-tjeneste (f.eks. et REST-API mot en database), slik at bare autentiserte brukere/prosesser får tilgang til tjenesten.
La forklaringen din dekke:
Steg 1: Innlogging
Identitetsbevis (credentials) sendes fra nettleseren/web-klienten (K) til web-tjeneren (T).
Eksempel: K sender brukernavn og passord til T.
Steg 2: Autentisering
T avgjør om identitetsbeviset gir grunnlag for å autentisere K.
Eksempel: T hasher K's passord og sammenligner med passordhash som T tidligere har forsikret seg om at tilhører K. Dersom de to er like, er K autentisert.
Steg 3: Opprett sesjon
Dersom T autentiserte K, lages et bevis/tegn/token (i form av en tekst-streng) på at brukeren er autentisert. T sender dette beviset til K, som en del av HTTP-hodet, på en egen tekstlinje med et navn-verdi-par, pre-fikset med Set-cookie:.
Eksempel: T lager en universelt unik identifikator (UUID), 00fb9526-1c1f-4d86-ac6e-c4ded9010d5e, som K skal kunne bruke til å bevise at den er autentisert.
I HTTP-hodet til T's respons inkluderes følgende linje:
Set-cookie: sesjonsid=00fb9526-1c1f-4d86-ac6e-c4ded9010d5e
Steg 4: Påfølgende forespørsler
Når K mottar HTTP-responsen og tolker linjen som begynner med Set-cookie:, vil den lagre navn-verdi-paret sammenholdt med T's identitet. De påfølgende forespørsler mot T, vil inneholde dette beviset på at K er autentisert. Dette gjøres ved å sette det i forespørslenes HTTP-hode – på en egen linje som begynner med Cookie:.
Eksempel: I HTTP-hodet til K's forespørsler er følgende linje inkludert (inntil informasjonskapselen/cookien ikke lenger er gyldig):
Cookie: sesjonsid=00fb9526-1c1f-4d86-ac6e-c4ded9010d5e
Forklar/vis hvordan
Hva er serviceworker?
Serviceworker er et programvareobjekt i nettleserene som er tilgjengelig for web-applikasjons-programmerere. De fungerer som en proxy mellom websiden og webtjeneren, slik at de kan avskjære og manipulere kommunikasjonen. De kan instrueres til å mellomlagre/cache HTTP-responser.
Hvordan brukes det for offline-funksjonalitet?
Dette kan utnyttes slik at når web-applikasjonen gjør en HTTP-forespørsel, blir den behandlet av serviceWorker'n før den faktisk blir sendt til tjeneren.
Den kan da være programmert til å:
Resultat: Web-applikasjonen kan fortsette å fungere selv uten internettforbindelse, ved å bruke cachede ressurser.
Del 1 (10%):
Del 2 (5%):
Definisjon:
Et virtuelt system, som fremstår som en selvstendig vert settes opp med et sett av tilhørende ressurser. F.eks. filer, prosesser, nettverksenheter, tilkoblede maskinvareenheter, etc.
Dette gjøres ved å bruke avgrensede deler av vertsystemets eksisterende ressurser.
Konteinere vs Virtuelle maskiner:
Slike virtuelle systemer, som er skapt v.h.a. operativsystem-nivå-virtualisering, kalles som regel for konteinere. En vesentlig forskjell mellom konteinere og det som kalles virtuelle maskiner (VM), er at konteinerne deler operativsystemkjerne med vertssystemet.
Eksempler:
Definisjon:
Retningslinjen går ut på at nettlesere (web browsers) i utgangspunktet begrenser mulighetene for skript å kommunisere med fremmed kode – webtjenester/-tjenere som har et annet opphav (origin) enn skriptet selv.
Opphav bestemmes av:
Hvor og hvordan håndheves det?
Dette håndheves i rådende nettlesere, ved at kommunikasjon med fremmed kode kun tillates i visse tilfeller/unntak, som kan bestemmes/settes av utviklerne.
CORS (Cross-Origin Resource Sharing):
I emnet har vi sett på eksempler/oppgaver hvor vi har brukt mekanismen Cross-origin resource sharing (CORS), for å lage slike unntak. I CORS gir den fremmede tjeneren nettleseren tillatelser (av typen Access-Control-*).