۲۱. امنیت کاربردی؛ دادهٔ کاربر، مجوز و مرز اعتماد
در سطح متوسط باید بتوانید توضیح بدهید داده از کجا وارد برنامه میشود و چه اختیاری پیدا میکند. عنوان هزینه، پیام تیکت، فایل JSON و دادهٔ localStorage همگی ورودیاند. اینکه یکی از آنها قبلاً در مرورگر ذخیره شده، معتبر بودنش را تضمین نمیکند. این درس روی چند مرز مشخص سه پروژه کار میکند؛ هدف، ادعای امنبودن کامل یک محصول یا تبدیل سرور آموزشی به سرویس عمومی نیست.
متن را به متن تبدیل کنید
برای نمایش عنوان هزینه و پیام تیکت، متن کاربر را وارد HTML قالبی نکنید. در کد همراه، helper با نام element از document.createElement و textContent استفاده میکند و ویژگیهای قابل تنظیم را محدود کرده است. مثال مستقل زیر را در کنسول صفحهٔ محلی اجرا کنید؛ فقط یک پاراگراف موقت میسازد و سپس میتوانید آن را حذف کنید.
const supplied = '<b>عنوانِ واردشده توسط کاربر</b>';
const paragraph = document.createElement('p');
paragraph.textContent = supplied;
document.body.append(paragraph);
console.log(paragraph.querySelector('b')); // null
paragraph.remove();انتظار داریم علامتهای <b> به صورت متن دیده شوند، نه یک عنصر bold واقعی. این راه برای محل نمایش متن مناسب است. برای ساخت URL، CSS یا ویژگیهایی با معنای اجرایی، صرف textContent راه حل عمومی نیست؛ هر محل قرارداد خودش را دارد. helper پروژه نیز مجوز افزودن onclick از ورودی نمیدهد. ایجاد کنترل با کد خود برنامه و تنظیم متن آن با دادهٔ کاربر، دو مرحلهٔ قابل بازبینی است.
اگر روزی ویرایشگر متن غنی اضافه کردید، جایگزینکردن همهٔ textContentها با innerHTML تصمیم درستی نیست. باید قالب مجاز، پاکسازی معتبر و سیاست لینک را مستقل طراحی کنید. در سه پروژهٔ این دوره چنین ویرایشگری نداریم؛ سادهنگهداشتن نمایش متن، سطح پیچیدگی را کاهش داده است. به دانشجو وعدهٔ اجرای HTML دلخواه داخل پیام نمیدهیم.
مالکیت را از بدنه نگیرید
در مرکز تیکت، سرور مالک را از نشست خودش تعیین میکند. در مرورگر صفحهٔ محلی، پس از انتخاب آلیس، نمونهٔ زیر باید 400 بدهد؛ فیلد owner طبق قرارداد ساخت مجاز نیست. این بررسی را فقط روی آزمایشگاه خودتان انجام دهید.
const response = await fetch('/api/tickets', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: 'تمرین مرز مالکیت تیکت',
message: 'این پیام آموزشی برای بررسی رد فیلد اضافه نوشته شده است.',
category: 'course',
owner: 'bob'
})
});
console.log(response.status); // 400
console.log((await response.json()).error); // فیلد اضافی مجاز نیست.حتی اگر UUID یک تیکت را بدانید، canRead باید برای همان درخواست بررسی شود. دادهٔ localStorage، نقش نوشتهشده در صفحه و مخفیبودن دکمه، مرجع مجوز نیستند. در مثال فروشگاه نیز تغییر مبلغ در DevTools نباید معیار پرداخت واقعی باشد. نسخهٔ فروشگاه فقط مجموع آموزشی را نمایش میدهد و هیچ پرداختی انجام نمیدهد؛ هنگام توسعهٔ محصول واقعی مرجع قیمت، موجودی و سفارش باید سرور باشد.
چند لایهٔ محدود در آزمایشگاه
server.mjs برای POST، Origin دقیق همان آزمایشگاه را بررسی میکند و نوع بدنه را application/json میخواهد. نشست آزمایشی کوکی HttpOnly و SameSite=Strict دارد. فایلهای صفحه نیز Content-Security-Policy محدود به مبدأ خودشان دریافت میکنند. این موارد مرزهای مشخصی را تقویت میکنند، ولی یک گزینه بهتنهایی تمام تهدیدها را حل نمیکند. بهویژه SameSite جای طراحی کامل دفاع CSRF در هر محصول را نمیگیرد.
انتخاب آزاد هویت staff در مسیر demo-session عمداً برای تمرین مالکیت وجود دارد؛ این مسیر نباید در سرویس عمومی باشد. کوکی آزمایشگاه محلی HTTP ویژگی Secure ندارد و همین کد را نباید بدون طراحی مجدد پشت دامنهٔ واقعی گذاشت. پایگاه دادهٔ تولیدی، مدیریت رمز عبور، بازیابی حساب، محدودسازی سوءاستفاده و ثبت رخداد عملیاتی خارج از دامنهٔ این پروژهاند.
ورودی خراب را زود و روشن رد کنید
پشتیبان هزینه باید نسخهٔ شناختهشده، آرایهٔ معتبر و شناسههای یکتا داشته باشد. JSON.parse فقط نحو را بررسی میکند؛ به معنی معتبر بودن مبلغ یا مجوز داده نیست. در API نیز محدودیت اندازهٔ بدنه و allowlist فیلدها وجود دارد. این بررسیها را در لایهٔ نزدیک به ورودی انجام بدهید و بعد با مدل پاکشده کار کنید. نمایش خطای قابل فهم، بهتر از اصلاح خاموش دادهای است که معنایش را نمیدانیم.
تمرین، پاسخ راهنما و معیار تحویل
سه ورودی آزمایشی بسازید: عنوان با نشانههای HTML، فایل پشتیبان با شناسهٔ تکراری و درخواست ساخت تیکت با owner اضافی. پاسخ راهنما: اولی باید متن بماند، دومی بدون جایگزینی دادهٔ قبلی رد شود و سومی 400 بگیرد. سپس نقش هر کنترل را با یک جمله توضیح دهید: کنترل نمایش، اعتبار داده یا مجوز. اینها را یک واژهٔ کلی «امنیت» ننامید.
تحویل قابل قبول باید ثابت کند رد ورودی، دادهٔ قبلی را خراب نکرده است. متن خطا هم نباید پیام کاربر یا اطلاعات حساس را بیدلیل در URL قرار دهد. هیچیک از تمرینها مجوز آزمودن سایتهای دیگر نیست؛ همه روی پروژهٔ محلی و دادهٔ ساختگی اجرا میشوند. نتیجهٔ نهایی، شناخت مرز اعتماد و توان توضیح محدودیتهای همین طراحی است.
منابع رسمی برای مطالعهٔ بیشتر
https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent
https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
تألیف و مثالهای اختصاصی: تحریریه درسآوند.