Programmering i Ruby

Den Pragmatiske Programmerers Veiledning

Forrige < Innhold ^
Neste >

Putte Ruby bak lås og slå



Walter Webcoder har en fantastisk ide til en portalside: Den Aritmetiske Webside. Omkranset av alle slags kule matematiske lenker og reklamefaner som skal gjøre ham rik, er en enkel, sentral ramme som inneholdet et tekstfelt og en knapp. Brukere skriver inn et aritmetrisk uttrykk i feltet, trykker knappen og svaret vises. Alle kalkulatorene i hele verden blir overflødige over natten, Walter selger seg ut og pensjonerer seg for å dedikere livet til sin samling av bilregisteringsnumre.

Det er enkelt å implementere kalkulatoren, tenker Walter. Han henter ut innholdet fra skjemafeltet ved hjelp av Ruby sitt CGI bibliotekt og bruker eval metoden for å evaluere strengen som et uttrykk.

require 'cgi'

cgi = CGI::new("html4")

# Fetch the value of the form field "expression" expr = cgi["expression"].to_s

begin   result = eval(expr) rescue Exception => detail   # handle bad expressions end

# display result back to user...

Omtrent syv sekunder etter at Walter gjør denne applikasjonen tilgjengelig for omverdenen, taster en tolv år gammel unge fra Waxahachie som har problemer med kjertlene og ikke noe ordentlig liv, inn ``system("rm *")'' i skjemaet og, som applikasjonen hans, går Walters drømmer i stykker.

Walter lærte en viktig lærepenge: Alle eksterne data er farlige. Ikke slipp dem nær grensesnitt som kan modifisere systemet ditt. I dette tilfellet var innholdet i skjemafeltet de eksterne data og kallet til eval var sikkerhetsbruddet.

Heldigvis tilbyr Ruby støtte for å redusere denne risikoen. All informasjon fra omverdenen kan markeres som tainted (besudlet/smittet). Når man kjører i en sikker modus, vil potensielt farlige metodekall heve et SecurityError unntak dersom de mottar et tainted objekt.

Sikkerhetsnivåer

Variabelen $SAFE bestemmer hvor paranoid Ruby skal være Tabell 20.1 på side 257(??) viser detaljene om hva som sjekkes ved de forskjellige sikkerhetsnivåene.

$SAFE Begrensninger
0 Det foretas ingen sjekking av hvordan ekstern (tainted) data brukes. Dette er standard modus i Ruby.
>= 1 Ruby tillatter ikke bruk av tainted data i forbindelse med potensielt farlige operasjoner.
>= 2 Ruby forbyr lasting av programfiler fra globalt skrivbare kataloger.
>= 3 Alle nye objekter som lages ansees for å være tainted.
>= 4 Ruby deler praktisk talt det kjørende program i to. Ikke-taintede objekter kan ikke endres. Typisk vil dette brukes for å lage en sandkasse: programmet setter opp et miljø på et lavere $SAFE-nivå og deretter setter $SAFE til 4 for å forhindre påfølgende endringer til det miljøet.

Standardverdien til $SAFE er null under de fleste omstendigheter. Men, dersom et Ruby-skript kjøres med setuid eller setgid,[Et Unix-skript kan flagges til å kjøre under en annen bruker eller gruppe id enn personen som kjører det. Dette tillater skriptet å ha privilegier som brukeren ikke har; skriptet kan få tak i ressurser brukeren vanligvis ikke ville hatt tilgang til. Disse skriptene kalles setuid eller setgid.] blir sikkerhetsnivået automatisk satt til 1. Nivået kan også settes ved hjelp av kommandolinje-opsjonen -T og ved å tilordne en verdi til $SAFE i programmet. Det er ikke mulig å redusere verdien til $SAFE ved tilordning.

Den gjeldende $SAFE-verdien arves når nye tråder lages. Men, inne i den enkelte tråd kan $SAFE-verdien endres uten å påvirke verdien i de andre trående. Dette gjør det mulig å implementere sikre ``sandkasser'', områder hvor ekstern kode kan kjøre trygt uten fare for resten av applikasjonen eller systemet. Gjør dette ved å pakke koden du laster inn fra en fil i sin egen, anonyme modul. Det vil beskytte ditt programs navnerom fra uønskede endringer.

f=open(fileName,"w")
f.print ...   # write untrusted program into file.
f.close
Thread.start {
  $SAFE = 4
  load(fileName, true)
}

Med $SAFE-nivå 4, kan du bare laste innepakkede filer. Titt i Kernel::load på side 418(??) for detaljer.

Tainted objekter

Et hvert Ruby objekt som kommer fra en ekstern kilde (for eksempel en streng fra en fil eller en miljøvariabel) blir automatisk markert som tainted. Hvis programmet ditt bruker et tainted objekt til å lage et nytt objekt, så vil det nye objektet også bli tainted, som vist i koden under. Alle objekter med eksterne data et eller annet sted i historikken sin vil bli tainted. Denne prosessen skjer uavhengig av det gjeldende sikkerhetsnivået. Du kan sjekke tainted-status til et objekt ved å bruke Object#tainted? .

# internal data
# =============
x1 = "a string"
x1.tainted? » false
x2 = x1[2, 4]
x2.tainted? » false
x1 =~ /([a-z])/ » 0
$1.tainted? » false

# external data
# =============
y1 = ENV["HOME"]
y1.tainted? » true
y2 = y1[2, 4]
y2.tainted? » true
y1 =~ /([a-z])/ » 1
$1.tainted? » true

Du kan tvinge et objekt til å bli tainted ved å kalle taint metoden. Hvis sikkerhetsnivået er under 3, kan du gjerne tainted-statusen ved å kalle untaint. [ Det er mange tvilsomme måter å gjøre dette på uten å bruke untaint. Vi lar det være opp til din mørkere side å finne dem. ] Dette er tydeligvis ikke noe man skal ta lett på.

Det er åpenbart at Walter burde ha kjørt sitt CGI skript på sikkerhetsnivå 1. Det ville ha hevet et unntak når programmet prøvde å sende data fra skjemaet til eval. Når dette så hadde skjedd, ville Walter hatt et antall muligheter. Han kunne ha implementert en skikkelig uttrykksparser, som omgår risikoen forbundet med å bruke eval. Men, da han er lat, er det mer sannsynlig at han hadde gjort en enkelt sjekk på skjemadataene og fjernet tainted-statusen dersom det så uskyldig nok ut.

require 'cgi';

$SAFE = 1

cgi = CGI::new("html4")

expr = cgi["field"].to_s

if expr =~ %r{^-+*/\d\seE.()*$}   expr.untaint   result = eval(expr)   # display result back to user... else   # display error message... end

Personlig syntes vi Walter fremdeles tar unødvendige risikoer. Vi ville nok helst ha sett en ordentlig parser her, men å implementere en vil ikke lære oss noe om tainting, så vi begir oss videre på veien.

Og husk---verden er et skummelt sted. Vær forsiktig.

Definisjon til sikkerhetsnivåene
$SAFE >= 1
  • Miljøvariablene RUBYLIB og RUBYOPT prosesseres ikke og den gjeldende katalogen legges ikke til stien(path). processed, and the current directory is not added to the path.
  • Kommandolinjeopsjonene -e, -i, -I, -r, -s, -S, and -x forbys.
  • Kan ikke starte prosesser fra $PATH hvis noen katalog i den kan skrives til av alle.
  • Kan ikke manipulere eller 'chroot' til en katalog dersom navnet dens er en tainted streng.
  • Kan ikke glob tainted strenger.
  • Kan ikke eval tainted strenger.
  • Kan hverken laste (load) eller kreve inn (require) en fil hvis navn er en tainted streng.
  • Kan ikke manipulere eller spørre om status til en fil eller et rør (pipe) hvis navnet er en tainted streng.
  • Kan ikke utføre en systemkommando eller program fra en tainted streng.
  • Kan ikke sende en tainted streng til trap.

$SAFE >= 2

$SAFE >= 3
  • Alle objekter som lages er tainted.
  • Kan ikke fjerne tainted-status på objekter.

$SAFE >= 4
  • Kan ikke modifisere en ikke-tainted tabell, hashtabell eller streng.
  • Kan ikke modifisere en global variabel.
  • Kan ikke aksessere instansvariabler til ikke-taintede objekter.
  • Kan ikke endre en miljøvariabel.
  • Kan ikke lukke eller gjenåpne ikke-taintede filer.
  • Kan ikke fryse ikke-taintede objekter.
  • Kan ikke endre synlighet til metoder (private/public/protected).
  • Kan ikke lage et alias i en ikke-tainted klasse eller modul.
  • Kan ikke hente ut metainformasjon (slik som metode- eller variabellister).
  • Kan ikke definere, redefinere, fjerne eller definere vekk en metode i en ikke-tainted klasse eller modul.
  • Kan ikke modifisere Object.
  • Kan ikke fjerne instansvariabler eller konstanter fra ikke-taintede objekter.
  • Kan ikke manipulere tråder, terminere en annen tråd enn den gjeldende eller sette abort_on_exception.
  • Kan ikke ha trådlokale variabler.
  • Kan ikke heve et unntak i en tråd med en lavere $SAFE verdi.
  • Kan ikke flytte tråder mellom ThreadGroups.
  • Kan ikke kalle exit, exit! eller abort.
  • Kan bare laste innepakkede filer og kan ikke inkludere moduler i ikke-taintede klasser eller moduler.
  • Kan ikke konvertere symbolidentifikatorer til objektreferanser.
  • Kan ikke skrive til filer eller rør (pipes).
  • Kan ikke bruke autoload.
  • Kan ikke gjøre objekter tainted.

( In progress translation to Norwegian by NorwayRUG. $Revision: 1.6 $ )
$Log: taint.xml,v $
Revision 1.6  2002/12/06 20:55:32  kent
Første kladd. Viktigste behov som gjenstår: Bestemme hva tainted skal oversettes med.

Revision 1.5  2002/12/06 20:40:35  kent
Frem til den siste tabellen.

Revision 1.4  2002/12/03 21:17:06  kent
Frem til "Tainted objects", omtrent halvveis.


Forrige < Innhold ^
Neste >

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide".
Translation to norwegian by Norway Ruby User Group.
Copyright for the english original authored by David Thomas and Andrew Hunt:
Copyright © 2001 Addison Wesley Longman, Inc.
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at
http://www.opencontent.org/openpub/).

(Please note that the license for the original has changed from the above. The above is the license of the original version that was used as a foundation for the translation efforts.)

Copyright for the norwegian translation:
Copyright © 2002 Norway Ruby User Group.
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at
http://www.opencontent.org/openpub/).
Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.
Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.