Der vermaledeite Kassenzettel

Meine Mutter ist seit etwa einem Jahr Mitinhaberin eines Einzelhandelsgeschäfts. Das hat zwar den Vorteil, dass ich kein Geld mehr für Haushaltswaren ausgeben kann, allerdings bin ich leider auch technisch versiert und darf mich deshalb im Gegenzug um das Kassensystem kümmern.

Da gibt es nur eine Sache, die ungeheuer nervt.

Über die man sich sonst im täglichen Geschäftsleben keine Gedanken macht.

Die natürlichste Steuer der Welt.

Die Mehrwertsteuer1.

Beispiel gefällig?

# Anz. Artikel Stk. Summe MwSt.
1 3x Tomate 0,85 € 2,55 € 7 %
2 2x Stift 1,55 € 3,10 € 19 %

Wie viel zahlt der Kunde für seinen Einkauf? Ok, das ist einfach: 5,65 € natürlich.

Doch wie viel Mehrwertsteuer muss der Händler abführen? Wenn die Mehrwertsteuer zunächst auf die einzelnen Artikel berechnet, dann gerundet und dann aufsummiert wird, kommen wir auf 18 + 50 = 68 ct. Wenn zunächst in den einzelnen Mehrwertsteuerkategorien aufsummiert und dann die Steuer berechnet wird, kommen wir hingegen auf lediglich 17 + 49 = 66 ct.
(Fragte ich, wie die Berechnung denn nun vorzunehmen sei, bekam ich bislang keine zufriedenstellende Antwort.  Selbst der Blick ins Umsatzsteuergesetz half nicht wirklich weiter. Wenn das jemand beantworten kann, ab damit in die Kommentare.)

Und es kommt noch lustiger. Auf Rechnungen wird gemeinhin der Nettostückpreis eines jeden Artikels mit aufgeführt:

# Anz. Artikel Stk. (Netto) Stk. (Brutto) Summe MwSt.
1 3x Tomate 0,79 € 0,85 € 2,55 € 7 %
2 2x Stift 1,30 € 1,55 € 3,10 € 19 %

Das ist sehr hübsch. Nur sollte niemand auf die Idee kommen, mal nachzurechnen. Bei der Tomate sieht es nämlich folgendermaßen aus:

3 Tomaten à 0,79 € ergeben 2,37 € Netto, plus 7 % Mehrwertsteuer (17 ct) kommen wir auf 2,54 €.

Hoppla.

Und andersrum kann das auch ganz einfach schief gehen.

Fazit

Ohne Steuern wäre das alles viel zu einfach.

  1. oder Umsatzsteuer, die als Mehrwertsteuer auftritt.

Bilder zerlegen

Demletzt habe ich in mein Impressum eine kleine Spielerei eingebaut. Die Adresse ist nicht als Text hinterlegt, sondern als Bild. Beziehungsweise, als 8 Bilder, die übereinandergelegt das eigentliche Bild ergeben.

Um das umzusetzen, habe ich mir ein kleines Python-Skript gebastelt, das die Grafik für mich aufsplittet und auch gleich dazu noch das passende HTML-Snippet erzeugt.

Hier gibt es das Projekt auf GitHub.

Die Benutzung ist total einfach:

./pngfragments.py [Pfad-zur-Bilddatei] [Anzahl-der-Teilbilder]

Daraufhin wird das Bild geöffnet, die Menge aller nicht-transparenten Pixel wird zufällig auf X etwa gleich große Teilmengen aufgeteilt und aus diesen werden dann im Ordner ‚output‘ die Teilbilder erstellt. Zusätzlich wird noch ein passendes HTML-Snippet und eine vollständige HTML-Datei mit diesem Snippet erstellt, mit dem das vollständige Bild dargestellt werden kann.

Je größer die Zahl der Teilbilder gewählt wird, über desto mehr Bilder werden die Pixel verteilt und desto weniger erkennt man logischerweise auch auf den Teilbildern (siehe auch bei den Beispielen)

Technische Daten

Das Skript ist für Python3 geschrieben. Es verarbeitet PNG,- JPEG- und TIFF-Dateien. Für die Grafikbastelei wird die Python Imaging Library (PIL) benutzt, das entsprechende Python-Paket muss also verfügbar sein.

[Update]
By popular demand ist nun auch ein zweites Skript enthalten, mit dem man sich ein Textbild gleich direkt miterstellen kann.

Beispiele

Adresse

Teilbilder

item-0.png

item-1.png

item-2.png

item-3.png

item-4.png

item-5.png

item-6.png

item-7.png

Avatar

ava.jpg

ava.jpg

Aufteilung in 2 Bilder

ava-0.png

ava-0.png

ava-1.png

ava-1.png

Aufteilung in 4 Bilder

ava-0.png

ava-0.png

ava-1.png

ava-1.png

ava-2.png

ava-2.png

ava-3.png

ava-3.png

SQL-Injection für Einsteiger

Es gibt viele Internetdienste, bei denen man sich mit Benutzernamen und Passwort anmelden muss. Das Formular sieht dann zum Beispiel so aus:

Eine handelsübliche Loginmaske.

Eine handelsübliche Loginmaske.

Total einfach: Man trägt seinen Benutzernamen (z. B. hszemi) und sei Passwort (z. B. geheim) ein, klickt auf „Anmelden“ und wird auf die Startseite des „internen“ Bereichs weitergeleitet.

Basics

Im Hintergrund läuft dabei in der Regel Folgendes ab:

Es gibt einen Datenbankserver mit einer Datenbank, in der sich eine Tabelle befindet, in der alle Benutzerkonten eingetragen sind: In der Spalte „benutzername“ steht der jeweilige Benutzername und in der Spalte „passwort“ jeweils das dazugehörige Passwort – allerdings nicht im Klartext1, sondern in der Regel wird eine Einwegfunktion verwendet (zum Beispiel MD5), die aus dem Passwort eine Prüfsumme berechnet, und lediglich die wird in der Datenbank gespeichert.

Das hat den Vorteil, dass jemand, der Zugriff auf die Datenbank hat, nicht die Passwörter sieht, sondern theoretisch so lange Wörter durch die Einwegfunktion jagen muss, bis zufällig einmal die richtige Prüfsumme herauskommt.

Die Benutzertabelle in der Datenbank sieht also etwa so aus:

ID benutzername passwort
1 Safura 4fdf7cf087cbf024d2e50a14256016f7
2 Erna 908fc4942e1f629d4c6629808da70165
3 Hans ccd5cc2741cd6347ac791e76e3062528
4 Jean b195d8b5aee9b903334bc8360099a90f

Zurück zum Login-Formular. Wird auf den „Anmelden“-Knopf geklickt, so werden an den Webserver die Inhalte der beiden Textfelder gesendet. Dort wird dann geprüft, ob es eine Zeile in der Benutzertabelle gibt, in der in der Spalte „benutzername“ der Text aus dem Formularfeld „Benutzername“ steht UND in deren Passwortfeld der Wert steht, der herauskommt, wenn man die Einwegfunktion auf den Inhalt des Formularfelds „Passwort“ anwendet.

Solche Datenbankabfragen werden gemeinhin in einer eigenen Sprache formuliert, der „Structured Query Language“ oder kurz „SQL“. Das ist eine Art sehr einfaches Englisch. Eine einfache SQL-Anfrage hat folgende Struktur:

SELECT (Liste von Tabellenspalten oder einfach * für alle Spalten)
FROM (Liste von Tabellen, aus denen die Spalten kommen)
WHERE (Bedingungen, um die Zeilen auszuwählen)

Wenn sich nun also Hans mit dem Passwort hanspasswort anmelden will, müsste folgende Anfrage ausgeführt werden:

SELECT *
FROM benutzer
WHERE benutzername = ‚Hans‘ AND passwort = MD5(‚hanspasswort‘)2

Wenn die Anfrage eine Zeile als Ergebnis zurückliefert, hat Hans das richtige Passwort eingegeben und darf in den „internen“ Bereich. Falls es keine Zeile in der Tabelle gibt, die beide Bedingungen erfüllt, hat Hans ein falsches Passwort eingegeben und muss draußen bleiben.

Der Angriff

Die SQL-Abfrage muss nun allerdings für jeden Anmeldevorgang dynamisch zusammengebastelt werden. Nehmen wir an, der eingegebene Benutzername steckt immer in der Variable $BENUTZER und das eingegebene Passwort in der Variable $PASSWORT. Dann könnte man folgende SQL-Abfrage ausführen:

SELECT *
FROM benutzer
WHERE benutzername = ‚$BENUTZER‚ AND passwort = MD5(‚$PASSWORT‚)

Das blöde dabei: So eine SQL-Abfrage ist erstmal nur ein gewöhnlicher String (heißt eine Zeichenkette). Man könnte jetzt als Benutzernamen folgendes eingeben:

Safura‘ OR ‚1‘ = ‚1

Dann wird aus dem WHERE-Teil (der die Auswahl der Zeilen bestimmt) folgendes:

WHERE benutzername = ‚Safura‘ OR ‚1‘ = ‚1‘ AND passwort = MD5(‚…‘)

Da in SQL eine UND-Verknüpfung (AND) vor einer ODER-Verknüpfung (OR) ausgewertet wird und ‚1‘ = ‚1‘ logischerweise immer den Wert WAHR hat, werden plötzlich alle Zeilen zurückgeliefert, in denen entweder der Benutzername gleich Safura oder das eingegebene Passwort hinterlegt ist, oder beides.

Das heißt, mit diesem Trick kann man sich als jeder beliebige Nutzer anmelden, ohne das Passwort zu kennen. Alternativ kann man auch einen Quatsch-Benutzernamen angeben und als Passwort häufig genutzte Passwörter durchraten und wird bei einem Treffer als der Benutzer angemeldet, dessen Passwort man erraten hat – ohne zuvor den Benutzernamen zu kennen.

Warum funktioniert das?

Beim Zusammenbasteln der Anfrage oben wurden die Benutzereingaben direkt verwendet. Der eingegebene Benutzername wurde dann so gewählt, dass er zur Grundstuktur der SQL-Anfrage passt, aber ein anderes (vom Angreifer gewünschtes) Ergebnis zurückgeliefert wird. Durch die Anführungszeichen wird der eigentliche Benutzername beendet und das folgende OR wird als SQL-Schlüsselwort (statt als Inhalt des Benutzernamens) behandelt – mit fatalen Folgen.

Was tut man dagegen?

Eine der wichtigsten Regeln beim Programmieren: Vertraue gar niemals nicht irgendwelchen Benutzereingaben. Dass im obigen Beispiel der eingegebene Benutzername ohne vorherige Prüfung einfach so in die SQL-Abfrage eingebaut wird, ist grob fahrlässig – was leider nicht heißt, dass so etwas in der freien Wildbahn nicht vorkommt.

Für Datenbankabfragen die Variablen enthalten sollte man wo möglich Prepared Statements verwenden, die sich darum kümmern, dass eine Zahl wirklich eine Zahl und ein String wirklich ein String bleibt, und entsprechende problematische Zeichen zuverlässig herausfiltern.

Falls dafür ein Umbau der gesamten Webanwendung nötig und dieser zu aufwändig wäre, sollte man als Sofortmaßnahme auf jeden Fall die Benutzereingaben vorfiltern. PHP bietet hier zum Beispiel die Funktion mysqli_real_escape_string() an, die das übernimmt.

Die „abgehärtete“ Datenbankabfrage sähe dann so aus:

SELECT *
FROM benutzer
WHERE benutzername = ‚mysqli_real_escape_string($BENUTZER)
AND passwort = MD5(‚mysqli_real_escape_string(
$PASSWORT)‚)

Fazit

Nun gut, diese Sicherheitslücke ist also bei uns drin.
Die Reparatur würde aber Geld und/oder Zeit kosten, deshalb machen wir da nix. Das weiß schließlich keiner dass die Lücke da ist und wie man sie ausnutzt weiß außer Ihnen auch niemand.

Ääh ja. SQL-Injections gehören zum kleinen 1×1 eines jeden Skriptkiddies. Gutes Gelingen 🙂

  1. Wobei es das auch gibt. Sollte man das machen?
  2. MD5(‚hanspasswort‘) berechnet in diesem Fall den Wert der Einwegfunktion für die Eingabe ‚hanspasswort‘