Sicheres Sandboxing von Benutzerskripten in einem C ++ - Programm

8

Ich habe an einem persönlichen Projekt in C # gearbeitet, dessen Zweck mehr oder weniger darin besteht, dem Benutzer das Ausführen von Skripten zu ermöglichen, die von anderen Benutzern geschrieben wurden, und die Berechtigungen dieses Skripts einzuschränken. Mein Programm kompiliert die Skripte mithilfe einer Bibliothek eines Drittanbieters, speichert sie mithilfe der .NET Code Access Security-Mechanismen in Sandboxen und stellt sicher, dass sie nur über die Berechtigungen verfügen, die der Benutzer ihnen erteilen möchte.

Im Großen und Ganzen sind meine Sicherheitsanforderungen:

  • Der Benutzer sollte in der Lage sein, den Zugriff des nicht vertrauenswürdigen Skripts nur auf bestimmte Teile des Dateisystems zu beschränken, einschließlich des Verbots des gesamten Dateisystemzugriffs
  • Der Benutzer sollte in der Lage sein, die Netzwerkverbindungen des nicht vertrauenswürdigen Skripts nur auf bestimmte IP-Adressen oder Hostnamen zu beschränken, einschließlich des Verbots aller Netzwerkverbindungen
  • Es ist in Ordnung, wenn das Benutzerskript es schafft, die Hostanwendung zu hängen oder zu beenden, aber das Benutzerskript darf die Berechtigungsbeschränkungen nicht umgehen können (dh Denial-of-Service ist in Ordnung, Verletzung nicht).

Ich denke darüber nach, etwas Ähnliches in C ++ zu tun, als eine Art persönliche Übung. Offensichtlich sind die Dinge komplizierter, wenn nativer Code direkt ausgeführt wird, selbst wenn die Benutzerskripte in einer Skriptsprache wie Lua geschrieben sind.

Der erste Ansatz, den ich mir vorstellen kann, besteht darin, meine eigenen Hooks in die Standardbibliotheksfunktionen der Skriptumgebung einzufügen. Wenn die Skriptsprache beispielsweise Lua ist, müsste ich, anstatt io.open normal verfügbar zu machen, einen Wrapper verfügbar machen, der die Argumente mit den Berechtigungen des Skripts vergleicht, bevor sie an die ursprüngliche Implementierung übergeben werden.

Mein Anliegen bei diesem Ansatz ist, dass er die Menge meines eigenen Codes, der für die Sicherheit zuständig ist, drastisch erhöht und daher eine potenzielle Sicherheitslücke darstellt, die ich selbst geschrieben habe. Mit anderen Worten, wenn ich mit .NET CAS arbeite, kann ich darauf vertrauen, dass Microsoft seine Arbeit im Sandbox-Code gut gemacht hat, anstatt meinem eigenen Sandbox-Code vertrauen zu müssen.

Gibt es Alternativen, die mir nicht bekannt sind?

Vojislav Stojkovic
quelle
@ RobertHarvey Linux-Containerisierungstechniken wie Seccomp helfen im Allgemeinen nur, wenn der nicht vertrauenswürdige Code in einem separaten Prozess ausgeführt wird, gelten jedoch nicht für die Isolierung während des Prozesses. Natürlich könnte eine ziemlich einfache Lösung des Problems genau das beinhalten, einen separaten Prozess, der als Dienst für das Hauptprogramm fungiert und nur über IPC-Mechanismen kommuniziert. Ich frage mich, ob Windows ähnliche Isolationsfunktionen auf Betriebssystemebene hat.
Amon
1
@amon: Du meinst wie ein ... Windows- Prozess? Sicher tut es das.
Robert Harvey
Zunächst einmal haben Sie die größte Sicherheitslücke in Ihr System eingeführt: Foreign Code . Alles andere ist ein Versuch, diesen Schaden zu mindern. Im Wesentlichen erstellen Sie einen Interpreter, der diesen Fremdcode hoffentlich sicher ausführt. Verbessern Sie Ihr Verständnis von Interpretation und wenden Sie diese Ideen an, um das gewünschte Verhalten und die relative Sicherheit durchzusetzen. Zweitens: Nehmen Sie nicht an, dass ein Sandkasten sicher ist, sondern fügen Sie immer zusätzliche Verteidigungsebenen hinzu.
Kain0_0
1
@VojislavStojkovic Kannst du nicht einfach eine Javascript-Engine verwenden? Standardmäßig haben diese Engines keinen Zugriff auf das Dateisystem (nicht ganz sicher über das Netzwerk, aber Sie können dies überprüfen). Ein Beispiel ist QJSEngine . Mit diesen Engines können Sie Javascript für die Interoperabilität benutzerdefinierte C ++ - Codefunktionen bereitstellen.
RandomGuy

Antworten:

1

Wie andere bereits festgestellt haben, ist das Ausführen von Fremdcode das größte Problem bei dieser Art der Implementierung. Wie in Kain0_0s Kommentar vorgeschlagen, wäre eine VM der am besten geeignete Weg, um die Freiheit von Fremdcode aufrechtzuerhalten, ohne den Host-Computer zu gefährden (zu viel). Dies ist im Grunde das, was Continuous Integration Services wie CircleCI tun.

Dies erleichtert auch die Implementierung der Benutzeroberfläche erheblich, da Sie Bilder mit allen gewünschten Konfigurations- und Sicherheitsfunktionen abrufen können. Sie müssen sich auch keine Sorgen machen, ob der Code auf Ihrem Host ausgeführt werden kann.

Dafür würde ich:

  • Erstellen Sie Snapshots von Benutzerskriptumgebungen, die ich mit Docker behandeln möchte (eine Umgebung für C #, eine für Python usw. mit den entsprechenden Sicherheitskonfigurationen).

  • Starten Sie auf Benutzeranforderung Docker-relevante Instanzen über den Code-Trigger und fügen Sie das Fremdskript in den Einstiegspunkt der Docker-Instanz ein.

  • Der Code wird in der Docker-Instanz mit Benutzerberechtigungen ausgeführt, Dateien werden geschrieben, möglicherweise wird hier und da eine Verbindung hergestellt, die Ausgabe abgerufen und die Umgebung wird dann zerstört

  • Da Docker-Container als Prozesse ausgeführt werden, können sie relativ einfach beendet werden, dh wenn sie ein bestimmtes Zeitlimit überschreiten. Wenn ein Fehler auftritt, können sie sofort beendet werden.

Lassen Sie Ihren Hauptcode grundsätzlich den Benutzerauslöser, die Einstiegspunktinjektion und die automatische Zerstörungslogik für Docker verwalten, das für das Sandboxing für diese Art von Operation verantwortlich ist.

lucasgcb
quelle
1
Gute Antwort. Wenn Sie Ihrer Antwort etwas hinzufügen möchten, weisen alle gängigen Sandbox-Lösungen zahlreiche Fehler auf, die es dem Schadcode ermöglichen, aus seiner Sandbox zu entkommen. Dies gilt für CAS (zum Beispiel CVE-2015-2504) und noch mehr für Java (Exploits für das Browser-Java-Plugin sind zu zahlreich, um hier aufgelistet zu werden). Docker hat zwar auch Schwachstellen, aber ich habe den Eindruck, dass die Angriffsfläche viel geringer ist als alles, was auf der Ebene einer bestimmten Sprache / eines bestimmten Frameworks implementiert ist.
Arseni Mourzenko