Spaces:
Sleeping
Sleeping
Update main.go
Browse files
main.go
CHANGED
|
@@ -3,21 +3,21 @@ package main
|
|
| 3 |
import (
|
| 4 |
"database/sql"
|
| 5 |
"fmt"
|
|
|
|
| 6 |
"log"
|
| 7 |
"net/http"
|
| 8 |
"os"
|
|
|
|
| 9 |
|
| 10 |
_ "github.com/lib/pq"
|
| 11 |
)
|
| 12 |
|
| 13 |
-
// User represents the database structure we are querying
|
| 14 |
type User struct {
|
| 15 |
ID int
|
| 16 |
Username string
|
| 17 |
Flag string
|
| 18 |
}
|
| 19 |
|
| 20 |
-
// connectDB handles the connection to Supabase/Postgres
|
| 21 |
func connectDB() (*sql.DB, error) {
|
| 22 |
connStr := os.Getenv("DATABASE_URL")
|
| 23 |
if connStr == "" {
|
|
@@ -27,31 +27,47 @@ func connectDB() (*sql.DB, error) {
|
|
| 27 |
}
|
| 28 |
|
| 29 |
func main() {
|
| 30 |
-
// 1.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
| 32 |
html := `
|
| 33 |
<!DOCTYPE html>
|
| 34 |
<html>
|
| 35 |
<head>
|
| 36 |
-
<title>
|
| 37 |
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
| 38 |
</head>
|
| 39 |
-
<body class="bg-gray-900 text-white flex items-center justify-center h-screen">
|
| 40 |
-
<div class="bg-gray-800 p-8 rounded shadow-lg w-96">
|
| 41 |
-
<h1 class="text-2xl mb-
|
| 42 |
-
<p class="text-gray-400 mb-6 text-center text-
|
|
|
|
| 43 |
<form action="/login" method="POST" class="space-y-4">
|
| 44 |
<div>
|
| 45 |
-
<label class="block text-sm font-medium">Username</label>
|
| 46 |
-
<input type="text" name="username" class="w-full p-2 bg-gray-700 rounded border border-gray-600 focus:border-
|
| 47 |
</div>
|
| 48 |
<div>
|
| 49 |
-
<label class="block text-sm font-medium">Password</label>
|
| 50 |
-
<input type="password" name="password" class="w-full p-2 bg-gray-700 rounded border border-gray-600 focus:border-
|
| 51 |
</div>
|
| 52 |
-
<button type="submit" class="w-full bg-
|
| 53 |
</form>
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
</div>
|
| 56 |
</body>
|
| 57 |
</html>
|
|
@@ -60,7 +76,7 @@ func main() {
|
|
| 60 |
w.Write([]byte(html))
|
| 61 |
})
|
| 62 |
|
| 63 |
-
//
|
| 64 |
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
|
| 65 |
if r.Method != http.MethodPost {
|
| 66 |
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
@@ -70,6 +86,29 @@ func main() {
|
|
| 70 |
username := r.FormValue("username")
|
| 71 |
password := r.FormValue("password")
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
db, err := connectDB()
|
| 74 |
if err != nil {
|
| 75 |
http.Error(w, "Database connection failed", http.StatusInternalServerError)
|
|
@@ -78,51 +117,42 @@ func main() {
|
|
| 78 |
}
|
| 79 |
defer db.Close()
|
| 80 |
|
| 81 |
-
// --- THE VULNERABILITY
|
| 82 |
-
//
|
| 83 |
-
// A user can input "' OR '1'='1" to bypass the check.
|
| 84 |
query := fmt.Sprintf("SELECT id, username, flag FROM users WHERE username = '%s' AND password = '%s'", username, password)
|
| 85 |
|
| 86 |
-
// Log the query to the console (useful for debugging the CTF/seeing the injection)
|
| 87 |
log.Printf("Executing Query: %s\n", query)
|
| 88 |
|
| 89 |
var user User
|
| 90 |
-
//
|
| 91 |
err = db.QueryRow(query).Scan(&user.ID, &user.Username, &user.Flag)
|
| 92 |
|
| 93 |
if err != nil {
|
| 94 |
if err == sql.ErrNoRows {
|
| 95 |
w.WriteHeader(http.StatusUnauthorized)
|
| 96 |
-
w.Write([]byte(
|
| 97 |
-
<div style="color: red; text-align: center; margin-top: 50px; font-family: monospace;">
|
| 98 |
-
<h1>ACCESS DENIED</h1>
|
| 99 |
-
<p>Invalid credentials.</p>
|
| 100 |
-
<a href="/" style="color: white;">Try Again</a>
|
| 101 |
-
</div>
|
| 102 |
-
`))
|
| 103 |
} else {
|
| 104 |
http.Error(w, "Query error: "+err.Error(), http.StatusInternalServerError)
|
| 105 |
}
|
| 106 |
return
|
| 107 |
}
|
| 108 |
|
| 109 |
-
//
|
| 110 |
w.WriteHeader(http.StatusOK)
|
| 111 |
fmt.Fprintf(w, `
|
| 112 |
-
<div style="
|
| 113 |
-
<h1>
|
| 114 |
-
<p>
|
| 115 |
<p style="font-size: 24px; border: 1px dashed #4ade80; display: inline-block; padding: 10px;">FLAG: %s</p>
|
| 116 |
</div>
|
| 117 |
`, user.Username, user.Flag)
|
| 118 |
})
|
| 119 |
|
| 120 |
-
// Hugging Face Spaces usually serves on port 7860
|
| 121 |
port := os.Getenv("PORT")
|
| 122 |
if port == "" {
|
| 123 |
port = "7860"
|
| 124 |
}
|
| 125 |
|
| 126 |
-
log.Printf("CTF
|
| 127 |
log.Fatal(http.ListenAndServe(":"+port, nil))
|
| 128 |
}
|
|
|
|
| 3 |
import (
|
| 4 |
"database/sql"
|
| 5 |
"fmt"
|
| 6 |
+
"io/ioutil"
|
| 7 |
"log"
|
| 8 |
"net/http"
|
| 9 |
"os"
|
| 10 |
+
"strings"
|
| 11 |
|
| 12 |
_ "github.com/lib/pq"
|
| 13 |
)
|
| 14 |
|
|
|
|
| 15 |
type User struct {
|
| 16 |
ID int
|
| 17 |
Username string
|
| 18 |
Flag string
|
| 19 |
}
|
| 20 |
|
|
|
|
| 21 |
func connectDB() (*sql.DB, error) {
|
| 22 |
connStr := os.Getenv("DATABASE_URL")
|
| 23 |
if connStr == "" {
|
|
|
|
| 27 |
}
|
| 28 |
|
| 29 |
func main() {
|
| 30 |
+
// 1. Endpoint to View Source Code (Crucial for the CTF)
|
| 31 |
+
http.HandleFunc("/source", func(w http.ResponseWriter, r *http.Request) {
|
| 32 |
+
content, err := ioutil.ReadFile("main.go")
|
| 33 |
+
if err != nil {
|
| 34 |
+
http.Error(w, "Could not read source code.", http.StatusInternalServerError)
|
| 35 |
+
return
|
| 36 |
+
}
|
| 37 |
+
w.Header().Set("Content-Type", "text/plain")
|
| 38 |
+
w.Write(content)
|
| 39 |
+
})
|
| 40 |
+
|
| 41 |
+
// 2. Login Page
|
| 42 |
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
| 43 |
html := `
|
| 44 |
<!DOCTYPE html>
|
| 45 |
<html>
|
| 46 |
<head>
|
| 47 |
+
<title>Secure Login System v2.0</title>
|
| 48 |
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
| 49 |
</head>
|
| 50 |
+
<body class="bg-gray-900 text-white flex flex-col items-center justify-center h-screen">
|
| 51 |
+
<div class="bg-gray-800 p-8 rounded shadow-lg w-96 border-t-4 border-blue-500">
|
| 52 |
+
<h1 class="text-2xl mb-2 text-center font-bold text-blue-400">SECURE GATEWAY</h1>
|
| 53 |
+
<p class="text-gray-400 mb-6 text-center text-xs">Protected by GoWAF™ technology</p>
|
| 54 |
+
|
| 55 |
<form action="/login" method="POST" class="space-y-4">
|
| 56 |
<div>
|
| 57 |
+
<label class="block text-sm font-medium text-gray-300">Username</label>
|
| 58 |
+
<input type="text" name="username" class="w-full p-2 bg-gray-700 rounded border border-gray-600 focus:border-blue-500 outline-none" placeholder="Enter username">
|
| 59 |
</div>
|
| 60 |
<div>
|
| 61 |
+
<label class="block text-sm font-medium text-gray-300">Password</label>
|
| 62 |
+
<input type="password" name="password" class="w-full p-2 bg-gray-700 rounded border border-gray-600 focus:border-blue-500 outline-none" placeholder="••••••">
|
| 63 |
</div>
|
| 64 |
+
<button type="submit" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition">AUTHENTICATE</button>
|
| 65 |
</form>
|
| 66 |
+
|
| 67 |
+
<div class="mt-6 border-t border-gray-700 pt-4 text-center">
|
| 68 |
+
<p class="text-xs text-gray-500">Developers only:</p>
|
| 69 |
+
<a href="/source" class="text-sm text-blue-400 hover:underline">View Source Code</a>
|
| 70 |
+
</div>
|
| 71 |
</div>
|
| 72 |
</body>
|
| 73 |
</html>
|
|
|
|
| 76 |
w.Write([]byte(html))
|
| 77 |
})
|
| 78 |
|
| 79 |
+
// 3. The Hardened (but still vulnerable) Login Endpoint
|
| 80 |
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
|
| 81 |
if r.Method != http.MethodPost {
|
| 82 |
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
|
|
| 86 |
username := r.FormValue("username")
|
| 87 |
password := r.FormValue("password")
|
| 88 |
|
| 89 |
+
// --- CHALLENGE PART 1: HEADER CHECK ---
|
| 90 |
+
// The student must read the code to realize they need to change their User-Agent.
|
| 91 |
+
ua := r.Header.Get("User-Agent")
|
| 92 |
+
if ua != "Secure-CTF-Browser/1.0" {
|
| 93 |
+
http.Error(w, "Security Alert: Browser not authorized. Please use 'Secure-CTF-Browser/1.0'", http.StatusForbidden)
|
| 94 |
+
return
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
// --- CHALLENGE PART 2: THE TRAP ---
|
| 98 |
+
// We explicitly block "admin" to prevent simple brute forcing.
|
| 99 |
+
// They must use SQL injection to select the admin row *without* sending "admin" as the username string.
|
| 100 |
+
if username == "admin" {
|
| 101 |
+
http.Error(w, "Direct login as 'admin' is disabled for security reasons.", http.StatusForbidden)
|
| 102 |
+
return
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
// --- CHALLENGE PART 3: THE WAF (Input Filtering) ---
|
| 106 |
+
// We block spaces. The student must use SQL comments (/**/) or tabs to bypass this.
|
| 107 |
+
if strings.Contains(username, " ") || strings.Contains(password, " ") {
|
| 108 |
+
http.Error(w, "WAF Detection: Spaces are not allowed in input fields.", http.StatusBadRequest)
|
| 109 |
+
return
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
db, err := connectDB()
|
| 113 |
if err != nil {
|
| 114 |
http.Error(w, "Database connection failed", http.StatusInternalServerError)
|
|
|
|
| 117 |
}
|
| 118 |
defer db.Close()
|
| 119 |
|
| 120 |
+
// --- THE VULNERABILITY ---
|
| 121 |
+
// Still using fmt.Sprintf, but now the input is heavily restricted by the checks above.
|
|
|
|
| 122 |
query := fmt.Sprintf("SELECT id, username, flag FROM users WHERE username = '%s' AND password = '%s'", username, password)
|
| 123 |
|
|
|
|
| 124 |
log.Printf("Executing Query: %s\n", query)
|
| 125 |
|
| 126 |
var user User
|
| 127 |
+
// QueryRow returns the first matching row. If they inject ' OR '1'='1, it will return the first user in the DB (Admin).
|
| 128 |
err = db.QueryRow(query).Scan(&user.ID, &user.Username, &user.Flag)
|
| 129 |
|
| 130 |
if err != nil {
|
| 131 |
if err == sql.ErrNoRows {
|
| 132 |
w.WriteHeader(http.StatusUnauthorized)
|
| 133 |
+
w.Write([]byte("Invalid credentials."))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
} else {
|
| 135 |
http.Error(w, "Query error: "+err.Error(), http.StatusInternalServerError)
|
| 136 |
}
|
| 137 |
return
|
| 138 |
}
|
| 139 |
|
| 140 |
+
// Success
|
| 141 |
w.WriteHeader(http.StatusOK)
|
| 142 |
fmt.Fprintf(w, `
|
| 143 |
+
<div style="font-family: monospace; background: #111; color: #4ade80; padding: 20px; text-align: center;">
|
| 144 |
+
<h1>SYSTEM BREACHED</h1>
|
| 145 |
+
<p>User: %s</p>
|
| 146 |
<p style="font-size: 24px; border: 1px dashed #4ade80; display: inline-block; padding: 10px;">FLAG: %s</p>
|
| 147 |
</div>
|
| 148 |
`, user.Username, user.Flag)
|
| 149 |
})
|
| 150 |
|
|
|
|
| 151 |
port := os.Getenv("PORT")
|
| 152 |
if port == "" {
|
| 153 |
port = "7860"
|
| 154 |
}
|
| 155 |
|
| 156 |
+
log.Printf("CTF Hard Mode listening on port %s", port)
|
| 157 |
log.Fatal(http.ListenAndServe(":"+port, nil))
|
| 158 |
}
|