lab2 доработки

This commit is contained in:
2026-03-16 12:08:42 +03:00
parent b6b5e16d63
commit 37eff0ed9b
6 changed files with 164 additions and 63 deletions

View File

@@ -71,6 +71,8 @@ confaccess
PRACTICE2_DIR=/tmp/practice2 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 bruteforce alice --max-length 4
``` ```
Алгоритм хэширования: **SHA-256**. Перебор выполняется через утилиту access (confaccess): bruteforce не имеет доступа к passwd-файлу и проверяет пароли только через `confaccess --check`.
Перебор выполняется напрямую по хэшу из passwd-файла. Алгоритм хэширования в access: **SHA-256**.
Фиксируется время перебора и количество итераций до нахождения пароля. Фиксируется время перебора и количество итераций до нахождения пароля.
Перебор останавливается автоматически при достижении лимита 8 часов. Перебор останавливается автоматически при достижении лимита 8 часов.

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse
import getpass import getpass
import hashlib import hashlib
import shutil import shutil
@@ -195,7 +196,40 @@ def cmd_remove(args: list[str], login: str, perms: str) -> None:
print("Done.") 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: 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))) signal.signal(signal.SIGINT, lambda _s, _f: (print("\nBye."), sys.exit(0)))
login, user = authenticate() login, user = authenticate()

View File

@@ -1,15 +1,16 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import hashlib
import itertools import itertools
import shutil
import string import string
import subprocess
import sys import sys
import time import time
from pathlib import Path from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent)) sys.path.insert(0, str(Path(__file__).parent))
from config import PASSWD_FILE from config import BIN_DIR
CHARSET = string.ascii_letters + string.digits + "!@#$%^&*()" CHARSET = string.ascii_letters + string.digits + "!@#$%^&*()"
FIRST_CHARS = string.ascii_letters FIRST_CHARS = string.ascii_letters
@@ -17,19 +18,24 @@ FIRST_CHARS = string.ascii_letters
MAX_HOURS = 8 MAX_HOURS = 8
def hash_password(password: str) -> str: def get_access_cmd() -> list[str]:
return hashlib.sha256(password.encode("ascii")).hexdigest() """Resolve path to access utility (confaccess)."""
# setup.sh installs as confaccess; pyproject exposes as access
for name in ("confaccess", "access"):
def get_target_hash(login: str) -> str | None: path = shutil.which(name)
if not PASSWD_FILE.exists(): if path:
return None return [path]
with open(PASSWD_FILE) as f: # Fallback: same directory as bruteforce (development)
for line in f: script_dir = Path(__file__).resolve().parent
parts = line.strip().split(":", 4) access_script = script_dir / "access.py"
if len(parts) == 5 and parts[0] == login: if access_script.exists():
return parts[1] return [sys.executable, str(access_script)]
return None # 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: def max_combinations(length: int) -> int:
@@ -38,14 +44,31 @@ def max_combinations(length: int) -> int:
return len(FIRST_CHARS) * (len(CHARSET) ** (length - 1)) 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 count = 0
start = time.perf_counter() 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: if length == 1:
for first in FIRST_CHARS: for first in FIRST_CHARS:
count += 1 if time.perf_counter() - start > MAX_HOURS * 3600:
if hash_password(first) == target_hash: return None
if check(first):
return first, count, time.perf_counter() - start return first, count, time.perf_counter() - start
return None 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): for rest in itertools.product(CHARSET, repeat=length - 1):
if time.perf_counter() - start > MAX_HOURS * 3600: if time.perf_counter() - start > MAX_HOURS * 3600:
return None return None
count += 1
password = first + "".join(rest) password = first + "".join(rest)
if hash_password(password) == target_hash: if check(password):
return password, count, time.perf_counter() - start return password, count, time.perf_counter() - start
return None return None
def main() -> 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("login", help="Target username")
parser.add_argument( parser.add_argument(
"--max-length", "--max-length",
@@ -72,34 +96,45 @@ def main() -> None:
) )
args = parser.parse_args() args = parser.parse_args()
target_hash = get_target_hash(args.login) cmd = get_access_cmd() + ["--check", args.login]
if not target_hash: proc = subprocess.Popen(
print(f"User '{args.login}' not found in passwd file.") cmd,
return 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}") total_start = time.perf_counter()
print(f"Hash: {target_hash}") for length in range(1, args.max_length + 1):
print(f"Charset size: {len(CHARSET)} ({len(FIRST_CHARS)} valid for first char)") total = max_combinations(length)
print(f"Algorithm: SHA-256") print(f"Length {length}: max {total:>15,} combinations")
print()
for length in range(1, args.max_length + 1): result = brute_force_length(args.login, length, proc)
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
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: print(f"\nPassword not found within length {args.max_length}.")
password, count, elapsed = result finally:
print(f" >>> FOUND: '{password}'") if proc.stdin:
print(f" Iterations: {count:,}") proc.stdin.close()
print(f" Time: {elapsed:.4f}s") if proc.poll() is None:
print(f" Speed: {count / elapsed:,.0f} hashes/s") proc.terminate()
return proc.wait()
else:
print(f" Not found at length {length} (timeout or exhausted)")
print(f"\nPassword not found within length {args.max_length}.")
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -275,3 +275,10 @@
**Go:** **Go:**
* пакет `hash` * пакет `hash`
---
Примечание:
Brute Force должен запускать утилиту из-под себя и через неё пытаться подобрать пароль, он не должен лезть в файл напрямую.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -400,9 +400,9 @@ A: C (Availability Impact: Complete) — полное нарушение дос
\subsection{Описание реализации} \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}. Поддерживаемые команды приведены в таблице~\ref{tab:commands}.
@@ -427,11 +427,11 @@ A: C (Availability Impact: Complete) — полное нарушение дос
Копирование разрешено только в каталог \texttt{confdata} или внутри него. Копирование из \texttt{confdata} в другие каталоги и перезапись существующих файлов запрещены. Копирование разрешено только в каталог \texttt{confdata} или внутри него. Копирование из \texttt{confdata} в другие каталоги и перезапись существующих файлов запрещены.
Программа взлома паролей (bruteforce) выполняет последовательный перебор паролей начиная с длины 1, проверяя все допустимые комбинации символов. Для каждой комбинации вычисляется SHA-256-хэш и сравнивается с целевым хэшем из файла паролей. При нахождении совпадения фиксируются найденный пароль, количество итераций и затраченное время. Перебор прекращается при обнаружении пароля, достижении заданной максимальной длины или истечении восьмичасового лимита. Программа взлома паролей (bruteforce, исходный код в приложении 3) не имеет доступа к файлу паролей и выполняет перебор исключительно через утилиту confaccess. При запуске bruteforce создаёт один процесс \texttt{confaccess --check <логин>} и передаёт ему пароли построчно; утилита проверяет каждый пароль (хэширование SHA-256 и сравнение с данными из \texttt{passwd}) и возвращает результат. Перебор выполняется последовательно, начиная с длины 1. При нахождении совпадения фиксируются найденный пароль, количество итераций и затраченное время. Перебор прекращается при обнаружении пароля, достижении заданной максимальной длины или истечении восьмичасового лимита.
\subsection{Развёртывание системы} \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!] \begin{figure}[h!]
\centering \centering
\includegraphics[width=0.85\linewidth]{img/lab2-access-log.png} \includegraphics[width=0.6\linewidth]{img/lab2-access-log.png}
\caption{Содержимое файла журнала access.log} \caption{Содержимое файла журнала access.log}
\label{fig:lab2-access-log} \label{fig:lab2-access-log}
\end{figure} \end{figure}
@@ -511,33 +511,34 @@ sudo ./setup.sh
N(n) = 52 \cdot 72^{n-1} 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 символа. На рисунке~\ref{fig:lab2-bruteforce} показан пример вывода программы взлома для пароля длиной 3 символа.
\begin{figure}[h!] \begin{figure}[h!]
\centering \centering
\includegraphics[width=0.7\linewidth]{img/lab2-bruteforce.png} \includegraphics[width=0.5\linewidth]{img/lab2-bruteforce.png}
\caption{Результат работы программы взлома bruteforce} \caption{Результат работы программы взлома bruteforce}
\label{fig:lab2-bruteforce} \label{fig:lab2-bruteforce}
\end{figure} \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!] \begin{table}[h!]
\centering \centering
\caption{Результаты исследования стойкости паролей (SHA-256, $v = 1{,}50 \times 10^6$ Х/с)} \caption{Результаты исследования стойкости паролей (SHA-256, $v = 7{,}8 \times 10^4$ проверок/с)}
\label{tab:bruteforce} \label{tab:bruteforce}
\begin{tabularx}{\textwidth}{crrcc} \begin{tabularx}{\textwidth}{crrcc}
\toprule \toprule
Длина & $N$ & $t_{\max}$, с & Эксп. итераций & Эксп. время, с \\ Длина & $N$ & $t_{\max}$, с & Эксп. итераций & Эксп. время, с \\
\midrule \midrule
3 & $269\,568$ & $0{,}18$ & $163\,000$ & $0{,}22$ \\ 2 & $3\,744$ & $0{,}048$ & $1\,847$ & $0{,}024$ \\
4 & $19\,408\,896$ & $12{,}9$ & $14\,160\,000$ & $9{,}5$ \\ 3 & $269\,568$ & $3{,}5$ & $134\,847$ & $1{,}73$ \\
5 & $1\,397\,440\,512$ & $930$ & $698\,000\,000$ & $464$ \\ 4 & $19\,408\,896$ & $249$ & $11\,623\,412$ & $149$ \\
6 & $1{,}01 \times 10^{11}$ & $66\,954$ & \multicolumn{2}{c}{не проводился} \\ 5 & $1\,397\,440\,512$ & $17\,916$ & \multicolumn{2}{c}{не проводился} \\
7 & $7{,}24 \times 10^{12}$ & ${\approx}4{,}8 \times 10^{6}$ & \multicolumn{2}{c}{не проводился} \\ 6 & $1{,}01 \times 10^{11}$ & $1{,}29 \times 10^6$ & \multicolumn{2}{c}{не проводился} \\
8 & $5{,}22 \times 10^{14}$ & ${\approx}3{,}5 \times 10^{8}$ & \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 \bottomrule
\end{tabularx} \end{tabularx}
\end{table} \end{table}
@@ -546,7 +547,8 @@ N(n) = 52 \cdot 72^{n-1}
В ходе практической работы была разработана система доступа пользователей к конфиденциальным данным, включающая утилиту управления пользователями, утилиту доступа и программу взлома паролей методом грубой силы. Реализован механизм хэширования паролей на основе алгоритма SHA-256, система разграничения прав доступа и журналирование всех операций. В ходе практической работы была разработана система доступа пользователей к конфиденциальным данным, включающая утилиту управления пользователями, утилиту доступа и программу взлома паролей методом грубой силы. Реализован механизм хэширования паролей на основе алгоритма SHA-256, система разграничения прав доступа и журналирование всех операций.
Теоретический анализ показал, что количество итераций, необходимых для полного перебора паролей, экспоненциально возрастает с увеличением их длины. Экспериментальное исследование позволило оценить реальную скорость перебора и подтвердить теоретические оценки. Полученные результаты демонстрируют, что использование паролей длиной 6 символов и более существенно затрудняет атаку методом грубой силы, а пароли длиной 8 и более символов при применении SHA-256 практически не поддаются взлому за приемлемое время. Теоретический анализ показал, что количество итераций, необходимых для полного перебора паролей, экспоненциально возрастает с увеличением их длины. Экспериментальное исследование позволило оценить реальную скорость перебора и подтвердить теоретические оценки. Полученные результаты демонстрируют, что использование паролей длиной 6 символов и более существенно затрудняет атаку методом грубой силы.
\newpage \newpage
\section*{Заключение} \section*{Заключение}
@@ -559,5 +561,26 @@ N(n) = 52 \cdot 72^{n-1}
\newpage \newpage
\printbibliography[heading=bibintoc] \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} \end{document}