Express web xavfsizligi

Ilovangizda endi foydalanuvchi hisobi, parol va fayl bor. Shu bilan birga xavf ham paydo bo’ldi: oldin eng yomoni sinov ma’lumotining yo’qolishi edi, endi esa birovning hisobiga kirib olish mumkin.

Bu darsda beshta amaliy himoya qatlamini qo’shamiz. Nazariy tomoni amaliy web xavfsizligi darsida yoritilgan — bu yerda kod yozamiz.

XSS: foydalanuvchi matni kod bo’lib qolganda

Hujum yo’li oddiy: vazifa sarlavhasiga matn o’rniga teg yoziladi.

const ejs = require("ejs");

const title = "<script>alert(1)</script>";

console.log("Ekranlangan:", ejs.render("<li><%= title %></li>", { title }));
console.log("Xom:", ejs.render("<li><%- title %></li>", { title }));
node ejs-escape.js
Ekranlangan: <li>&lt;script&gt;alert(1)&lt;/script&gt;</li>
Xom: <li><script>alert(1)</script></li>

Farq bitta belgida, oqibat esa katta.

Yozuv Nima qiladi
<%= %> Maxsus belgilarni ekranlaydi — matn matn bo’lib qoladi
<%- %> HTML ni xom holda qo’yadi — teg bajariladi

Birinchi holatda &lt;script&gt; — brauzer buni matn deb ko’rsatadi, foydalanuvchi ekranda <script>alert(1)</script> yozuvini o’qiydi. Ikkinchisida esa haqiqiy skript sahifaga tushdi va ishga tushadi.

Tekshirish usuli oddiy: ilovangizga <b>qalin</b> deb vazifa qo’shing. Ro’yxatda shu matn o’z holicha ko’rinsa — himoya ishlayapti. Qalin harflar chiqsa — <%- %> yozilgan joyni qidiring.

CSRF: begona saytdan kelgan so’rov

Hujum shunday ko’rinadi: siz saytingizga kirgansiz, keyin boshqa sahifaga o’tasiz va u sahifa fon rejimida sizning saytingizga POST /tasks/5/delete yuboradi. Siz hech narsa bosmadingiz, lekin vazifa o’chdi.

Birinchi himoya allaqachon bor — sessiya darsida qo’yilgan sameSite: "lax". U cookie’ni boshqa saytdan kelgan POST so’roviga qo’shmaydi.

Ikkinchi qatlam — token:

const crypto = require("crypto");

// Sessiyada token bo'lmasa yaratamiz
function csrfToken(request, response, next) {
  if (!request.session.csrfToken) {
    request.session.csrfToken = crypto.randomBytes(32).toString("hex");
  }

  // Shablonlar uchun ochiq qilamiz
  response.locals.csrfToken = request.session.csrfToken;
  next();
}

// Holatni o'zgartiradigan so'rovlarni tekshiradi
function checkCsrf(request, response, next) {
  const sent = request.body._csrf;

  if (!sent || sent !== request.session.csrfToken) {
    return response.status(403).json({ error: "So'rov tasdiqlanmadi" });
  }

  next();
}

module.exports = { csrfToken, checkCsrf };

Formaga yashirin maydon qo’shiladi:

<form action="/tasks" method="POST">
  <input type="hidden" name="_csrf" value="<%= csrfToken %>" />
  <input type="text" name="title" />
  <button type="submit">Qo'shish</button>
</form>

Nega bu ishlaydi: hujumchining sayti sizning sessiyangizdagi tokenni o’qiy olmaydi. Cookie avtomatik yuboriladi, token esa yo’q — server so’rovni rad etadi.

helmet: HTTP sarlavhalari

npm install helmet
const helmet = require("helmet");

app.use(helmet());

Bitta qator o’nlab himoya sarlavhasini qo’shadi:

curl -i http://localhost:3000/
HTTP/1.1 200 OK
Content-Security-Policy: default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN

Eng muhim uchtasi:

Sarlavha Nima qiladi
Content-Security-Policy Skript faqat o’z domeningizdan yuklanadi — XSS ta’sirini kamaytiradi
X-Frame-Options Sahifangizni begona saytga iframe qilib joylashtirishni to’sadi
X-Content-Type-Options Brauzer fayl turini “taxmin qilishini” o’chiradi

Rate limiting: urinishlarni cheklash

Kirish marshruti himoyasiz bo’lsa, hujumchi soatiga minglab parol sinaydi.

npm install express-rate-limit
const rateLimit = require("express-rate-limit");

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 daqiqa
  limit: 5,
  message: {
    error: "Juda ko'p urinish. 15 daqiqadan keyin qayta urinib ko'ring",
  },
  standardHeaders: "draft-7",
  legacyHeaders: false,
});

app.post("/login", loginLimiter, loginHandler);

Bu yerdagi loginHandlerkirish darsida yozilgan marshrut funksiyasi.

Ketma-ket olti marta so’rang:

1-urinish: HTTP/1.1 401 Unauthorized
2-urinish: HTTP/1.1 401 Unauthorized
3-urinish: HTTP/1.1 401 Unauthorized
4-urinish: HTTP/1.1 401 Unauthorized
5-urinish: HTTP/1.1 401 Unauthorized
6-urinish: HTTP/1.1 429 Too Many Requests

Oltinchi so’rov marshrutga umuman yetib bormadi. 429 Too Many Requests — aynan shu holat uchun mo’ljallangan kod.

Sirlarni .env da saqlash

Kodda qolgan maxfiy qiymatlarni yig’ib olamiz: sessiya kaliti, JWT kaliti, tashqi xizmatlar kalitlari.

Node.js 20 dan beri .env faylini o’qish uchun paket kerak emas:

SESSION_SECRET=bu-yerga-uzun-tasodifiy-qator-yozing
JWT_SECRET=bu-yerga-boshqa-uzun-qator-yozing
NODE_ENV=development
node --env-file=.env app.js
app.use(
  session({
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    cookie: {
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      sameSite: "lax",
    },
  })
);

Kalit yetishmasa, server ishga tushmasin:

const requiredVariables = ["SESSION_SECRET", "JWT_SECRET"];

for (const name of requiredVariables) {
  if (!process.env[name]) {
    console.error(`Muhit o'zgaruvchisi yo'q: ${name}`);
    process.exit(1);
  }
}

Nega shunday: standart qiymat qo’yilsa (|| "sinov-kalit"), serverga .env ni ko’chirish unutilganda ilova ishlayveradi — lekin hamma biladigan kalit bilan. Darhol to’xtash xavfsizroq.

npm audit

O’rnatgan paketlaringizda ham zaiflik topilishi mumkin:

npm audit
found 0 vulnerabilities

Zaiflik topilsa, ro’yxat va jiddiylik darajasi ko’rsatiladi. Ko’pincha yechim — yangilash:

npm audit fix

Himoya qatlamlari jadvali

Butun darsni bir joyga yig’amiz:

Hujum Himoya Qayerda
XSS <%= %>, textContent, CSP Shablon va frontend
CSRF sameSite, token Sessiya va forma
Sessiya o’g’irlash httpOnly, secure Cookie sozlamasi
Parol tanlash Rate limiting Kirish marshruti
SQL injection Parametrli so’rov Model qatlami
Sirlarning sizishi .env, .gitignore Loyiha sozlamasi

Bitta qatlam yetarli emas — har biri boshqasi ishlamay qolganda ushlab turadi.

Amaliy mashq

task-app loyihasida davom eting:

  1. Vazifa sarlavhasiga <b>qalin</b> yozib qo’shing va ro’yxatda matn o’z holicha ko’rinishini tasdiqlang;
  2. Shablonda bitta joyni ataylab <%- %> ga o’zgartirib, tegning bajarilishini ko’ring — so’ng darhol qaytaring;
  3. helmet() ni ulab, curl -i bilan yangi sarlavhalarni ko’ring;
  4. express-rate-limit ni kirish marshrutiga qo’shib, olti marta ketma-ket so’rang va 429 javobini oling;
  5. Sessiya va JWT kalitlarini .env ga ko’chirib, --env-file bilan ishga tushiring;
  6. .env ni .gitignore ga qo’shib, git status da ko’rinmasligini tekshiring.

2-qadam XSS ning butun mohiyatini bir necha soniyada ko’rsatadi: bitta belgi farqi matnni bajariladigan kodga aylantiradi.

Qisqacha xulosa

  • <%= %> ekranlaydi, <%- %> xom HTML qo’yadi — foydalanuvchi ma’lumoti faqat birinchisi orqali chiqariladi.
  • CSRF’ga qarshi sameSite: "lax" va formadagi yashirin token birga ishlaydi.
  • Authorization sarlavhasi bilan ishlaydigan API’da CSRF muammosi yo’q.
  • helmet() bitta qatorda o’nlab himoya sarlavhasini qo’shadi; CSP tashqi skriptlarni to’sadi va uni ataylab sozlash kerak.
  • express-rate-limit parol tanlash hujumini to’sadi va 429 qaytaradi.
  • Maxfiy kalitlar kodda emas, .env da; .env esa .gitignore da — kalit yetishmasa server ishga tushmasligi kerak.
  • npm audit paketlardagi zaiflikni ko’rsatadi; --force ni o’ylab ishlating.

Keyingi darsda API testlash — marshrutlarni qo’lda emas, avtomatik sinash.

Bu dars foydali bo'ldimi?

marta ko'rildi