Код для lab2
This commit is contained in:
1
lab2/.gitignore
vendored
Normal file
1
lab2/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.pyc
|
||||
1
lab2/.python-version
Normal file
1
lab2/.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.14
|
||||
99
lab2/README.md
Normal file
99
lab2/README.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Lab 2 — Authentication, Authorization & Brute Force Research
|
||||
|
||||
Система доступа к конфиденциальным данным с управлением пользователями и исследованием стойкости паролей.
|
||||
|
||||
## Структура каталогов
|
||||
|
||||
```
|
||||
$PRACTICE2_DIR/ # по умолчанию /usr/local/practice2
|
||||
├── etc/passwd # логин:sha256:id:права:ФИО
|
||||
├── confdata/ # конфиденциальные файлы
|
||||
├── bin/ # утилиты (usermgr, confaccess, bruteforce)
|
||||
└── log/ # usermgr.log, access.log
|
||||
```
|
||||
|
||||
Базовый каталог задаётся через переменную окружения `PRACTICE2_DIR`.
|
||||
Если переменная не задана — используется `/usr/local/practice2`.
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
chmod +x setup.sh
|
||||
|
||||
# для пути по умолчанию (/usr/local/practice2) нужен root:
|
||||
sudo ./setup.sh
|
||||
|
||||
# для тестирования без root:
|
||||
PRACTICE2_DIR=/tmp/practice2 ./setup.sh
|
||||
```
|
||||
|
||||
Скрипт создаёт структуру каталогов, копирует утилиты в `bin/` и выставляет права доступа.
|
||||
|
||||
### Добавить bin во временный PATH
|
||||
|
||||
```bash
|
||||
export PATH="/usr/local/practice2/bin:$PATH"
|
||||
|
||||
# или для тестовой директории:
|
||||
export PATH="/tmp/practice2/bin:$PATH"
|
||||
```
|
||||
|
||||
После этого утилиты доступны без полного пути:
|
||||
|
||||
```bash
|
||||
usermgr add alice
|
||||
confaccess
|
||||
bruteforce alice
|
||||
```
|
||||
|
||||
## Использование
|
||||
|
||||
### usermgr — управление пользователями
|
||||
|
||||
```bash
|
||||
usermgr add alice # добавить пользователя (интерактивный ввод)
|
||||
usermgr list # список пользователей
|
||||
usermgr edit alice --permissions rw
|
||||
usermgr edit alice --full-name "Иванов Иван"
|
||||
usermgr passwd alice # сменить пароль
|
||||
usermgr delete alice # удалить пользователя
|
||||
```
|
||||
|
||||
Права: `r` — чтение, `w` — запись, `d` — удаление.
|
||||
|
||||
Требования к паролю: первый символ — буква (A–Z, a–z), далее — буквы, цифры и `!@#$%^&*()`.
|
||||
|
||||
### confaccess — доступ к данным
|
||||
|
||||
```bash
|
||||
confaccess
|
||||
# или с явным указанием базовой директории:
|
||||
PRACTICE2_DIR=/tmp/practice2 confaccess
|
||||
```
|
||||
|
||||
После аутентификации доступны команды:
|
||||
|
||||
```
|
||||
create <file> создать новый пустой файл в confdata [requires: w]
|
||||
read <file> вывести содержимое файла [requires: r]
|
||||
append <file> <text> дописать строку в файл [requires: w]
|
||||
copy <src> <dst> скопировать файл в confdata [requires: r, w]
|
||||
remove <file> удалить файл из confdata [requires: d]
|
||||
help / exit
|
||||
```
|
||||
|
||||
Пути к файлам указываются относительно `confdata/` (или абсолютные).
|
||||
Для `copy`: src — любой путь, dst — внутри confdata, перезапись запрещена.
|
||||
Выход — `exit` или Ctrl+C.
|
||||
|
||||
### bruteforce — взлом пароля
|
||||
|
||||
```bash
|
||||
bruteforce alice
|
||||
bruteforce alice --max-length 4
|
||||
```
|
||||
|
||||
Алгоритм хэширования: **SHA-256**.
|
||||
Перебор выполняется напрямую по хэшу из passwd-файла.
|
||||
Фиксируется время перебора и количество итераций до нахождения пароля.
|
||||
Перебор останавливается автоматически при достижении лимита 8 часов.
|
||||
244
lab2/access.py
Normal file
244
lab2/access.py
Normal file
@@ -0,0 +1,244 @@
|
||||
#!/usr/bin/env python3
|
||||
import getpass
|
||||
import hashlib
|
||||
import shutil
|
||||
import signal
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from config import CONFDATA_DIR, LOG_DIR, PASSWD_FILE
|
||||
|
||||
HELP_TEXT = """\
|
||||
Commands:
|
||||
create <file> create new empty file [requires: w]
|
||||
read <file> print file contents [requires: r]
|
||||
append <file> <text> append text line to file [requires: w]
|
||||
copy <src> <dst> copy file into confdata [requires: r, w]
|
||||
remove <file> delete file from confdata [requires: d]
|
||||
help show this help
|
||||
exit exit"""
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
return hashlib.sha256(password.encode("ascii")).hexdigest()
|
||||
|
||||
|
||||
def log_action(login: str, action: str) -> None:
|
||||
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
timestamp = datetime.now().isoformat(sep=" ", timespec="seconds")
|
||||
with open(LOG_DIR / "access.log", "a") as f:
|
||||
f.write(f"{timestamp} [{login}] {action}\n")
|
||||
|
||||
|
||||
def read_users() -> dict[str, dict]:
|
||||
users: dict[str, dict] = {}
|
||||
if not PASSWD_FILE.exists():
|
||||
return users
|
||||
with open(PASSWD_FILE) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split(":", 4)
|
||||
if len(parts) != 5:
|
||||
continue
|
||||
users[parts[0]] = {
|
||||
"password_hash": parts[1],
|
||||
"id": parts[2],
|
||||
"permissions": parts[3],
|
||||
"full_name": parts[4],
|
||||
}
|
||||
return users
|
||||
|
||||
|
||||
def authenticate() -> tuple[str, dict]:
|
||||
users = read_users()
|
||||
while True:
|
||||
try:
|
||||
login = input("Login: ").strip()
|
||||
password = getpass.getpass("Password: ")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
print("\nBye.")
|
||||
sys.exit(0)
|
||||
|
||||
user = users.get(login)
|
||||
if user and user["password_hash"] == hash_password(password):
|
||||
return login, user
|
||||
log_action(login if login else "-", "LOGIN_FAILED")
|
||||
print("Invalid credentials. Try again.")
|
||||
|
||||
|
||||
def confdata_path(arg: str) -> Path:
|
||||
p = Path(arg)
|
||||
if not p.is_absolute():
|
||||
p = CONFDATA_DIR / p
|
||||
return p.resolve()
|
||||
|
||||
|
||||
def is_in_confdata(path: Path) -> bool:
|
||||
try:
|
||||
path.relative_to(CONFDATA_DIR.resolve())
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def cmd_read(args: list[str], login: str, perms: str) -> None:
|
||||
if "r" not in perms:
|
||||
print("Permission denied (requires: r)")
|
||||
return
|
||||
if len(args) != 1:
|
||||
print("Usage: read <file>")
|
||||
return
|
||||
path = confdata_path(args[0])
|
||||
if not is_in_confdata(path):
|
||||
print("Access denied: file must be inside confdata")
|
||||
return
|
||||
if not path.exists():
|
||||
print(f"File not found: {path.name}")
|
||||
return
|
||||
print(path.read_text(), end="")
|
||||
log_action(login, f"READ {path}")
|
||||
|
||||
|
||||
def cmd_create(args: list[str], login: str, perms: str) -> None:
|
||||
if "w" not in perms:
|
||||
print("Permission denied (requires: w)")
|
||||
return
|
||||
if len(args) != 1:
|
||||
print("Usage: create <file>")
|
||||
return
|
||||
path = confdata_path(args[0])
|
||||
if not is_in_confdata(path):
|
||||
print("Access denied: file must be inside confdata")
|
||||
return
|
||||
if path.exists():
|
||||
print(f"File already exists: {path.name}")
|
||||
return
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.touch()
|
||||
log_action(login, f"CREATE {path}")
|
||||
print("Done.")
|
||||
|
||||
|
||||
def cmd_append(args: list[str], login: str, perms: str) -> None:
|
||||
if "w" not in perms:
|
||||
print("Permission denied (requires: w)")
|
||||
return
|
||||
if len(args) < 2:
|
||||
print("Usage: append <file> <text>")
|
||||
return
|
||||
path = confdata_path(args[0])
|
||||
if not is_in_confdata(path):
|
||||
print("Access denied: file must be inside confdata")
|
||||
return
|
||||
if not path.exists():
|
||||
print(f"File not found: {path.name}. Use 'create' first.")
|
||||
return
|
||||
text = " ".join(args[1:])
|
||||
with open(path, "a") as f:
|
||||
f.write(text + "\n")
|
||||
log_action(login, f"APPEND {path}")
|
||||
print("Done.")
|
||||
|
||||
|
||||
def cmd_copy(args: list[str], login: str, perms: str) -> None:
|
||||
if "r" not in perms or "w" not in perms:
|
||||
print("Permission denied (requires: r, w)")
|
||||
return
|
||||
if len(args) != 2:
|
||||
print("Usage: copy <src> <dst>")
|
||||
return
|
||||
|
||||
src = Path(args[0]).resolve()
|
||||
dst = confdata_path(args[1])
|
||||
|
||||
if not is_in_confdata(dst):
|
||||
print("Access denied: destination must be inside confdata")
|
||||
return
|
||||
if dst.is_dir():
|
||||
dst = dst / src.name
|
||||
if dst.exists():
|
||||
print(f"Destination already exists: {dst.name}")
|
||||
return
|
||||
if not src.exists():
|
||||
print(f"Source not found: {args[0]}")
|
||||
return
|
||||
if src.is_dir():
|
||||
print("Copying directories is not supported")
|
||||
return
|
||||
|
||||
shutil.copy2(src, dst)
|
||||
log_action(login, f"COPY {src} -> {dst}")
|
||||
print("Done.")
|
||||
|
||||
|
||||
def cmd_remove(args: list[str], login: str, perms: str) -> None:
|
||||
if "d" not in perms:
|
||||
print("Permission denied (requires: d)")
|
||||
return
|
||||
if len(args) != 1:
|
||||
print("Usage: remove <file>")
|
||||
return
|
||||
path = confdata_path(args[0])
|
||||
if not is_in_confdata(path):
|
||||
print("Access denied: file must be inside confdata")
|
||||
return
|
||||
if not path.exists():
|
||||
print(f"File not found: {path.name}")
|
||||
return
|
||||
path.unlink()
|
||||
log_action(login, f"REMOVE {path}")
|
||||
print("Done.")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
signal.signal(signal.SIGINT, lambda _s, _f: (print("\nBye."), sys.exit(0)))
|
||||
|
||||
login, user = authenticate()
|
||||
perms = user["permissions"]
|
||||
full_name = user["full_name"]
|
||||
|
||||
log_action(login, "LOGIN")
|
||||
print(f"\nПривет, {full_name}")
|
||||
print(HELP_TEXT)
|
||||
|
||||
while True:
|
||||
try:
|
||||
line = input(f"\n{login}> ").strip()
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
log_action(login, "EXIT")
|
||||
print("\nBye.")
|
||||
break
|
||||
|
||||
if not line:
|
||||
continue
|
||||
|
||||
parts = line.split()
|
||||
command, args = parts[0], parts[1:]
|
||||
|
||||
if command == "exit":
|
||||
log_action(login, "EXIT")
|
||||
print("Bye.")
|
||||
break
|
||||
elif command == "help":
|
||||
print(HELP_TEXT)
|
||||
elif command == "create":
|
||||
cmd_create(args, login, perms)
|
||||
elif command == "read":
|
||||
cmd_read(args, login, perms)
|
||||
elif command == "append":
|
||||
cmd_append(args, login, perms)
|
||||
elif command == "copy":
|
||||
cmd_copy(args, login, perms)
|
||||
elif command == "remove":
|
||||
cmd_remove(args, login, perms)
|
||||
else:
|
||||
print(f"Unknown command: {command!r}. Type 'help' for available commands.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
106
lab2/bruteforce.py
Normal file
106
lab2/bruteforce.py
Normal file
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import hashlib
|
||||
import itertools
|
||||
import string
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from config import PASSWD_FILE
|
||||
|
||||
CHARSET = string.ascii_letters + string.digits + "!@#$%^&*()"
|
||||
FIRST_CHARS = string.ascii_letters
|
||||
|
||||
MAX_HOURS = 8
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
return hashlib.sha256(password.encode("ascii")).hexdigest()
|
||||
|
||||
|
||||
def get_target_hash(login: str) -> str | None:
|
||||
if not PASSWD_FILE.exists():
|
||||
return None
|
||||
with open(PASSWD_FILE) as f:
|
||||
for line in f:
|
||||
parts = line.strip().split(":", 4)
|
||||
if len(parts) == 5 and parts[0] == login:
|
||||
return parts[1]
|
||||
return None
|
||||
|
||||
|
||||
def max_combinations(length: int) -> int:
|
||||
if length == 1:
|
||||
return len(FIRST_CHARS)
|
||||
return len(FIRST_CHARS) * (len(CHARSET) ** (length - 1))
|
||||
|
||||
|
||||
def brute_force_length(target_hash: str, length: int) -> tuple[str, int, float] | None:
|
||||
count = 0
|
||||
start = time.perf_counter()
|
||||
|
||||
if length == 1:
|
||||
for first in FIRST_CHARS:
|
||||
count += 1
|
||||
if hash_password(first) == target_hash:
|
||||
return first, count, time.perf_counter() - start
|
||||
return None
|
||||
|
||||
for first in FIRST_CHARS:
|
||||
for rest in itertools.product(CHARSET, repeat=length - 1):
|
||||
if time.perf_counter() - start > MAX_HOURS * 3600:
|
||||
return None
|
||||
count += 1
|
||||
password = first + "".join(rest)
|
||||
if hash_password(password) == target_hash:
|
||||
return password, count, time.perf_counter() - start
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Brute force password cracker (SHA-256)")
|
||||
parser.add_argument("login", help="Target username")
|
||||
parser.add_argument(
|
||||
"--max-length",
|
||||
type=int,
|
||||
default=6,
|
||||
help="Maximum password length to try (default: 6)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
target_hash = get_target_hash(args.login)
|
||||
if not target_hash:
|
||||
print(f"User '{args.login}' not found in passwd file.")
|
||||
return
|
||||
|
||||
print(f"Target: {args.login}")
|
||||
print(f"Hash: {target_hash}")
|
||||
print(f"Charset size: {len(CHARSET)} ({len(FIRST_CHARS)} valid for first char)")
|
||||
print(f"Algorithm: SHA-256")
|
||||
print()
|
||||
|
||||
for length in range(1, args.max_length + 1):
|
||||
total = max_combinations(length)
|
||||
print(f"Length {length}: max {total:>15,} combinations")
|
||||
|
||||
result = brute_force_length(target_hash, length)
|
||||
|
||||
if result is not None:
|
||||
password, count, elapsed = result
|
||||
print(f" >>> FOUND: '{password}'")
|
||||
print(f" Iterations: {count:,}")
|
||||
print(f" Time: {elapsed:.4f}s")
|
||||
print(f" Speed: {count / elapsed:,.0f} hashes/s")
|
||||
return
|
||||
else:
|
||||
print(f" Not found at length {length} (timeout or exhausted)")
|
||||
|
||||
print(f"\nPassword not found within length {args.max_length}.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
11
lab2/config.py
Normal file
11
lab2/config.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
BASE_DIR = Path(os.environ.get("PRACTICE2_DIR", "/usr/local/practice2"))
|
||||
|
||||
ETC_DIR = BASE_DIR / "etc"
|
||||
CONFDATA_DIR = BASE_DIR / "confdata"
|
||||
BIN_DIR = BASE_DIR / "bin"
|
||||
LOG_DIR = BASE_DIR / "log"
|
||||
|
||||
PASSWD_FILE = ETC_DIR / "passwd"
|
||||
277
lab2/lab2.md
Normal file
277
lab2/lab2.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# Практическая работа №2
|
||||
|
||||
по дисциплине «Защита информации»
|
||||
|
||||
**Тема работы:** «Разработка и исследование системы аутентификации и авторизации»
|
||||
**Преподаватель:** Силиненко А.В.
|
||||
**Email:** [a_silinenko@mail.ru](mailto:a_silinenko@mail.ru)
|
||||
|
||||
---
|
||||
|
||||
## 1. Цели работы
|
||||
|
||||
* Разработать систему доступа пользователей к конфиденциальным данным;
|
||||
* Исследовать стойкость паролей к атаке методом грубой силы.
|
||||
|
||||
---
|
||||
|
||||
## 2. Задачи работы
|
||||
|
||||
### 2.1.
|
||||
|
||||
При необходимости установить на компьютер целевую ОС (Linux или MacOS), в которой производится разработка и использование системы.
|
||||
|
||||
### 2.2.
|
||||
|
||||
Разработать систему доступа пользователей к конфиденциальным данным, включающую:
|
||||
|
||||
* Выделенный каталог для хранения всех файлов системы;
|
||||
* Утилиту для работы с данными аутентификации и авторизации (паролями и правами доступа);
|
||||
* Утилиту доступа к конфиденциальным данным, обеспечивающую аутентификацию и авторизацию пользователя.
|
||||
|
||||
### 2.3.
|
||||
|
||||
Разработать программу взлома паролей методом грубой силы и исследовать стойкость паролей в зависимости от длины пароля и алгоритма хэширования.
|
||||
|
||||
---
|
||||
|
||||
## 3. Требования к работе
|
||||
|
||||
### 3.1. ОС
|
||||
|
||||
Работа выполняется в ОС **Linux** или **MacOS**.
|
||||
|
||||
Необходим доступ с правами суперпользователя.
|
||||
Если доступа нет — установить гипервизор **VirtualBox** ([https://www.virtualbox.org/](https://www.virtualbox.org/)) и развернуть гостевую ОС.
|
||||
|
||||
Допускается установка ОС как второй системы без VirtualBox.
|
||||
|
||||
---
|
||||
|
||||
### 3.2. Структура каталогов
|
||||
|
||||
Необходимо создать дерево каталогов:
|
||||
|
||||
```
|
||||
/usr/local/practice2/
|
||||
├── etc # хранение данных аутентификации и авторизации
|
||||
├── confdata # хранение конфиденциальных файлов
|
||||
├── bin # разработанные утилиты
|
||||
└── log # файлы регистрации
|
||||
```
|
||||
|
||||
**Права доступа:** чтение, запись и выполнение только для пользователя `root`.
|
||||
|
||||
---
|
||||
|
||||
### 3.3. Требования к утилите управления пользователями
|
||||
|
||||
#### Файл хранения данных
|
||||
|
||||
Файл:
|
||||
|
||||
```
|
||||
/usr/local/practice2/etc/passwd
|
||||
```
|
||||
|
||||
Структура записи:
|
||||
|
||||
```
|
||||
<логин>:<хэш_пароля>:<идентификатор>:<права>:<ФИО>
|
||||
```
|
||||
|
||||
Одна строка — один пользователь.
|
||||
|
||||
**Права на файл:** чтение и запись только для `root`.
|
||||
|
||||
#### Права доступа
|
||||
|
||||
* `r` — чтение
|
||||
* `w` — запись
|
||||
* `d` — удаление
|
||||
|
||||
#### Требования к функционалу
|
||||
|
||||
Утилита должна обеспечивать:
|
||||
|
||||
* Добавление нового пользователя:
|
||||
|
||||
* логин
|
||||
* ФИО
|
||||
* права доступа
|
||||
* пароль + подтверждение
|
||||
* Проверку корректности данных
|
||||
* Хранение пароля в виде **хэш-значения**
|
||||
* Использование алгоритма хэширования согласно индивидуальному заданию
|
||||
* Редактирование существующего пользователя
|
||||
* Изменение пароля
|
||||
* Удаление пользователя
|
||||
* Регистрацию всех действий с файлом `passwd`
|
||||
|
||||
#### Требования к паролю
|
||||
|
||||
* Кодировка: ASCII
|
||||
* Разрешены:
|
||||
|
||||
* A–Z
|
||||
* a–z
|
||||
* 0–9
|
||||
* `!@#$%^&*()`
|
||||
* Первый символ не может быть цифрой или спецсимволом
|
||||
|
||||
---
|
||||
|
||||
### 3.4. Требования к утилите доступа к конфиденциальным данным
|
||||
|
||||
#### Авторизация
|
||||
|
||||
При запуске:
|
||||
|
||||
1. Запрос логина
|
||||
2. Запрос пароля
|
||||
3. Проверка корректности
|
||||
|
||||
Если данные корректны:
|
||||
|
||||
* Вывод: `Привет, <ФИО>`
|
||||
* Краткая справка
|
||||
* Приглашение к вводу команд
|
||||
|
||||
Если данные некорректны:
|
||||
|
||||
* Повторный запрос
|
||||
|
||||
Остановка — по сигналу **SIGINT (Ctrl+C)**.
|
||||
|
||||
---
|
||||
|
||||
### Поддерживаемые команды
|
||||
|
||||
| Команда | Описание | Требуемые права |
|
||||
| -------- | ------------------------ | --------------- |
|
||||
| `read` | Вывод содержимого файла | r |
|
||||
| `append` | Добавление данных в файл | w |
|
||||
| `copy` | Копирование файла | r + w |
|
||||
| `remove` | Удаление файла | d |
|
||||
| `exit` | Выход | — |
|
||||
| `help` | Справка | — |
|
||||
|
||||
#### Ограничения copy
|
||||
|
||||
* Разрешено копирование:
|
||||
|
||||
* в каталог `confdata`
|
||||
* внутри `confdata`
|
||||
* Запрещено:
|
||||
|
||||
* из `confdata` в другие каталоги
|
||||
* перезапись существующих файлов внутри `confdata`
|
||||
|
||||
#### Дополнительные требования
|
||||
|
||||
* Утилита доступна для запуска всем пользователям ОС
|
||||
* Все действия с конфиденциальными данными логируются
|
||||
|
||||
---
|
||||
|
||||
### 3.5. Программа взлома паролей
|
||||
|
||||
Должна:
|
||||
|
||||
* Запускать утилиту доступа
|
||||
* Перебирать пароли методом brute force
|
||||
* Учитывать используемый алгоритм хэширования
|
||||
* Фиксировать:
|
||||
|
||||
* время перебора
|
||||
* количество итераций до взлома
|
||||
|
||||
---
|
||||
|
||||
### 3.6. Общие требования
|
||||
|
||||
* Язык программирования — любой
|
||||
* Код должен быть снабжен комментариями
|
||||
|
||||
---
|
||||
|
||||
### 3.7. Требования к исследованию
|
||||
|
||||
Провести исследование для длин паролей:
|
||||
|
||||
```
|
||||
3, 4, 5, 6, 7, 8 символов
|
||||
```
|
||||
|
||||
Необходимо:
|
||||
|
||||
* Рассчитать максимальное количество итераций
|
||||
* По экспериментам (3–4 символа) оценить время для 5–8
|
||||
* Проверить теорию экспериментально
|
||||
* Прервать эксперимент, если длительность > 8 часов
|
||||
* Проводить тестирование без активных задач на ПК
|
||||
|
||||
---
|
||||
|
||||
## 4. Требования к отчету
|
||||
|
||||
В отчете необходимо указать:
|
||||
|
||||
* Актуальность темы
|
||||
|
||||
* Цель и задачи
|
||||
|
||||
* Требования к системе
|
||||
|
||||
* Характеристики ПК (процессор, память)
|
||||
|
||||
* ОС и среду разработки
|
||||
|
||||
* Используемый язык
|
||||
|
||||
* Алгоритм хэширования
|
||||
|
||||
* Примеры сборки (если применимо)
|
||||
|
||||
* Примеры работы утилит
|
||||
|
||||
* Пример расчета количества итераций и времени взлома
|
||||
|
||||
* Таблицу или график с результатами:
|
||||
|
||||
* рассчитанное количество итераций и время
|
||||
* полученные экспериментальные данные
|
||||
|
||||
* Выводы
|
||||
|
||||
---
|
||||
|
||||
## 5. Справочная информация
|
||||
|
||||
### Поддержка алгоритмов хэширования
|
||||
|
||||
**Linux:**
|
||||
|
||||
* `md5sum`
|
||||
* `sha1sum`
|
||||
* `sha256sum`
|
||||
* `sha512sum`
|
||||
* `b2sum`
|
||||
* библиотека `openssl`
|
||||
|
||||
**C++:**
|
||||
|
||||
* `std::hash`
|
||||
|
||||
**Python:**
|
||||
|
||||
* модуль `hashlib`
|
||||
|
||||
**Java:**
|
||||
|
||||
* `java.security.MessageDigest`
|
||||
* Spring Security
|
||||
|
||||
**Go:**
|
||||
|
||||
* пакет `hash`
|
||||
19
lab2/pyproject.toml
Normal file
19
lab2/pyproject.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[project]
|
||||
name = "lab2"
|
||||
version = "0.1.0"
|
||||
description = "Authentication, authorization and brute force research system"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = []
|
||||
|
||||
[project.scripts]
|
||||
usermgr = "usermgr:main"
|
||||
access = "access:main"
|
||||
bruteforce = "bruteforce:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
include = ["*.py"]
|
||||
38
lab2/setup.sh
Executable file
38
lab2/setup.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BASE_DIR="${PRACTICE2_DIR:-/usr/local/practice2}"
|
||||
|
||||
echo "Setting up directory structure at: $BASE_DIR"
|
||||
|
||||
mkdir -p "$BASE_DIR/etc" "$BASE_DIR/confdata" "$BASE_DIR/bin" "$BASE_DIR/log"
|
||||
|
||||
touch "$BASE_DIR/etc/passwd"
|
||||
|
||||
cp "$SCRIPT_DIR/config.py" "$BASE_DIR/bin/config.py"
|
||||
cp "$SCRIPT_DIR/usermgr.py" "$BASE_DIR/bin/usermgr"
|
||||
cp "$SCRIPT_DIR/access.py" "$BASE_DIR/bin/confaccess"
|
||||
cp "$SCRIPT_DIR/bruteforce.py" "$BASE_DIR/bin/bruteforce"
|
||||
|
||||
chmod +x "$BASE_DIR/bin/usermgr" "$BASE_DIR/bin/confaccess" "$BASE_DIR/bin/bruteforce"
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
chmod 700 "$BASE_DIR" "$BASE_DIR/etc" "$BASE_DIR/confdata" "$BASE_DIR/bin" "$BASE_DIR/log"
|
||||
chmod 600 "$BASE_DIR/etc/passwd"
|
||||
echo "Permissions set (root-only)."
|
||||
else
|
||||
echo "Warning: not running as root; skipping permission hardening."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Done. Directory layout:"
|
||||
ls -la "$BASE_DIR"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " $BASE_DIR/bin/usermgr add <login>"
|
||||
echo " $BASE_DIR/bin/confaccess"
|
||||
echo " $BASE_DIR/bin/bruteforce <login>"
|
||||
echo ""
|
||||
echo "To add bin to PATH temporarily:"
|
||||
echo " export PATH=\"$BASE_DIR/bin:\$PATH\""
|
||||
246
lab2/usermgr.py
Normal file
246
lab2/usermgr.py
Normal file
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import getpass
|
||||
import hashlib
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from config import LOG_DIR, PASSWD_FILE
|
||||
|
||||
ALLOWED_CHARS = set(
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()"
|
||||
)
|
||||
FIRST_CHAR_ALLOWED = set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
|
||||
VALID_PERM_CHARS = set("rwd")
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
return hashlib.sha256(password.encode("ascii")).hexdigest()
|
||||
|
||||
|
||||
def validate_password(password: str) -> str | None:
|
||||
if not password:
|
||||
return "password cannot be empty"
|
||||
if password[0] not in FIRST_CHAR_ALLOWED:
|
||||
return "first character must be a letter (A-Z, a-z)"
|
||||
invalid = [ch for ch in password if ch not in ALLOWED_CHARS]
|
||||
if invalid:
|
||||
return f"invalid characters: {''.join(set(invalid))!r}"
|
||||
return None
|
||||
|
||||
|
||||
def validate_permissions(perms: str) -> str | None:
|
||||
if not perms:
|
||||
return "permissions cannot be empty"
|
||||
for ch in perms:
|
||||
if ch not in VALID_PERM_CHARS:
|
||||
return f"invalid permission {ch!r}; allowed: r, w, d"
|
||||
if len(set(perms)) != len(perms):
|
||||
return "duplicate permissions"
|
||||
return None
|
||||
|
||||
|
||||
def log_action(action: str) -> None:
|
||||
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
timestamp = datetime.now().isoformat(sep=" ", timespec="seconds")
|
||||
with open(LOG_DIR / "usermgr.log", "a") as f:
|
||||
f.write(f"{timestamp} {action}\n")
|
||||
|
||||
|
||||
def read_users() -> list[dict]:
|
||||
if not PASSWD_FILE.exists():
|
||||
return []
|
||||
users = []
|
||||
with open(PASSWD_FILE) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split(":", 4)
|
||||
if len(parts) != 5:
|
||||
continue
|
||||
users.append(
|
||||
{
|
||||
"login": parts[0],
|
||||
"password_hash": parts[1],
|
||||
"id": parts[2],
|
||||
"permissions": parts[3],
|
||||
"full_name": parts[4],
|
||||
}
|
||||
)
|
||||
return users
|
||||
|
||||
|
||||
def write_users(users: list[dict]) -> None:
|
||||
PASSWD_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(PASSWD_FILE, "w") as f:
|
||||
for u in users:
|
||||
f.write(
|
||||
f"{u['login']}:{u['password_hash']}:{u['id']}:{u['permissions']}:{u['full_name']}\n"
|
||||
)
|
||||
|
||||
|
||||
def find_user(users: list[dict], login: str) -> dict | None:
|
||||
return next((u for u in users if u["login"] == login), None)
|
||||
|
||||
|
||||
def next_uid(users: list[dict]) -> str:
|
||||
if not users:
|
||||
return "1"
|
||||
return str(max(int(u["id"]) for u in users) + 1)
|
||||
|
||||
|
||||
def prompt_password() -> str:
|
||||
while True:
|
||||
password = getpass.getpass("Password: ")
|
||||
err = validate_password(password)
|
||||
if err:
|
||||
print(f"Invalid password: {err}")
|
||||
continue
|
||||
confirm = getpass.getpass("Confirm password: ")
|
||||
if password != confirm:
|
||||
print("Passwords do not match.")
|
||||
continue
|
||||
return password
|
||||
|
||||
|
||||
def cmd_add(args: argparse.Namespace) -> None:
|
||||
users = read_users()
|
||||
login = args.login
|
||||
|
||||
if find_user(users, login):
|
||||
print(f"User '{login}' already exists.")
|
||||
sys.exit(1)
|
||||
|
||||
full_name = input("Full name: ").strip()
|
||||
if not full_name:
|
||||
print("Full name cannot be empty.")
|
||||
sys.exit(1)
|
||||
|
||||
perms = input("Permissions (any combination of r, w, d): ").strip()
|
||||
err = validate_permissions(perms)
|
||||
if err:
|
||||
print(f"Invalid permissions: {err}")
|
||||
sys.exit(1)
|
||||
|
||||
password = prompt_password()
|
||||
|
||||
uid = next_uid(users)
|
||||
users.append(
|
||||
{
|
||||
"login": login,
|
||||
"password_hash": hash_password(password),
|
||||
"id": uid,
|
||||
"permissions": perms,
|
||||
"full_name": full_name,
|
||||
}
|
||||
)
|
||||
write_users(users)
|
||||
log_action(f"ADD login={login} id={uid} permissions={perms} full_name='{full_name}'")
|
||||
print(f"User '{login}' added (id={uid}).")
|
||||
|
||||
|
||||
def cmd_edit(args: argparse.Namespace) -> None:
|
||||
users = read_users()
|
||||
user = find_user(users, args.login)
|
||||
if not user:
|
||||
print(f"User '{args.login}' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
changed = []
|
||||
|
||||
if args.full_name is not None:
|
||||
if not args.full_name:
|
||||
print("Full name cannot be empty.")
|
||||
sys.exit(1)
|
||||
user["full_name"] = args.full_name
|
||||
changed.append("full_name")
|
||||
|
||||
if args.permissions is not None:
|
||||
err = validate_permissions(args.permissions)
|
||||
if err:
|
||||
print(f"Invalid permissions: {err}")
|
||||
sys.exit(1)
|
||||
user["permissions"] = args.permissions
|
||||
changed.append("permissions")
|
||||
|
||||
if not changed:
|
||||
print("Nothing to change. Use --full-name and/or --permissions.")
|
||||
sys.exit(0)
|
||||
|
||||
write_users(users)
|
||||
log_action(f"EDIT login={args.login} changed={','.join(changed)}")
|
||||
print(f"User '{args.login}' updated: {', '.join(changed)}.")
|
||||
|
||||
|
||||
def cmd_passwd(args: argparse.Namespace) -> None:
|
||||
users = read_users()
|
||||
user = find_user(users, args.login)
|
||||
if not user:
|
||||
print(f"User '{args.login}' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
password = prompt_password()
|
||||
user["password_hash"] = hash_password(password)
|
||||
write_users(users)
|
||||
log_action(f"PASSWD login={args.login}")
|
||||
print(f"Password for '{args.login}' updated.")
|
||||
|
||||
|
||||
def cmd_delete(args: argparse.Namespace) -> None:
|
||||
users = read_users()
|
||||
if not find_user(users, args.login):
|
||||
print(f"User '{args.login}' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
users = [u for u in users if u["login"] != args.login]
|
||||
write_users(users)
|
||||
log_action(f"DELETE login={args.login}")
|
||||
print(f"User '{args.login}' deleted.")
|
||||
|
||||
|
||||
def cmd_list(_args: argparse.Namespace) -> None:
|
||||
users = read_users()
|
||||
if not users:
|
||||
print("No users.")
|
||||
return
|
||||
print(f"{'Login':<16} {'ID':<4} {'Perms':<6} Full Name")
|
||||
print("-" * 52)
|
||||
for u in users:
|
||||
print(f"{u['login']:<16} {u['id']:<4} {u['permissions']:<6} {u['full_name']}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="User management utility for practice2")
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
p_add = sub.add_parser("add", help="Add a new user")
|
||||
p_add.add_argument("login", help="Username (login)")
|
||||
p_add.set_defaults(func=cmd_add)
|
||||
|
||||
p_edit = sub.add_parser("edit", help="Edit an existing user")
|
||||
p_edit.add_argument("login", help="Username to edit")
|
||||
p_edit.add_argument("--full-name", dest="full_name", help="New full name")
|
||||
p_edit.add_argument("--permissions", help="New permissions (e.g. rwd)")
|
||||
p_edit.set_defaults(func=cmd_edit)
|
||||
|
||||
p_passwd = sub.add_parser("passwd", help="Change user password")
|
||||
p_passwd.add_argument("login", help="Username")
|
||||
p_passwd.set_defaults(func=cmd_passwd)
|
||||
|
||||
p_del = sub.add_parser("delete", help="Delete a user")
|
||||
p_del.add_argument("login", help="Username to delete")
|
||||
p_del.set_defaults(func=cmd_delete)
|
||||
|
||||
p_list = sub.add_parser("list", help="List all users")
|
||||
p_list.set_defaults(func=cmd_list)
|
||||
|
||||
args = parser.parse_args()
|
||||
args.func(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
8
lab2/uv.lock
generated
Normal file
8
lab2/uv.lock
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.14"
|
||||
|
||||
[[package]]
|
||||
name = "lab2"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
Reference in New Issue
Block a user