Compare commits

8 Commits

Author SHA1 Message Date
c73f7dc042 Подписи к приложениям 2026-04-04 11:32:33 +03:00
d3a40d4ef7 Переезд на md5 2026-04-04 11:25:35 +03:00
7c83fe5e1a Исправления lab4 2026-04-04 10:21:42 +03:00
642e3cf0c7 lab5 2026-04-02 16:40:25 +03:00
c373e8f5d9 lab4 2026-03-26 09:18:16 +03:00
c62f6284d2 названия файлов в приложениях 2026-03-16 14:46:49 +03:00
8aeb9a4244 lab3 2026-03-16 14:43:38 +03:00
37eff0ed9b lab2 доработки 2026-03-16 12:08:42 +03:00
43 changed files with 3337 additions and 98 deletions

View File

@@ -6,7 +6,7 @@
``` ```
$PRACTICE2_DIR/ # по умолчанию /usr/local/practice2 $PRACTICE2_DIR/ # по умолчанию /usr/local/practice2
├── etc/passwd # логин:sha256:id:права:ФИО ├── etc/passwd # логин:md5:id:права:ФИО
├── confdata/ # конфиденциальные файлы ├── confdata/ # конфиденциальные файлы
├── bin/ # утилиты (usermgr, confaccess, bruteforce) ├── bin/ # утилиты (usermgr, confaccess, bruteforce)
└── log/ # usermgr.log, access.log └── log/ # usermgr.log, access.log
@@ -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: **MD5** (по индивидуальному заданию).
Фиксируется время перебора и количество итераций до нахождения пароля. Фиксируется время перебора и количество итераций до нахождения пароля.
Перебор останавливается автоматически при достижении лимита 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
@@ -23,7 +24,7 @@ Commands:
def hash_password(password: str) -> str: def hash_password(password: str) -> str:
return hashlib.sha256(password.encode("ascii")).hexdigest() return hashlib.md5(password.encode("ascii"), usedforsecurity=False).hexdigest()
def log_action(login: str, action: str) -> None: def log_action(login: str, action: str) -> None:
@@ -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"Target: {args.login}")
print(f"Hash: {target_hash}")
print(f"Charset size: {len(CHARSET)} ({len(FIRST_CHARS)} valid for first char)") print(f"Charset size: {len(CHARSET)} ({len(FIRST_CHARS)} valid for first char)")
print(f"Algorithm: SHA-256")
print() print()
total_start = time.perf_counter()
for length in range(1, args.max_length + 1): for length in range(1, args.max_length + 1):
total = max_combinations(length) total = max_combinations(length)
print(f"Length {length}: max {total:>15,} combinations") print(f"Length {length}: max {total:>15,} combinations")
result = brute_force_length(target_hash, length) result = brute_force_length(args.login, length, proc)
if result is not None: if result is not None:
password, count, elapsed = result password, count, elapsed = result
total_elapsed = time.perf_counter() - total_start
print(f" >>> FOUND: '{password}'") print(f" >>> FOUND: '{password}'")
print(f" Iterations: {count:,}") print(f" Iterations: {count:,}")
print(f" Time: {elapsed:.4f}s") print(f" Time (len): {elapsed:.4f}s")
print(f" Speed: {count / elapsed:,.0f} hashes/s") print(f" Time (total): {total_elapsed:.4f}s")
print(f" Speed: {count / elapsed:,.0f} attempts/s")
return return
else: else:
print(f" Not found at length {length} (timeout or exhausted)") 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__": if __name__ == "__main__":

View File

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

View File

@@ -1,19 +0,0 @@
[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"]

View File

@@ -18,7 +18,7 @@ VALID_PERM_CHARS = set("rwd")
def hash_password(password: str) -> str: def hash_password(password: str) -> str:
return hashlib.sha256(password.encode("ascii")).hexdigest() return hashlib.md5(password.encode("ascii"), usedforsecurity=False).hexdigest()
def validate_password(password: str) -> str | None: def validate_password(password: str) -> str | None:

8
lab2/uv.lock generated
View File

@@ -1,8 +0,0 @@
version = 1
revision = 3
requires-python = ">=3.14"
[[package]]
name = "lab2"
version = "0.1.0"
source = { editable = "." }

2
lab3/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*.pyc
*.pdf

94
lab3/README.md Normal file
View File

@@ -0,0 +1,94 @@
# Lab 3 — DAC + MAC
Система доступа к конфиденциальным данным с дискреционным (DAC) и мандатным (MAC) управлением доступом. Основана на Lab 2.
## Структура каталогов
```
$PRACTICE3_DIR/ # по умолчанию /usr/local/practice3
├── etc/
│ ├── passwd # логин:sha256:id:права:ФИО
│ ├── access_mode # BOTH | DAC_ONLY | MAC_ONLY
│ ├── acl # матрица доступа (DAC)
│ ├── subject_labels # метки субъектов (MAC)
│ └── object_labels # метки объектов (MAC)
├── confdata/ # конфиденциальные файлы
├── bin/ # usermgr, confaccess, bruteforce
└── log/ # usermgr.log, access.log
```
## Режимы проверки доступа
| Режим | DAC | MAC |
|-------|-----|-----|
| BOTH (по умолчанию) | да | да |
| DAC_ONLY | да | нет |
| MAC_ONLY | нет | да |
Режим задаётся через `usermgr set-mode` (только root).
## Установка
```bash
chmod +x setup.sh
# для пути по умолчанию нужен root:
sudo ./setup.sh
# для тестирования без root:
PRACTICE3_DIR=/tmp/practice3 ./setup.sh
```
## Использование
### usermgr — управление пользователями
```bash
usermgr add alice
usermgr list
usermgr edit alice --full-name "Иванов Иван" --permissions rw
usermgr edit alice --label 1 # метка субъекта (root only)
usermgr set-label report.txt 2 # метка объекта (root only)
usermgr set-mode DAC_ONLY # режим проверки (root only)
usermgr show-mode # текущий режим (root only)
usermgr passwd alice
usermgr delete alice
```
### confaccess — доступ к данным
```bash
confaccess
PRACTICE3_DIR=/tmp/practice3 confaccess
```
Команды после аутентификации:
```
create <file> создать файл (владелец = текущий пользователь)
read <file> прочитать файл
append <file> <text> дописать в файл
copy <src> <dst> скопировать в confdata
remove <file> удалить файл
grant <user> <path> <perms> выдать права (только владелец)
help / exit
```
### DAC (дискреционный доступ)
- Каждый объект имеет владельца (создатель)
- Владелец может выдавать права через `grant <user> <path> <perms>`
- Права: r (чтение), w (запись), d (удаление)
### MAC (мандатный доступ, Белл–Лападула)
- Метки: 0 — несекретно, 1 — ДСП, 2 — секретно
- Нет чтения сверху: субъект читает только объекты с уровнем ≤ своего
- Нет записи вниз: субъект пишет только в объекты с уровнем ≥ своего
### bruteforce
```bash
bruteforce alice
bruteforce alice --max-length 4
```

138
lab3/bruteforce.py Normal file
View File

@@ -0,0 +1,138 @@
#!/usr/bin/env python3
import argparse
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 BIN_DIR
CHARSET = string.ascii_letters + string.digits + "!@#$%^&*()"
FIRST_CHARS = string.ascii_letters
MAX_HOURS = 8
def get_access_cmd() -> list[str]:
"""Resolve path to confaccess utility."""
for name in ("confaccess", "access"):
path = shutil.which(name)
if path:
return [path]
script_dir = Path(__file__).resolve().parent
confaccess_script = script_dir / "confaccess.py"
if confaccess_script.exists():
return [sys.executable, str(confaccess_script)]
for name in ("confaccess", "access"):
bin_cmd = BIN_DIR / name
if bin_cmd.exists():
return [str(bin_cmd)]
return ["confaccess"]
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(
login: str, length: int, proc: subprocess.Popen[str]
) -> tuple[str, int, float] | None:
"""Try all passwords of given length via batch process. proc = confaccess --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:
if time.perf_counter() - start > MAX_HOURS * 3600:
return None
if check(first):
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
password = first + "".join(rest)
if check(password):
return password, count, time.perf_counter() - start
return None
def main() -> None:
parser = argparse.ArgumentParser(
description="Brute force password cracker (via confaccess utility)"
)
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()
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()
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")
result = brute_force_length(args.login, length, proc)
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)")
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__":
main()

518
lab3/confaccess.py Normal file
View File

@@ -0,0 +1,518 @@
#!/usr/bin/env python3
"""Access to confidential data with DAC and MAC."""
import argparse
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 (
ACL_FILE,
ACCESS_MODE_FILE,
CONFDATA_DIR,
LOG_DIR,
OBJECT_LABELS_FILE,
PASSWD_FILE,
SUBJECT_LABELS_FILE,
)
HELP_TEXT = """\
Commands:
create <file> create new empty file
read <file> print file contents
append <file> <text> append text line to file
copy <src> <dst> copy file into confdata
remove <file> delete file from confdata
grant <user> <path> <perms> grant access (owner only)
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 read_access_mode() -> str:
if not ACCESS_MODE_FILE.exists():
return "BOTH"
raw = ACCESS_MODE_FILE.read_text().strip().upper()
if raw in ("BOTH", "DAC_ONLY", "MAC_ONLY"):
return raw
return "BOTH"
def normalize_path(path: str) -> str:
"""Normalize path relative to confdata (no ./, no trailing /)."""
p = path.strip().lstrip("./")
parts = [x for x in p.split("/") if x]
return "/".join(parts) if parts else ""
def read_acl() -> dict[str, dict]:
"""Read ACL: path -> {owner, perms: {user: perms_str}}."""
acl: dict[str, dict] = {}
if not ACL_FILE.exists():
return acl
for line in ACL_FILE.read_text().splitlines():
line = line.strip()
if not line:
continue
parts = line.split(":", 2)
if len(parts) < 3:
continue
path, owner, assignments = parts[0], parts[1], parts[2]
path = normalize_path(path)
perms: dict[str, str] = {}
for ass in assignments.split(","):
if ":" in ass:
u, p = ass.split(":", 1)
perms[u.strip()] = p.strip()
acl[path] = {"owner": owner, "perms": perms}
return acl
def write_acl(acl: dict[str, dict]) -> None:
ACL_FILE.parent.mkdir(parents=True, exist_ok=True)
lines = []
for path in sorted(acl.keys()):
entry = acl[path]
owner = entry["owner"]
perms = entry["perms"]
ass = ",".join(f"{u}:{p}" for u, p in sorted(perms.items()))
lines.append(f"{path}:{owner}:{ass}")
ACL_FILE.write_text("\n".join(lines) + "\n")
def read_subject_labels() -> dict[str, int]:
labels: dict[str, int] = {}
if not SUBJECT_LABELS_FILE.exists():
return labels
for line in SUBJECT_LABELS_FILE.read_text().splitlines():
line = line.strip()
if not line:
continue
parts = line.split(":", 1)
if len(parts) == 2 and parts[1] in ("0", "1", "2"):
labels[parts[0]] = int(parts[1])
return labels
def read_object_labels() -> dict[str, int]:
labels: dict[str, int] = {}
if not OBJECT_LABELS_FILE.exists():
return labels
for line in OBJECT_LABELS_FILE.read_text().splitlines():
line = line.strip()
if not line:
continue
parts = line.split(":", 1)
if len(parts) == 2 and parts[1] in ("0", "1", "2"):
labels[parts[0]] = int(parts[1])
return labels
def write_object_labels(labels: dict[str, int]) -> None:
OBJECT_LABELS_FILE.parent.mkdir(parents=True, exist_ok=True)
lines = [f"{path}:{level}" for path, level in sorted(labels.items())]
OBJECT_LABELS_FILE.write_text("\n".join(lines) + "\n")
def dac_allows(login: str, action: str, rel_path: str) -> bool:
"""Check DAC: does login have required permission on object?"""
rel_path = normalize_path(rel_path)
acl = read_acl()
if action in ("create", "create_dst"):
# New object: not in ACL yet, any authenticated user can create
if rel_path not in acl:
return True
entry = acl.get(rel_path)
if not entry:
return False
perms = entry["perms"].get(login, "")
if action == "read":
return "r" in perms
if action in ("write", "append", "create_dst"):
return "w" in perms
if action == "remove":
return "d" in perms
if action == "grant":
return entry["owner"] == login
return False
def dac_owner(rel_path: str) -> str | None:
"""Return owner of path or None."""
rel_path = normalize_path(rel_path)
acl = read_acl()
entry = acl.get(rel_path)
return entry["owner"] if entry else None
def mac_allows(login: str, action: str, rel_path: str, obj_level: int | None = None) -> bool:
"""Check MAC (Bell-LaPadula)."""
labels = read_subject_labels()
subj_level = labels.get(login, 0)
obj_labels = read_object_labels()
norm_path = normalize_path(rel_path)
if obj_level is None:
obj_level = obj_labels.get(norm_path)
if action == "read":
# No read up: subject_level >= object_level
obj_lev = obj_level if obj_level is not None else 0
return subj_level >= obj_lev
if action in ("write", "append", "remove"):
obj_lev = obj_level if obj_level is not None else 0
return subj_level <= obj_lev
if action == "create_dst":
# New object gets subject's label; subject can write to same level
return True
return True
def check_access(login: str, action: str, rel_path: str, mode: str) -> bool:
if mode in ("BOTH", "DAC_ONLY"):
if not dac_allows(login, action, rel_path):
return False
if mode in ("BOTH", "MAC_ONLY"):
if not mac_allows(login, action, rel_path):
return False
return True
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 rel_path_from_confdata(path: Path) -> str:
try:
rel = path.relative_to(CONFDATA_DIR.resolve())
return "/".join(rel.parts)
except ValueError:
return ""
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, mode: str) -> None:
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
rel = rel_path_from_confdata(path)
if not check_access(login, "read", rel, mode):
print("Permission denied")
return
print(path.read_text(), end="")
log_action(login, f"READ {path}")
def cmd_create(args: list[str], login: str, mode: str) -> None:
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
rel = rel_path_from_confdata(path)
# DAC: new object, add to ACL with owner=login
# MAC: new object gets subject's label
if mode in ("BOTH", "DAC_ONLY"):
acl = read_acl()
acl[rel] = {"owner": login, "perms": {login: "rwd"}}
write_acl(acl)
if mode in ("BOTH", "MAC_ONLY"):
labels = read_subject_labels()
subj_level = labels.get(login, 0)
obj_labels = read_object_labels()
obj_labels[rel] = subj_level
write_object_labels(obj_labels)
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, mode: str) -> None:
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
rel = rel_path_from_confdata(path)
if not check_access(login, "append", rel, mode):
print("Permission denied")
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, mode: str) -> None:
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
dst_rel = rel_path_from_confdata(dst)
src_rel = rel_path_from_confdata(src) if is_in_confdata(src) else None
if src_rel is not None:
if not check_access(login, "read", src_rel, mode):
print("Permission denied (read source)")
return
if not check_access(login, "create_dst", dst_rel, mode):
print("Permission denied (create destination)")
return
shutil.copy2(src, dst)
if mode in ("BOTH", "DAC_ONLY"):
acl = read_acl()
acl[dst_rel] = {"owner": login, "perms": {login: "rwd"}}
write_acl(acl)
if mode in ("BOTH", "MAC_ONLY"):
labels = read_subject_labels()
subj_level = labels.get(login, 0)
obj_labels = read_object_labels()
obj_labels[dst_rel] = subj_level
write_object_labels(obj_labels)
log_action(login, f"COPY {src} -> {dst}")
print("Done.")
def cmd_remove(args: list[str], login: str, mode: str) -> None:
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
rel = rel_path_from_confdata(path)
if not check_access(login, "remove", rel, mode):
print("Permission denied")
return
path.unlink()
if mode in ("BOTH", "DAC_ONLY"):
acl = read_acl()
acl.pop(rel, None)
write_acl(acl)
if mode in ("BOTH", "MAC_ONLY"):
obj_labels = read_object_labels()
obj_labels.pop(rel, None)
write_object_labels(obj_labels)
log_action(login, f"REMOVE {path}")
print("Done.")
def cmd_grant(args: list[str], login: str, mode: str) -> None:
if mode not in ("BOTH", "DAC_ONLY"):
print("grant is only available in DAC or BOTH mode")
return
if len(args) != 3:
print("Usage: grant <user> <path> <perms>")
return
target_user, path_arg, perms = args[0], args[1], args[2]
for ch in perms:
if ch not in "rwd":
print(f"Invalid permission {ch!r}; allowed: r, w, d")
return
path = confdata_path(path_arg)
if not is_in_confdata(path):
print("Access denied: path must be inside confdata")
return
if not path.exists():
print(f"File not found: {path_arg}")
return
rel = rel_path_from_confdata(path)
if not dac_allows(login, "grant", rel):
print("Permission denied (only owner can grant)")
return
acl = read_acl()
entry = acl.get(rel)
if not entry:
print("ACL entry not found")
return
entry["perms"][target_user] = perms
write_acl(acl)
log_action(login, f"GRANT {target_user} {rel} {perms}")
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:
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()
full_name = user["full_name"]
mode = read_access_mode()
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, cmd_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(cmd_args, login, mode)
elif command == "read":
cmd_read(cmd_args, login, mode)
elif command == "append":
cmd_append(cmd_args, login, mode)
elif command == "copy":
cmd_copy(cmd_args, login, mode)
elif command == "remove":
cmd_remove(cmd_args, login, mode)
elif command == "grant":
cmd_grant(cmd_args, login, mode)
else:
print(f"Unknown command: {command!r}. Type 'help' for available commands.")
if __name__ == "__main__":
main()

15
lab3/config.py Normal file
View File

@@ -0,0 +1,15 @@
import os
from pathlib import Path
BASE_DIR = Path(os.environ.get("PRACTICE3_DIR", "/usr/local/practice3"))
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"
ACCESS_MODE_FILE = ETC_DIR / "access_mode"
ACL_FILE = ETC_DIR / "acl"
SUBJECT_LABELS_FILE = ETC_DIR / "subject_labels"
OBJECT_LABELS_FILE = ETC_DIR / "object_labels"

108
lab3/lab3.md Normal file
View File

@@ -0,0 +1,108 @@
# Практическая работа №3
**Дисциплина:** Защита информации
**Тема:** Реализация моделей дискреционного и мандатного управления доступом
**Преподаватель:** Силиненко А.В.
**Email:** a_silinenko@mail.ru
---
## 1. Цели работы
- Изучить особенности моделей дискреционного (DAC) и мандатного (MAC) управления доступом.
- На базе разработанной в практической работе 2 системы аутентификации и авторизации реализовать DAC и MAC.
---
## 2. Задачи работы
### 2.1
Изучить особенности дискреционного (произвольного) управления доступом, а также модель Харрисона–Руззо–Ульмана, основанную на DAC.
### 2.2
На базе разработанной в практической работе 2 системы аутентификации и авторизации разработать систему, реализующую DAC, включая:
- владение объектами;
- произвольное назначение прав доступа к объектам;
- контроль на основе матрицы или списка доступа.
### 2.3
Изучить особенности мандатного (принудительного) управления доступом, а также модель Белла–Лападулы, основанную на MAC.
### 2.4
На базе разработанной в практической работе 2 системы аутентификации и авторизации разработать систему, реализующую MAC, включая:
- метки безопасности для субъектов и объектов;
- свойства безопасности модели Белла–Лападулы.
---
## 3. Требования к работе
### 3.1
Работа выполняется в ОС **Linux** или **MacOS**.
### 3.2
Для выполнения работы необходимо создать отдельное дерево каталогов, например:
```
/usr/local/practice3/etc файлы настроек
/usr/local/practice3/confdata файлы с конфиденциальной информацией
/usr/local/practice3/bin исполняемые файлы
/usr/local/practice3/log файлы регистрации
```
Права доступа к созданным каталогам: **чтение, запись и выполнение только для пользователя root**.
При необходимости возможно создание дополнительных каталогов.
---
### 3.3 Требования к системе DAC
- у каждого объекта должен быть владелец, который может произвольно назначать и передавать права доступа к этому объекту;
- контроль доступа к объектам должен осуществляться на основе **матрицы или списка доступа**, где хранится информация о правах доступа субъектов к объектам.
---
### 3.4 Требования к системе MAC
- каждому субъекту и объекту должна присваиваться **метка безопасности суперпользователем**;
- предусмотреть **не менее трех уровней меток безопасности**, например:
- несекретно
- для служебного пользования (ДСП)
- секретно
- субъекты не могут менять метки безопасности;
- контроль доступа должен осуществляться на основе свойств безопасности **модели Белла–Лападулы**:
- «нет чтения сверху»
- «нет записи вниз»
- дискреционное свойство
---
## 4. Требования к отчету
В разделе отчета должны быть приведены:
- актуальность темы работы в контексте дисциплины;
- цель и задачи работы;
- требования к разрабатываемым системам;
- краткие сведения о DAC и MAC;
- примеры компиляции и сборки приложений (если используется компилируемый язык);
- примеры запуска и работы утилит доступа к конфиденциальной информации;
- примеры содержимого служебных файлов:
- матрицы (или списка) доступа для DAC
- меток конфиденциальности для MAC;
- выводы по проделанной работе.
---
Примечание
Та система котрая нами уже разработана в lab2
1. Необходимо выпустить ее реинкарнацию с учетом требований к тому что это должен быть дискреционный подход к управлению доступа
Свойства системы которые необходимо будет реализовать будет в задании
2. Необходимо реализовать мандатный подход к управлению доступом
Структура остается прежней нам просто нужно добавить компоненты для первого и второго подходов

43
lab3/setup.sh Executable file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BASE_DIR="${PRACTICE3_DIR:-/usr/local/practice3}"
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"
echo "BOTH" > "$BASE_DIR/etc/access_mode"
touch "$BASE_DIR/etc/acl"
touch "$BASE_DIR/etc/subject_labels"
touch "$BASE_DIR/etc/object_labels"
cp "$SCRIPT_DIR/config.py" "$BASE_DIR/bin/config.py"
cp "$SCRIPT_DIR/usermgr.py" "$BASE_DIR/bin/usermgr"
cp "$SCRIPT_DIR/confaccess.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" "$BASE_DIR/etc/access_mode" \
"$BASE_DIR/etc/acl" "$BASE_DIR/etc/subject_labels" "$BASE_DIR/etc/object_labels"
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\""

378
lab3/usermgr.py Normal file
View File

@@ -0,0 +1,378 @@
#!/usr/bin/env python3
import argparse
import getpass
import hashlib
import os
import sys
from datetime import datetime
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from config import (
ACCESS_MODE_FILE,
ETC_DIR,
LOG_DIR,
OBJECT_LABELS_FILE,
PASSWD_FILE,
SUBJECT_LABELS_FILE,
)
ALLOWED_CHARS = set(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()"
)
FIRST_CHAR_ALLOWED = set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
VALID_PERM_CHARS = set("rwd")
VALID_LABELS = frozenset({"0", "1", "2"})
def require_root() -> None:
"""Exit if not running as root."""
if os.geteuid() != 0:
print("Must run as root", file=sys.stderr)
sys.exit(1)
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 read_access_mode() -> str:
"""Read access mode; default BOTH if file missing."""
if not ACCESS_MODE_FILE.exists():
return "BOTH"
raw = ACCESS_MODE_FILE.read_text().strip().upper()
if raw in ("BOTH", "DAC_ONLY", "MAC_ONLY"):
return raw
return "BOTH"
def write_access_mode(mode: str) -> None:
ETC_DIR.mkdir(parents=True, exist_ok=True)
ACCESS_MODE_FILE.write_text(mode + "\n")
def read_subject_labels() -> dict[str, int]:
"""Read subject labels; login -> level (0, 1, 2)."""
labels: dict[str, int] = {}
if not SUBJECT_LABELS_FILE.exists():
return labels
for line in SUBJECT_LABELS_FILE.read_text().splitlines():
line = line.strip()
if not line:
continue
parts = line.split(":", 1)
if len(parts) == 2 and parts[1] in VALID_LABELS:
labels[parts[0]] = int(parts[1])
return labels
def write_subject_labels(labels: dict[str, int]) -> None:
ETC_DIR.mkdir(parents=True, exist_ok=True)
lines = [f"{login}:{level}" for login, level in sorted(labels.items())]
SUBJECT_LABELS_FILE.write_text("\n".join(lines) + "\n")
def read_object_labels() -> dict[str, int]:
"""Read object labels; path (relative to confdata) -> level."""
labels: dict[str, int] = {}
if not OBJECT_LABELS_FILE.exists():
return labels
for line in OBJECT_LABELS_FILE.read_text().splitlines():
line = line.strip()
if not line:
continue
parts = line.split(":", 1)
if len(parts) == 2 and parts[1] in VALID_LABELS:
labels[parts[0]] = int(parts[1])
return labels
def write_object_labels(labels: dict[str, int]) -> None:
ETC_DIR.mkdir(parents=True, exist_ok=True)
lines = [f"{path}:{level}" for path, level in sorted(labels.items())]
OBJECT_LABELS_FILE.write_text("\n".join(lines) + "\n")
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:
if args.label is not None:
require_root()
users = read_users()
user = find_user(users, args.login)
if not user:
print(f"User '{args.login}' not found.")
sys.exit(1)
changed: list[str] = []
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 args.label is not None:
if args.label not in VALID_LABELS:
print(f"Invalid label {args.label!r}; allowed: 0, 1, 2")
sys.exit(1)
labels = read_subject_labels()
labels[args.login] = int(args.label)
write_subject_labels(labels)
changed.append("label")
if not changed:
print("Nothing to change. Use --full-name, --permissions, and/or --label.")
sys.exit(0)
if "full_name" in changed or "permissions" in changed:
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 cmd_set_mode(args: argparse.Namespace) -> None:
require_root()
mode = args.mode.upper()
if mode not in ("BOTH", "DAC_ONLY", "MAC_ONLY"):
print(f"Invalid mode {args.mode!r}; allowed: BOTH, DAC_ONLY, MAC_ONLY")
sys.exit(1)
write_access_mode(mode)
log_action(f"SET_MODE mode={mode}")
print(f"Access mode set to {mode}.")
def cmd_show_mode(_args: argparse.Namespace) -> None:
require_root()
mode = read_access_mode()
print(f"Current access mode: {mode}")
def cmd_set_label(args: argparse.Namespace) -> None:
require_root()
if args.label not in VALID_LABELS:
print(f"Invalid label {args.label!r}; allowed: 0, 1, 2")
sys.exit(1)
path = args.path
if path.startswith("./"):
path = path[2:]
path = "/".join(p for p in path.split("/") if p)
labels = read_object_labels()
labels[path] = int(args.label)
write_object_labels(labels)
log_action(f"SET_LABEL path={path} label={args.label}")
print(f"Object label for '{path}' set to {args.label}.")
def main() -> None:
parser = argparse.ArgumentParser(description="User management utility for practice3")
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.add_argument("--label", help="Security label (0, 1, 2) - root only")
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)
p_set_mode = sub.add_parser("set-mode", help="Set access mode (BOTH/DAC_ONLY/MAC_ONLY) - root only")
p_set_mode.add_argument("mode", help="BOTH, DAC_ONLY, or MAC_ONLY")
p_set_mode.set_defaults(func=cmd_set_mode)
p_show_mode = sub.add_parser("show-mode", help="Show current access mode - root only")
p_show_mode.set_defaults(func=cmd_show_mode)
p_set_label = sub.add_parser("set-label", help="Set object security label - root only")
p_set_label.add_argument("path", help="Path relative to confdata")
p_set_label.add_argument("label", help="0, 1, or 2")
p_set_label.set_defaults(func=cmd_set_label)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()

536
lab4/README.md Normal file
View File

@@ -0,0 +1,536 @@
Сетевые адаптеры:
Adapter 1: NAT
для выхода в интернет (DNS, HTTP)
Adapter 2: Internal Network
имя: intnet
для связи с VM2
sudo vim /etc/netplan/01-netcfg.yaml
network:
version: 2
ethernets:
enp0s3: # NAT
dhcp4: true
enp0s8: # internal
addresses: [192.168.100.1/24]
network:
version: 2
ethernets:
enp0s3: # NAT
dhcp4: true
enp0s8: # internal
addresses: [192.168.100.2/24]
sudo chmod 600 /etc/netplan/01-netcfg.yaml
sudo netplan apply
sudo iptables -F
sudo iptables -X
sudo iptables -t nat -F
Ниже — учебная инструкция **только для настройки и проверки iptables**, без шагов про установку ОС, VirtualBox, netplan и утилит. Будем исходить из твоего текущего стенда:
* `firewall-host` — защищаемый хост, IP `192.168.100.1`
* `external-client` — внешний клиент, IP `192.168.100.2`
* у обеих машин есть NAT-интерфейс `enp0s3`
* внутренний интерфейс — `enp0s8`
* SSH к обеим машинам у тебя уже работает через проброс портов VirtualBox на `127.0.0.1:40001` и `127.0.0.1:40002`
Политика лабы: разрешить loopback, DNS, ping наружу, ping к защищаемому хосту только с одного IP, HTTP/HTTPS, а всё остальное запретить. Это прямо соответствует тексту задания.
---
# 2. Сначала разрешаем SSH, чтобы не отрезать себе доступ
Так как ты подключаешься к `firewall-host` по SSH, нужно **до включения блокировки** разрешить входящие SSH-подключения.
В твоём случае подключение приходит на саму Ubuntu-машину уже **после NAT VirtualBox**, то есть для Linux это обычный входящий TCP на порт `22`.
Выполни на `firewall-host`:
```bash
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
```
Пояснение:
* `-A INPUT` — добавить правило в конец цепочки `INPUT`, которая обрабатывает пакеты, входящие на этот хост.
* `-p tcp` — правило относится только к протоколу TCP.
* `--dport 22` — порт назначения 22, то есть SSH-сервер.
* `-j ACCEPT` — разрешить такой трафик.
Теперь разрешим ответы сервера по уже установленному SSH-соединению:
```bash
sudo iptables -A OUTPUT -p tcp --sport 22 -j ACCEPT
```
Пояснение:
* `-A OUTPUT` — добавить правило в цепочку `OUTPUT`, то есть для пакетов, исходящих с этого хоста.
* `-p tcp` — правило касается TCP.
* `--sport 22` — исходный порт 22; это ответы SSH-сервера клиенту.
* `-j ACCEPT` — разрешить.
Такой вариант рабочий, но в Linux обычно лучше использовать правило состояний соединений. Поэтому следующим шагом мы добавим его тоже.
---
# 3. Разрешаем уже установленные и связанные соединения
Это одно из самых важных правил. Оно позволяет не расписывать вручную каждый ответный пакет.
```bash
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
```
Пояснение:
* `-A INPUT` — правило для входящих пакетов.
* `-m conntrack` — подключить модуль отслеживания состояний соединений.
* `--ctstate ESTABLISHED,RELATED` — матчить пакеты:
* `ESTABLISHED` — относящиеся к уже установленному соединению;
* `RELATED` — связанные с уже существующим соединением.
* `-j ACCEPT` — разрешить.
И аналогично для исходящих:
```bash
sudo iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
```
Пояснение:
* здесь то же самое, но для исходящих пакетов.
Это правило особенно важно для:
* SSH-сессии, в которой ты уже сидишь;
* ответов от DNS-сервера;
* ответов от HTTP/HTTPS-серверов;
* ответов на исходящий ping (`echo-reply`).
**Порядок имеет значение.** Раз это правило стоит **раньше**, чем узкие правила вида «принять UDP/TCP с `--sport 53` или TCP с `--sport 80/443`» или «`echo-reply`», то **все ответные пакеты** по уже разрешённым исходящим запросам срабатывают здесь. Отдельные строки `INPUT` для `sport` DNS/HTTP(S) и для входящего `echo-reply` становятся **лишними**: до них очередь не дойдёт, а счётчики у таких правил останутся нулевыми.
На `INPUT` в учебной конфигурации **осмысленно оставить** явные разрешения только для **нового** входящего трафика, которое не описывается как `ESTABLISHED`/`RELATED`: SSH (если нужен), loopback и входящий `echo-request` только с разрешённого адреса (см. раздел 8).
---
# 4. Разрешаем loopback
Loopback — это локальное взаимодействие внутри самой ОС через интерфейс `lo`. По условию лабы его нужно разрешить.
```bash
sudo iptables -A INPUT -i lo -j ACCEPT
```
Пояснение:
* `-i lo` — входящий интерфейс `lo`, то есть loopback.
* `-j ACCEPT` — разрешить.
```bash
sudo iptables -A OUTPUT -o lo -j ACCEPT
```
Пояснение:
* `-o lo` — исходящий интерфейс `lo`.
* `-j ACCEPT` — разрешить.
---
# 5. Разрешаем DNS
По условию нужно разрешить взаимодействие с DNS-сервером. Обычно DNS-запросы идут по UDP на порт `53`. Иногда может использоваться и TCP 53, поэтому для учебной работы лучше разрешить оба варианта.
## UDP DNS
```bash
sudo iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
```
Пояснение:
* `-A OUTPUT` — правило для исходящих запросов.
* `-p udp` — DNS чаще всего использует UDP.
* `--dport 53` — порт назначения 53, стандартный порт DNS.
* `-j ACCEPT` — разрешить.
Входящие ответы DNS (UDP с `sport 53`) после этого правила пропускаются правилом `ESTABLISHED,RELATED` на `INPUT`; отдельная строка `INPUT --sport 53` не нужна и при типичном порядке правил всё равно не получила бы трафика.
## TCP DNS
```bash
sudo iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
```
Пояснение:
* TCP используется реже, но может понадобиться для больших DNS-ответов или специальных случаев.
* входящие TCP-ответы от DNS идут через `ESTABLISHED,RELATED` на `INPUT`.
---
# 6. Разрешаем HTTP и HTTPS
По заданию нужно разрешить доступ к любым внешним серверам по HTTP/HTTPS.
## HTTP
```bash
sudo iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT
```
Пояснение:
* `-p tcp` — HTTP работает по TCP.
* `--dport 80` — порт назначения 80, стандартный HTTP.
* правило разрешает открывать веб-страницы по HTTP.
Входящие ответы HTTP (и далее HTTPS) принимаются правилом `ESTABLISHED,RELATED` на `INPUT`; отдельное `INPUT --sport 80` не требуется.
## HTTPS
```bash
sudo iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT
```
Пояснение:
* порт 443 — стандартный HTTPS.
Входящие ответы HTTPS обрабатываются на `INPUT` через `ESTABLISHED,RELATED`.
---
# 7. Разрешаем ping наружу
По заданию нужно разрешить использование `ping` для проверки достижимости любых компьютеров во внешней сети. Для `ping` используется протокол ICMP. Конкретно запрос — это `echo-request`, ответ — `echo-reply`.
```bash
sudo iptables -A OUTPUT -p icmp --icmp-type echo-request -j ACCEPT
```
Пояснение:
* `-p icmp` — правило для протокола ICMP.
* `--icmp-type echo-request` — ICMP-пакеты типа “эхо-запрос”, то есть сам ping-запрос.
* `-j ACCEPT` — разрешить отправку.
Ответы `echo-reply` на этот исходящий ping ядро относит к той же «сессии»; на `INPUT` они проходят через `ESTABLISHED,RELATED`. Отдельное правило `INPUT -p icmp --icmp-type echo-reply` не обязательно и при раннем conntrack дублирует его бесполезно.
---
# 8. Разрешаем ping к защищаемому хосту только с одного адреса
По заданию защищаемый хост должен отвечать на ping только от одного конкретного внешнего адреса. В твоём стенде таким адресом будет `192.168.100.2`, то есть `external-client`.
```bash
sudo iptables -A INPUT -p icmp --icmp-type echo-request -s 192.168.100.2 -d 192.168.100.1 -j ACCEPT
```
Пояснение:
* `-p icmp` — ICMP.
* `--icmp-type echo-request` — входящий ping-запрос.
* `-s 192.168.100.2` — источник должен быть именно `192.168.100.2`.
* `-d 192.168.100.1` — адрес назначения — защищаемый хост.
* `-j ACCEPT` — разрешить.
```bash
sudo iptables -A OUTPUT -p icmp --icmp-type echo-reply -s 192.168.100.1 -d 192.168.100.2 -j ACCEPT
```
Пояснение:
* это правило разрешает отправку ответа ping именно этому клиенту.
* `-s 192.168.100.1` — источник ответа, сам firewall-host.
* `-d 192.168.100.2` — получатель ответа, только разрешённый клиент.
---
# 9. Только теперь включаем политику DROP по умолчанию
Когда все разрешающие правила уже стоят, можно включить запрет всего остального.
```bash
sudo iptables -P INPUT DROP
```
Пояснение:
* `-P` — установить политику по умолчанию для цепочки.
* `INPUT` — цепочка входящих пакетов.
* `DROP` — все пакеты, которые не подошли ни под одно разрешающее правило, будут отбрасываться.
```bash
sudo iptables -P OUTPUT DROP
```
Пояснение:
* то же самое для исходящих пакетов.
```bash
sudo iptables -P FORWARD DROP
```
Пояснение:
* цепочка `FORWARD` нужна для транзитных пакетов через хост.
* в этой лабораторной маршрутизатор делать не требуется, поэтому безопасно оставить `DROP`.
---
# 10. Проверяем, что SSH не отвалился
Сразу после установки политик выполни:
```bash
sudo iptables -L -n -v --line-numbers
```
Пояснение:
* убедись, что правила SSH, conntrack, loopback, исходящие DNS/HTTP(S)/ICMP и узкое правило входящего ping с `192.168.100.2` стоят в списке.
* на `INPUT` основной рост счётчиков у «ответного» трафика обычно виден у строки `ESTABLISHED,RELATED`, а не у отдельных `--sport` (если ты их вообще не добавляешь).
Открой **вторую SSH-сессию** к `firewall-host`:
```bash
ssh -p 40001 arity@127.0.0.1
```
Пояснение:
* это контрольная проверка.
* пока первая сессия ещё жива, ты проверяешь, что новое подключение тоже проходит.
* если вторая сессия открылась, значит SSH точно не заблокирован.
---
# 11. Проверка правил по заданию
## Проверка DNS
На `firewall-host`:
```bash
nslookup example.com
```
Пояснение:
* `nslookup` отправляет DNS-запрос серверу имён.
* если команда возвращает IP-адрес сайта, значит DNS разрешён.
## Проверка HTTP
```bash
curl http://example.com
```
Пояснение:
* `curl` делает HTTP-запрос к веб-серверу.
* если приходит HTML-ответ, правило HTTP работает.
## Проверка HTTPS
```bash
curl https://example.com
```
Пояснение:
* то же самое, но по HTTPS на порт 443.
## Проверка ping наружу
```bash
ping -c 4 8.8.8.8
```
Пояснение:
* `ping` — утилита проверки достижимости узла.
* `-c 4` — отправить только 4 запроса, а не бесконечно.
* `8.8.8.8` — внешний IP-адрес.
* если ответы приходят, исходящий ping разрешён.
## Проверка ping к защищаемому хосту с разрешённого адреса
На `external-client`:
```bash
ping -c 4 192.168.100.1
```
Пояснение:
* клиент `192.168.100.2` должен успешно пинговать `firewall-host`, потому что именно этот источник разрешён.
## Проверка блокировки лишнего трафика
Надёжный вариант — **TCP на порт, который не разрешён политикой**, на второй ВМ в `intnet` (у тебя это `external-client`).
На `external-client` подними слушатель на порт, например `8080`:
```bash
python3 -m http.server 8080
```
На `firewall-host` попробуй подключиться:
```bash
timeout 5 nc -vz 192.168.100.2 8080
```
Пояснение:
* исходящий TCP на `192.168.100.2:8080` не попадает под разрешённые `dport 53`/`80`/`443`, поэтому при политике `OUTPUT DROP` соединение не устанавливается (обычно таймаут).
* проверка к `example.com:22` для отчёта часто бессмысленна: порт 22 у публичных имён часто закрыт независимо от твоего МЭ.
---
# 12. Контроль трафика через tcpdump
По заданию нужно показать прохождение пакетов через `tcpdump`.
Общий просмотр трафика:
```bash
sudo tcpdump -i any -n
```
Пояснение:
* `tcpdump` — сниффер пакетов.
* `-i any` — слушать все интерфейсы сразу.
* `-n` — не преобразовывать адреса в имена.
Просмотр только ICMP:
```bash
sudo tcpdump -i any -n icmp
```
Пояснение:
* фильтр `icmp` покажет только ping-трафик.
Просмотр DNS:
```bash
sudo tcpdump -i any -n port 53
```
Пояснение:
* `port 53` — показать DNS-пакеты.
Просмотр HTTP/HTTPS:
```bash
sudo tcpdump -i any -n 'tcp port 80 or tcp port 443'
```
Пояснение:
* кавычки нужны, чтобы shell корректно передал выражение целиком.
* `or` — логическое “или”.
* выражение показывает трафик к HTTP и HTTPS.
Просмотр SSH:
```bash
sudo tcpdump -i any -n tcp port 22
```
Пояснение:
* поможет убедиться, что SSH-пакеты действительно проходят через фильтр.
---
# 13. Сохранение правил
Когда убедишься, что всё работает, сохрани конфигурацию:
```bash
sudo netfilter-persistent save
```
Пояснение:
* `netfilter-persistent` сохраняет текущие правила iptables, чтобы они восстановились после перезагрузки.
Можно дополнительно сохранить в файл:
```bash
sudo iptables-save > ~/iptables-lab4.rules
```
Пояснение:
* это текстовый дамп всех правил.
* удобно приложить к отчёту или использовать для восстановления.
---
# 14. Готовый набор команд в правильном порядке
Ниже — тот же порядок, но компактным блоком, чтобы ты мог выполнять по шагам:
```bash
sudo iptables-save > ~/iptables-before-lab.rules
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --sport 22 -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A OUTPUT -o lo -j ACCEPT
sudo iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT
sudo iptables -A OUTPUT -p icmp --icmp-type echo-request -j ACCEPT
sudo iptables -A INPUT -p icmp --icmp-type echo-request -s 192.168.100.2 -d 192.168.100.1 -j ACCEPT
sudo iptables -A OUTPUT -p icmp --icmp-type echo-reply -s 192.168.100.1 -d 192.168.100.2 -j ACCEPT
sudo iptables -P INPUT DROP
sudo iptables -P OUTPUT DROP
sudo iptables -P FORWARD DROP
```
---
# 16. Что написать в учебном смысле про SSH как “дополнение”
Можно формулировать так:
> В базовом задании SSH не входит в перечень разрешённого трафика, однако для сохранения удалённого доступа к стенду было добавлено дополнительное правило, разрешающее входящие TCP-соединения на порт 22 защищаемого хоста. Правило было установлено до включения политик DROP по умолчанию, чтобы не потерять административный доступ к системе.
Это хорошо звучит и по сути верно.

69
lab4/lab4.md Normal file
View File

@@ -0,0 +1,69 @@
# Защита информации, 2026
## Практическая работа №4
**по дисциплине «Защита информации»**
**Тема работы:** «Межсетевое экранирование»
**Преподаватель:** Силиненко А.В.
**Email:** a_silinenko@mail.ru
## 1. Цель работы
Получение базовых знаний по настройке межсетевого экрана (МЭ) в ОС Linux/MacOS.
## 2. Задачи практической работы
### 2.1.
Изучение МЭ iptables или любого другого, обеспечивающего выполнение задания.
### 2.2.
Реализация заданной политики доступа.
## 3. Ход практической работы
### 3.1.
Установить или убедиться в наличии iptables.
### 3.2.
Изучить МЭ iptables, в том числе:
- установка МЭ;
- принципы и порядок обработки пакетов, основные цепочки (таблицы) обработки;
- действия по умолчанию;
- возможные параметры правил фильтрации.
### 3.3.
Изучить команды вывода таблиц фильтрации, добавления, редактирования и удаления правил фильтрации.
### 3.4.
Реализовать следующую политику доступа:
- разрешить локальное взаимодействие через интерфейс loopback;
- разрешить взаимодействие с DNS-сервером;
- разрешить использование утилиты ping для проверки достижимости компьютеров с любыми IP-адресами во внешней сети;
- разрешить использование утилиты ping для проверки достижимости защищаемого хоста только с конкретного адреса внешней сети;
- разрешить доступ по протоколам HTTP/HTTPS к любым внешним серверам;
- блокировать все пакеты, не удовлетворяющие указанным выше условиям.
Правила политики доступа должны содержать (там, где это уместно):
- IP-адрес (сеть) источника;
- IP-адрес (сеть) приемника;
- транспортный протокол;
- для протоколов TCP и UDP: порт приемника;
- для протокола ICMP: тип и код сообщения.
### 3.5.
Произвести проверку корректности реализации заданной политики, используя программы ping, nslookup (или аналоги), web-браузер.
Также убедиться, что весь трафик, кроме разрешенного, блокируется.
### 3.6.
Проконтролировать прохождение пакетов утилитой tcpdump.
## 4. Требования к отчету
### 4.1.
В разделе отчета, посвященному данной работе, должны быть приведены:
- актуальность темы работы в контексте курса;
- цели и задачи работы;
- схема стенда, в т.ч. защищаемый компьютер с МЭ, внешняя сеть, DNS-сервер(а), IP-адреса хостов;
- принципы обработки пакетов в цепочках (таблицах) iptables;
- примеры команд добавления правил фильтрации;
- набор правил фильтрации, реализующий заданную политику доступа;
- проверку реализованной политики демонстрацию примеров пропуска разрешенного трафика и блокировки запрещенного;
- пример контроля трафика (вывод tcpdump) при проверках реализованной политики;
- выводы по проделанной работе.

27
lab4/stand.mmd Normal file
View File

@@ -0,0 +1,27 @@
graph TB
subgraph host["Хост-машина"]
subgraph fw["firewall-host (защищаемый хост, iptables)"]
fw_lo["lo: 127.0.0.1/8"]
fw_nat["enp0s3 &lpar;NAT&rpar;<br/>10.0.2.15/24"]
fw_int["enp0s8 &lpar;Internal&rpar;<br/>192.168.100.1/24"]
end
subgraph ec["external-client (внешний клиент)"]
ec_nat["enp0s3 &lpar;NAT&rpar;<br/>10.0.2.15/24"]
ec_int["enp0s8 &lpar;Internal&rpar;<br/>192.168.100.2/24"]
end
end
fw_int <--->|"Internal Network &lpar;intnet&rpar;<br/>192.168.100.0/24"| ec_int
inet["Интернет<br/>&lpar;DNS: 10.0.2.3, HTTP/HTTPS,<br/>ICMP: 8.8.8.8 и др.&rpar;"]
fw_nat -->|"NAT"| inet
style fw fill:#e8f4e8,stroke:#2d7d2d,stroke-width:2px
style ec fill:#e8e8f4,stroke:#2d2d7d,stroke-width:2px
style host fill:#f9f9f9,stroke:#999,stroke-width:1px
style inet fill:#fff3e0,stroke:#e65100,stroke-width:2px
style fw_lo fill:#fff,stroke:#666
style fw_nat fill:#fff,stroke:#666
style fw_int fill:#fff,stroke:#666
style ec_nat fill:#fff,stroke:#666
style ec_int fill:#fff,stroke:#666

2
lab5/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
key.txt
*.pyc

76
lab5/README.md Normal file
View File

@@ -0,0 +1,76 @@
# Практическая работа №5 — Конфиденциальный обмен сообщениями
Клиент-серверное приложение для обмена сообщениями по TCP с поддержкой
шифрования 3DES-CBC (с затравкой; ключ сессии из SHA-256 от ключа файла и соли),
контроля целостности сообщения **MD5** (по варианту, как в работе №2) и опциональной
аутентификации отправителя **HMAC-SHA256**.
## Установка
```bash
uv sync
```
## Ключ шифрования
Ключ хранится в текстовом файле (не менее 32 символов). Права на файл — только
чтение владельцем (`600`). Программа проверяет права при запуске и отказывается
работать, если файл доступен группе или другим пользователям.
```bash
openssl rand -hex 32 > key.txt
chmod 600 key.txt
```
## Запуск
### Сервер
```bash
uv run main.py server --port 9000 # без шифрования
uv run main.py server --port 9000 --encrypt # 3DES-CBC
uv run main.py server --port 9000 --encrypt --integrity # + MD5 digest
uv run main.py server --port 9000 --encrypt --integrity --test-integrity # отправка с повреждённым хэшем
uv run main.py server --port 9000 --encrypt --hmac # 3DES + HMAC-SHA256
uv run main.py server --port 9000 --hmac # только HMAC (без шифрования)
uv run main.py server --port 9000 --key /path/to/key.txt --encrypt # другой файл ключа
```
### Клиент
```bash
uv run main.py client 127.0.0.1 9000
uv run main.py client 127.0.0.1 9000 --encrypt
uv run main.py client 127.0.0.1 9000 --encrypt --integrity
uv run main.py client 127.0.0.1 9000 --encrypt --integrity --test-integrity
uv run main.py client 127.0.0.1 9000 --encrypt --hmac
uv run main.py client 127.0.0.1 9000 --encrypt --hmac --test-integrity
```
## Режимы работы
| Флаг | Описание |
|------|----------|
| *(без флагов)* | Открытый текст, без проверки целостности |
| `--encrypt` | Шифрование 3DES-CBC с затравкой (соль + IV генерируются случайно для каждого сообщения) |
| `--integrity` | Контроль целостности — MD5-хэш открытого текста (по индивидуальному заданию) |
| `--hmac` | HMAC-SHA256 — контроль целостности + аутентификация отправителя (требует ключ, несовместим с `--integrity`) |
| `--test-integrity` | Режим тестирования: отправляется заведомо некорректный хэш/HMAC (требует `--integrity` или `--hmac`) |
## Анализ трафика с помощью tcpdump
Для наблюдения за передаваемыми данными удобно использовать `tcpdump`.
При работе на одной машине (loopback):
```bash
sudo tcpdump -i lo -X -n tcp port 9000
```
При работе по сети (замените `eth0` на имя сетевого интерфейса):
```bash
sudo tcpdump -i eth0 -X -n tcp port 9000
```
- **Без шифрования** — в дампе видны сообщения открытым текстом (JSON с полем `"data": "текст сообщения"`).
- **С шифрованием** — в дампе видны только шифротекст, соль и IV в hex; читаемый текст отсутствует.

68
lab5/lab5.md Normal file
View File

@@ -0,0 +1,68 @@
# Практическая работа №5
по дисциплине «Защита информации»
**Тема работы:** «Разработка клиент-серверного приложения для конфиденциального обмена сообщениями»
**Преподаватель:** Силиненко А.В.
**Email:** a_silinenko@mail.ru
## 1. Цель и задачи работы
### 1.1. Цель работы
Разработка клиент-серверного приложения для конфиденциального обмена сообщениями с использованием шифрования и контроля целостности.
### 1.2. Задачи работы
- получение базовых знаний по использованию криптографических функций библиотеки `crypto` в ОС Linux или другой библиотеки, обеспечивающей реализацию требований задания;
- разработка клиент-серверного приложения для обмена шифрованными сообщениями с контролем целостности;
- проверка работы приложения в различных режимах использования с помощью программы анализа трафика.
## 2. Требования к приложению
### 2.1. Общие требования
Разработать приложение типа «клиент-сервер», позволяющее обмениваться короткими сообщениями между клиентской и серверной частями приложения с обеспечением шифрования и контроля целостности.
### 2.2. Общие требования
- протокол взаимодействия: TCP;
- длина сообщения: не более 80 символов;
- полнодуплексный обмен сообщениями: возможность одновременного приема и передачи сообщений;
- IP-адрес и порт серверной части передается клиентской части приложения в качестве параметров запуска в командной строке;
- приложение должно быть способно функционировать как при запуске клиентской и серверной частей приложения на одном компьютере, так и на разных сетевых узлах;
- язык написания программы: любой.
### 2.3. Требования к функции шифрования
- приложение должно поддерживать функцию шифрования сообщений на основе симметричного шифрования с использованием библиотеки `crypto`, входящей в API `openssl`, или другой аналогичной библиотеки;
- алгоритм симметричного шифрования: согласно индивидуальному заданию;
В моём варианте это 3DES
- ключ шифрования длиной не менее 32 символов задается в файле;
- права доступа к файлу: чтение только владельцем;
- использование опции шифрования задается ключом командной строки при запуске программы.
### 2.4. Требования к функции контроля целостности
- приложение должно поддерживать функцию контроля целостности на основе подсчета хэш-значения передаваемого сообщения;
- алгоритм вычисления хэш-значения - в соответствии с индивидуальным заданием (см. практическую работу №2);
- использование опции контроля целостности задается ключом командной строки при запуске программы;
- предусмотреть режим тестирования, в котором при включенной опции контроля целостности отправляются сообщения с некорректным хэш-значением.
### 2.5. Необязательные требования
- реализовать шифрование с «затравкой» («солью»), обеспечивающей разные шифрованные сообщения при одном и том же открытом тексте;
- реализовать для разработанного приложения опцию выработки и использования сессионного ключа на основе алгоритма Диффи-Хеллмана вместо заранее заданного ключа.
## 3. Требования к отчету
### 3.1. В разделе отчета, посвященном данной работе, должны быть приведены
- актуальность темы работы в контексте курса;
- цели и задачи работы;
- краткое описание особенностей используемого алгоритма симметричного шифрования, а также шифрования «с затравкой» и алгоритма Диффи-Хеллмана, если реализованы соответствующие требования;
- команды компиляции и сборки исполняемых модулей приложения;
- описание процедуры проверки работы приложения с примерами, включая выводы программы анализа трафика, подтверждающие реализацию требований передачи сообщений без шифрования, с шифрованием, с шифрованием и контролем целостности;
- описание проверки опции контроля целостности, в которой отправляются сообщения с некорректным хэш-значением;
- описание проверки шифрования «с затравкой» и алгоритма Диффи-Хеллмана, если реализованы соответствующие требования;
- текст разработанного приложения (клиентской и серверной частей) с комментариями - в приложении к отчету;
- выводы по проделанной работе.

367
lab5/main.py Normal file
View File

@@ -0,0 +1,367 @@
from __future__ import annotations
import argparse
import hashlib
import hmac
import json
import os
import socket
import stat
import struct
import sys
import threading
from cryptography.hazmat.decrepit.ciphers.algorithms import TripleDES
from cryptography.hazmat.primitives.ciphers import Cipher, modes
from cryptography.hazmat.primitives import padding as sym_padding
MAX_MSG_LEN = 80
DEFAULT_KEY_FILE = "key.txt"
def check_key_permissions(path: str) -> None:
st = os.stat(path)
mode = st.st_mode
bad = stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH
if mode & bad:
current = oct(mode & 0o777)
print(f"[!] Key file permissions too open ({current}): {path}")
print(f" Run: chmod 600 {path}")
sys.exit(1)
def load_key(path: str) -> str:
if not os.path.isfile(path):
print(f"[!] Key file not found: {path}")
sys.exit(1)
check_key_permissions(path)
with open(path) as f:
key = f.read().strip()
if len(key) < 32:
print(f"[!] Key must be at least 32 characters (got {len(key)})")
sys.exit(1)
return key
def derive_3des_key(file_key: str, salt: bytes) -> bytes:
return hashlib.sha256(file_key.encode() + salt).digest()[:24]
def encrypt_message(plaintext: str, file_key: str) -> tuple[bytes, bytes, bytes]:
salt = os.urandom(16)
key = derive_3des_key(file_key, salt)
iv = os.urandom(8)
padder = sym_padding.PKCS7(64).padder()
padded = padder.update(plaintext.encode()) + padder.finalize()
encryptor = Cipher(TripleDES(key), modes.CBC(iv)).encryptor()
ct = encryptor.update(padded) + encryptor.finalize()
return salt, iv, ct
def decrypt_message(salt: bytes, iv: bytes, ct: bytes, file_key: str) -> str:
key = derive_3des_key(file_key, salt)
decryptor = Cipher(TripleDES(key), modes.CBC(iv)).decryptor()
padded = decryptor.update(ct) + decryptor.finalize()
unpadder = sym_padding.PKCS7(64).unpadder()
plaintext = unpadder.update(padded) + unpadder.finalize()
return plaintext.decode()
def compute_hash(text: str) -> str:
return hashlib.md5(text.encode(), usedforsecurity=False).hexdigest()
def compute_hmac(file_key: str, text: str, salt: bytes) -> str:
return hmac.new(file_key.encode(), text.encode() + salt, hashlib.sha256).hexdigest()
def recv_exactly(sock: socket.socket, n: int) -> bytes | None:
buf = b""
while len(buf) < n:
chunk = sock.recv(n - len(buf))
if not chunk:
return None
buf += chunk
return buf
def send_packet(sock: socket.socket, data: bytes) -> None:
sock.sendall(struct.pack("!I", len(data)) + data)
def recv_packet(sock: socket.socket) -> dict | None:
header = recv_exactly(sock, 4)
if header is None:
return None
length = struct.unpack("!I", header)[0]
payload = recv_exactly(sock, length)
if payload is None:
return None
return json.loads(payload.decode())
def do_send(
sock: socket.socket,
text: str,
file_key: str | None,
use_encrypt: bool,
use_integrity: bool,
use_hmac: bool,
test_integrity: bool,
) -> None:
msg: dict = {}
print(f" [TX] plaintext: {text}")
enc_salt: bytes | None = None
if use_encrypt:
assert file_key is not None
salt, iv, ct = encrypt_message(text, file_key)
enc_salt = salt
derived_hex = derive_3des_key(file_key, salt).hex()
msg["encrypted"] = True
msg["salt"] = salt.hex()
msg["iv"] = iv.hex()
msg["data"] = ct.hex()
print(f" [TX] salt: {salt.hex()}")
print(f" [TX] 3DES key: {derived_hex}")
print(f" [TX] IV: {iv.hex()}")
print(f" [TX] ciphertext: {ct.hex()}")
else:
msg["encrypted"] = False
msg["data"] = text
if use_integrity:
h = compute_hash(text)
if test_integrity:
h = hashlib.md5(b"CORRUPTED_" + os.urandom(4), usedforsecurity=False).hexdigest()
print(f" [TX] MD5: {h} (CORRUPTED!)")
else:
print(f" [TX] MD5: {h}")
msg["hash"] = h
if use_hmac:
assert file_key is not None
hmac_salt = enc_salt if enc_salt is not None else os.urandom(16)
h = compute_hmac(file_key, text, hmac_salt)
msg["hmac_salt"] = hmac_salt.hex()
if test_integrity:
h = hmac.new(b"WRONG_KEY", os.urandom(8), hashlib.sha256).hexdigest()
print(f" [TX] HMAC salt: {hmac_salt.hex()}")
print(f" [TX] HMAC-256: {h} (CORRUPTED!)")
else:
print(f" [TX] HMAC salt: {hmac_salt.hex()}")
print(f" [TX] HMAC-256: {h}")
msg["hmac"] = h
send_packet(sock, json.dumps(msg).encode())
print()
def do_recv(
msg: dict,
file_key: str | None,
) -> str | None:
encrypted = msg.get("encrypted", False)
if encrypted:
salt = bytes.fromhex(msg["salt"])
iv = bytes.fromhex(msg["iv"])
ct = bytes.fromhex(msg["data"])
print(f" [RX] ciphertext: {ct.hex()}")
print(f" [RX] salt: {salt.hex()}")
print(f" [RX] IV: {iv.hex()}")
if file_key is None:
print(" [RX] ERROR: message is encrypted but no key loaded")
return None
derived_hex = derive_3des_key(file_key, salt).hex()
print(f" [RX] 3DES key: {derived_hex}")
try:
text = decrypt_message(salt, iv, ct, file_key)
except Exception as e:
print(f" [RX] decryption failed: {e}")
return None
print(f" [RX] decrypted: {text}")
else:
text = msg["data"]
print(f" [RX] plaintext: {text}")
if "hash" in msg:
received_hash = msg["hash"]
computed_hash = compute_hash(text)
ok = received_hash == computed_hash
print(f" [RX] recv hash: {received_hash}")
print(f" [RX] calc hash: {computed_hash}")
if ok:
print(" [RX] integrity: OK")
else:
print(" [RX] integrity: FAIL - message may have been tampered with!")
if "hmac" in msg:
hmac_salt = bytes.fromhex(msg["hmac_salt"])
received_hmac = msg["hmac"]
if file_key is None:
print(" [RX] ERROR: HMAC present but no key loaded")
else:
computed_h = compute_hmac(file_key, text, hmac_salt)
ok = hmac.compare_digest(received_hmac, computed_h)
print(f" [RX] HMAC salt: {hmac_salt.hex()}")
print(f" [RX] recv HMAC: {received_hmac}")
print(f" [RX] calc HMAC: {computed_h}")
if ok:
print(" [RX] HMAC auth: OK - sender verified")
else:
print(" [RX] HMAC auth: FAIL - sender NOT verified!")
print()
return text
def receiver_thread(
sock: socket.socket,
file_key: str | None,
stop_event: threading.Event,
) -> None:
try:
while not stop_event.is_set():
msg = recv_packet(sock)
if msg is None:
print("\n[*] Connection closed by remote side.")
stop_event.set()
break
do_recv(msg, file_key)
except OSError:
if not stop_event.is_set():
print("\n[*] Connection lost.")
stop_event.set()
def sender_thread(
sock: socket.socket,
file_key: str | None,
use_encrypt: bool,
use_integrity: bool,
use_hmac: bool,
test_integrity: bool,
stop_event: threading.Event,
) -> None:
try:
while not stop_event.is_set():
try:
text = input()
except EOFError:
stop_event.set()
break
if stop_event.is_set():
break
if len(text) > MAX_MSG_LEN:
print(f" [!] Message too long ({len(text)} > {MAX_MSG_LEN}), truncated.")
text = text[:MAX_MSG_LEN]
do_send(sock, text, file_key, use_encrypt, use_integrity, use_hmac, test_integrity)
except OSError:
if not stop_event.is_set():
stop_event.set()
def run_session(
sock: socket.socket,
addr: tuple,
file_key: str | None,
use_encrypt: bool,
use_integrity: bool,
use_hmac: bool,
test_integrity: bool,
) -> None:
flags = []
if use_encrypt:
flags.append("3DES")
if use_integrity:
flags.append("MD5")
if use_hmac:
flags.append("HMAC-SHA256")
if test_integrity:
flags.append("test-integrity")
mode_str = ", ".join(flags) if flags else "plaintext"
print(f"[*] Session with {addr[0]}:{addr[1]} [{mode_str}]")
print("[*] Type messages and press Enter to send. Ctrl+C / Ctrl+D to quit.\n")
stop = threading.Event()
rx = threading.Thread(target=receiver_thread, args=(sock, file_key, stop), daemon=True)
tx = threading.Thread(target=sender_thread, args=(sock, file_key, use_encrypt, use_integrity, use_hmac, test_integrity, stop), daemon=True)
rx.start()
tx.start()
try:
while not stop.is_set():
stop.wait(0.5)
except KeyboardInterrupt:
print("\n[*] Interrupted.")
stop.set()
sock.close()
def run_server(args: argparse.Namespace) -> None:
file_key = load_key(args.key) if (args.encrypt or args.hmac) else None
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(("0.0.0.0", args.port))
srv.listen(1)
print(f"[*] Listening on 0.0.0.0:{args.port} ...")
conn, addr = srv.accept()
print(f"[*] Client connected: {addr[0]}:{addr[1]}")
srv.close()
run_session(conn, addr, file_key, args.encrypt, args.integrity, args.hmac, args.test_integrity)
def run_client(args: argparse.Namespace) -> None:
file_key = load_key(args.key) if (args.encrypt or args.hmac) else None
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((args.host, args.port))
print(f"[*] Connected to {args.host}:{args.port}")
run_session(sock, (args.host, args.port), file_key, args.encrypt, args.integrity, args.hmac, args.test_integrity)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Encrypted messaging (3DES-CBC; integrity MD5 per assignment; optional HMAC-SHA256)"
)
sub = parser.add_subparsers(dest="role", required=True)
for name, sp in [("server", sub.add_parser("server", help="Start server")),
("client", sub.add_parser("client", help="Start client"))]:
if name == "server":
sp.add_argument("--port", type=int, required=True)
else:
sp.add_argument("host")
sp.add_argument("port", type=int)
sp.add_argument("--encrypt", action="store_true", help="Enable 3DES-CBC encryption")
sp.add_argument("--integrity", action="store_true", help="MD5 integrity hash (variant / lab 2)")
sp.add_argument("--hmac", action="store_true", help="HMAC-SHA256 integrity + authentication")
sp.add_argument("--test-integrity", action="store_true", help="Send corrupted hash/HMAC")
sp.add_argument("--key", default=DEFAULT_KEY_FILE, help="Path to key file (default: key.txt)")
return parser
def main() -> None:
args = build_parser().parse_args()
if args.integrity and args.hmac:
print("[!] --integrity and --hmac are mutually exclusive")
sys.exit(1)
if args.test_integrity and not (args.integrity or args.hmac):
print("[!] --test-integrity requires --integrity or --hmac")
sys.exit(1)
if args.role == "server":
run_server(args)
else:
run_client(args)
if __name__ == "__main__":
main()

9
lab5/pyproject.toml Normal file
View File

@@ -0,0 +1,9 @@
[project]
name = "lab5"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"cryptography>=46.0.6",
]

109
lab5/uv.lock generated Normal file
View File

@@ -0,0 +1,109 @@
version = 1
revision = 3
requires-python = ">=3.14"
[[package]]
name = "cffi"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
{ url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
{ url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
{ url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
{ url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
{ url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
{ url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
{ url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
{ url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
{ url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
{ url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
{ url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
{ url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
{ url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
{ url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
{ url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
]
[[package]]
name = "cryptography"
version = "46.0.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a4/ba/04b1bd4218cbc58dc90ce967106d51582371b898690f3ae0402876cc4f34/cryptography-46.0.6.tar.gz", hash = "sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759", size = 750542, upload-time = "2026-03-25T23:34:53.396Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/47/23/9285e15e3bc57325b0a72e592921983a701efc1ee8f91c06c5f0235d86d9/cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8", size = 7176401, upload-time = "2026-03-25T23:33:22.096Z" },
{ url = "https://files.pythonhosted.org/packages/60/f8/e61f8f13950ab6195b31913b42d39f0f9afc7d93f76710f299b5ec286ae6/cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30", size = 4275275, upload-time = "2026-03-25T23:33:23.844Z" },
{ url = "https://files.pythonhosted.org/packages/19/69/732a736d12c2631e140be2348b4ad3d226302df63ef64d30dfdb8db7ad1c/cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a", size = 4425320, upload-time = "2026-03-25T23:33:25.703Z" },
{ url = "https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175", size = 4278082, upload-time = "2026-03-25T23:33:27.423Z" },
{ url = "https://files.pythonhosted.org/packages/5b/ba/d5e27f8d68c24951b0a484924a84c7cdaed7502bac9f18601cd357f8b1d2/cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463", size = 4926514, upload-time = "2026-03-25T23:33:29.206Z" },
{ url = "https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97", size = 4457766, upload-time = "2026-03-25T23:33:30.834Z" },
{ url = "https://files.pythonhosted.org/packages/01/59/562be1e653accee4fdad92c7a2e88fced26b3fdfce144047519bbebc299e/cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c", size = 3986535, upload-time = "2026-03-25T23:33:33.02Z" },
{ url = "https://files.pythonhosted.org/packages/d6/8b/b1ebfeb788bf4624d36e45ed2662b8bd43a05ff62157093c1539c1288a18/cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507", size = 4277618, upload-time = "2026-03-25T23:33:34.567Z" },
{ url = "https://files.pythonhosted.org/packages/dd/52/a005f8eabdb28df57c20f84c44d397a755782d6ff6d455f05baa2785bd91/cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19", size = 4890802, upload-time = "2026-03-25T23:33:37.034Z" },
{ url = "https://files.pythonhosted.org/packages/ec/4d/8e7d7245c79c617d08724e2efa397737715ca0ec830ecb3c91e547302555/cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738", size = 4457425, upload-time = "2026-03-25T23:33:38.904Z" },
{ url = "https://files.pythonhosted.org/packages/1d/5c/f6c3596a1430cec6f949085f0e1a970638d76f81c3ea56d93d564d04c340/cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c", size = 4405530, upload-time = "2026-03-25T23:33:40.842Z" },
{ url = "https://files.pythonhosted.org/packages/7e/c9/9f9cea13ee2dbde070424e0c4f621c091a91ffcc504ffea5e74f0e1daeff/cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f", size = 4667896, upload-time = "2026-03-25T23:33:42.781Z" },
{ url = "https://files.pythonhosted.org/packages/ad/b5/1895bc0821226f129bc74d00eccfc6a5969e2028f8617c09790bf89c185e/cryptography-46.0.6-cp311-abi3-win32.whl", hash = "sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2", size = 3026348, upload-time = "2026-03-25T23:33:45.021Z" },
{ url = "https://files.pythonhosted.org/packages/c3/f8/c9bcbf0d3e6ad288b9d9aa0b1dee04b063d19e8c4f871855a03ab3a297ab/cryptography-46.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124", size = 3483896, upload-time = "2026-03-25T23:33:46.649Z" },
{ url = "https://files.pythonhosted.org/packages/01/41/3a578f7fd5c70611c0aacba52cd13cb364a5dee895a5c1d467208a9380b0/cryptography-46.0.6-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275", size = 7117147, upload-time = "2026-03-25T23:33:48.249Z" },
{ url = "https://files.pythonhosted.org/packages/fa/87/887f35a6fca9dde90cad08e0de0c89263a8e59b2d2ff904fd9fcd8025b6f/cryptography-46.0.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4", size = 4266221, upload-time = "2026-03-25T23:33:49.874Z" },
{ url = "https://files.pythonhosted.org/packages/aa/a8/0a90c4f0b0871e0e3d1ed126aed101328a8a57fd9fd17f00fb67e82a51ca/cryptography-46.0.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b", size = 4408952, upload-time = "2026-03-25T23:33:52.128Z" },
{ url = "https://files.pythonhosted.org/packages/16/0b/b239701eb946523e4e9f329336e4ff32b1247e109cbab32d1a7b61da8ed7/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707", size = 4270141, upload-time = "2026-03-25T23:33:54.11Z" },
{ url = "https://files.pythonhosted.org/packages/0f/a8/976acdd4f0f30df7b25605f4b9d3d89295351665c2091d18224f7ad5cdbf/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361", size = 4904178, upload-time = "2026-03-25T23:33:55.725Z" },
{ url = "https://files.pythonhosted.org/packages/b1/1b/bf0e01a88efd0e59679b69f42d4afd5bced8700bb5e80617b2d63a3741af/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b", size = 4441812, upload-time = "2026-03-25T23:33:57.364Z" },
{ url = "https://files.pythonhosted.org/packages/bb/8b/11df86de2ea389c65aa1806f331cae145f2ed18011f30234cc10ca253de8/cryptography-46.0.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca", size = 3963923, upload-time = "2026-03-25T23:33:59.361Z" },
{ url = "https://files.pythonhosted.org/packages/91/e0/207fb177c3a9ef6a8108f234208c3e9e76a6aa8cf20d51932916bd43bda0/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013", size = 4269695, upload-time = "2026-03-25T23:34:00.909Z" },
{ url = "https://files.pythonhosted.org/packages/21/5e/19f3260ed1e95bced52ace7501fabcd266df67077eeb382b79c81729d2d3/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4", size = 4869785, upload-time = "2026-03-25T23:34:02.796Z" },
{ url = "https://files.pythonhosted.org/packages/10/38/cd7864d79aa1d92ef6f1a584281433419b955ad5a5ba8d1eb6c872165bcb/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a", size = 4441404, upload-time = "2026-03-25T23:34:04.35Z" },
{ url = "https://files.pythonhosted.org/packages/09/0a/4fe7a8d25fed74419f91835cf5829ade6408fd1963c9eae9c4bce390ecbb/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d", size = 4397549, upload-time = "2026-03-25T23:34:06.342Z" },
{ url = "https://files.pythonhosted.org/packages/5f/a0/7d738944eac6513cd60a8da98b65951f4a3b279b93479a7e8926d9cd730b/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736", size = 4651874, upload-time = "2026-03-25T23:34:07.916Z" },
{ url = "https://files.pythonhosted.org/packages/cb/f1/c2326781ca05208845efca38bf714f76939ae446cd492d7613808badedf1/cryptography-46.0.6-cp314-cp314t-win32.whl", hash = "sha256:97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed", size = 3001511, upload-time = "2026-03-25T23:34:09.892Z" },
{ url = "https://files.pythonhosted.org/packages/c9/57/fe4a23eb549ac9d903bd4698ffda13383808ef0876cc912bcb2838799ece/cryptography-46.0.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4", size = 3471692, upload-time = "2026-03-25T23:34:11.613Z" },
{ url = "https://files.pythonhosted.org/packages/c4/cc/f330e982852403da79008552de9906804568ae9230da8432f7496ce02b71/cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a", size = 7162776, upload-time = "2026-03-25T23:34:13.308Z" },
{ url = "https://files.pythonhosted.org/packages/49/b3/dc27efd8dcc4bff583b3f01d4a3943cd8b5821777a58b3a6a5f054d61b79/cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8", size = 4270529, upload-time = "2026-03-25T23:34:15.019Z" },
{ url = "https://files.pythonhosted.org/packages/e6/05/e8d0e6eb4f0d83365b3cb0e00eb3c484f7348db0266652ccd84632a3d58d/cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77", size = 4414827, upload-time = "2026-03-25T23:34:16.604Z" },
{ url = "https://files.pythonhosted.org/packages/2f/97/daba0f5d2dc6d855e2dcb70733c812558a7977a55dd4a6722756628c44d1/cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290", size = 4271265, upload-time = "2026-03-25T23:34:18.586Z" },
{ url = "https://files.pythonhosted.org/packages/89/06/fe1fce39a37ac452e58d04b43b0855261dac320a2ebf8f5260dd55b201a9/cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410", size = 4916800, upload-time = "2026-03-25T23:34:20.561Z" },
{ url = "https://files.pythonhosted.org/packages/ff/8a/b14f3101fe9c3592603339eb5d94046c3ce5f7fc76d6512a2d40efd9724e/cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d", size = 4448771, upload-time = "2026-03-25T23:34:22.406Z" },
{ url = "https://files.pythonhosted.org/packages/01/b3/0796998056a66d1973fd52ee89dc1bb3b6581960a91ad4ac705f182d398f/cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70", size = 3978333, upload-time = "2026-03-25T23:34:24.281Z" },
{ url = "https://files.pythonhosted.org/packages/c5/3d/db200af5a4ffd08918cd55c08399dc6c9c50b0bc72c00a3246e099d3a849/cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d", size = 4271069, upload-time = "2026-03-25T23:34:25.895Z" },
{ url = "https://files.pythonhosted.org/packages/d7/18/61acfd5b414309d74ee838be321c636fe71815436f53c9f0334bf19064fa/cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa", size = 4878358, upload-time = "2026-03-25T23:34:27.67Z" },
{ url = "https://files.pythonhosted.org/packages/8b/65/5bf43286d566f8171917cae23ac6add941654ccf085d739195a4eacf1674/cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58", size = 4448061, upload-time = "2026-03-25T23:34:29.375Z" },
{ url = "https://files.pythonhosted.org/packages/e0/25/7e49c0fa7205cf3597e525d156a6bce5b5c9de1fd7e8cb01120e459f205a/cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb", size = 4399103, upload-time = "2026-03-25T23:34:32.036Z" },
{ url = "https://files.pythonhosted.org/packages/44/46/466269e833f1c4718d6cd496ffe20c56c9c8d013486ff66b4f69c302a68d/cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72", size = 4659255, upload-time = "2026-03-25T23:34:33.679Z" },
{ url = "https://files.pythonhosted.org/packages/0a/09/ddc5f630cc32287d2c953fc5d32705e63ec73e37308e5120955316f53827/cryptography-46.0.6-cp38-abi3-win32.whl", hash = "sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c", size = 3010660, upload-time = "2026-03-25T23:34:35.418Z" },
{ url = "https://files.pythonhosted.org/packages/1b/82/ca4893968aeb2709aacfb57a30dec6fa2ab25b10fa9f064b8882ce33f599/cryptography-46.0.6-cp38-abi3-win_amd64.whl", hash = "sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f", size = 3471160, upload-time = "2026-03-25T23:34:37.191Z" },
]
[[package]]
name = "lab5"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "cryptography" },
]
[package.metadata]
requires-dist = [{ name = "cryptography", specifier = ">=46.0.6" }]
[[package]]
name = "pycparser"
version = "3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 18 KiB

BIN
report/img/lab3-acl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
report/img/lab3-labels.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
report/img/lab3-setup.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
report/img/lab4-stand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
report/img/lab5-enc-int.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
report/img/lab5-enc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
report/img/lab5-hmac.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
report/img/lab5-plain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
report/img/lab5-salt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View File

@@ -176,6 +176,9 @@
\begin{enumerate} \begin{enumerate}
\item Практическая работа №1. Анализ уязвимостей программного обеспечения. Данная практическая работа посвящена анализу уязвимостей программного обеспечения с использованием Банка данных угроз безопасности информации ФСТЭК России~\cite{fstec-bdu}. В ходе выполнения работы предусмотрено изучение структуры разделов «Угрозы» и «Уязвимости», а также поиск уязвимостей по заданным критериям. Особое внимание уделяется выявлению уязвимостей, соответствующих используемым версиям операционных систем личных устройств, и рассмотрению возможных мер по их устранению. \item Практическая работа №1. Анализ уязвимостей программного обеспечения. Данная практическая работа посвящена анализу уязвимостей программного обеспечения с использованием Банка данных угроз безопасности информации ФСТЭК России~\cite{fstec-bdu}. В ходе выполнения работы предусмотрено изучение структуры разделов «Угрозы» и «Уязвимости», а также поиск уязвимостей по заданным критериям. Особое внимание уделяется выявлению уязвимостей, соответствующих используемым версиям операционных систем личных устройств, и рассмотрению возможных мер по их устранению.
\item Практическая работа №2. Разработка и исследование системы аутентификации и авторизации. Данная практическая работа посвящена разработке системы доступа пользователей к конфиденциальным данным и исследованию стойкости паролей к атаке методом грубой силы. В ходе выполнения работы реализованы утилита управления пользователями, утилита доступа к конфиденциальным данным и программа перебора паролей, а также проведено экспериментальное исследование зависимости времени взлома от длины пароля. \item Практическая работа №2. Разработка и исследование системы аутентификации и авторизации. Данная практическая работа посвящена разработке системы доступа пользователей к конфиденциальным данным и исследованию стойкости паролей к атаке методом грубой силы. В ходе выполнения работы реализованы утилита управления пользователями, утилита доступа к конфиденциальным данным и программа перебора паролей, а также проведено экспериментальное исследование зависимости времени взлома от длины пароля.
\item Практическая работа №3. Реализация моделей дискреционного и мандатного управления доступом. Данная практическая работа посвящена расширению системы из работы №2 путём реализации моделей DAC (дискреционный доступ) и MAC (мандатный доступ) на основе модели Белла–Лападулы.
\item Практическая работа №4. Межсетевое экранирование средствами \texttt{iptables}. Данная практическая работа посвящена настройке межсетевого экрана в Linux, формированию политики фильтрации сетевого трафика и экспериментальной проверке пропуска разрешённых соединений и блокировки запрещённых пакетов с использованием \texttt{tcpdump}.
\item Практическая работа №5. Разработка клиент-серверного приложения для конфиденциального обмена сообщениями. Данная практическая работа посвящена разработке TCP-приложения с поддержкой шифрования 3DES-CBC с затравкой (выработка ключа сессии на основе SHA-256), контроля целостности сообщения на основе MD5 в соответствии с индивидуальным заданием, дополнительного режима HMAC-SHA256, а также проверке корректности шифрования с помощью анализа трафика.
\end{enumerate} \end{enumerate}
\newpage \newpage
@@ -360,7 +363,7 @@ A: C (Availability Impact: Complete) — полное нарушение дос
\begin{enumerate} \begin{enumerate}
\item Разработать систему доступа пользователей к конфиденциальным данным, включающую утилиту управления пользователями и утилиту доступа к конфиденциальным данным. \item Разработать систему доступа пользователей к конфиденциальным данным, включающую утилиту управления пользователями и утилиту доступа к конфиденциальным данным.
\item Разработать программу взлома паролей методом грубой силы. \item Разработать программу взлома паролей методом грубой силы.
\item Исследовать стойкость паролей в зависимости от их длины при использовании алгоритма хэширования SHA-256. \item Исследовать стойкость паролей в зависимости от их длины при использовании алгоритма хэширования MD5 (индивидуальный вариант задания).
\end{enumerate} \end{enumerate}
\subsection{Требования к системе} \subsection{Требования к системе}
@@ -396,13 +399,13 @@ A: C (Availability Impact: Complete) — полное нарушение дос
\item Операционная система: Ubuntu 25.10 \item Операционная система: Ubuntu 25.10
\end{itemize} \end{itemize}
В качестве среды разработки использовался редактор Cursor. Язык программирования — Python 3.14. Алгоритм хэширования — SHA-256, реализованный с использованием модуля \texttt{hashlib} стандартной библиотеки Python. В качестве среды разработки использовался редактор Cursor. Язык программирования — Python 3.14. Для хэширования паролей используется алгоритм MD5 (индивидуальный вариант задания), реализованный с использованием модуля \texttt{hashlib} стандартной библиотеки Python.
\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} — вывод списка всех пользователей. Пароль хранится в виде MD5-хэша (128 бит, 32 шестнадцатеричных символа). Все операции с файлом паролей регистрируются в журнале \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 +430,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 <логин>} и передаёт ему пароли построчно; утилита проверяет каждый пароль (хэширование MD5 и сравнение с данными из \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 +499,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,42 +514,615 @@ 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. Величина $v$ получена по трём запускам программы bruteforce на том же ПК для паролей длиной 2, 3 и~4 символа (хэширование MD5): средняя скорость составила $v \approx 2{,}4 \times 10^4$ проверок в секунду. Столбцы \textit{Эксп. итераций} и \textit{Эксп. время} отражают число попыток и длительность перебора на длине $n$ до нахождения пароля в соответствующем запуске.
На рисунке~\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 расчётное максимальное время при измеренном $v$ составляет около $6 \times 10^4$~с (порядка суток), поэтому полный перебор на практике не выполнялся; для больших длин $t_{\max}$ ещё выше. Результаты приведены в таблице~\ref{tab:bruteforce}.
\begin{table}[h!] \begin{table}[h!]
\centering \centering
\caption{Результаты исследования стойкости паролей (SHA-256, $v = 1{,}50 \times 10^6$ Х/с)} \caption{Результаты исследования стойкости паролей (MD5, $v \approx 2{,}4 \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{,}16$ & $134$ & $0{,}006$ \\
4 & $19\,408\,896$ & $12{,}9$ & $14\,160\,000$ & $9{,}5$ \\ 3 & $269\,568$ & $11$ & $9\,638$ & $0{,}40$ \\
5 & $1\,397\,440\,512$ & $930$ & $698\,000\,000$ & $464$ \\ 4 & $19\,408\,896$ & $818$ & $693\,926$ & $30{,}5$ \\
6 & $1{,}01 \times 10^{11}$ & $66\,954$ & \multicolumn{2}{c}{не проводился} \\ 5 & $1\,397\,440\,512$ & $58\,900$ & \multicolumn{2}{c}{не проводился} \\
7 & $7{,}24 \times 10^{12}$ & ${\approx}4{,}8 \times 10^{6}$ & \multicolumn{2}{c}{не проводился} \\ 6 & $1{,}01 \times 10^{11}$ & $4{,}24 \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}3{,}05 \times 10^{8}$ & \multicolumn{2}{c}{не проводился} \\
8 & $5{,}22 \times 10^{14}$ & ${\approx}2{,}2 \times 10^{10}$ & \multicolumn{2}{c}{не проводился} \\
\bottomrule \bottomrule
\end{tabularx} \end{tabularx}
\end{table} \end{table}
\subsection{Выводы} \subsection{Выводы}
В ходе практической работы была разработана система доступа пользователей к конфиденциальным данным, включающая утилиту управления пользователями, утилиту доступа и программу взлома паролей методом грубой силы. Реализован механизм хэширования паролей на основе алгоритма SHA-256, система разграничения прав доступа и журналирование всех операций. В ходе практической работы была разработана система доступа пользователей к конфиденциальным данным, включающая утилиту управления пользователями, утилиту доступа и программу взлома паролей методом грубой силы. Реализован механизм хэширования паролей на основе алгоритма MD5 (индивидуальный вариант задания), система разграничения прав доступа и журналирование всех операций.
Теоретический анализ показал, что количество итераций, необходимых для полного перебора паролей, экспоненциально возрастает с увеличением их длины. Экспериментальное исследование позволило оценить реальную скорость перебора и подтвердить теоретические оценки. Полученные результаты демонстрируют, что использование паролей длиной 6 символов и более существенно затрудняет атаку методом грубой силы.
\newpage
\section{Реализация моделей дискреционного и мандатного управления доступом}
\subsection{Актуальность темы}
Модели управления доступом являются фундаментом систем защиты информации. Дискреционное управление (DAC) позволяет владельцам объектов гибко назначать права, однако не защищает от утечки информации через легитимных пользователей. Мандатное управление (MAC) на основе меток конфиденциальности обеспечивает принудительный контроль потока информации и применяется в системах с повышенными требованиями к защите. Изучение и практическая реализация обеих моделей в контексте дисциплины <<Защита информации>> позволяет закрепить понимание механизмов разграничения доступа и их ограничений.
\subsection{Цели и задачи работы}
Практическая работа №3 по дисциплине <<Защита информации>> посвящена реализации моделей дискреционного (DAC) и мандатного (MAC) управления доступом на базе системы, разработанной в практической работе №2.
Цель работы: изучить особенности моделей DAC и MAC и реализовать их в рамках существующей системы аутентификации и авторизации.
Задачи работы:
\begin{enumerate}
\item Изучить особенности дискреционного управления доступом и модель Харрисона–Руззо–Ульмана.
\item Разработать систему DAC с владением объектами, произвольным назначением прав и контролем на основе матрицы (списка) доступа.
\item Изучить особенности мандатного управления доступом и модель Белла–Лападулы.
\item Разработать систему MAC с метками безопасности для субъектов и объектов и свойствами модели Белла–Лападулы.
\end{enumerate}
\subsection{Краткие сведения о DAC и MAC}
\textbf{Дискреционное управление доступом (DAC)} основано на произвольном назначении прав владельцем объекта. Каждый объект имеет владельца, который может назначать и передавать права доступа другим субъектам. Контроль осуществляется на основе матрицы доступа или списков доступа (ACL), где для каждой пары (субъект, объект) указаны разрешённые операции. Модель Харрисона–Руззо–Ульмана формализует операции над матрицей доступа (создание/удаление субъектов и объектов, выдача и отзыв прав).
\textbf{Мандатное управление доступом (MAC)} основано на метках конфиденциальности, назначаемых суперпользователем. Субъекты и объекты получают метки (уровни), и доступ определяется правилами, не зависящими от воли пользователей. Модель Белла–Лападулы реализует два основных свойства: <<нет чтения сверху>> (субъект может читать только объекты с уровнем не выше своего) и <<нет записи вниз>> (субъект может записывать только в объекты с уровнем не ниже своего), что предотвращает утечку информации с высокого уровня на низкий.
\subsection{Требования к разрабатываемым системам}
Структура каталогов аналогична работе №2, но с базовым путём \texttt{/usr/local/practice3} (переменная \texttt{PRACTICE3\_DIR}):
\begin{itemize}
\item \texttt{etc/} — файлы настроек: \texttt{passwd}, \texttt{access\_mode}, \texttt{acl}, \texttt{subject\_labels}, \texttt{object\_labels};
\item \texttt{confdata/} — конфиденциальные файлы;
\item \texttt{bin/} — утилиты;
\item \texttt{log/} — журналы.
\end{itemize}
\textbf{DAC:} у каждого объекта — владелец; владелец может назначать права через команду \texttt{grant}; контроль по списку доступа (ACL) в формате \\ \texttt{путь:владелец:пользователь:права,...}.
\textbf{MAC:} метки безопасности (0 — несекретно, 1 — ДСП, 2 — секретно) назначаются суперпользователем через \texttt{usermgr}; субъекты не могут менять метки; проверка по свойствам Белла–Лападулы.
Режим проверки (BOTH — DAC и MAC, DAC\_ONLY, MAC\_ONLY) задаётся администратором через \texttt{usermgr set-mode} и доступен только root.
\subsection{Описание реализации}
Система построена на базе кода из практической работы №2. Утилита \texttt{usermgr} расширена админскими подкомандами (приложение 5): \texttt{set-mode BOTH|DAC\_ONLY|MAC\_ONLY} — установка режима проверки; \texttt{show-mode} — вывод текущего режима; \texttt{edit <логин> --label <0|1|2>} — установка метки субъекта; \texttt{set-label <путь> <0|1|2>} — установка метки объекта. Все админские команды требуют прав root.
Утилита \texttt{confaccess} (приложение 6) полностью переработана. При \texttt{create} объект получает владельца (текущий пользователь) и запись в ACL; при операциях \texttt{read}, \texttt{append}, \texttt{remove} проверяется наличие соответствующего права в ACL. Команда \texttt{grant <пользователь> <путь> <права>} доступна только владельцу объекта. Для MAC при каждой операции проверяются свойства Белла–Лападулы: чтение разрешено при уровне субъекта $\ge$ уровня объекта; запись (append, create, remove) — при уровне субъекта $\le$ уровня объекта. Конфигурация путей вынесена в \texttt{config.py} (приложение 7). Скрипт \texttt{setup.sh} (приложение 8) создаёт структуру для practice3 и инициализирует файлы \texttt{access\_mode}, \texttt{acl}, \texttt{subject\_labels}, \texttt{object\_labels}.
\subsection{Развёртывание и примеры работы}
Установка выполняется аналогично работе №2:
\begin{verbatim}
chmod +x setup.sh
PRACTICE3_DIR=/tmp/practice3 ./setup.sh
\end{verbatim}
Результат выполнения скрипта представлен на рисунке~\ref{fig:lab3-setup}.
\begin{figure}[h!]
\centering
% Заглушка: добавить скриншот img/lab3-setup.png
\includegraphics[width=0.6\linewidth]{img/lab3-setup.png}
\caption{Результат выполнения скрипта setup.sh (practice3)}
\label{fig:lab3-setup}
\end{figure}
Примеры работы утилит: переключение режима \texttt{usermgr set-mode}, установка меток, работа \texttt{confaccess} с командами \texttt{create}, \texttt{grant}, \texttt{read} — на рисунках~\ref{fig:lab3-usermgr} и~\ref{fig:lab3-confaccess}.
\begin{figure}[h!]
\centering
% Заглушка: добавить скриншот img/lab3-usermgr-mode.png
\includegraphics[width=0.6\linewidth]{img/lab3-usermgr-mode.png}
\caption{Работа usermgr: set-mode, show-mode, set-label}
\label{fig:lab3-usermgr}
\end{figure}
\begin{figure}[h!]
\centering
% Заглушка: добавить скриншот img/lab3-confaccess.png
\includegraphics[width=0.6\linewidth]{img/lab3-confaccess.png}
\caption{Работа confaccess: create, grant, read}
\label{fig:lab3-confaccess}
\end{figure}
\subsection{Примеры содержимого служебных файлов}
\textbf{Список доступа (ACL) для DAC} — файл \texttt{etc/acl}. Каждая строка: \texttt{путь:владелец:пользователь1:права1,пользователь2:права2,...}. Пример:
\begin{verbatim}
report.txt:alice:alice:rwd,bob:r
secret.txt:bob:bob:rwd
\end{verbatim}
Владелец \texttt{report.txt} — alice (полные права rwd); bob имеет только чтение (r).
\textbf{Метки конфиденциальности для MAC} — файлы \texttt{etc/subject\_labels} (субъекты) и \texttt{etc/object\_labels} (объекты). Формат: \texttt{идентификатор:уровень}, где уровень — 0 (несекретно), 1 (ДСП), 2 (секретно). Пример:
\begin{verbatim}
# subject_labels
alice:1
bob:0
# object_labels
report.txt:0
secret.txt:2
\end{verbatim}
Примеры содержимого файлов ACL и меток представлены на рисунках~\ref{fig:lab3-acl} и~\ref{fig:lab3-labels}.
\begin{figure}[h!]
\centering
% Заглушка: добавить скриншот img/lab3-acl.png
\includegraphics[width=0.7\linewidth]{img/lab3-acl.png}
\caption{Содержимое файла etc/acl (список доступа DAC)}
\label{fig:lab3-acl}
\end{figure}
\begin{figure}[h!]
\centering
% Заглушка: добавить скриншот img/lab3-labels.png
\includegraphics[width=0.7\linewidth]{img/lab3-labels.png}
\caption{Содержимое файлов subject\_labels и object\_labels (метки MAC)}
\label{fig:lab3-labels}
\end{figure}
\subsection{Выводы}
В ходе практической работы №3 на базе системы из работы №2 реализованы модели дискреционного и мандатного управления доступом. DAC реализован через список доступа (ACL) с владельцами объектов и командой \texttt{grant} для произвольного назначения прав. MAC реализован на основе модели Белла–Лападулы с тремя уровнями меток и проверкой свойств <<нет чтения сверху>> и <<нет записи вниз>>. Режимы проверки (BOTH, DAC\_ONLY, MAC\_ONLY) позволяют гибко настраивать систему; переключение доступно только администратору (root). Полученный опыт демонстрирует принципиальные различия между дискреционным и мандатным подходами к управлению доступом и их практическую реализацию в информационных системах.
\newpage
\section{Межсетевое экранирование средствами iptables}
\subsection{Актуальность темы}
Межсетевое экранирование является одним из базовых механизмов сетевой защиты информационных систем. Локальный хостовый межсетевой экран позволяет минимизировать поверхность атаки, явно разрешая только необходимый трафик и блокируя все остальные соединения по принципу <<запрещено всё, что не разрешено>>. Практическая настройка \texttt{iptables} в рамках курса <<Защита информации>> позволяет закрепить представление о пакетной фильтрации, ролях цепочек \texttt{INPUT}, \texttt{OUTPUT}, \texttt{FORWARD} и о проверке работоспособности правил на реальном стенде.
\subsection{Цели и задачи работы}
Цель работы: получение базовых знаний по настройке межсетевого экрана в ОС Linux и реализация заданной политики доступа.
Задачи работы:
\begin{enumerate}
\item Изучить межсетевой экран \texttt{iptables}: принципы обработки пакетов, основные цепочки, действия по умолчанию, параметры правил фильтрации.
\item Развернуть лабораторный стенд на базе виртуальных машин VirtualBox.
\item Реализовать заданную политику доступа: разрешить loopback, DNS, исходящий ping, входящий ping только от конкретного адреса, HTTP/HTTPS; заблокировать всё остальное.
\item Проверить корректность реализации политики.
\item Проконтролировать прохождение пакетов утилитой \texttt{tcpdump}.
\end{enumerate}
\subsection{Схема стенда}
Стенд реализован на базе двух виртуальных машин VirtualBox с ОС Ubuntu Server 22.04. Каждая машина имеет два сетевых адаптера:
\begin{itemize}
\item \textbf{Adapter~1 (NAT)} --- доступ в Интернет (DNS, HTTP/HTTPS, ICMP);
\item \textbf{Adapter~2 (Internal Network <<intnet>>)} --- изолированная сеть \texttt{192.168.100.0/24} для связи между машинами.
\end{itemize}
Схема стенда представлена на рисунке~\ref{fig:lab4-topology}, адресация узлов --- в таблице~\ref{tab:lab4-addr}.
\begin{figure}[H]
\centering
\includegraphics[width=0.85\linewidth]{img/lab4-stand.png}
\caption{Схема лабораторного стенда}
\label{fig:lab4-topology}
\end{figure}
\begin{table}[H]
\centering
\caption{Адресация узлов стенда}
\label{tab:lab4-addr}
\begin{tabularx}{\textwidth}{l l l X}
\toprule
Узел & Интерфейс & IP-адрес & Назначение \\
\midrule
\multirow{2}{*}{firewall-host} & enp0s3 (NAT) & 10.0.2.15/24 & Доступ в Интернет \\
& enp0s8 (intnet) & 192.168.100.1/24 & Внутренняя сеть \\
\midrule
\multirow{2}{*}{external-client} & enp0s3 (NAT) & 10.0.2.15/24 & Доступ в Интернет \\
& enp0s8 (intnet) & 192.168.100.2/24 & Внутренняя сеть \\
\bottomrule
\end{tabularx}
\end{table}
DNS-запросы обрабатываются службой \texttt{systemd-resolved} (\texttt{127.0.0.53}), перенаправляющей их DNS-серверу NAT-шлюза VirtualBox (\texttt{10.0.2.3}). Управление виртуальными машинами осуществлялось по SSH через проброс портов VirtualBox.
\subsection{Принципы обработки пакетов в \texttt{iptables}}
Утилита \texttt{iptables} организует правила фильтрации в виде таблиц и цепочек. В работе использовалась таблица \texttt{filter} (по умолчанию), содержащая три встроенные цепочки:
\begin{itemize}
\item \texttt{INPUT} --- входящие пакеты, адресованные локальным процессам;
\item \texttt{OUTPUT} --- исходящие пакеты локальных процессов;
\item \texttt{FORWARD} --- транзитные пакеты (маршрутизация).
\end{itemize}
Пакет последовательно сравнивается с правилами цепочки. При совпадении выполняется действие: \texttt{ACCEPT} (пропустить), \texttt{DROP} (молча отбросить) или \texttt{REJECT} (отбросить с уведомлением). Если ни одно правило не сработало, применяется политика по умолчанию.
Модуль \texttt{conntrack} позволяет отслеживать состояния соединений. Правило с параметром \texttt{-{}-ctstate ESTABLISHED,RELATED} пропускает ответные пакеты уже установленных соединений без необходимости дублировать на цепочке \texttt{INPUT} отдельные правила по исходным портам для DNS, HTTP/HTTPS и для \texttt{echo-reply} на исходящий \texttt{ping}: при расположении этого правила \textbf{выше} узких правил ответный трафик срабатывает в нём, а счётчики у гипотетических строк \texttt{INPUT} с~\texttt{-{}-sport~53}/\texttt{80}/\texttt{443} оставались~бы нулевыми. На~\texttt{INPUT} явно заданы только новые входящие сценарии: SSH (для стенда), loopback и входящий \texttt{echo-request} с разрешённого адреса \texttt{192.168.100.2}.
\subsection{Реализация политики фильтрации}
Перед настройкой все существующие правила были сброшены:
\begin{verbatim}
sudo iptables -F && sudo iptables -X
sudo iptables -t nat -F && sudo iptables -t mangle -F
\end{verbatim}
Примеры команд добавления правил:
Разрешение loopback-трафика (\texttt{-i~lo} --- входящий интерфейс loopback):
\begin{verbatim}
sudo iptables -A INPUT -i lo -j ACCEPT
\end{verbatim}
Разрешение DNS-запросов (\texttt{-p~udp}, порт~53):
\begin{verbatim}
sudo iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
\end{verbatim}
Разрешение входящего ping только от конкретного адреса (\texttt{-{}-icmp-type echo-request} --- тип~8, параметры \texttt{-s} и \texttt{-d} --- адреса источника и назначения):
\begin{verbatim}
sudo iptables -A INPUT -p icmp --icmp-type echo-request \
-s 192.168.100.2 -d 192.168.100.1 -j ACCEPT
\end{verbatim}
Полный набор правил, реализующий заданную политику доступа:
{\footnotesize
\begin{verbatim}
# SSH (для сохранения удалённого доступа к стенду)
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --sport 22 -j ACCEPT
# Уже установленные и связанные соединения
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Loopback
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A OUTPUT -o lo -j ACCEPT
# DNS (UDP и TCP, порт 53; ответы на INPUT через ESTABLISHED,RELATED)
sudo iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
# HTTP и HTTPS (ответы на INPUT через ESTABLISHED,RELATED)
sudo iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT
# Исходящий ping (echo-reply на INPUT через ESTABLISHED,RELATED)
sudo iptables -A OUTPUT -p icmp --icmp-type echo-request -j ACCEPT
# Входящий ping только от 192.168.100.2
sudo iptables -A INPUT -p icmp --icmp-type echo-request \
-s 192.168.100.2 -d 192.168.100.1 -j ACCEPT
sudo iptables -A OUTPUT -p icmp --icmp-type echo-reply \
-s 192.168.100.1 -d 192.168.100.2 -j ACCEPT
# Политики по умолчанию
sudo iptables -P INPUT DROP
sudo iptables -P OUTPUT DROP
sudo iptables -P FORWARD DROP
\end{verbatim}
}
Правило для SSH не входит в задание, но необходимо для сохранения удалённого доступа; оно добавлено до включения политик \texttt{DROP}. Отдельные правила \texttt{INPUT} с~\texttt{-{}-sport} для ответов DNS/HTTP(S) и с~\texttt{echo-reply} для исходящего \texttt{ping} не используются: этот трафик обрабатывается правилом \texttt{ESTABLISHED,RELATED}.
Итоговое состояние таблицы \texttt{filter} (\texttt{iptables -L -n -v -{}-line-numbers}):
{\scriptsize
\begin{verbatim}
Chain INPUT (policy DROP 0 packets, 0 bytes)
num pkts bytes target prot in source destination
1 162 10124 ACCEPT tcp * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
2 312 142K ACCEPT all * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
3 12 780 ACCEPT all lo 0.0.0.0/0 0.0.0.0/0
4 8 672 ACCEPT icmp * 192.168.100.2 192.168.100.1 icmptype 8
Chain FORWARD (policy DROP 0 packets, 0 bytes)
Chain OUTPUT (policy DROP 0 packets, 0 bytes)
num pkts bytes target prot out source destination
1 119 21028 ACCEPT tcp * 0.0.0.0/0 0.0.0.0/0 tcp spt:22
2 289 33680 ACCEPT all * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
3 12 780 ACCEPT all * lo 0.0.0.0/0 0.0.0.0/0
4 28 1982 ACCEPT udp * 0.0.0.0/0 0.0.0.0/0 udp dpt:53
5 0 0 ACCEPT tcp * 0.0.0.0/0 0.0.0.0/0 tcp dpt:53
6 4 240 ACCEPT tcp * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
7 10 600 ACCEPT tcp * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
8 4 336 ACCEPT icmp * 0.0.0.0/0 0.0.0.0/0 icmptype 8
9 8 672 ACCEPT icmp * 192.168.100.1 192.168.100.2 icmptype 0
\end{verbatim}
}
\subsection{Проверка реализованной политики}
\textbf{DNS.} Команда \texttt{nslookup} на firewall-host успешно разрешает имена:
{\footnotesize
\begin{verbatim}
$ nslookup example.com
Server: 127.0.0.53
Address: 127.0.0.53#53
Non-authoritative answer:
Name: example.com
Address: 104.18.26.120
Name: example.com
Address: 104.18.27.120
\end{verbatim}
}
\textbf{HTTP/HTTPS.} Утилита \texttt{curl} успешно получает ответ от внешнего сервера как по HTTP, так и по HTTPS:
{\footnotesize
\begin{verbatim}
$ curl http://example.com
<!doctype html><html lang="en"><head><title>Example Domain
</title>...</html>
$ curl https://example.com
<!doctype html><html lang="en"><head><title>Example Domain
</title>...</html>
\end{verbatim}
}
\textbf{Исходящий ping.} Защищаемый хост пингует внешние адреса:
{\footnotesize
\begin{verbatim}
$ ping -c 4 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=255 time=12.0 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=255 time=11.9 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=255 time=11.3 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=255 time=11.4 ms
--- 8.8.8.8 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss
\end{verbatim}
}
\textbf{Входящий ping от разрешённого адреса.} С external-client (\texttt{192.168.100.2}) ping проходит:
{\footnotesize
\begin{verbatim}
arity@external-client:~$ ping -c 4 192.168.100.1
64 bytes from 192.168.100.1: icmp_seq=1 ttl=64 time=0.433 ms
64 bytes from 192.168.100.1: icmp_seq=2 ttl=64 time=0.369 ms
64 bytes from 192.168.100.1: icmp_seq=3 ttl=64 time=0.440 ms
64 bytes from 192.168.100.1: icmp_seq=4 ttl=64 time=0.398 ms
--- 192.168.100.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss
\end{verbatim}
}
\textbf{Блокировка запрещённого трафика.} На узле \texttt{external-client} запущен \texttt{python3 -m http.server 8080}; исходящий TCP на порт~8080 в политику не входит (разрешены только 53, 80, 443), поэтому с \texttt{firewall-host} соединение не устанавливается:
{\footnotesize
\begin{verbatim}
$ timeout 5 nc -vz 192.168.100.2 8080
nc: connect to 192.168.100.2 port 8080 (tcp) failed: Connection timed out
\end{verbatim}
}
\subsection{Контроль трафика средствами \texttt{tcpdump}}
Для подтверждения прохождения пакетов использовалась утилита \texttt{tcpdump}. Ниже приведён вывод при выполнении \texttt{ping} от external-client к firewall-host:
{\footnotesize
\begin{verbatim}
$ sudo tcpdump -i any -n icmp
18:26:20.960046 enp0s8 In IP
192.168.100.2 > 192.168.100.1:
ICMP echo request, id 2, seq 1, length 64
18:26:20.960119 enp0s8 Out IP
192.168.100.1 > 192.168.100.2:
ICMP echo reply, id 2, seq 1, length 64
18:26:21.990542 enp0s8 In IP
192.168.100.2 > 192.168.100.1:
ICMP echo request, id 2, seq 2, length 64
18:26:21.990587 enp0s8 Out IP
192.168.100.1 > 192.168.100.2:
ICMP echo reply, id 2, seq 2, length 64
...
8 packets captured
\end{verbatim}
}
На интерфейсе \texttt{enp0s8} видны входящие ICMP echo-request от \texttt{192.168.100.2} и исходящие echo-reply, что подтверждает работу правила входящего ping от разрешённого адреса.
Аналогичным образом были зафиксированы DNS-пакеты (\texttt{sudo tcpdump -i any -n port 53}): запрос от \texttt{127.0.0.1} к \texttt{127.0.0.53} через loopback, перенаправление к \texttt{10.0.2.3} через \texttt{enp0s3} и получение ответа. При выполнении \texttt{curl http://example.com} вывод \texttt{tcpdump} с фильтром \texttt{tcp port 80} зафиксировал трёхстороннее TCP-рукопожатие (SYN, SYN-ACK, ACK), HTTP-запрос \texttt{GET / HTTP/1.1} и ответ \texttt{HTTP/1.1 200 OK}, подтверждая корректную работу правила для порта~80.
\subsection{Выводы}
В ходе практической работы №4 на стенде из двух виртуальных машин VirtualBox настроена политика межсетевого экранирования средствами \texttt{iptables}. Реализована схема с политикой \texttt{DROP} по умолчанию и разрешающими правилами для loopback, DNS, исходящего ping, входящего ping от адреса \texttt{192.168.100.2} и HTTP/HTTPS; ответный трафик по этим сценариям на цепочке \texttt{INPUT} обрабатывается правилом \texttt{ESTABLISHED,RELATED}. Экспериментальная проверка подтвердила пропуск разрешённого трафика и блокировку соединений, не описанных в политике. Анализ трафика утилитой \texttt{tcpdump} продемонстрировал соответствие наблюдаемой сетевой активности заданным правилам фильтрации.
\newpage
\section{Разработка клиент-серверного приложения для конфиденциального обмена сообщениями}
\subsection{Актуальность темы}
Обеспечение конфиденциальности и целостности данных при передаче по сети является одной из ключевых задач информационной безопасности. Даже при использовании защищённых протоколов транспортного уровня понимание механизмов симметричного шифрования и контроля целостности на уровне приложения позволяет проектировать системы с дополнительным эшелоном защиты. Практическая реализация клиент-серверного приложения с шифрованием и хэш-контролем в рамках курса <<Защита информации>> закрепляет навыки работы с криптографическими примитивами и демонстрирует их влияние на передаваемый трафик.
\subsection{Цели и задачи работы}
Цель работы: разработка клиент-серверного приложения для обмена короткими сообщениями по протоколу TCP с обеспечением шифрования и контроля целостности.
Задачи работы:
\begin{enumerate}
\item Получить базовые знания по использованию криптографических функций библиотеки \texttt{cryptography} в Python.
\item Разработать клиент-серверное приложение для обмена шифрованными сообщениями с контролем целостности.
\item Проверить работу приложения в различных режимах, в том числе с помощью программы анализа трафика \texttt{tcpdump}.
\end{enumerate}
\subsection{Описание алгоритма шифрования}
\textbf{3DES (Triple DES)} — симметричный блочный шифр, основанный на трёхкратном применении алгоритма DES в режиме EDE (EncryptDecryptEncrypt) с тремя независимыми подключами. Размер блока — 64 бита (8 байт), длина ключа — 192 бита (24 байта, из которых 168 бит являются эффективными ключевыми битами, а оставшиеся 24 бита — биты чётности). Несмотря на то что 3DES считается устаревшим и уступает AES по производительности, он по-прежнему обеспечивает достаточный уровень стойкости для учебных задач.
В разработанном приложении используется режим сцепления блоков \textbf{CBC} (Cipher Block Chaining): каждый блок открытого текста перед шифрованием складывается по модулю 2 с предыдущим блоком шифротекста (для первого блока — с вектором инициализации IV). Это обеспечивает зависимость каждого блока шифротекста от всех предшествующих блоков, затрудняя анализ повторяющихся фрагментов. Дополнение до границы блока выполняется по схеме PKCS7.
\textbf{Шифрование с затравкой (солью).} Для каждого сообщения генерируется случайная 16-байтовая затравка (salt). Ключ 3DES длиной 24 байта вычисляется как первые 24 байта хэша SHA-256 от конкатенации мастер-ключа из файла и затравки: $K_{\mathrm{3DES}} = \mathrm{SHA\text{-}256}(K_{\mathrm{file}} \mathbin\| \mathrm{salt})[{:}24]$. Кроме того, для каждого сообщения генерируется случайный 8-байтовый IV. Затравка и IV передаются вместе с шифротекстом в открытом виде, что позволяет получателю воспроизвести ключ. Благодаря уникальности затравки и IV одинаковые открытые тексты порождают различные шифротексты, что исключает атаки на основе повторения.
\textbf{Контроль целостности} реализован с помощью хэш-функции MD5 для открытого текста сообщения (пункт~2.4 задания: алгоритм хэширования в соответствии с индивидуальным заданием, как в практической работе №2). При включённой опции \texttt{-{}-integrity} отправитель вычисляет MD5-хэш открытого текста и передаёт его вместе с данными. Получатель вычисляет MD5 самостоятельно и сравнивает с полученным значением; при расхождении выводится предупреждение о нарушении целостности. Выработка 24-байтового ключа 3DES из файлового ключа и соли, напротив, выполняется через SHA-256 и не входит в требование п.~2.4 о хэше сообщения.
\textbf{HMAC-SHA256.} Дополнительно реализован режим HMAC (Hash-based Message Authentication Code) на основе SHA-256: $\mathrm{HMAC} = \mathrm{HMAC\text{-}SHA256}(K_{\mathrm{file}},\; \mathrm{plaintext} \mathbin\| \mathrm{salt})$. В отличие от дайджеста в режиме \texttt{-{}-integrity}, HMAC использует секретный ключ и обеспечивает аутентификацию отправителя. Режимы \texttt{-{}-integrity} (MD5 дайджест сообщения) и \texttt{-{}-hmac} (HMAC-SHA256) взаимоисключающие.
\subsection{Описание реализации}
Приложение реализовано на языке Python~3.14 в виде единого файла \texttt{main.py} (исходный код приведён в приложении~9). Для криптографических операций используется библиотека \texttt{cryptography}. Управление зависимостями осуществляется менеджером \texttt{uv}.
\textbf{Протокол обмена.} Сообщения передаются по TCP в формате: 4~байта длины (big-endian) + JSON-объект. Структура JSON-объекта:
\begin{verbatim}
{
"encrypted": true/false,
"salt": "hex (16 байт)",
"iv": "hex (8 байт)",
"data": "hex шифротекст / открытый текст",
"hash": "MD5 hex (если включён --integrity)"
}
\end{verbatim}
\textbf{Полнодуплексный обмен} обеспечивается двумя потоками: один читает ввод пользователя и отправляет сообщения, второй принимает входящие сообщения и выводит их на экран.
\textbf{Режимы запуска} задаются ключами командной строки: \texttt{-{}-encrypt} включает шифрование 3DES-CBC, \texttt{-{}-integrity} включает контроль целостности на основе MD5, \texttt{-{}-hmac} включает HMAC-SHA256 (несовместим с \texttt{-{}-integrity}), \texttt{-{}-test-integrity} активирует режим отправки сообщений с заведомо некорректным хэшем или HMAC (требует \texttt{-{}-integrity} или \texttt{-{}-hmac}).
\textbf{Защита ключа.} Мастер-ключ считывается из файла (по умолчанию \texttt{key.txt}). При запуске программа проверяет права доступа к файлу ключа и завершает работу с ошибкой, если файл доступен для чтения группе или другим пользователям (права более открытые, чем \texttt{600}).
\subsection{Команды сборки и запуска}
Установка зависимостей:
\begin{verbatim}
uv sync
\end{verbatim}
Создание файла ключа:
\begin{verbatim}
openssl rand -hex 32 > key.txt
chmod 600 key.txt
\end{verbatim}
Запуск сервера и клиента (примеры):
\begin{verbatim}
uv run main.py server --port 9000
uv run main.py client 127.0.0.1 9000
uv run main.py server --port 9000 --encrypt
uv run main.py client 127.0.0.1 9000 --encrypt
uv run main.py server --port 9000 --encrypt --integrity
uv run main.py client 127.0.0.1 9000 --encrypt --integrity
\end{verbatim}
\subsection{Проверка работы приложения}
\subsubsection{Передача сообщений без шифрования}
При запуске без флагов сообщения передаются открытым текстом. На рисунке~\ref{fig:lab5-plain} показан вывод серверной и клиентской частей приложения.
\begin{figure}[H]
\centering
\includegraphics[width=0.8\linewidth]{img/lab5-plain.png}
\caption{Обмен сообщениями без шифрования}
\label{fig:lab5-plain}
\end{figure}
Анализ трафика утилитой \texttt{tcpdump} (рис.~\ref{fig:lab5-plain-tcpdump}) подтверждает, что текст сообщения передаётся в открытом виде и может быть прочитан непосредственно из дампа пакетов.
\begin{figure}[H]
\centering
\includegraphics[width=0.85\linewidth]{img/lab5-plain-tcpdump.png}
\caption{Дамп трафика (\texttt{tcpdump}) при передаче без шифрования — текст виден}
\label{fig:lab5-plain-tcpdump}
\end{figure}
\subsubsection{Передача сообщений с шифрованием}
При включённой опции \texttt{-{}-encrypt} сообщения шифруются алгоритмом 3DES-CBC с затравкой. На рисунке~\ref{fig:lab5-enc} показан вывод приложения: видны параметры шифрования (соль, производный ключ, IV, шифротекст) и результат расшифровки.
\begin{figure}[H]
\centering
\includegraphics[width=0.85\linewidth]{img/lab5-enc.png}
\caption{Обмен сообщениями с шифрованием 3DES-CBC}
\label{fig:lab5-enc}
\end{figure}
Дамп трафика (рис.~\ref{fig:lab5-enc-tcpdump}) показывает, что в пакетах присутствует только шифротекст в виде hex-строк; читаемый текст сообщения отсутствует.
\begin{figure}[H]
\centering
\includegraphics[width=0.85\linewidth]{img/lab5-enc-tcpdump.png}
\caption{Дамп трафика при передаче с шифрованием — текст не виден}
\label{fig:lab5-enc-tcpdump}
\end{figure}
\subsubsection{Шифрование с контролем целостности}
При одновременном использовании флагов \texttt{-{}-encrypt} и \texttt{-{}-integrity} к зашифрованному сообщению добавляется MD5-хэш открытого текста. На стороне получателя после расшифровки вычисляется собственный MD5 и сравнивается с полученным (рис.~\ref{fig:lab5-enc-int}).
\begin{figure}[H]
\centering
\includegraphics[width=0.85\linewidth]{img/lab5-enc-int.png}
\caption{Обмен сообщениями с шифрованием и контролем целостности}
\label{fig:lab5-enc-int}
\end{figure}
\subsubsection{Тестирование контроля целостности с некорректным хэшем}
В режиме \texttt{-{}-test-integrity} отправитель намеренно передаёт некорректный хэш. Получатель обнаруживает расхождение и выводит предупреждение (рис.~\ref{fig:lab5-test-int}).
\begin{figure}[H]
\centering
\includegraphics[width=0.85\linewidth]{img/lab5-test-int.png}
\caption{Обнаружение некорректного хэш-значения}
\label{fig:lab5-test-int}
\end{figure}
\subsubsection{Демонстрация шифрования с затравкой}
При отправке одного и того же сообщения дважды шифротексты различаются благодаря случайной затравке и IV (рис.~\ref{fig:lab5-salt}).
\begin{figure}[H]
\centering
\includegraphics[width=0.85\linewidth]{img/lab5-salt.png}
\caption{Разные шифротексты для одинаковых сообщений (эффект затравки)}
\label{fig:lab5-salt}
\end{figure}
\subsubsection{Контроль целостности и аутентификация с HMAC-SHA256}
При использовании флага \texttt{-{}-hmac} вместо \texttt{-{}-integrity} к каждому сообщению прикладывается код HMAC-SHA256, вычисленный с использованием секретного ключа и случайной затравки. На рисунке~\ref{fig:lab5-hmac} показан обмен сообщениями в режиме \texttt{-{}-encrypt -{}-hmac}: видны HMAC-затравка, вычисленный HMAC на стороне отправителя и результат проверки на стороне получателя. При отправке одного и того же сообщения повторно значение HMAC отличается благодаря новой затравке.
\begin{figure}[H]
\centering
\includegraphics[width=0.85\linewidth]{img/lab5-hmac.png}
\caption{Обмен сообщениями с HMAC-SHA256: аутентификация и контроль целостности}
\label{fig:lab5-hmac}
\end{figure}
На рисунке~\ref{fig:lab5-hmac-fail} показана проверка режима \texttt{-{}-test-integrity} совместно с \texttt{-{}-hmac}: отправитель передаёт намеренно некорректный HMAC, получатель обнаруживает несоответствие и сообщает о неудачной аутентификации.
\begin{figure}[H]
\centering
\includegraphics[width=0.85\linewidth]{img/lab5-hmac-fail.png}
\caption{Обнаружение некорректного HMAC — отправитель не аутентифицирован}
\label{fig:lab5-hmac-fail}
\end{figure}
\subsection{Выводы}
В ходе практической работы №5 разработано клиент-серверное приложение для конфиденциального обмена сообщениями по протоколу TCP. Реализовано шифрование 3DES-CBC с затравкой (ключ сессии из SHA-256), обеспечивающей различные шифротексты при одинаковых открытых текстах, контроль целостности сообщения на основе MD5 (как в индивидуальном задании и практической работе №2) и дополнительный режим HMAC-SHA256 для аутентификации отправителя. Экспериментальная проверка с помощью \texttt{tcpdump} подтвердила, что при включённом шифровании содержимое сообщений не передаётся в открытом виде, а при включённом контроле целостности получатель успешно обнаруживает намеренно повреждённые сообщения.
Теоретический анализ показал, что количество итераций, необходимых для полного перебора паролей, экспоненциально возрастает с увеличением их длины. Экспериментальное исследование позволило оценить реальную скорость перебора и подтвердить теоретические оценки. Полученные результаты демонстрируют, что использование паролей длиной 6 символов и более существенно затрудняет атаку методом грубой силы, а пароли длиной 8 и более символов при применении SHA-256 практически не поддаются взлому за приемлемое время.
\newpage \newpage
\section*{Заключение} \section*{Заключение}
@@ -554,10 +1130,63 @@ N(n) = 52 \cdot 72^{n-1}
В ходе выполнения практической работы №1 был проведён анализ уязвимостей программного обеспечения с использованием Банка данных угроз ФСТЭК России. Были изучены структура разделов <<Угрозы>> и <<Уязвимости>>, определены версии операционных систем личного компьютера и смартфона, а также выполнен поиск уязвимостей с уровнем опасности <<Критический>> и <<Высокий>>. Проведён анализ наиболее актуальных уязвимостей, рассмотрены их характеристики и векторы CVSS, а также рекомендации по устранению. В процессе выполнения работы были получены практические навыки поиска, анализа и оценки уязвимостей информационных систем. В ходе выполнения практической работы №1 был проведён анализ уязвимостей программного обеспечения с использованием Банка данных угроз ФСТЭК России. Были изучены структура разделов <<Угрозы>> и <<Уязвимости>>, определены версии операционных систем личного компьютера и смартфона, а также выполнен поиск уязвимостей с уровнем опасности <<Критический>> и <<Высокий>>. Проведён анализ наиболее актуальных уязвимостей, рассмотрены их характеристики и векторы CVSS, а также рекомендации по устранению. В процессе выполнения работы были получены практические навыки поиска, анализа и оценки уязвимостей информационных систем.
В ходе выполнения практической работы №2 была разработана система доступа пользователей к конфиденциальным данным. Реализованы утилита управления пользователями с хэшированием паролей по алгоритму SHA-256, утилита доступа с разграничением прав и журналированием операций, а также программа взлома паролей методом грубой силы. Проведено исследование стойкости паролей в зависимости от их длины, результаты которого подтвердили экспоненциальную зависимость числа итераций от длины пароля и продемонстрировали практическую устойчивость достаточно длинных паролей к атаке полного перебора. В ходе выполнения практической работы №2 была разработана система доступа пользователей к конфиденциальным данным. Реализованы утилита управления пользователями с хэшированием паролей по алгоритму MD5 (индивидуальный вариант задания), утилита доступа с разграничением прав и журналированием операций, а также программа взлома паролей методом грубой силы. Проведено исследование стойкости паролей в зависимости от их длины, результаты которого подтвердили экспоненциальную зависимость числа итераций от длины пароля и продемонстрировали практическую устойчивость достаточно длинных паролей к атаке полного перебора.
В ходе выполнения практической работы №3 на базе системы из работы №2 реализованы модели дискреционного (DAC) и мандатного (MAC) управления доступом. DAC реализован через список доступа с владельцами объектов и командой выдачи прав; MAC — на основе модели Белла–Лападулы с тремя уровнями меток конфиденциальности. Реализация демонстрирует принципиальные различия между подходами и их практическое применение.
В ходе выполнения практической работы №4 на стенде из двух виртуальных машин VirtualBox настроена политика межсетевого экранирования средствами \texttt{iptables}. Реализованы правила фильтрации, разрешающие loopback-трафик, DNS, ICMP и HTTP/HTTPS, а все прочие соединения блокируются. Корректность работы правил подтверждена тестовыми соединениями и анализом пакетов утилитой \texttt{tcpdump}.
В ходе выполнения практической работы №5 разработано клиент-серверное приложение для конфиденциального обмена сообщениями по протоколу TCP. Реализовано шифрование 3DES-CBC с затравкой (производный ключ через SHA-256), контроль целостности сообщения MD5 и режим HMAC-SHA256 для аутентификации отправителя. Анализ трафика утилитой \texttt{tcpdump} подтвердил, что при включённом шифровании содержимое сообщений не доступно в открытом виде, а режим тестирования продемонстрировал корректное обнаружение нарушений целостности.
\newpage \newpage
\printbibliography[heading=bibintoc] \printbibliography[heading=bibintoc]
\newpage
\section*{Приложение 1. Исходный код файла usermgr.py из практической работы №2}
\addcontentsline{toc}{section}{Приложение 1. Исходный код файла usermgr.py из практической работы №2}
\label{app:usermgr}
\lstinputlisting{../lab2/usermgr.py}
\newpage
\section*{Приложение 2. Исходный код файла access.py из практической работы №2}
\addcontentsline{toc}{section}{Приложение 2. Исходный код файла access.py из практической работы №2}
\lstinputlisting{../lab2/access.py}
\newpage
\section*{Приложение 3. Исходный код файла bruteforce.py из практической работы №2}
\addcontentsline{toc}{section}{Приложение 3. Исходный код файла bruteforce.py из практической работы №2}
\label{app:bruteforce}
\lstinputlisting{../lab2/bruteforce.py}
\newpage
\section*{Приложение 4. Исходный код файла setup.sh из практической работы №2}
\addcontentsline{toc}{section}{Приложение 4. Исходный код файла setup.sh из практической работы №2}
\lstinputlisting{../lab2/setup.sh}
\newpage
\section*{Приложение 5. Исходный код файла usermgr.py из практической работы №3}
\addcontentsline{toc}{section}{Приложение 5. Исходный код файла usermgr.py из практической работы №3}
\lstinputlisting{../lab3/usermgr.py}
\newpage
\section*{Приложение 6. Исходный код файла confaccess.py из практической работы №3}
\addcontentsline{toc}{section}{Приложение 6. Исходный код файла confaccess.py из практической работы №3}
\lstinputlisting{../lab3/confaccess.py}
\newpage
\section*{Приложение 7. Исходный код файла config.py из практической работы №3}
\addcontentsline{toc}{section}{Приложение 7. Исходный код файла config.py из практической работы №3}
\lstinputlisting{../lab3/config.py}
\newpage
\section*{Приложение 8. Исходный код файла setup.sh из практической работы №3}
\addcontentsline{toc}{section}{Приложение 8. Исходный код файла setup.sh из практической работы №3}
\lstinputlisting{../lab3/setup.sh}
\newpage
\section*{Приложение 9. Исходный код файла main.py из практической работы №5}
\addcontentsline{toc}{section}{Приложение 9. Исходный код файла main.py из практической работы №5}
\label{app:lab5-main}
\lstinputlisting{../lab5/main.py}
\end{document} \end{document}