|
Listing 1: |
|---|
01 #include "inputguardian.c"
02 #include <stdio.h>
03
04 int main(int argc, char **argv)
05 {
06 char *ret = inputg_escape_html(argv[1]);
07 if (ret != 0)
08 printf("%s", ret);
09 return !ret;
10 }
|

Abbildung 2: Die grobe Struktur des toten Briefkastens. Inetd lauscht auf Port 1111 und übergibt nach einer Anfrage die Kontrolle an »toter-briefkasten«. Dieses Programm schreibt die Miteilungen in die »/var/lib/tb« und liest sie auch daraus.
RFC 2822 [6] und seine Vorgänger legen fest, wie eine E-Mail-Adresse aussieht. Der neue Standard schreibt vor, dass konforme Programme bestimmte alte Formate weiterhin unterstützen. Im selbst gestrickten Formular darf der Admin guten Gewissens auf Extrawürste wie Routen in der Mailadresse verzichten (»<Route:Adresse>«). Laut RFC dürfen die Adressen sogar verschachtelte Kommentare enthalten.
Lösung für die Shell: Meist genügt ein grobschlächtiger Ansatz. Es kommt nur durch, was der Form »Name@Domain« genügt. Den erlaubten Zeichensatz für Name und Domäne schränkt der Admin nach Belieben ein und setzt seine Wünsche mit »egrep« durch:
echo "$ADDR" | egrep '^[a-zA-Z0-9_+-.]+U @[a-zA-Z0-9-]+(.[a-zA-Z0-9-]+)*$' || U exit 1
Wem dieser einfache Ansatz zu ungenau ist, findet in Jeffrey Friedls Regular-Expression-Buch [7] einen regulären Ausdruck, der beinahe das komplette RFC abdeckt. Nur verschachtelte Kommentare kennt er nicht. Auf [8] ist ein Perl-Programm zu finden, das die Regex verwendet, um E-Mail-Adressen zu verifizieren. Es lässt sich recht bequem verwenden: »perl email-opt.pl “$ADDR” >/dev/null || exit 1«
Lösung für C und C++: Die Gateguardian-Funktion »inputg_is_simple_email_address()« implementiert den Egrep-Holzhammertest in C:
#include <inputguardian.c> [...] int rc; rc = inputg_is_simple_email_address(addr) if (rc == 0) exit(1);
Das genauere Perl-Programm in C oder C++ nachzubilden ist nicht empfehlenswert, da es bei komplexen Parsing-Aufgaben leicht zu Fehlern und unbeabsichtigten Nebeneffekten kommt.
Toter Briefkasten
Eine andere Art von Eingabekontrollfehlern begeht das Programm »toter-briefkasten« in Listing 2, das Gelegenheitsprogrammierer Gerd als Netzwerkdienst konzipiert hat. Der Internet-Superserver »inetd« startet das Programm, dessen Standard-Ein- und -Ausgabe er mit dem anfragenden Client aus dem Netzwerk verbindet (Abbildung 2).
Agentin Annette hinterlegt eine geheime Botschaft mit einem Zugangscode im toten Briefkasten. Um eine Verbindung herzustellen, verwendet sie »telnet Server 1111« und gibt dann die Botschaft in der Form Code:Botschaft ein:
geheim:Vera ist eine Verräterin! NächsterU Code: x
Der Dienst akzeptiert einzeilige Botschaften mit höchstens 1000 Zeichen (Zeile 18 in Listing 2) und hängt sie an die Datei »/var/lib/tb« an (Zeilen 29 und 30). Danach beendet er sich und schließt damit die Verbindung (Zeile 44). Kontaktmann Klaus fragt später die Botschaften ab, indem er nur den Geheimcode eingibt:
geheim geheim:Vera ist eine Verräterin! NächsterU Code: x
Der Dienst durchforstet die Datei »/var/lib/tb« nach allen Zeilen, die mit dem richtigen Geheimcode beginnen, und gibt sie aus (Zeilen 35 bis 41). Natürlich liefe eine geheime Kommunikation im echten Agentenleben über einen verschlüsselten Kanal und nicht im Klartext ab. Im konkreten Fall spielt das aber wegen der verborgenen Fehler im Programm keine Rolle – und da stehen Formatstrings an erster Stelle.
|
Listing 2: Toter Briefkasten |
|---|
01 #include <stdio.h>
02 #include <stdlib.h>
03 #include <string.h>
04
05 FILE *f;
06
07 void oeffne_briefkasten(char *mode)
08 {
09 f = fopen("/var/lib/tb", mode);
10 if (f == NULL)
11 exit(1);
12 }
13
14 int main(void)
15 {
16 int len;
17 char eingabe[1000];
18 char zeile[1000];
19
20 if (!gets(eingabe))
21 return 1;
22 len = strlen(eingabe);
23 if (eingabe[len - 1] == 'r') {
24 eingabe[len - 1] = 0;
25 len = len - 1;
26 }
27 if (strchr(eingabe, ':')) {
28 /* "key:botschaft" -> speichern */
29 oeffne_briefkasten("a");
30 fprintf(f, "%sn", eingabe);
31 }
32 else {
33 /* "key" -> botschaften lesen */
34 oeffne_briefkasten("r");
35 while (fgets(zeile, 1000, f)) {
36 if (strncmp(zeile, eingabe, len)
37 == 0 && zeile[len] == ':') {
38 /* key stimmt -> anzeigen */
39 printf(zeile);
40 }
41 }
42 }
43
44 return 0;
45 }
|
Falle 3: Formatstrings
Gerd ist bei seinem toten Briefkasten der gleiche Fehler unterlaufen wie im Beispielprogramm Patch-fstring aus Folge 3 [1]. Relevant war dort die Programmzeile »fprintf(f, string);«. Die Lösung war damals nicht möglich, sie entspricht aber Gerds Problem. In seinem totem Briefkasten steckt der Bug in Zeile 39: Hier lautet der Code »printf(zeile);«. Auf den ersten Blick scheint Letzteres äquivalent zu »printf(“%s”, zeile);« zu sein. Beide Male gibt Printf den Inhalt der Variablen »zeile« aus. Allerdings interpretiert das Printf-Kommando den Formatstring (erstes Argument), aber der stammt vom Anwender, etwa dem Feind Fred. Gerd hat es versäumt, alle Eingaben auf ihre Sicherheit zu prüfen.





