Безопасность — это не фича, которую можно добавить позже. Это свойство архитектуры, которое закладывается с первого дня. Стоимость исправления уязвимости растёт экспоненциально на каждом этапе: проектирование → разработка → тестирование → production → после взлома.
CIA-триада
Три фундаментальных свойства информационной безопасности:
Свойство
Описание
Пример нарушения
Confidentiality
Данные доступны только авторизованным
Утечка базы пользователей
Integrity
Данные не изменены несанкционированно
Подмена суммы платежа
Availability
Система доступна когда нужна
DDoS-атака
Дополнительные свойства:
Свойство
Описание
Authentication
Подтверждение идентичности
Authorization
Проверка прав доступа
Non-repudiation
Невозможность отказаться от совершённого действия
Accountability
Отслеживание действий до конкретного субъекта
Threat Modeling
Threat modeling — систематический процесс выявления угроз безопасности на этапе проектирования.
Методология STRIDE
Угроза
Описание
Нарушает
Пример
Spoofing
Подмена идентичности
Authentication
Поддельный JWT токен
Tampering
Изменение данных
Integrity
Модификация запроса в transit
Repudiation
Отрицание действий
Non-repudiation
Нет audit log
Information Disclosure
Утечка информации
Confidentiality
SQL injection
Denial of Service
Отказ в обслуживании
Availability
DDoS
Elevation of Privilege
Повышение привилегий
Authorization
User → Admin
Процесс Threat Modeling
1. Определить assets → Что защищаем (данные, сервисы)
2. Определить trust boundaries → Границы доверия
3. Построить DFD → Data Flow Diagram
4. Применить STRIDE → Для каждого компонента
5. Оценить риски → Likelihood × Impact
6. Определить mitigations → Контрмеры
7. Валидировать → Проверка полноты
Оценка рисков
Impact: Low
Impact: Medium
Impact: High
Likelihood: High
Medium
High
Critical
Likelihood: Medium
Low
Medium
High
Likelihood: Low
Info
Low
Medium
Defense in Depth
Defense in depth (эшелонированная защита) — стратегия, при которой несколько уровней безопасности защищают систему. Если один уровень пробит — следующий остановит атакующего.
Уровни защиты
[Сеть] → Firewall, WAF, DDoS-защита
[Периметр] → API Gateway, rate limiting, IP filtering
[Транспорт] → TLS, mTLS, certificate pinning
[Приложение] → Validation, sanitization, auth
[Данные] → Encryption at rest, access control
[Audit] → Logging, monitoring, alerting
Уровень
Меры защиты
Сетевой
Firewall, VPN, network segmentation
Периметр
WAF, API Gateway, rate limiting
Транспортный
TLS 1.3, mTLS, HSTS
Приложение
Input validation, CSRF protection, CSP
Данные
Encryption at rest, column-level encryption
Идентичность
MFA, OAuth 2.0, session management
Мониторинг
Audit logs, SIEM, anomaly detection
Принцип наименьших привилегий
Каждый компонент системы должен иметь только те привилегии, которые необходимы для выполнения его функции.
Контекст
Правильно
Неправильно
БД-пользователь
Отдельный user с SELECT/INSERT
root для приложения
API-ключ
Scope: только нужные endpoints
Полный доступ
Файловая система
Только нужные директории
chmod 777
Контейнер
Non-root user
root внутри контейнера
IAM роли
Конкретные permissions
AdministratorAccess
Security Mindset
Принципы безопасного проектирования
Принцип
Описание
Secure by Default
Безопасная конфигурация из коробки
Fail Securely
При ошибке — закрыть доступ, не открыть
Don't Trust Input
Любой ввод может быть вредоносным
Minimize Attack Surface
Меньше endpoints = меньше рисков
Separation of Duties
Разделение полномочий
Keep It Simple
Простой код легче проверять
Базовые практики безопасности
<?php
declare(strict_types=1);
namespace App\Security;
/**
* Security utilities for common operations.
*/
final class SecurityUtils
{
/**
* Constant-time string comparison to prevent timing attacks.
*/
public static function secureCompare(string $expected, string $actual): bool
{
return hash_equals($expected, $actual);
}
/**
* Generate a cryptographically secure random token.
*/
public static function generateToken(int $length = 32): string
{
return bin2hex(random_bytes($length));
}
/**
* Hash a password using Argon2id (preferred over bcrypt for new projects).
*/
public static function hashPassword(string $password): string
{
return password_hash($password, PASSWORD_ARGON2ID, [
'memory_cost' => 65536, // 64 MB
'time_cost' => 4, // 4 iterations
'threads' => 3, // 3 parallel threads
]);
}
/**
* Verify password against hash.
*/
public static function verifyPassword(string $password, string $hash): bool
{
return password_verify($password, $hash);
}
/**
* Check if password hash needs rehashing (algorithm upgrade).
*/
public static function needsRehash(string $hash): bool
{
return password_needs_rehash($hash, PASSWORD_ARGON2ID, [
'memory_cost' => 65536,
'time_cost' => 4,
'threads' => 3,
]);
}
/**
* Sanitize a string for safe output in HTML context.
*/
public static function escapeHtml(string $input): string
{
return htmlspecialchars($input, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
}
package security
import (
"crypto/rand"
"crypto/subtle"
"encoding/hex"
"fmt"
"html/template"
"strings"
"golang.org/x/crypto/argon2"
)
// Argon2id parameters: 64 MB memory, 4 iterations, 3 lanes.
const (
argonSaltSize = 16
argonKeyLength = 32
argonMemoryKB = 64 * 1024
argonIterations = 4
argonParallelism = 3
)
// SecureCompare performs constant-time string comparison to prevent timing attacks.
func SecureCompare(expected, actual string) bool {
return subtle.ConstantTimeCompare([]byte(expected), []byte(actual)) == 1
}
// GenerateToken creates a cryptographically secure random token.
func GenerateToken(length int) (string, error) {
b := make([]byte, length)
if _, err := rand.Read(b); err != nil {
return "", fmt.Errorf("generate token: %w", err)
}
return hex.EncodeToString(b), nil
}
// HashPassword hashes a password using Argon2id.
func HashPassword(password string) (string, error) {
salt := make([]byte, argonSaltSize)
if _, err := rand.Read(salt); err != nil {
return "", fmt.Errorf("generate salt: %w", err)
}
hash := deriveKey(password, salt)
// Encode salt + hash for storage
return hex.EncodeToString(salt) + ":" + hex.EncodeToString(hash), nil
}
// VerifyPassword checks a password against a stored "salt:hash" value.
// The digests are compared in constant time: a plain == on the hex strings
// leaks, through its timing, how many leading bytes an attacker guessed right.
func VerifyPassword(password, stored string) bool {
saltHex, hashHex, ok := strings.Cut(stored, ":")
if !ok {
return false
}
salt, err := hex.DecodeString(saltHex)
if err != nil {
return false
}
expected, err := hex.DecodeString(hashHex)
if err != nil {
return false
}
return subtle.ConstantTimeCompare(deriveKey(password, salt), expected) == 1
}
func deriveKey(password string, salt []byte) []byte {
return argon2.IDKey(
[]byte(password),
salt,
argonIterations,
argonMemoryKB,
argonParallelism,
argonKeyLength,
)
}
// EscapeHTML sanitizes a string for safe output in HTML context.
// In Go templates, auto-escaping is built-in via html/template.
func EscapeHTML(input string) string {
// Go's html/template auto-escapes; for manual use:
return template.HTMLEscapeString(input)
}
using System.Buffers.Text;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using Konscious.Security.Cryptography;
namespace App.Security;
/// Security utilities for common operations.
public static class SecurityUtils
{
private const int SaltSize = 16;
private const int HashSize = 32;
private const int MemoryKb = 65536; // 64 MB
private const int Iterations = 4;
private const int Parallelism = 3;
// Constant-time comparison to prevent timing attacks.
public static bool SecureCompare(string expected, string actual)
=> CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(expected),
Encoding.UTF8.GetBytes(actual));
// Cryptographically secure random token.
public static string GenerateToken(int length = 32)
=> Convert.ToHexString(RandomNumberGenerator.GetBytes(length)).ToLowerInvariant();
// Argon2id password hash. ASP.NET Core Identity ships PBKDF2 by default;
// Konscious.Security.Cryptography provides Argon2id for new projects.
public static string HashPassword(string password)
{
byte[] salt = RandomNumberGenerator.GetBytes(SaltSize);
byte[] hash = DeriveKey(password, salt);
return $"{Convert.ToHexString(salt)}:{Convert.ToHexString(hash)}".ToLowerInvariant();
}
public static bool VerifyPassword(string password, string stored)
{
string[] parts = stored.Split(':');
if (parts.Length != 2)
{
return false;
}
byte[] salt = Convert.FromHexString(parts[0]);
byte[] expected = Convert.FromHexString(parts[1]);
return CryptographicOperations.FixedTimeEquals(DeriveKey(password, salt), expected);
}
// Escape a string for safe output in an HTML context.
// Razor auto-escapes by default; this is for manual string building.
public static string EscapeHtml(string input)
=> HtmlEncoder.Default.Encode(input);
private static byte[] DeriveKey(string password, byte[] salt)
{
using var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password))
{
Salt = salt,
MemorySize = MemoryKb,
Iterations = Iterations,
DegreeOfParallelism = Parallelism,
};
return argon2.GetBytes(HashSize);
}
}
import hmac
import secrets
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError, VerificationError
from markupsafe import escape
# Argon2id parameters: 64 MB memory, 4 iterations, 3 lanes.
_hasher = PasswordHasher(
memory_cost=65536,
time_cost=4,
parallelism=3,
hash_len=32,
salt_len=16,
)
def secure_compare(expected: str, actual: str) -> bool:
"""Constant-time string comparison to prevent timing attacks."""
return hmac.compare_digest(expected, actual)
def generate_token(length: int = 32) -> str:
"""Cryptographically secure random token."""
return secrets.token_hex(length)
def hash_password(password: str) -> str:
"""Hash a password using Argon2id. Salt is embedded in the returned string."""
return _hasher.hash(password)
def verify_password(password: str, stored_hash: str) -> bool:
"""Verify password against hash without leaking failure reason."""
try:
return _hasher.verify(stored_hash, password)
except (VerifyMismatchError, VerificationError):
return False
def needs_rehash(stored_hash: str) -> bool:
"""Check if the hash was produced with outdated parameters."""
return _hasher.check_needs_rehash(stored_hash)
def escape_html(value: str) -> str:
"""Escape a string for safe output in an HTML context.
Jinja2 auto-escapes by default; this is for manual string building.
"""
return str(escape(value))