Datenvalidierung UTF8 mit BiDi-Steuerzeichen (TrojanSource 2.0)

15.07.2024

Ich bin heute nochmal inspiriert worden, weiter über die Trojan Source Vulnerability nachzudenken. Meiner Meinung nach bestehen hier noch Probleme - speziell bei Nutzereingaben oder Daten, die über externe Schnittstellen ampfangen werden.

Ich hatte bisher große Plattformen und dedizierte Editorkomponenten auf Verwundbarkeit durch TrojanSource-Angriffe untersucht. Ich hatte dabei völlig ganz normale GUI-Komponenten außer Acht gelassen.

Das holte ich nunmehr nach, indem in eine einfache Swing-Oberfläche in einem Java-Programm erstellte mit einem einzeiligen Texteingabefeld und einer mehrzeiligen Texteingabekomponente. Beide waren - auch in der neuesten Java-Version noch gegen solche Angriffe verwundbar. Nachdem ich meine diesbezügliche NEugierde befriedigt hatte überlegte ich, wie man dieses Verhalten eventuell für Angriffe ausnützen könnte.

Man könnte zunächst die Usability einer Anwendung beschneiden: Man stelle sich vor, dass ein Angreifer Daten in der Datenbank der Anwendung manipuliert und dort solche Unicode-Zeichen in die Felder einfügt, wie sie auch für TrojanSource-Angriffe verwendet werden. Wird dies korrekt erledigt, ändert sich an der Anzeige in der Applikation, die darauf nicht vorbereitet ist nichts. Allerdings kann nun nichtmehr erfolgreich gesucht werden, weil die Texte in der Datenbank nun nicht mehr mit etwaigen Suchbegriffen übereinstimmen.

Ein weiteres Szenario ist die Validierung eingegebener (oder über eine externe Schnittstelle empfangener) numerischer Werte:

Ich habe dazu einige Versuche angestellt und dazu nicht nur Java, sondern auch C, C++ und Python daraufhin untersucht, was passiert, wenn ein String in eine Integerzahl umgewandelt werden soll, der Zeichen aus einem TrojanSource-Angriff enthalten:

//...
	System.out.println(de.elbosso.util.Utilities.hexdump(tf.getText().getBytes()));
	java.text.NumberFormat nf=java.text.NumberFormat.getIntegerInstance();
	System.out.println(nf.parse(tf.getText()));
	System.out.println(Integer.valueOf(tf.getText()));
//...

Java Programmierer können sich auf Bordmittel verlassen - jeder der hier gezeigten Wege zum Parsen eines Strings in eine Zahl schlägt wegen nicht erlaubter Zeichen im String fehl.

 <stdio.h>
 <stdlib.h>
 <errno.h>

int main() { printf("300‮⁦400⁩⁦500\n"); int a=atoi("300‮⁦400⁩⁦500"); printf("%d\n",a); a=0; printf("sscanf: %d\n",sscanf("300‮⁦400⁩⁦500","%d",&a)); printf("%d\n",a); char * end; a=0; a=strtol ("300‮⁦400⁩⁦500", &end, 10); if (*end != '\0' || errno == ERANGE) printf("error %d\n",a); else printf("%d\n",a);

return 0; }

Mit C war es schon problematischer: nur strtol bietet die Möglichkeit, einen solchen Angriff zu erkennen - dazu muss amn aber die hier skizzierte Fehlererkennung explizit durchführen. Ansonsten verhält sich auch diese Methode exakt wie die anderen: sie liest so lange Zeichen ein und wandelt sie um, bis sie auf ein Zeichen stößt, das laut Grammatik in einer Ganzzahl nicht vorkommt und beendet das Parsen - das Ergebnis wird aus den erfolgreich eingelesenen Zeichen gebildet.

 <iostream>
 <sstream>

int main() { std::cout << "300‮⁦400⁩⁦500\n"; int i = std::stoi("300‮⁦400⁩⁦500"); std::cout << i << "\n"; i=0; if(std::istringstream("300‮⁦400⁩⁦500") >> i) std::cout << i << "\n"; else std::cout << "error " << i << "\n"; i==0; if(std::istringstream("huhu") >> i) std::cout << i << "\n"; else std::cout << "error\n"; i=0; char c; std::istringstream ss("300‮⁦400⁩⁦500"); ss >> i; if (ss.fail() || ss.get(c)) std::cout << i << "\n"; else std::cout << "error " << i << "\n"; return 0; }

C++ ist in meinen Tests der schwierigste Kandidat gewesen: Keiner der hier als Bordmittel getesteten Varianten zeigte einen Fehler auf - nicht einmal der letzte Codeabschnitt, in dem der Stream explizit nach dem Auftreten von Fehlern gefragt wird. Meine Empfehlung daher ist die gleiche, die auch das Internet gibt: lieber auch in C++ Programmen auf strtol zurückgreifen!

import string
from ast import literal_eval

print(literal_eval('300‮⁦400⁩⁦500')) print(eval('300‮⁦400⁩⁦500')) print(int('300‮⁦400⁩⁦500'))

Python hingegen schlägt sich ungefähr so gut wie Java - egal, welche der hier gezeigten drei Möglichkeiten man anwendet, um einen String in eine Ganzzahl umzuwandeln - jede wirft eine Exception, sobald versucht wird, TrojanSource-Controlcharacters in die Inputstrings zu schmuggeln.

Alle Artikel rss Wochenübersicht Monatsübersicht Github Repositories Gitlab Repositories Mastodon Über mich home xmpp


Vor 5 Jahren hier im Blog

  • Synchronisieren Zwischenablage mit XPRA

    01.09.2019

    Nachdem ich neulich mein XPRA-Skript nochmal ein wenig erneuert habe, hatte ich ein Problem:

    Weiterlesen...

Neueste Artikel

  • Will it go round in circles - Nashville Jam

    Eine neue Musikreihe/Show auf Youtube gefunden...

    Weiterlesen...
  • Diskless Setup über PXE mit readonly root FS für eine beliebige Anzahl Clients

    Ich habe neulich über meinen kleinen Erfolg berichtet, einen Futro über das Netzwerk zu booten. Das war allerdings nur das Minimalziel...

    Weiterlesen...
  • Futro s920 Booten übers Netz

    Ich habe hier bereits von den Anfängen meiner Versuche berichtet, Ersatz für die Raspis und vergleichbarer Hardware zu finden.

    Weiterlesen...

Manche 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, muß 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 GitHub-Repositories findet man hier - meine öffentlichen GitLab-Repositories finden sich dagegen hier.