112 lines
5.2 KiB
Markdown
112 lines
5.2 KiB
Markdown
# Selective Security
|
|
|
|
Willkommen zum Write-up für **Selective Security**. Dies ist eine einführende "Web" (Web Exploitation) Challenge, die eine der kritischsten und am weitesten verbreiteten Schwachstellen in der Geschichte von Webanwendungen demonstriert: **SQL Injection (SQLi)**.
|
|
|
|
In dieser Challenge wird uns ein scheinbar sicheres Login-Portal präsentiert, das "Standard"-Benutzer von "Administratoren" trennt. Unsere Mission ist es, den Authentifizierungsmechanismus zu umgehen und Zugriff auf das eingeschränkte Admin-Dashboard zu erhalten, um die Flagge abzurufen.
|
|
|
|
---
|
|
|
|
## 1. Erste Erkundung
|
|
|
|
Die Challenge stellt uns einen Link zu einem "Internen Blog-Portal" und ein herunterladbares Archiv zur Verfügung: `selective_security.tar.xz`.
|
|
|
|
Wenn wir das Portal besuchen, werden wir von einem Login-Formular begrüßt. Wir können versuchen, uns mit zufälligen Anmeldeinformationen (z.B. `guest`/`guest`) anzumelden, was uns Zugriff als "Standardbenutzer" gewährt. Wir sehen einen einfachen Blog-Feed, aber keine Flagge. Die Challenge-Beschreibung sagt uns, dass die "tatsächlichen administrativen Funktionen durch eine strenge Datenbanküberprüfung geschützt sind". Um die Flagge zu erhalten, müssen wir uns als **admin**-Benutzer anmelden.
|
|
|
|
## 2. Quellcode-Analyse
|
|
|
|
Da wir den Quellcode in `selective_security.tar.xz` erhalten haben, können wir genau sehen, wie der Server unseren Anmeldeversuch behandelt. Nach dem Entpacken des Archivs finden wir eine einzelne Datei: `main.go`.
|
|
|
|
Wenn wir uns die `loginHandler`-Funktion ansehen, sehen wir, wie die Anwendung zwischen Benutzern unterscheidet:
|
|
|
|
```go
|
|
func loginHandler(w http.ResponseWriter, r *http.Request) {
|
|
// ...
|
|
username := r.FormValue("username")
|
|
password := r.FormValue("password")
|
|
|
|
if username == "admin" {
|
|
handleAdminLogin(w, password)
|
|
} else {
|
|
// Standardbenutzer erhalten das fakeUserTmpl (keine Flagge)
|
|
data := map[string]string{"Username": username}
|
|
renderTemplate(w, fakeUserTmpl, data)
|
|
}
|
|
}
|
|
```
|
|
|
|
Wenn wir den Benutzernamen `admin` angeben, ruft die Anwendung `handleAdminLogin` auf. Hier findet die "strenge Datenbanküberprüfung" statt:
|
|
|
|
```go
|
|
func handleAdminLogin(w http.ResponseWriter, password string) {
|
|
// Query erstellen
|
|
query := fmt.Sprintf("SELECT id FROM users WHERE username = 'admin' AND password = '%s'", password)
|
|
log.Println("Executing Query:", query)
|
|
|
|
var id int
|
|
err := db.QueryRow(query).Scan(&id)
|
|
|
|
if err == nil {
|
|
// ERFOLG: Die Datenbank hat einen passenden Datensatz gefunden!
|
|
data := map[string]string{"Flag": globalFlag}
|
|
renderTemplate(w, successTmpl, data)
|
|
} else {
|
|
// ... Fehlerbehandlung ...
|
|
}
|
|
}
|
|
```
|
|
|
|
## 3. Die Schwachstelle: SQL Injection
|
|
|
|
Die Schwachstelle liegt darin, wie die SQL-Abfrage konstruiert wird. Die Anwendung verwendet `fmt.Sprintf`, um unser `password` direkt in den Abfrage-String einzufügen:
|
|
|
|
`"SELECT id FROM users WHERE username = 'admin' AND password = '%s'"`
|
|
|
|
Dies ist eine klassische **SQL Injection** Schwachstelle. Da die Anwendung keine **parametrisierten Abfragen** (Platzhalter wie `?`) verwendet, behandelt sie unsere Eingabe als Teil des SQL-Befehls selbst und nicht nur als Daten.
|
|
|
|
## 4. Entwicklung des Exploits
|
|
|
|
Wir kennen das Passwort des Admins nicht, aber wir können SQL-Syntax verwenden, um die Logik der `WHERE`-Klausel zu ändern. Unser Ziel ist es, die gesamte Bedingung zu **WAHR** auszuwerten, damit die Datenbank ein Ergebnis zurückgibt.
|
|
|
|
Wenn wir den folgenden Payload als Passwort eingeben:
|
|
`' OR '1'='1`
|
|
|
|
Die endgültige von der Datenbank ausgeführte Abfrage wird zu:
|
|
```sql
|
|
SELECT id FROM users WHERE username = 'admin' AND password = '' OR '1'='1'
|
|
```
|
|
|
|
### Aufschlüsselung der Logik:
|
|
1. `username = 'admin' AND password = ''`: Dieser Teil wird zuerst ausgewertet (aufgrund der Operator-Rangfolge) und ist wahrscheinlich **Falsch**.
|
|
2. `OR '1'='1'`: Dieser Teil ist immer **Wahr**.
|
|
3. `Falsch ODER Wahr` ergibt **Wahr**.
|
|
|
|
Die Datenbank ignoriert die falsche Passwortprüfung und gibt die ID des Admins zurück. Der Go-Code sieht, dass eine Zeile zurückgegeben wurde (`err == nil`) und gewährt uns Zugriff auf das Dashboard.
|
|
|
|
## 5. Ausnutzung
|
|
|
|
1. Navigieren Sie zur Login-Seite.
|
|
2. Benutzername eingeben: `admin`
|
|
3. Passwort eingeben: `' OR '1'='1`
|
|
4. Klicken Sie auf **Login**.
|
|
|
|
Die Seite "Administrator Access Granted" erscheint und zeigt die Flagge an.
|
|
|
|
**Flag:** `{flag:Sql_Inj3ct10n_Is_Ez_Pz_Read_From_File}`
|
|
|
|
---
|
|
|
|
## Gelernte Lektionen
|
|
|
|
Diese Challenge hebt hervor, warum Sie **niemals** Benutzereingaben vertrauen sollten, wenn Sie Datenbankabfragen erstellen. Selbst eine einzige Schwachstelle wie diese kann einem Angreifer vollen Zugriff auf sensible Daten oder administrative Konten gewähren.
|
|
|
|
Um dies zu verhindern, verwenden Sie immer **parametrisierte Abfragen** (auch bekannt als Prepared Statements). In Go wäre der sichere Weg, diese Abfrage zu schreiben:
|
|
|
|
```go
|
|
// SICHERE VERSION
|
|
db.QueryRow("SELECT id FROM users WHERE username = 'admin' AND password = ?", password)
|
|
```
|
|
|
|
Durch die Verwendung des `?`-Platzhalters stellt der Datenbanktreiber sicher, dass die Eingabe strikt als String behandelt wird, was es für den Benutzer unmöglich macht, "auszubrechen" und SQL-Befehle zu injizieren.
|
|
|
|
Frohes Hacken!
|