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 Codeberg Repositories Mastodon Über mich home xmpp


Vor 5 Jahren hier im Blog

  • Vorhaben 2020

    03.01.2020

    Genau wie letztes Jahr habe ich auch dieses Jahr wieder ein "Listche" verfasst, um mir all die interessanten Vorhaben zu notieren, die ich mit mittlerem zeitlichen Horizont anzugehen gedenke.

    Weiterlesen...

Neueste Artikel

  • Migration der Webseite und aller OpenSource Projekte

    In eigener Sache...

    Weiterlesen...
  • 38c3 - Nachlese

    Nach dem ersten Teil von mir als interessant eingestufter Vorträge des Chaos Communication Congress 2024 hier nun die Nachlese

    Weiterlesen...
  • 38c3 - Empfehlungen

    Nach dem So - wie auch im letzten Jahr: Meine Empfehlungen für Vorträge vom Chaos Communication Congress 2024 - vulgo: 38c3:

    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, 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.