Tainted - Ruby Sicherheit

Ruby hat seit jeher ein mehrstufiges Sicherheitskonzept implementiert. Damit kann gesteuert werden, welche sicherheitsrelevanten Aktionen mit einem Ruby Script ausgeführt werden dürfen.

Sicherheitsrelevante Operationen

Gefährliche Operationen aus Ruby Sicht sind Zugriffe auf Umgebungsvariablen, das Dateisystem oder Kommandozeilenbefehle. Also Zugriffe auf Entitäten, die außerhalb von Ruby selbst definiert sind.
Alles, was aus diesen Quellen stammt ist unsicher (oder Tainted):

File.read('README.md').tainted? # => true
ENV['LANG'].tainted? # => true

Intern erzeugte Objekte dagegen nicht:

'Ruby'.tainted? # => false
Fixnum.tainted? # => false

Das Tainted Flag ist an jedem Objekt vorhanden und je nach SAFE Level können mit Tainted Objekten Operationen ausgeführt werden oder nicht.
Die Tainted Eigenschaft wird Objekte weitergegeben:

home = ENV['HOME']
home.tainted? # => true
user = home.split('/')[-1]
user.tainted? # => true

Ruby SAFE Levels

Ruby Skripte können seit der Version 2.3.0 in zwei verschiedenen SAFE Level (0 und 1) ausgeführt werden. Die Level 2 - 4 werden seitdem nicht mehr unterstützt:

$SAFE = 3 # => $SAFE=2 to 4 are obsolete (ArgumentError)

Standardmäßig ist das Level 0 gesetzt:

$SAFE # => 0

SAFE Level können nur hochgesetzt, aber nie runtergesetzt werden:

$SAFE # => 0
$SAFE = 1
$SAFE # => 1
$SAFE = 0 # => SecurityError: tried to downgrade safe level from 1 to 0

Ruby - Tainted Love

Was passiert, wenn mit einem Tainted Objekt eine Operation ausgeführt wird, die in dem SAFE Level nicht erlaubt ist, soll kurz demonstriert werden.

# operation.txt
puts 'Dangerous operation.'

Die Textdatei wird eingelesen:

operation = File.read('operation.txt')

Im SAFE Level 0 kann dieser String evaluiert werden, im SAFE Level 1 nicht mehr:

$SAFE # => 0
eval(operation) # => "Dangerous operation."
$SAFE = 1
eval(operation) # => SecurityError: Insecure operation - eval

Ein Objekt kann auch als Tainted markiert werden:

ruby = 'Ruby'
ruby.tainted? # => false
ruby.taint
ruby.tainted? # => true

und auch wieder Untainted werden:

ruby.untaint
ruby.tainted? # => false

Allerdings sollte dann auch klar sein, ob der externen Quelle vertraut werden kann oder nicht.
Klassen können konsequenterweise ebenfalls Tainted werden, was aber nicht dazu führt, dass Instanzen der Klasse ebenfalls Tainted sind:

String.tainted? # => false
String.taint
String.tainted? # => true
String.new.tainted? # => false

Andere Ruby Implementierungen, wie JRuby haben keine SAFE Level implementiert. JRuby basiert auf dem Sicherheitskonzept der JVM.