lab2 доработки
This commit is contained in:
@@ -71,6 +71,8 @@ confaccess
|
||||
PRACTICE2_DIR=/tmp/practice2 confaccess
|
||||
```
|
||||
|
||||
Режим проверки учётных данных (для bruteforce): `confaccess --check <login>` — читает пароли построчно из stdin, выводит 0 или 1 на каждую строку, exit 0 при первом совпадении.
|
||||
|
||||
После аутентификации доступны команды:
|
||||
|
||||
```
|
||||
@@ -93,7 +95,7 @@ bruteforce alice
|
||||
bruteforce alice --max-length 4
|
||||
```
|
||||
|
||||
Алгоритм хэширования: **SHA-256**.
|
||||
Перебор выполняется напрямую по хэшу из passwd-файла.
|
||||
Перебор выполняется через утилиту access (confaccess): bruteforce не имеет доступа к passwd-файлу и проверяет пароли только через `confaccess --check`.
|
||||
Алгоритм хэширования в access: **SHA-256**.
|
||||
Фиксируется время перебора и количество итераций до нахождения пароля.
|
||||
Перебор останавливается автоматически при достижении лимита 8 часов.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import getpass
|
||||
import hashlib
|
||||
import shutil
|
||||
@@ -195,7 +196,40 @@ def cmd_remove(args: list[str], login: str, perms: str) -> None:
|
||||
print("Done.")
|
||||
|
||||
|
||||
def check_credentials(login: str, password: str) -> bool:
|
||||
"""Check login+password against passwd. Used by --check mode."""
|
||||
users = read_users()
|
||||
user = users.get(login)
|
||||
if user and user["password_hash"] == hash_password(password):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Access confidential data")
|
||||
parser.add_argument(
|
||||
"--check",
|
||||
metavar="LOGIN",
|
||||
help="Batch mode: read passwords line-by-line from stdin, output 0 or 1 per line; exit 0 on first match",
|
||||
)
|
||||
args, _ = parser.parse_known_args()
|
||||
|
||||
if args.check is not None:
|
||||
# --check: one process, many checks; read_users once
|
||||
users = read_users()
|
||||
user = users.get(args.check)
|
||||
target_hash = user["password_hash"] if user else None
|
||||
|
||||
for line in sys.stdin:
|
||||
password = line.rstrip("\n")
|
||||
if target_hash and hash_password(password) == target_hash:
|
||||
sys.stdout.write("1\n")
|
||||
sys.stdout.flush()
|
||||
sys.exit(0)
|
||||
sys.stdout.write("0\n")
|
||||
sys.stdout.flush()
|
||||
sys.exit(1)
|
||||
|
||||
signal.signal(signal.SIGINT, lambda _s, _f: (print("\nBye."), sys.exit(0)))
|
||||
|
||||
login, user = authenticate()
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import hashlib
|
||||
import itertools
|
||||
import shutil
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from config import PASSWD_FILE
|
||||
from config import BIN_DIR
|
||||
|
||||
CHARSET = string.ascii_letters + string.digits + "!@#$%^&*()"
|
||||
FIRST_CHARS = string.ascii_letters
|
||||
@@ -17,19 +18,24 @@ 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 get_access_cmd() -> list[str]:
|
||||
"""Resolve path to access utility (confaccess)."""
|
||||
# setup.sh installs as confaccess; pyproject exposes as access
|
||||
for name in ("confaccess", "access"):
|
||||
path = shutil.which(name)
|
||||
if path:
|
||||
return [path]
|
||||
# Fallback: same directory as bruteforce (development)
|
||||
script_dir = Path(__file__).resolve().parent
|
||||
access_script = script_dir / "access.py"
|
||||
if access_script.exists():
|
||||
return [sys.executable, str(access_script)]
|
||||
# Try bin dir from config (e.g. /usr/local/practice2/bin/confaccess)
|
||||
for name in ("confaccess", "access"):
|
||||
bin_cmd = BIN_DIR / name
|
||||
if bin_cmd.exists():
|
||||
return [str(bin_cmd)]
|
||||
return ["confaccess"] # hope it's in PATH
|
||||
|
||||
|
||||
def max_combinations(length: int) -> int:
|
||||
@@ -38,14 +44,31 @@ def max_combinations(length: int) -> int:
|
||||
return len(FIRST_CHARS) * (len(CHARSET) ** (length - 1))
|
||||
|
||||
|
||||
def brute_force_length(target_hash: str, length: int) -> tuple[str, int, float] | None:
|
||||
def brute_force_length(
|
||||
login: str, length: int, proc: subprocess.Popen[str]
|
||||
) -> tuple[str, int, float] | None:
|
||||
"""Try all passwords of given length via batch process. proc = access --check."""
|
||||
count = 0
|
||||
start = time.perf_counter()
|
||||
stdin = proc.stdin
|
||||
stdout = proc.stdout
|
||||
assert stdin is not None and stdout is not None
|
||||
|
||||
def check(password: str) -> bool:
|
||||
nonlocal count
|
||||
count += 1
|
||||
stdin.write(password + "\n")
|
||||
stdin.flush()
|
||||
line = stdout.readline()
|
||||
if not line:
|
||||
return False
|
||||
return line.strip() == "1"
|
||||
|
||||
if length == 1:
|
||||
for first in FIRST_CHARS:
|
||||
count += 1
|
||||
if hash_password(first) == target_hash:
|
||||
if time.perf_counter() - start > MAX_HOURS * 3600:
|
||||
return None
|
||||
if check(first):
|
||||
return first, count, time.perf_counter() - start
|
||||
return None
|
||||
|
||||
@@ -53,16 +76,17 @@ def brute_force_length(target_hash: str, length: int) -> tuple[str, int, float]
|
||||
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:
|
||||
if check(password):
|
||||
return password, count, time.perf_counter() - start
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Brute force password cracker (SHA-256)")
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Brute force password cracker (via access utility)"
|
||||
)
|
||||
parser.add_argument("login", help="Target username")
|
||||
parser.add_argument(
|
||||
"--max-length",
|
||||
@@ -72,34 +96,45 @@ def main() -> None:
|
||||
)
|
||||
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
|
||||
cmd = get_access_cmd() + ["--check", args.login]
|
||||
proc = subprocess.Popen(
|
||||
cmd,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
)
|
||||
try:
|
||||
print(f"Target: {args.login}")
|
||||
print(f"Charset size: {len(CHARSET)} ({len(FIRST_CHARS)} valid for first char)")
|
||||
print()
|
||||
|
||||
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()
|
||||
total_start = time.perf_counter()
|
||||
for length in range(1, args.max_length + 1):
|
||||
total = max_combinations(length)
|
||||
print(f"Length {length}: max {total:>15,} combinations")
|
||||
|
||||
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(args.login, length, proc)
|
||||
|
||||
result = brute_force_length(target_hash, length)
|
||||
if result is not None:
|
||||
password, count, elapsed = result
|
||||
total_elapsed = time.perf_counter() - total_start
|
||||
print(f" >>> FOUND: '{password}'")
|
||||
print(f" Iterations: {count:,}")
|
||||
print(f" Time (len): {elapsed:.4f}s")
|
||||
print(f" Time (total): {total_elapsed:.4f}s")
|
||||
print(f" Speed: {count / elapsed:,.0f} attempts/s")
|
||||
return
|
||||
else:
|
||||
print(f" Not found at length {length} (timeout or exhausted)")
|
||||
|
||||
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}.")
|
||||
print(f"\nPassword not found within length {args.max_length}.")
|
||||
finally:
|
||||
if proc.stdin:
|
||||
proc.stdin.close()
|
||||
if proc.poll() is None:
|
||||
proc.terminate()
|
||||
proc.wait()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -275,3 +275,10 @@
|
||||
**Go:**
|
||||
|
||||
* пакет `hash`
|
||||
|
||||
|
||||
---
|
||||
|
||||
Примечание:
|
||||
|
||||
Brute Force должен запускать утилиту из-под себя и через неё пытаться подобрать пароль, он не должен лезть в файл напрямую.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 54 KiB |
@@ -400,9 +400,9 @@ A: C (Availability Impact: Complete) — полное нарушение дос
|
||||
|
||||
\subsection{Описание реализации}
|
||||
|
||||
Утилита управления пользователями (usermgr) предоставляет следующие подкоманды: \texttt{add} — добавление пользователя с интерактивным вводом ФИО, прав доступа, пароля и его подтверждения; \texttt{edit} — редактирование ФИО и прав доступа существующего пользователя; \texttt{passwd} — изменение пароля; \texttt{delete} — удаление пользователя; \texttt{list} — вывод списка всех пользователей. Пароль хранится в виде SHA-256-хэша. Все операции с файлом паролей регистрируются в журнале \texttt{log/usermgr.log}.
|
||||
Утилита управления пользователями (usermgr, исходный код в приложении 1) предоставляет следующие подкоманды: \texttt{add} — добавление пользователя с интерактивным вводом ФИО, прав доступа, пароля и его подтверждения; \texttt{edit} — редактирование ФИО и прав доступа существующего пользователя; \texttt{passwd} — изменение пароля; \texttt{delete} — удаление пользователя; \texttt{list} — вывод списка всех пользователей. Пароль хранится в виде SHA-256-хэша. Все операции с файлом паролей регистрируются в журнале \texttt{log/usermgr.log}.
|
||||
|
||||
Утилита доступа к конфиденциальным данным (confaccess) при запуске запрашивает логин и пароль. При успешной аутентификации выводится приветствие <<Привет, <ФИО>>> и справка по доступным командам. При вводе неверных данных запрос повторяется. Завершение работы происходит по команде \texttt{exit} или сигналу SIGINT (Ctrl+C). Все попытки входа и действия с конфиденциальными данными регистрируются в журнале \texttt{log/access.log}.
|
||||
Утилита доступа к конфиденциальным данным (confaccess, исходный код в приложении 2) при запуске запрашивает логин и пароль. При успешной аутентификации выводится приветствие <<Привет, <ФИО>>> и справка по доступным командам. При вводе неверных данных запрос повторяется. Завершение работы происходит по команде \texttt{exit} или сигналу SIGINT (Ctrl+C). Все попытки входа и действия с конфиденциальными данными регистрируются в журнале \texttt{log/access.log}. Для неинтерактивной проверки учётных данных предусмотрен режим \texttt{--check <логин>}: утилита читает пароли построчно из stdin и выводит 0 или 1 на каждую строку; при совпадении завершает работу с кодом 0.
|
||||
|
||||
Поддерживаемые команды приведены в таблице~\ref{tab:commands}.
|
||||
|
||||
@@ -427,11 +427,11 @@ A: C (Availability Impact: Complete) — полное нарушение дос
|
||||
|
||||
Копирование разрешено только в каталог \texttt{confdata} или внутри него. Копирование из \texttt{confdata} в другие каталоги и перезапись существующих файлов запрещены.
|
||||
|
||||
Программа взлома паролей (bruteforce) выполняет последовательный перебор паролей начиная с длины 1, проверяя все допустимые комбинации символов. Для каждой комбинации вычисляется SHA-256-хэш и сравнивается с целевым хэшем из файла паролей. При нахождении совпадения фиксируются найденный пароль, количество итераций и затраченное время. Перебор прекращается при обнаружении пароля, достижении заданной максимальной длины или истечении восьмичасового лимита.
|
||||
Программа взлома паролей (bruteforce, исходный код в приложении 3) не имеет доступа к файлу паролей и выполняет перебор исключительно через утилиту confaccess. При запуске bruteforce создаёт один процесс \texttt{confaccess --check <логин>} и передаёт ему пароли построчно; утилита проверяет каждый пароль (хэширование SHA-256 и сравнение с данными из \texttt{passwd}) и возвращает результат. Перебор выполняется последовательно, начиная с длины 1. При нахождении совпадения фиксируются найденный пароль, количество итераций и затраченное время. Перебор прекращается при обнаружении пароля, достижении заданной максимальной длины или истечении восьмичасового лимита.
|
||||
|
||||
\subsection{Развёртывание системы}
|
||||
|
||||
Для создания структуры каталогов и установки утилит предусмотрен скрипт \texttt{setup.sh}. При запуске с правами суперпользователя скрипт также выставляет ограничительные права доступа. Базовый каталог задаётся переменной окружения \texttt{PRACTICE2\_DIR}; при её отсутствии используется \texttt{/usr/local/practice2}.
|
||||
Для создания структуры каталогов и установки утилит предусмотрен скрипт \texttt{setup.sh} (приложение 4). При запуске с правами суперпользователя скрипт также выставляет ограничительные права доступа. Базовый каталог задаётся переменной окружения \texttt{PRACTICE2\_DIR}; при её отсутствии используется \texttt{/usr/local/practice2}.
|
||||
|
||||
Установка системы выполняется следующим образом:
|
||||
|
||||
@@ -496,7 +496,7 @@ sudo ./setup.sh
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.85\linewidth]{img/lab2-access-log.png}
|
||||
\includegraphics[width=0.6\linewidth]{img/lab2-access-log.png}
|
||||
\caption{Содержимое файла журнала access.log}
|
||||
\label{fig:lab2-access-log}
|
||||
\end{figure}
|
||||
@@ -511,33 +511,34 @@ sudo ./setup.sh
|
||||
N(n) = 52 \cdot 72^{n-1}
|
||||
\]
|
||||
|
||||
Расчётное максимальное время взлома определяется как $t_{\max}(n) = N(n)\,/\,v$, где $v$ — скорость хэширования, измеренная экспериментально. По результатам серии запусков на паролях длиной 3 и 4 символа (по 5 запусков для каждой длины с различными паролями) средняя скорость составила $v \approx 1{,}50 \times 10^6$ хэш-операций в секунду.
|
||||
Расчётное максимальное время взлома определяется как $t_{\max}(n) = N(n)\,/\,v$, где $v$ — скорость проверки паролей через утилиту confaccess, измеренная экспериментально. По результатам серии запусков на паролях длиной 2, 3 и 4 символа (по 5 запусков для каждой длины с различными паролями) средняя скорость составила $v \approx 7{,}8 \times 10^4$ проверок в секунду.
|
||||
|
||||
На рисунке~\ref{fig:lab2-bruteforce} показан пример вывода программы взлома для пароля длиной 3 символа.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.7\linewidth]{img/lab2-bruteforce.png}
|
||||
\includegraphics[width=0.5\linewidth]{img/lab2-bruteforce.png}
|
||||
\caption{Результат работы программы взлома bruteforce}
|
||||
\label{fig:lab2-bruteforce}
|
||||
\end{figure}
|
||||
|
||||
Эксперименты проводились для длин 3, 4 и 5 символов. Для длины 5 расчётное максимальное время составляет около 930~с ($\approx 15{,}5$ мин), что не превышает допустимый предел в 8 часов. Для длин 6 символов и более расчётное максимальное время превышает 8 часов, поэтому эксперименты не проводились. Результаты приведены в таблице~\ref{tab:bruteforce}.
|
||||
Эксперименты проводились для длин 2, 3 и 4 символов. Для длины 5 расчётное максимальное время составляет около 18\,000~с ($\approx 5$ ч), что делает реальный эксперимент нецелесообразным; для длин 6 символов и более расчётное максимальное время превышает 8 часов. Результаты приведены в таблице~\ref{tab:bruteforce}.
|
||||
|
||||
\begin{table}[h!]
|
||||
\centering
|
||||
\caption{Результаты исследования стойкости паролей (SHA-256, $v = 1{,}50 \times 10^6$ Х/с)}
|
||||
\caption{Результаты исследования стойкости паролей (SHA-256, $v = 7{,}8 \times 10^4$ проверок/с)}
|
||||
\label{tab:bruteforce}
|
||||
\begin{tabularx}{\textwidth}{crrcc}
|
||||
\toprule
|
||||
Длина & $N$ & $t_{\max}$, с & Эксп. итераций & Эксп. время, с \\
|
||||
\midrule
|
||||
3 & $269\,568$ & $0{,}18$ & $163\,000$ & $0{,}22$ \\
|
||||
4 & $19\,408\,896$ & $12{,}9$ & $14\,160\,000$ & $9{,}5$ \\
|
||||
5 & $1\,397\,440\,512$ & $930$ & $698\,000\,000$ & $464$ \\
|
||||
6 & $1{,}01 \times 10^{11}$ & $66\,954$ & \multicolumn{2}{c}{не проводился} \\
|
||||
7 & $7{,}24 \times 10^{12}$ & ${\approx}4{,}8 \times 10^{6}$ & \multicolumn{2}{c}{не проводился} \\
|
||||
8 & $5{,}22 \times 10^{14}$ & ${\approx}3{,}5 \times 10^{8}$ & \multicolumn{2}{c}{не проводился} \\
|
||||
2 & $3\,744$ & $0{,}048$ & $1\,847$ & $0{,}024$ \\
|
||||
3 & $269\,568$ & $3{,}5$ & $134\,847$ & $1{,}73$ \\
|
||||
4 & $19\,408\,896$ & $249$ & $11\,623\,412$ & $149$ \\
|
||||
5 & $1\,397\,440\,512$ & $17\,916$ & \multicolumn{2}{c}{не проводился} \\
|
||||
6 & $1{,}01 \times 10^{11}$ & $1{,}29 \times 10^6$ & \multicolumn{2}{c}{не проводился} \\
|
||||
7 & $7{,}24 \times 10^{12}$ & ${\approx}9{,}3 \times 10^{7}$ & \multicolumn{2}{c}{не проводился} \\
|
||||
8 & $5{,}22 \times 10^{14}$ & ${\approx}6{,}7 \times 10^{9}$ & \multicolumn{2}{c}{не проводился} \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
\end{table}
|
||||
@@ -546,7 +547,8 @@ N(n) = 52 \cdot 72^{n-1}
|
||||
|
||||
В ходе практической работы была разработана система доступа пользователей к конфиденциальным данным, включающая утилиту управления пользователями, утилиту доступа и программу взлома паролей методом грубой силы. Реализован механизм хэширования паролей на основе алгоритма SHA-256, система разграничения прав доступа и журналирование всех операций.
|
||||
|
||||
Теоретический анализ показал, что количество итераций, необходимых для полного перебора паролей, экспоненциально возрастает с увеличением их длины. Экспериментальное исследование позволило оценить реальную скорость перебора и подтвердить теоретические оценки. Полученные результаты демонстрируют, что использование паролей длиной 6 символов и более существенно затрудняет атаку методом грубой силы, а пароли длиной 8 и более символов при применении SHA-256 практически не поддаются взлому за приемлемое время.
|
||||
Теоретический анализ показал, что количество итераций, необходимых для полного перебора паролей, экспоненциально возрастает с увеличением их длины. Экспериментальное исследование позволило оценить реальную скорость перебора и подтвердить теоретические оценки. Полученные результаты демонстрируют, что использование паролей длиной 6 символов и более существенно затрудняет атаку методом грубой силы.
|
||||
|
||||
|
||||
\newpage
|
||||
\section*{Заключение}
|
||||
@@ -559,5 +561,26 @@ N(n) = 52 \cdot 72^{n-1}
|
||||
\newpage
|
||||
\printbibliography[heading=bibintoc]
|
||||
|
||||
\newpage
|
||||
\section*{Приложение 1}
|
||||
\addcontentsline{toc}{section}{Приложение 1}
|
||||
\label{app:usermgr}
|
||||
\lstinputlisting{../lab2/usermgr.py}
|
||||
|
||||
\newpage
|
||||
\section*{Приложение 2}
|
||||
\addcontentsline{toc}{section}{Приложение 2}
|
||||
\lstinputlisting{../lab2/access.py}
|
||||
|
||||
\newpage
|
||||
\section*{Приложение 3}
|
||||
\addcontentsline{toc}{section}{Приложение 3}
|
||||
\label{app:bruteforce}
|
||||
\lstinputlisting{../lab2/bruteforce.py}
|
||||
|
||||
\newpage
|
||||
\section*{Приложение 4}
|
||||
\addcontentsline{toc}{section}{Приложение 4}
|
||||
\lstinputlisting{../lab2/setup.sh}
|
||||
|
||||
\end{document}
|
||||
Reference in New Issue
Block a user