Modul 8 / 12
File Upload Security
Dokumenter, bilder, GIS-data: type, størrelse, navn, lagring og tilgang.
Hvorfor frontend-sjekk ikke holder
accept=".pdf" og file.type er satt av browseren/brukeren. Requesten kan sendes med hvilket som helst innhold, navn og Content-Type.
- ›File type: sjekk magic bytes på server, ikke bare extension
- ›MIME type: Content-Type er brukerstyrt; sett den selv ved lagring
- ›Extension: allowlist (.pdf, .png, .geojson), aldri blocklist
- ›File size: grense på server og i bucket-konfig
- ›Filename: generer egen nøkkel (uuid), lagre originalnavn som metadata
- ›SVG og HTML: kan inneholde script — servér med Content-Disposition: attachment eller fra eget domene
- ›Executables/arkiver: avvis eller skann; zip-bomber for GIS-pakker (shapefile .zip)
- ›Metadata: EXIF kan inneholde GPS — strip for bilder som deles
- ›Storage permissions: privat bucket, signed URLs med kort levetid
- ›Authorisation: storage-policies som sjekker eierskap på path (userId/...)
Storage-policy
Vulnerable · sql
-- Offentlig bucket, alle kan laste opp hvor som helst
insert into storage.buckets (id, name, public) values ('docs','docs', true);
create policy "upload" on storage.objects
for insert to authenticated with check (bucket_id = 'docs'); Secure · sql
insert into storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
values ('docs','docs', false, 10485760, array['application/pdf','image/png']);
create policy "own folder write" on storage.objects
for insert to authenticated
with check (bucket_id = 'docs'
and (storage.foldername(name))[1] = auth.uid()::text);
create policy "own folder read" on storage.objects
for select to authenticated
using (bucket_id = 'docs'
and (storage.foldername(name))[1] = auth.uid()::text);Testene
- ›Last opp test.html med <script>console.log(1)</script> omdøpt til .pdf — åpnes den som HTML?
- ›Last opp SVG med <script> — kjøres den når URL-en åpnes direkte?
- ›Last opp til en annen brukers mappe (endre path i request)
- ›Åpne en fil-URL i et inkognitovindu — er den tilgjengelig uten login?
- ›Last opp fil over grensen via curl, forbi UI
Kontroller i din egen app
Labs for denne modulen
Quiz
Scenario 1
Opplastingsskjemaet har accept="image/*" og sjekker file.type.startsWith('image/'). Bucket er offentlig. Er HTML-opplasting mulig?
Scenario 2
Brukere laster opp GeoJSON som vises på et kart med popup som viser feature.properties.name via innerHTML. Risiko?