Ich haber bereits wiederholt über den Java SecurityManager geschrieben. Heute will ich ihn nutzen, um ein Konzept sicherer Softwareentwicklung zu illustrieren.
Der etwas sperrige Titel bezieht sich auf ein Konzept defensiver Softwareentwicklung - das Aufspalten einer Anwendung in isolierte, miteinander kommunizierende Teile.
Ich werde dieses Vorgehen am Java SecurityManager demonstrieren - auch wenn er aus Java verschwinden wird.
Bei aller Kritik an dem Konzept und der Implementierung - Ich will damit auch versuchen zu zeigen, dass es eine schlechte Idee ist, ihn zu entfernen.
Die Idee des Artikels an sich: viele Verwundbarkeiten in Software heute verstecken sich in Code zum Parsen serialisierter Daten. Ein Beispiel dafür sind die Lücken in Multimedia-Dateien - also solche, die zum Einlesen von Bildern oder Videos verwendet werden. Diese Inhalte sind meist durch Angreifer kontrollierbar und können so gestaltet werden, dsas sie spezifische Lücken in den Decodern angreifen.
Mein Beispielszenario stellt so etwas in minimaler Form nach: Ein Programm liest eine Zeile aus einer Textdatei und interpretiert den Text als BeanShell-Skript.
Es ist eindeutig zu sehen, dass diese Ausführung von vom Angreifer einfach zu kontrollierendem Code viel Schadenspotential birgt - das müsste man idealerweise ausschließen oder wenigstens deutlich reduzieren.
Die Anwendung besteht aus drei Klassen, die in getrennten JAR-Dateien untergebracht sind:
Der Ablauf des Kontrollflusses ist trivial: Zunächst wird die Methode read aufgerufen, deren Ergebnis anschließend als Input für parse dient:
Der Code:
package ex;
import bsh.EvalError;
import java.security.Policy;
public class Experiment
{
public static void main(java.lang.String[] args) throws EvalError, java.io.IOException, java.net.URISyntaxException, java.security.NoSuchAlgorithmException
{
java.net.URL url=Experiment.class.getClassLoader().getResource("compartmentalization.policy");
java.security.URIParameter params=new java.security.URIParameter(url.toURI());
Policy.setPolicy(Policy.getInstance("JavaPolicy",params));
System.setSecurityManager(new SecurityManager());
main2(args);
}
public static void main2(java.lang.String[] args) throws java.io.IOException
{
new Experiment().execute();
}
public Experiment()
{
super();
}
public void execute()throws java.io.IOException
{
exe.FileReader fr=new exe.FileReader();
exf.VulnerableComponent vc=new exf.VulnerableComponent();
java.io.File f=new java.io.File("/tmp/exa.txt");
java.lang.String raw=fr.read(f);
int a=vc.parse(raw);
System.out.println("Result of operation in VulnerableComponent is: "+a);
}
}
package exe;
public class FileReader extends java.lang.Object
{
public FileReader()
{
super();
}
public java.lang.String read(java.io.File f) throws java.io.IOException
{
java.io.FileInputStream fis=new java.io.FileInputStream(f);
java.io.InputStreamReader isr=new java.io.InputStreamReader(fis);
java.io.BufferedReader br=new java.io.BufferedReader(isr);
java.lang.String rv=br.readLine();
br.close();
isr.close();
fis.close();
return rv;
}
}
package exf;
public class VulnerableComponent extends java.lang.Object
{
public VulnerableComponent()
{
super();
}
public int parse(java.lang.String input)
{
java.lang.Integer rv=0;
try
{
bsh.Interpreter interpreter=new bsh.Interpreter();
rv=(java.lang.Integer)interpreter.eval(input);
}
catch(java.lang.Throwable t)
{
t.printStackTrace();
}
return rv.intValue();
}
}
Der angenommene Schadcode besteht aus einer Zeile, die versucht, eine Datei auf dem System zu lesen:
java.io.File f=new java.io.File("/tmp/exa.txt");java.io.FileInputStream fis=new java.io.FileInputStream(f);java.io.Reader isr=new java.io.InputStreamReader(fis);int i=isr.read();isr.close();fis.close();3+6
Ganz am Ende sieht man, dass der Code so tut, als ob er tatsächlich nur ein mathematischer Ausdruck wäre: Wenn die Anwendung ihn ausführt, ist das Ergebnis die Summe aus 3 und 6. Der Schadcode wird hier dargestellt durch das Lesen aus einer Datei. Prinzipiell könnte hier aber beliebiger Code stehen. Ich habe mich dafür entschieden, weil es die Präsentation der Ideen einfacher macht.
Die drei Klassen werden in jeweils eigene JAR-Dateien deployed, die zur Ausführung alle zum Klassenpfad hinzugefügt werden.
Ändert man den hier gezeigten Code der Methode main und deaktiviert den SecurityManager - in dem man die entsprechenden Zeilen zum Beispiel auskommentiert - und führt die Anwendung anschließend aus, zeigt sich kein Fehler, die Anwendung wird mit der Anzeige des Ergebnisses des BeanShell-Skripts abgeschlossen - der Schadcode wurde ausgeführt.
Hier kommt nun der SecurityManager zum Einsatz: Er wird aktiviert und in einer Policy-Datei werden die Rechte zur Ausführungszeit definiert (diese Datei muss im Classpath liegen):
grant codeBase "file:bsh.jar" {
permission java.security.SecurityPermission "getProperty.package.access";
permission java.util.PropertyPermission "user.dir", "read";
permission java.lang.RuntimePermission "accessDeclaredMembers";
};
grant codeBase "file:exe.jar" {
permission java.io.FilePermission "/tmp/exa.txt", "read";
};
grant codeBase "file:exf.jar" {
permission java.security.SecurityPermission "getProperty.package.access";
permission java.util.PropertyPermission "user.dir", "read";
permission java.lang.RuntimePermission "accessDeclaredMembers";
};
grant codeBase "file:ex.jar" {
permission java.io.FilePermission "/tmp/exa.txt", "read";
permission java.security.SecurityPermission "getProperty.package.access";
permission java.util.PropertyPermission "user.dir", "read";
permission java.lang.RuntimePermission "accessDeclaredMembers";
};
Für die unterschiedlichen Codebasen (JAR-Dateien) werden die jeweils notwendigen Rechte definiert, wobei gilt, dass jede Codebase zunächst über gar keine Rechte verfügt. Zu Beginn wird definiert, welche Rechte BeanShell benötigt, um überhaupt Code interpretieren und ausführen zu können.
Anschließend werden die Rechte des FileReader definiert - er darf als einziger Dateien lesen.
Die VulnerableComponent benutzt den BeanShell-Interpreter und muss daher über alle seine Rechte verfügen. In diesem einfachen Beispiel benötigt die Komponente selbst keine dedizierten Privilegien, daher ist dieser Abschnitt eine Kopie der Informationen der BeanShell-Codebase.
Da die Komponente Main alle drei anderen verwendet sind die ihr zugewiesenen Rechte eine Vereinigungsmenge der Rechte der einzelnen Komponenten.
Führt man die Anwendung unter der Kontrolle eines SecurityManager unter Angabe der gezeigten Policy aus, wird die Ausführung mit einem Fehler beendet:
Caused by: java.security.AccessControlException: access denied ("java.io.FilePermission" "/tmp/exa.txt" "read")
at bsh.BSHAllocationExpression.constructObject(BSHAllocationExpression.java:159)
at bsh.BSHAllocationExpression.objectAllocation(BSHAllocationExpression.java:113)
at bsh.BSHAllocationExpression.eval(BSHAllocationExpression.java:63)
at bsh.BSHPrimaryExpression.eval(BSHPrimaryExpression.java:124)
at bsh.BSHPrimaryExpression.eval(BSHPrimaryExpression.java:67)
at bsh.BSHAssignment.eval(BSHAssignment.java:40)
at bsh.BSHVariableDeclarator.eval(BSHVariableDeclarator.java:80)
at bsh.BSHTypedVariableDeclaration.eval(BSHTypedVariableDeclaration.java:82)
at bsh.Interpreter.eval(Interpreter.java:698)
at bsh.Interpreter.eval(Interpreter.java:775)
at bsh.Interpreter.eval(Interpreter.java:766)
at exf.VulnerableComponent.parse(VulnerableComponent.java:10)
at ex.Main.main(Main.java:55)
Wie man sehen kann wird der Zugriff auf die Datei effektiv unterbunden: access denied ("java.io.FilePermission" "/tmp/exa.txt" "read")
Wenn man seine Anwendung effektiv strukturiert und die Rechte der einzelnen Komponenten stringent moderiert, kann man Software sehr viel sicherer und unanfälliger gegen die Folgen existierender Sicherheitslücken gestalten. Heutzutage findet man vergleichbare Mechanismen nicht nur in Java - es gibt keine Gründe, diese in sicherheitskritischen Bereichen nicht anzuwenden!
Man kann dazu natürlich noch folgendes anmerken: Eigentlich sollten Komponenten wie die hier beispielhaft entworfene VulnerableComponent niemals selbst mit dem Betriebssystem interagieren. Würde man als Beispiel etwa eine Komponente zum Logging heranziehen, so könnte man die Konfigurationsdatei so wie im beschriebenen Beispiel zunächst über die Komponente FileReader einlesen und dann an die Logging-Komponente übergeben. Diese liest die enthaltenen Informationen und setzt entsprechende Senken für die formatierten und gefilterten Logging-Messages auf. Das würde aber bedeuten, dass sie Dateien schreiben können muss - und da wir externen Bibliotheken in unserem Szenario nicht vertrauen, würde das Möglichkeiten für eingeschleusten Schadcode eröffnen, das System zu stören. Wenn man aber der Logging-Komponente dies Rechte entzieht, Dateien zu schreiben oder über Sockets zu kommunizieren fällt der Komfort weg, die Logging-Senken per Konfiguration definieren zu können. Statt dessen muss der Core der Anwendung in diesem Szenario eine Schnittstelle für die Logging-Komponente anbieten, in die diese die gefilterten und formatierten Messages abgeben kann - der Core, der per Definition aus vertrauenswürdigem Code besteht kann diese dann persistieren.
Paste.gg in Docker
08.07.2020
Ich habe hier verschiedentlich über Zuwachs in meinem Docker-Zoo berichtet. Heute folgt leider ein Bericht über einen Misserfolg...
WeiterlesenAndroid Basteln C und C++ Chaos Datenbanken Docker dWb+ ESP Wifi Garten Geo Go GUI Gui Hardware Java Java. Komponenten Jupyter JupyterBinder Komponenten Links Linux Markdown Markup Music Numerik OpenSource PKI-X.509-CA Präsentationen Python QBrowser Rants Raspi Revisited Security Software-Test sQLshell TeleGrafana Verschiedenes Video Virtualisierung Windows Upcoming...
Eine Präsentation zum besseren Verständnis einiger Konzepte sicherer Softwareentwicklung
WeiterlesenIch habe ja bereits eine kleine Präsentation zum Thema PKI und OpenSSH verfasst - hier folgt nun - mit einigen Links zu externen Ressourcen zu diesem Thema angereichert - eine strukturierte Zusammenfassung diverser Use Cases und deren Lösung mit einer OpenSSH PKI
WeiterlesenUnd wieder habe ich mich mit einem System beschäftigt, das sich in die Themen Chaos und Fraktale oder Strange Attractors einordnen lässt.
WeiterlesenManche nennen es Blog, manche Web-Seite - ich schreibe hier hin und wieder über meine Erlebnisse, Rückschläge und Erleuchtungen bei meinen Hobbies.
Wer daran teilhaben und eventuell sogar davon profitieren möchte, muss damit leben, daß ich hin und wieder kleine Ausflüge in Bereiche mache, die nichts mit IT, Administration oder Softwareentwicklung zu tun haben.
Ich wünsche allen Lesern viel Spaß und hin und wieder einen kleinen AHA!-Effekt...
PS: Meine öffentlichen Codeberg-Repositories findet man hier.