سیاست امنیتی را با شناخت منابع شروع کنید

یک سایت ممکن است اسکریپت، فونت، تصویر، فرم و اتصال API را از چند منبع بارگیری کند. اگر بدون شناخت این مسیرها یک سیاست سخت‌گیرانه فعال کنیم، احتمال دارد ورود کاربر یا فرم تماس از کار بیفتد. CSP به مرورگر می‌گوید چه منابع و رفتارهایی مجازند؛ طراحی مفید آن از موجودی واقعی سایت شروع می‌شود. این کنترل مکمل کدنویسی امن است و جای escape خروجی یا اعتبارسنجی سمت سرور را نمی‌گیرد.

مسیرهای حساس را فهرست کنید: ورود، پرداخت، آپلود، فرم و محتوای تعبیه‌شده. برای هر منبع بنویسید چرا لازم است و آیا می‌توان آن را محلی سرو کرد. تصاویر محلی علاوه بر کاهش وابستگی شبکه، سیاست img-src را ساده‌تر می‌کنند. اضافه‌کردن دامنه‌های فراوان فقط برای ساکت‌کردن کنسول می‌تواند سیاست را بی‌اثر کند؛ هر مجوز باید دلیل مشخص داشته باشد.

مشاهده، اصلاح و سپس اعمال محدودیت

در شروع از هدر Content-Security-Policy-Report-Only استفاده کنید تا تخلف‌ها دیده شوند، بدون اینکه همان سیاست منبع را مسدود کند. مثال زیر سیاست هم‌مبدأ را برای سایت ساده بررسی می‌کند و endpoint جمع‌آوری گزارش تنظیم نمی‌کند؛ در این مرحله کنسول مرورگر را می‌بینیم. برای جمع‌آوری مرکزی، مسیر گزارش و سیاست حداقل‌سازی داده را جدا پیاده کنید. هدرهای تکراری اعمال‌شده از پراکسی و اپلیکیشن را نیز بررسی کنید.

اگر سایت اسکریپت inline دارد، حذف آن‌ها به فایل مستقل یا استفاده درست از nonce و hash را بررسی کنید. nonce باید برای هر پاسخ غیرقابل پیش‌بینی و با تگ همان پاسخ هماهنگ باشد؛ یک مقدار ثابت در تنظیم سرور این خاصیت را ندارد. self نیز به‌تنهایی همیشه یک سیاست اسکریپت قوی نیست، به‌ویژه اگر همان مبدأ آپلود یا پاسخ قابل کنترل مهاجم سرو کند. نمونه آموزشی برای مشاهده اولیه است، نه نسخه نهایی همه سایت‌ها.

گزارش تخلف همیشه اثبات حمله نیست. افزونه مرورگر، ابزار تحلیل و مسیرهای قدیمی می‌توانند رخداد تولید کنند. آن‌ها را با مسیر صفحه، نوع منبع و نسخه انتشار دسته‌بندی کنید، سپس نقص واقعی را اصلاح کنید. در آزمایش، فرم‌ها و درخواست‌های API را هم بررسی کنید؛ دیدن صفحه اصلی سالم به معنی کارکرد کل محصول نیست. صفحه خطا و پاسخ کش‌شده نیز باید هدر مورد انتظار را داشته باشند.

نمونه کد و روش بررسی

پیش‌نیاز و خروجی مورد انتظار در توضیح مثال آمده است. ابتدا نمونه را در محیط آزمایشی اجرا کنید و سپس آن را با داده و محدودیت‌های پروژه خود تطبیق دهید.

1. Nginx؛ نمونه سیاست در حالت Report-Only

# Place inside the intended test server {} block.
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'" always;

این تنظیم را ابتدا در کانفیگ آزمایشی بررسی کنید؛ nginx -t صحت نحو را می‌سنجد. در Network وجود هدر و در Console تخلف‌ها را ببینید. Report-Only اجرای منابع را مسدود نمی‌کند. سایت دارای inline style، سرویس بیرونی یا iframe ممکن است نیاز به اصلاح معماری یا مجوز دقیق داشته باشد.

2. ساخت nonce تازه برای هر پاسخ

برای دیدن رابطه هدر و تگ، این فایل را با python nonce_server.py اجرا کنید. nonce با secrets ساخته می‌شود و همان مقدار در هدر و script همان پاسخ استفاده می‌شود. این HTML وابستگی بیرونی ندارد تا بررسی سیاست روشن باشد.

# nonce_server.py; localhost demonstration.
from http.server import BaseHTTPRequestHandler, HTTPServer
from secrets import token_urlsafe

ENFORCE = False

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        nonce = token_urlsafe(24)
        policy = (
            "default-src 'none'; "
            f"script-src 'nonce-{nonce}'; "
            "object-src 'none'; base-uri 'none'; "
            "form-action 'self'; frame-ancestors 'none'"
        )
        body = (
            '<!doctype html><meta charset="utf-8"><title>Nonce demo</title>'
            '<h1>CSP nonce exercise</h1><output>Waiting</output>'
            f'<script nonce="{nonce}">'
            'document.querySelector("output").textContent="Script executed";'
            '</script>'
        ).encode()
        self.send_response(200)
        header = "Content-Security-Policy" if ENFORCE else "Content-Security-Policy-Report-Only"
        self.send_header(header, policy)
        self.send_header("Cache-Control", "no-store")
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

if __name__ == "__main__":
    HTTPServer(("127.0.0.1", 8767), Handler).serve_forever()

در http://127.0.0.1:8767 باید Script executed دیده شود. با reload مقدار nonce عوض می‌شود. no-store در این تمرین از بازاستفاده پاسخ دارای nonce جلوگیری می‌کند؛ اگر در محصول HTML را کش می‌کنید، هماهنگی بدنه و هدر را در همان لایه طراحی کنید.

3. آزمون تطابق nonce و تغییر آن میان پاسخ‌ها

هدر درست کافی نیست؛ مقدار script نیز باید با آن یکسان باشد. این آزمون دو پاسخ مستقل می‌گیرد و هم تطابق درون هر پاسخ و هم تفاوت میان پاسخ‌ها را بررسی می‌کند.

# Run while nonce_server.py is listening.
import re
import urllib.request

def snapshot():
    with urllib.request.urlopen("http://127.0.0.1:8767", timeout=2) as response:
        policy = response.headers.get("Content-Security-Policy-Report-Only")
        if policy is None:
            policy = response.headers["Content-Security-Policy"]
        page = response.read().decode()
    header_nonce = re.search(r"'nonce-([^']+)'", policy).group(1)
    tag_nonce = re.search(r'<script nonce="([^"]+)"', page).group(1)
    assert header_nonce == tag_nonce
    return header_nonce

first, second = snapshot(), snapshot()
assert first != second
print("Matching, fresh nonces verified")

انتظار Matching, fresh nonces verified داریم. این آزمون بررسی متنی است و جای اجرای مرورگر را نمی‌گیرد. یک تست مرورگر نیز باید اجرای اسکریپت مجاز و جلوگیری از اسکریپت فاقد nonce را در حالت اجرایی بررسی کند.

4. مشاهده رخداد تخلف در محیط آزمایش

listener را در فایل یا script مجاز قرار دهید؛ خود آن نباید به دلیل سیاست از اجرا بازبماند. در نسخه آزمایشی سرور، ابتدا Report-Only و سپس ENFORCE=True را بررسی کنید. تفاوت disposition نشان می‌دهد تخلف فقط گزارش شده یا سیاست اجرایی بوده است.

// Put this in an already permitted script before the test interaction.
window.addEventListener("securitypolicyviolation", event => {
  console.table({
    directive: event.effectiveDirective,
    resource: event.blockedURI,
    disposition: event.disposition,
  });
});

// In nonce_server.py, switch ENFORCE to True only in the test environment.
// Reload and verify the response uses Content-Security-Policy.
// An inline script without the matching nonce must then be blocked.

در محصول، قبل از اعمال سیاست روی همه کاربران، ورود، فرم، آپلود، صفحات خطا و سرویس‌های بیرونی را بررسی کنید. blockedURI می‌تواند اطلاعات مسیر داشته باشد؛ این نمونه فقط کنسول محلی را پر می‌کند و داده‌ای به سرویس گزارش‌گیری نمی‌فرستد. نسخه سیاست و مسیر بازگشت را کنار گزارش آزمایش نگه دارید.

معیار پذیرش قبل از انتشار

وقتی فهرست تخلف‌های مشروع حل شد و مسیرهای پذیرش گذشت، درباره تبدیل سیاست به هدر اجرایی تصمیم بگیرید و راه بازگشت داشته باشید. ابتدا روی محیط آزمایش و دامنه محدود اعمال کنید. گزارش نهایی باید سیاست، مسیرهای آزموده‌شده و استثناهای باقی‌مانده را ثبت کند. ارزش CSP در محدودیت دقیق و قابل نگهداری است؛ طول زیاد هدر یا خالی‌بودن تصادفی کنسول به‌تنهایی نشانه موفقیت نیست.

چک‌لیست اجرای عملی

  • موجودی اسکریپت، تصویر، فرم و API تهیه شود.
  • ابتدا Report-Only در محیط آزمایش بررسی شود.
  • nonce ثابت استفاده نشود.
  • مسیرهای حساس پیش از اعمال اجرایی تست شوند.

منبع

تدوین و راهنمای عملی: دانشنامه لیان.