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