112 lines
4.7 KiB
Markdown
112 lines
4.7 KiB
Markdown
# Selective Security
|
|
|
|
Welcome to the write-up for **Selective Security**. This is an introductory "web" (Web Exploitation) challenge that demonstrates one of the most critical and pervasive vulnerabilities in web application history: **SQL Injection (SQLi)**.
|
|
|
|
In this challenge, we are presented with a seemingly secure login portal that separates "standard" users from "administrators." Our mission is to bypass the authentication mechanism and gain access to the restricted Admin Dashboard to retrieve the flag.
|
|
|
|
---
|
|
|
|
## 1. Initial Reconnaissance
|
|
|
|
The challenge provides us with a link to an "Internal Blog Portal" and a downloadable archive: `selective_security.tar.xz`.
|
|
|
|
When we visit the portal, we are greeted by a login form. We can try logging in with random credentials (e.g., `guest`/`guest`), which grants us access as a "Standard User." We see a basic blog feed, but no flag. The challenge description tells us that the "actual administrative features are protected by a strict database verification check." To get the flag, we need to log in as the **admin** user.
|
|
|
|
## 2. Source Code Analysis
|
|
|
|
Since we are given the source code in `selective_security.tar.xz`, we can see exactly how the server handles our login attempt. After extracting the archive, we find a single file: `main.go`.
|
|
|
|
Looking at the `loginHandler` function, we see how the application distinguishes between users:
|
|
|
|
```go
|
|
func loginHandler(w http.ResponseWriter, r *http.Request) {
|
|
// ...
|
|
username := r.FormValue("username")
|
|
password := r.FormValue("password")
|
|
|
|
if username == "admin" {
|
|
handleAdminLogin(w, password)
|
|
} else {
|
|
// Standard users get the fakeUserTmpl (no flag)
|
|
data := map[string]string{"Username": username}
|
|
renderTemplate(w, fakeUserTmpl, data)
|
|
}
|
|
}
|
|
```
|
|
|
|
If we provide the username `admin`, the application calls `handleAdminLogin`. This is where the "strict database verification" happens:
|
|
|
|
```go
|
|
func handleAdminLogin(w http.ResponseWriter, password string) {
|
|
// Build query
|
|
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 {
|
|
// SUCCESS: The database found a matching record!
|
|
data := map[string]string{"Flag": globalFlag}
|
|
renderTemplate(w, successTmpl, data)
|
|
} else {
|
|
// ... handle error ...
|
|
}
|
|
}
|
|
```
|
|
|
|
## 3. The Vulnerability: SQL Injection
|
|
|
|
The vulnerability lies in how the SQL query is constructed. The application uses `fmt.Sprintf` to insert our `password` directly into the query string:
|
|
|
|
`"SELECT id FROM users WHERE username = 'admin' AND password = '%s'"`
|
|
|
|
This is a classic **SQL Injection** vulnerability. Because the application does not use **parameterized queries** (placeholders like `?`), it treats our input as part of the SQL command itself rather than just data.
|
|
|
|
## 4. Developing the Exploit
|
|
|
|
We don't know the admin's password, but we can use SQL syntax to change the logic of the `WHERE` clause. Our goal is to make the entire condition evaluate to **TRUE**, so the database returns a result.
|
|
|
|
If we enter the following payload as the password:
|
|
`' OR '1'='1`
|
|
|
|
The final query executed by the database becomes:
|
|
```sql
|
|
SELECT id FROM users WHERE username = 'admin' AND password = '' OR '1'='1'
|
|
```
|
|
|
|
### Breaking down the logic:
|
|
1. `username = 'admin' AND password = ''`: This part is evaluated first (due to operator precedence) and is likely **False**.
|
|
2. `OR '1'='1'`: This part is always **True**.
|
|
3. `False OR True` results in **True**.
|
|
|
|
The database ignores the incorrect password check and returns the admin's ID. The Go code sees that a row was returned (`err == nil`) and grants us access to the dashboard.
|
|
|
|
## 5. Exploitation
|
|
|
|
1. Navigate to the login page.
|
|
2. Enter Username: `admin`
|
|
3. Enter Password: `' OR '1'='1`
|
|
4. Click **Login**.
|
|
|
|
The "Administrator Access Granted" page appears, displaying the flag.
|
|
|
|
**Flag:** `{flag:Sql_Inj3ct10n_Is_Ez_Pz_Read_From_File}`
|
|
|
|
---
|
|
|
|
## Lessons Learned
|
|
|
|
This challenge highlights why you should **never** trust user input when building database queries. Even a single vulnerability like this can give an attacker full access to sensitive data or administrative accounts.
|
|
|
|
To prevent this, always use **parameterized queries** (also known as prepared statements). In Go, the secure way to write this query would be:
|
|
|
|
```go
|
|
// SECURE VERSION
|
|
db.QueryRow("SELECT id FROM users WHERE username = 'admin' AND password = ?", password)
|
|
```
|
|
|
|
By using the `?` placeholder, the database driver ensures that the input is treated strictly as a string, making it impossible for the user to "break out" and inject SQL commands.
|
|
|
|
Happy Hacking!
|