সরাসরি পর্বে যান
পর্ব ৪৪ · ৮০-এর মধ্যে · মডিউল ৬

Web hardening — security header, WAF, dependency আর নিরাপদ error

Web Hardening — Security Headers, WAF, Dependencies & Safe Errors

Taught in Bangla, this lesson builds a production hardening checklist for your own site: what each security header stops, rolling out CSP and HSTS safely, finding exposed .git, .env and backup files in your web root, safe error handling, WAF limits, Subresource Integrity and lockfiles, CMS plugins, cPanel permissions, backups and uptime monitoring.

এই পর্বে যা শিখবেন

  • নিজের সাইটের জন্য একটি production checklist বানিয়ে স্তর ধরে মিলিয়ে দেখা
  • কোন security header কী থামায়, কী থামায় না, আর কোনগুলো এখন বাতিল
  • CSP আর HSTS ধাপে ধাপে ছাড়া, যাতে সাইট না ভাঙে আর ফেরার পথ থাকে
  • নিজের web root-এ .git, .env, backup আর ভুলে থাকা পাতা খুঁজে সরানো
  • নিরাপদ error handling আর WAF-এর সীমা, positive ও negative security-র পার্থক্য
  • SRI, lockfile, CMS plugin, cPanel-এর permission, backup আর uptime monitoring

১ · Hardening কী — দোকান খোলার আগের শেষ টহল

রাতে দোকান বন্ধ করার আগে মালিক কী করেন? সামনের শাটারে তালা দেন, পেছনের দরজা দেখেন, গুদামের জানালা টানেন, ক্যাশবাক্সের চাবি পকেটে নেন। নতুন কিছু বানান না। শুধু দেখেন, যা আছে তা ঠিক অবস্থায় আছে কি না। অনেক চুরি হয় এই খোলা জানালা দিয়েই, শাটার ভেঙে নয়।

Web hardening ঠিক এই টহল। পর্ব ৩৮ থেকে ৪৩ পর্যন্ত আমরা কোডের ভুল নিয়ে কথা বলেছি: authorization, injection, XSS, CSRF, file handling আর API. এবার প্রশ্ন আলাদা। কোড ঠিক থাকলেও সাইটটি যেভাবে চালু আছে, সেই চালু রাখার ধরনে কোনো ফাঁক আছে কি? OWASP Top 10:2025-এ Security Misconfiguration দ্বিতীয় স্থানে আর Software Supply Chain Failures তৃতীয় স্থানে আছে। আর নতুন যোগ হয়েছে Mishandling of Exceptional Conditions, যা error সামলানোর ভুলের কথা বলে। এই পর্বের বিষয়গুলো তাই ছোটখাটো সাজসজ্জা নয়।

এই পর্বের একটি স্থির উদাহরণ আছে: একটি কাল্পনিক অনলাইন বাজার, shop.example. সাইটটি আপনিই চালান। সব সংখ্যা মনগড়া, সব ডেটা পাতার ভেতরের ছোট খেলনা তালিকা। কোনো কোড বাইরে কোনো অনুরোধ পাঠায় না।

সীমা স্পষ্ট: এই পর্বের প্রতিটি যাচাই আপনার নিজের সাইটের জন্য, অথবা যে সাইটের মালিকের লিখিত অনুমতি আপনার কাছে আছে। অন্যের সাইটে path খুঁজে দেখা বা scanner চালানো কৌতূহল নয়, আইনি ঝুঁকি। বিস্তারিত আছে পর্ব ৬-এ। এটি আইনি পরামর্শ নয়।

একটি অনুরোধের পথে কোন স্তরে কোন সুরক্ষা

Hardening controls at each layer, with monitoring across all of them

Web hardening-এর স্তর Browser Header মানে: HSTS, CSP, nosniff, SRI CDN আর WAF Rule, rate limit, virtual patch Web server TLS, header পাঠানো, listing বন্ধ Application Debug বন্ধ, নিরাপদ error, admin সীমা Dependency আর plugin Lockfile, update, কম plugin Data আর file Permission, backup, restore পরীক্ষা Monitoring আর log, সব স্তরে
ছবি ৪৪.১ — কোনো একটি স্তর একাই যথেষ্ট নয়। এক স্তর ব্যর্থ হলে পরেরটি ক্ষতি সীমিত করে।

ছবিটি defense in depth-এর (পর্ব ৩) web সংস্করণ। এবার পুরো পর্বের মূল ফল, একটি production checklist. প্রতিটি লাইন পরে কোনো না কোনো অংশে ব্যাখ্যা করা হয়েছে। নিজের সাইটের জন্য এটি কপি করে নিন।

২ · Security header — browser-কে দেওয়া লিখিত নির্দেশ

অফিসের দারোয়ানকে মালিক একটি চিরকুট দিয়ে রাখলেন: “শুধু মূল গেট দিয়ে ঢুকতে দেবেন। বাইরের লোক ক্যামেরার ঘরে যাবেন না।” Security header সেই চিরকুট। Server প্রতিটি উত্তরের সাথে কিছু বাড়তি লাইন পাঠায়, আর browser সেই নিয়ম মেনে চলে। Header কোনো ভুল কোড সারায় না। ভুল কোডের ক্ষতি সীমিত করে, আর ভুল ব্যবহারের কিছু পথ বন্ধ করে।

Headerকী করেকী করে না
HSTS (Strict-Transport-Security)নির্দিষ্ট সময় এই host-এ শুধু HTTPS ব্যবহার করতে বলে। RFC 6797 (২০১২)প্রথম ভিজিট রক্ষা করে না, যদি না preload থাকে। HTTP-তে পাঠানো header browser উপেক্ষা করে
CSP (Content-Security-Policy)কোন উৎসের script, style, ছবি ইত্যাদি চলবে, তার তালিকা। XSS-এর ক্ষতি কমায়XSS-এর বদলি নয়। Output encoding-ই মূল সমাধান (পর্ব ৪০)
X-Content-Type-Options: nosniffBrowser-কে ঘোষিত Content-Type মানতে বলে, MIME sniffing বন্ধ করেভুল ফাইল upload হওয়া ঠেকায় না (পর্ব ৪২)
Referrer-Policyঅন্য সাইটে যাওয়ার সময় URL-এর কতটুকু যাবে, তা ঠিক করেURL-এ token রাখার ভুল সারায় না
Permissions-PolicyCamera, microphone, location-এর মতো browser ক্ষমতা বন্ধ বা সীমিত করেক্ষমতা ছাড়া অন্য script-এর কাজ থামায় না
CSP frame-ancestorsকে আপনার পাতা iframe-এ বসাতে পারবে, তা বলে। Clickjacking কমায় (পর্ব ৪১)পুরোনো browser-এর জন্য X-Frame-Options পাশে রাখা যায়

Referrer-Policy-তে একটি ভালো খবর আছে। Header না দিলেও এখনকার browser সাধারণত strict-origin-when-cross-origin ধরে নেয়: নিজের সাইটের ভেতরে পুরো URL যায়, অন্য সাইটে শুধু origin, আর HTTPS থেকে HTTP-তে নামলে কিছুই যায় না। তবু header স্পষ্ট করে দেওয়া ভালো, কারণ তখন আপনার নীতি সব browser-এ একই থাকে।

COOP আর COEP সম্পর্কে একটি কথা: Cross-Origin-Opener-Policy আর Cross-Origin-Embedder-Policy পাতাকে অন্য origin থেকে আলাদা করে। দুটি মিলিয়ে সঠিকভাবে দিলে পাতা “cross-origin isolated” হয়, তখন SharedArrayBuffer-এর মতো ক্ষমতা খোলে। COEP ভুলভাবে দিলে বাইরের ছবি, widget আর payment popup ভাঙতে পারে। সাধারণ ব্যবসায়িক সাইটের প্রথম ধাপে এটি জরুরি নয়। প্রয়োজন হলে আলাদা করে পরীক্ষা করে নিন।

যেগুলো অবসরে গেছে

X-XSS-Protection একসময় সাধারণ পরামর্শ ছিল। এখন এটি কোনো spec-এর অংশ নয়, MDN একে deprecated বলে আর CSP ব্যবহার করতে বলে। উল্টো, এই filter কখনো নিরাপদ সাইটেও সমস্যা তৈরি করতে পারত। তাই পুরোনো tutorial-এর 1; mode=block কপি করবেন না। Header না পাঠানো, অথবা 0 পাঠানোই ভালো। Public-Key-Pins (HPKP), Expect-CT আর Feature-Policy-ও একই পথের। শেষেরটির জায়গা নিয়েছে Permissions-Policy.

নিচের খেলনা auditor তিনটি কাল্পনিক সাইটের header-এর তালিকা পড়ে নম্বর দেয়। মান ও grade আমাদের নিজের বানানো স্কেল, কোনো standard নয়। Header-গুলো পাতার ভেতরের object-এ আছে, কোনো অনুরোধ যায় না।

// Toy header auditor. Reads in-memory objects only; sends no request.
const sites = {
  'shop-a.example': { 'strict-transport-security': 'max-age=31536000; includeSubDomains',
    'content-security-policy': "default-src 'self'; object-src 'none'; base-uri 'self'",
    'x-content-type-options': 'nosniff', 'referrer-policy': 'strict-origin-when-cross-origin',
    'permissions-policy': 'camera=(), microphone=()', 'server': 'nginx' },
  'shop-b.example': { 'strict-transport-security': 'max-age=86400',
    'content-security-policy-report-only': "default-src 'self'",
    'x-content-type-options': 'nosniff', 'referrer-policy': 'no-referrer' },
  'shop-c.example': { 'strict-transport-security': 'max-age=300',
    'content-security-policy': "default-src * 'unsafe-inline'",
    'x-xss-protection': '1; mode=block', 'server': 'Apache/2.4.41', 'x-powered-by': 'PHP/7.4.3' },
};
const YEAR_HALF = 15768000; // 182.5 days, in seconds
const checks = [
  ['HSTS', h => { const m = /max-age=(\d+)/.exec(h['strict-transport-security'] || '');
    return !m ? 0 : Number(m[1]) >= YEAR_HALF ? 20 : 10; }],
  ['CSP', h => { const c = h['content-security-policy'];
    if (!c) return h['content-security-policy-report-only'] ? 10 : 0;
    return /unsafe-inline|default-src \*/.test(c) ? 10 : 30; }],
  ['nosniff', h => h['x-content-type-options'] === 'nosniff' ? 15 : 0],
  ['Referrer', h => h['referrer-policy'] && h['referrer-policy'] !== 'unsafe-url' ? 15 : 0],
  ['Permissions', h => h['permissions-policy'] ? 10 : 0],
  ['NoLeak', h => /\d/.test(h['server'] || '') || h['x-powered-by'] ? 0 : 10],
  ['Retired', h => (h['x-xss-protection'] || '0') !== '0' ? -5 : 0],
];
const grade = s => s >= 90 ? 'A' : s >= 70 ? 'B' : s >= 50 ? 'C' : s >= 30 ? 'D' : 'F';
for (const [name, headers] of Object.entries(sites)) {
  const parts = checks.map(([label, fn]) => [label, fn(headers)]);
  const total = Math.max(0, parts.reduce((sum, p) => sum + p[1], 0));
  console.log(`${name}  score ${total}/100  grade ${grade(total)}`);
  console.log('  ' + parts.map(([l, v]) => `${l}=${v}`).join(' '));
}

আউটপুট:

shop-a.example  score 100/100  grade A
  HSTS=20 CSP=30 nosniff=15 Referrer=15 Permissions=10 NoLeak=10 Retired=0
shop-b.example  score 60/100  grade C
  HSTS=10 CSP=10 nosniff=15 Referrer=15 Permissions=0 NoLeak=10 Retired=0
shop-c.example  score 15/100  grade F
  HSTS=10 CSP=10 nosniff=0 Referrer=0 Permissions=0 NoLeak=0 Retired=-5

shop-a ১০০ পেল। shop-b-তে CSP এখনো Report-Only, HSTS মাত্র ১ দিন, আর Permissions-Policy নেই: ১০ + ১০ + ১৫ + ১৫ + ০ + ১০ = ৬০. এটি খারাপ সাইট নয়, মাঝপথে থাকা সাইট। shop-c-তে CSP আছে, কিন্তু default-src * আর unsafe-inline প্রায় সব খুলে রাখে। Version-ও ফাঁস হচ্ছে, আর অবসরপ্রাপ্ত header-এর জন্য ৫ কাটা গেছে। শিক্ষা: header থাকা আর header কাজের হওয়া এক জিনিস নয়। আবার ভালো নম্বর মানে সাইট নিরাপদ, এমনও নয়। Header-এর নম্বর কোডের access control সম্পর্কে কিছুই বলে না।

৩ · Header চালু করার কাজ — ধাপে ধাপে, ফেরার পথ রেখে

নতুন header সরাসরি চালু করলে দুই রকম বিপদ। কড়া CSP আপনার নিজের payment script, chat widget বা বিজ্ঞাপন আটকে দিতে পারে। আর বড় মেয়াদের HSTS দেওয়ার পর HTTPS-এ সমস্যা হলে যাঁরা header দেখেছেন, তাঁদের browser HTTP-তে ফিরবে না। ঈদের সেলের রাতে এটি জানার সময় নয়।

নিরাপদ পথটির নাম staged rollout: আগে শুধু দেখা, তারপর অল্প করে চালু, প্রতি ধাপে ফেরার রাস্তা মাপা। CSP-র জন্য browser-এর একটি বিশেষ header আছে, Content-Security-Policy-Report-Only. এটি কিছু আটকায় না, শুধু জানায় কী আটকাত। এটি IDS-এর শুধু-দেখা অবস্থা আর WAF-এর count mode-এর মতোই একটি audit mode. নিচের ডায়াগ্রামে shop.example-এর rollout দেখুন। সংখ্যাগুলো কাল্পনিক।

shop.example-এর অবস্থা
CSP
Violation (দিনে)
HSTS max-age
HSTS ফেরার সময়
  1. তালিকা বানানো। সাইটে কোন উৎস থেকে script, style, ছবি আর font আসে, তা লিখে ফেলা হলো। Browser-এর DevTools-এর Network tab এখানে কাজে লাগে। Subdomain-এর তালিকাও বানানো হলো, কারণ পরে HSTS-এ লাগবে।
  2. Report-Only চালু। default-src 'self' ধরনের নীতি শুধু রিপোর্টের জন্য দেওয়া হলো। সাইটে কিছুই ভাঙেনি। কিন্তু দিনে ২৪০টি violation এলো। অর্থাৎ নীতি এখনই enforce করলে এতগুলো জিনিস আটকে যেত।
  3. রিপোর্ট ধরে সারানো। Payment script-এর domain তালিকায় যোগ হলো, inline script আলাদা ফাইলে গেল। Violation ২৪০ থেকে নামল ১৮-তে। অন্তত এক পূর্ণ ব্যবসায়িক চক্র দেখা ভালো, যেমন মাসের শেষের রিপোর্ট পাতা বা বেতন দিবসের ভিড়।
  4. Enforce. Report-Only-র জায়গায় আসল CSP বসল। বাকি ৩টি violation এলো browser extension থেকে, যা সাইটের কোড নয়। এখন XSS হলেও অজানা উৎসের script চলবে না। CSP বদলানো যায় পরের response থেকেই, তাই এখানে ফেরা সহজ।
  5. HSTS ৩০০ সেকেন্ড। 300 ÷ 60 = 5 মিনিট। HTTPS-এ সমস্যা ধরা পড়লে ক্ষতি থাকবে সর্বোচ্চ ৫ মিনিট। আগে নিশ্চিত হওয়া দরকার যে HTTP থেকে HTTPS-এ 301 redirect কাজ করছে আর certificate ঠিক আছে।
  6. মেয়াদ বাড়ানো। কয়েক দিন পরে ৮৬,৪০০ (১ দিন), তারপর 604800 ÷ 86400 = 7 দিন। প্রতি ধাপের আগে dashboard-এ error দেখা হয়। এখন ভুল হলে যাঁরা header পেয়েছেন, তাঁদের browser সর্বোচ্চ ৭ দিন HTTP-তে ফিরবে না।
  7. পূর্ণ মেয়াদ। 31536000 ÷ 86400 = 365 দিন। সব subdomain HTTPS-এ আছে নিশ্চিত হলে তবেই includeSubDomains. Preload নেওয়ার সিদ্ধান্ত আলাদা, কারণ তালিকা থেকে বের হতে সময় লাগে। max-age=0 HSTS মুছতে পারে, কিন্তু সেটিও শুধু কাজ করা HTTPS-এর ওপর দিয়েই পৌঁছায়।

এবার বাস্তব config. নিচের দুটি উদাহরণের ধারণা একই। আপনার hosting অনুযায়ী একটি বেছে নিন। HSTS এখানে প্রথম ধাপে আছে, আর CSP আছে Report-Only-তে। মান বদলানোর আগে ওপরের ধাপগুলো মানুন। CSP-র /csp-report ঠিকানাটি উদাহরণ, সেখানে রিপোর্ট নেওয়ার ব্যবস্থা আপনাকেই করতে হবে। না থাকলে violation browser console-এও দেখা যায়।

# Apache 2.4; needs mod_headers and mod_alias. Test on staging first.
Options -Indexes

<IfModule mod_headers.c>
  Header always set Strict-Transport-Security "max-age=300"
  Header always set X-Content-Type-Options "nosniff"
  Header always set Referrer-Policy "strict-origin-when-cross-origin"
  Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
  Header always set Content-Security-Policy-Report-Only "default-src 'self'; object-src 'none'; base-uri 'self'; report-uri /csp-report"
  Header unset X-Powered-By
  Header always unset X-Powered-By
</IfModule>

# Hide dot-files, but keep /.well-known/ for ACME and security.txt
RedirectMatch 404 "/\.(?!well-known(/|$))"

# Backups do not belong here at all; this is only a second lock
<FilesMatch "\.(sql|bak|old)$">
  Require all denied
</FilesMatch>

.htaccess-এর একটি ভুল লাইনে পুরো সাইট 500 দিতে পারে। যেমন host যদি Options বদলাতে না দেয়। তাই আগের কপি রেখে এক এক ব্লক করে যোগ করুন, আর প্রতিবার সাইট খুলে দেখুন। ServerTokens এখানে দেওয়া যায় না, এটি মূল server config-এর জিনিস। Shared hosting-এ সেটি host-এর হাতে।

# Inside your own server block. Test with: sudo nginx -t
server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate     /etc/ssl/example.com/fullchain.pem;
    ssl_certificate_key /etc/ssl/example.com/privkey.pem;

    server_tokens off;
    autoindex off;

    add_header Strict-Transport-Security "max-age=300" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
    add_header Content-Security-Policy-Report-Only "default-src 'self'; object-src 'none'; base-uri 'self'; report-uri /csp-report" always;

    # Hide dot-files, keep /.well-known/
    location ~ /\.(?!well-known(/|$)) { deny all; }
}

always না দিলে error পাতায় header যায় না। আর একটি পরিচিত ফাঁদ: কোনো location-এ নিজের add_header থাকলে সাধারণত ওপরের স্তরের সব add_header সেখানে আর আসে না। নতুন version-এ এটি বদলানোর option থাকতে পারে, নিজের version-এর docs দেখুন। অনুশীলন ৪-এ এটি নিয়ে ভাববেন। Reverse proxy-র মূল ধারণা আছে Cloud & DevOps কোর্সে।

Header ঠিকমতো যাচ্ছে কি না দেখার সহজ উপায় browser-এর DevTools. অথবা নিজের সাইটের জন্য একটি লাইন:

curl -sI https://example.com/
HTTP/2 200
content-type: text/html; charset=UTF-8
strict-transport-security: max-age=300
x-content-type-options: nosniff
referrer-policy: strict-origin-when-cross-origin

ওপরের লেখাটি নমুনা আউটপুট — version-ভেদে আলাদা হতে পারে। সব header-ই দেখা উচিত, আর CDN থাকলে দেখুন সেটি header বদলে দিচ্ছে কি না। MDN HTTP Observatory-র মতো অনলাইন পরীক্ষকও আছে। সেগুলোও শুধু নিজের সাইটে ব্যবহার করুন।

৪ · নিজের সাইটে কী ফাঁস হচ্ছে — file আর তথ্যের হিসাব

বাড়ি বদলের পর পুরোনো চাবির গোছা অনেকে দরজার পাশেই ঝুলিয়ে রাখেন। Web সার্ভারেও এমন হয়। ডেভেলপার তাড়াহুড়োয় ফাইল রেখে যান, আর web root-এর ভেতরে যা আছে, server তা সাধারণত দিয়ে দেয়। এই ফাঁসকে বলে information disclosure. ঝুঁকিটি নিজের attack surface-এর অংশ, আর এটি খুঁজে পাওয়ার কাজ রক্ষকের।

জিনিসকেন ঝুঁকিসমাধান
Server banner, X-Powered-BySoftware আর version জানিয়ে দেয়, যা পুরোনো ত্রুটি মেলাতে সাহায্য করেVersion লুকান, কিন্তু আসল কাজ patch করা
Directory listingIndex ফাইল না থাকলে ফোল্ডারের ফাইল তালিকা দেখায়Apache-তে Options -Indexes, nginx-এ autoindex off
Default পাতা আর নমুনা ফাইলInstall-এর পরে থেকে যাওয়া পাতা, phpinfo, নমুনা scriptমুছে ফেলা। কাজের পরে কিছু ফেলে না রাখা
Admin panelসবার জন্য খোলা login পাতা, দুর্বল passwordMFA, IP সীমা বা VPN, default password বদল
Web root-এ backup.zip, .sql, .bak-এ পুরো কোড আর ডেটা থাকতে পারেWeb root-এর বাইরে, আদর্শভাবে সার্ভারেরই বাইরে
.git ফোল্ডারকোড আর পুরো ইতিহাস, মুছে ফেলা secret-সহRepository clone না করে শুধু build ফলাফল deploy
.env ফাইলDatabase password, API key এক জায়গায়Web root-এর বাইরে রাখা, ফাঁস হলে সব পাল্টানো

Version লুকানো সুরক্ষা নয়: এটি security through obscurity-এর সীমায় পড়ে। স্বয়ংক্রিয় scanner অনেক সময় version না দেখেই চেষ্টা করে। লুকানোর দাম কম, তাই করুন, কিন্তু এটিকে patch-এর বদলি ভাববেন না। Admin path-এর নাম বদলানোও একই রকম। এতে বিরক্তিকর traffic কমে, কিন্তু MFA-র জায়গা নেয় না।

সবচেয়ে ভালো যাচাই সার্ভারের ভেতর থেকে হয়, নিজের ফাইল দেখে। এতে কোনো network probe লাগে না। নিচের খেলনা কোড আপনার web root-এর একটি স্থির তালিকা পড়ে ঝুঁকি চিহ্নিত করে। কোনো সাইটে অনুরোধ যায় না, তালিকাটি array-এর ভেতরেই আছে।

// Static list of YOUR OWN web root (toy data). No request is sent anywhere.
const webRoot = [
  'index.html', 'css/style.css', 'robots.txt', 'uploads/photo.jpg',
  '.well-known/security.txt', '.git/config', '.env', 'db_dump.sql',
  'backup/site-2026-10.zip', 'wp-config.php.bak', 'phpinfo.php',
  'error_log', 'admin/index.php',
];
const rules = [
  [/^\.well-known\//, 'OK', 'public on purpose'],
  [/(^|\/)\.git\//, 'HIGH', 'remove; deploy without the repository'],
  [/(^|\/)\.env$/, 'HIGH', 'move outside web root; rotate every secret'],
  [/\.(sql|zip|tar|gz|bak|old)$|~$/, 'HIGH', 'move backups off the web root'],
  [/phpinfo\.php$/, 'MEDIUM', 'delete after use'],
  [/(^|\/)error_log$/, 'MEDIUM', 'keep logs outside web root'],
  [/^admin\//, 'REVIEW', 'add IP limit and MFA; not a secret path'],
];
const count = { OK: 0, HIGH: 0, MEDIUM: 0, REVIEW: 0 };
for (const path of webRoot) {
  const hit = rules.find(([re]) => re.test(path));
  const level = hit ? hit[1] : 'OK';
  count[level]++;
  if (level !== 'OK') console.log(level.padEnd(7) + path.padEnd(25) + hit[2]);
}
console.log('--');
console.log(`checked ${webRoot.length} paths:`, JSON.stringify(count));
console.log(count.HIGH > 0 ? 'RESULT: fix HIGH items before launch' : 'RESULT: clean');

আউটপুট:

HIGH   .git/config              remove; deploy without the repository
HIGH   .env                     move outside web root; rotate every secret
HIGH   db_dump.sql              move backups off the web root
HIGH   backup/site-2026-10.zip  move backups off the web root
HIGH   wp-config.php.bak        move backups off the web root
MEDIUM phpinfo.php              delete after use
MEDIUM error_log                keep logs outside web root
REVIEW admin/index.php          add IP limit and MFA; not a secret path
--
checked 13 paths: {"OK":5,"HIGH":5,"MEDIUM":2,"REVIEW":1}
RESULT: fix HIGH items before launch

১৩টি path-এর ৫টি HIGH. খেয়াল করুন .well-known/security.txt তালিকায় আসেনি, কারণ প্রথম rule-ই একে ইচ্ছাকৃতভাবে public ধরেছে। rules.find প্রথম মিলে থামে, তাই ক্রম গুরুত্বপূর্ণ। admin/index.php-কে আমরা HIGH বলিনি, কারণ admin পাতা থাকা ভুল নয়। প্রশ্ন হলো সেটি কীভাবে সুরক্ষিত। এই খেলনা তালিকায় HIGH মানে web root থেকে সরাতে হবে, কিন্তু সেটুকুই শেষ কাজ নয়।

একই কাজ বাস্তবে নিজের hosting account-এর terminal থেকে হয়। cPanel-এর Terminal বা SSH থাকলে এটি শুধু আপনার নিজের ফাইল পড়ে, বাইরে কোনো অনুরোধ পাঠায় না।

# Run only on your own account. Read-only: nothing is changed.
cd ~/public_html
find . \( -name '.env' -o -name '.git' -o -name '*.sql' \
       -o -name '*.bak' -o -name '*.old' -o -name 'phpinfo.php' \) -print

# Files or folders that any account may write to
find . -perm -o+w -print
মূল ধারণা

Public হয়ে যাওয়া secret-কে ফাঁস হওয়া ধরে নিন। .env মুছলেই কাজ শেষ নয়। ফাইলটি আগে কতদিন খোলা ছিল, access log দেখুন। সব password আর key বদলান (secret-এর পর্ব)। Git-এর ইতিহাস থেকে কীভাবে সরাবেন, তা আছে Git নিরাপত্তার পর্বে। তারপর deploy প্রক্রিয়াটি এমন করুন, যাতে এটি আবার না ঘটে।

৫ · নিরাপদ error — ব্যবহারকারীকে কম, log-এ বেশি

ব্যাংকের কাউন্টারে সিস্টেম নষ্ট হলে কর্মকর্তা বলেন, “দুঃখিত, এখন সার্ভারে সমস্যা, এই নম্বরটি রাখুন।” তিনি গ্রাহককে ভল্টের নকশা বা সার্ভার রুমের পাসওয়ার্ড শোনান না। ভেতরের রেজিস্টার খাতায় পুরো ঘটনা লেখেন। Web-এর নিরাপদ error handling-ও ঠিক তাই।

Development-এ debug mode খুব কাজের। কিন্তু production-এ এটি stack trace, ফাইলের পথ, query, এমনকি environment-এর মান দেখাতে পারে। কিছু framework-এর debug পাতায় ভেতরের console-ও থাকতে পারে, যা তখন সরাসরি কোড চালানোর ঝুঁকিতে দাঁড়ায়। তিনটি নিয়ম মনে রাখুন:

  • ব্যবহারকারী পান: সাধারণ বার্তা, সঠিক HTTP status আর একটি correlation id.
  • Log পায়: সময়, path, কারণ, id, তবে password, token আর PIN বাদ দিয়ে। Log-এর নীতি আছে পর্ব ৫৫-এ।
  • Error-এ দরজা বন্ধ থাকে: Permission যাচাই ব্যর্থ হলে উত্তর “না”। এটিই fail secure (পর্ব ৩)। OWASP Top 10:2025-এর A10 এই ধরনের ভুল নিয়েই।
// Toy error handler. Fake data; the clock is simulated, nothing is sent.
let clock = 1000, nextId = 41;
const SECRET_KEYS = ['password', 'token', 'pin'];
const scrub = ctx => Object.fromEntries(Object.entries(ctx).map(
  ([k, v]) => [k, SECRET_KEYS.includes(k) ? '[REDACTED]' : v]));
const logBook = [];

function handle(route, ctx, work, debug) {
  try {
    return { status: 200, body: work() };
  } catch (e) {
    clock += 5;
    const id = 'E-' + (nextId++).toString(16).padStart(4, '0');
    logBook.push({ id, t: clock, route, msg: e.message, ctx: scrub(ctx) });
    if (debug) return { status: 500, body: e.message + ' | ' + JSON.stringify(ctx) };
    return { status: 500, body: `Something went wrong. Reference: ${id}` };
  }
}
const failingQuery = () => {
  throw new Error('db.internal.example:3306 refused for user shop_app');
};
const ctx = { user: 'u-1042', token: 'example-token' };

const bad = handle('/checkout', ctx, failingQuery, true);
const good = handle('/checkout', ctx, failingQuery, false);
const fine = handle('/home', {}, () => 'welcome', false);

console.log('DEBUG ON  user sees:', bad.status, bad.body);
console.log('DEBUG OFF user sees:', good.status, good.body);
console.log('NORMAL    user sees:', fine.status, fine.body);
console.log('-- server log only --');
logBook.forEach(entry => console.log(JSON.stringify(entry)));

আউটপুট:

DEBUG ON  user sees: 500 db.internal.example:3306 refused for user shop_app | {"user":"u-1042","token":"example-token"}
DEBUG OFF user sees: 500 Something went wrong. Reference: E-002a
NORMAL    user sees: 200 welcome
-- server log only --
{"id":"E-0029","t":1005,"route":"/checkout","msg":"db.internal.example:3306 refused for user shop_app","ctx":{"user":"u-1042","token":"[REDACTED]"}}
{"id":"E-002a","t":1010,"route":"/checkout","msg":"db.internal.example:3306 refused for user shop_app","ctx":{"user":"u-1042","token":"[REDACTED]"}}

একই ভুল, দুই রকম ফল। Debug চালু থাকলে ব্যবহারকারী database-এর ঠিকানা, user আর token দেখেন। Debug বন্ধ থাকলে দেখেন শুধু E-002a. Support-এ ফোন করে এই reference বললেই কর্মী log-এ পুরো ঘটনা খুঁজে পান। আর log-এ token-এর জায়গায় [REDACTED], কারণ log নিজেও একটি ডেটা ভান্ডার। Status 500 সৎ থাকল। সব কিছুতে 200 দিয়ে “error” লেখা monitoring-কে অন্ধ করে দেয়।

Debug বন্ধ করার switch প্রতিটি stack-এ আলাদা। ঠিক নামটি নিজের version-এর docs থেকে মিলিয়ে নিন।

StackProduction setting
PHPdisplay_errors = Off, log_errors = On, expose_php = Off
WordPressWP_DEBUG false, অথবা WP_DEBUG_DISPLAY false
LaravelAPP_DEBUG=false
DjangoDEBUG = False আর সঠিক ALLOWED_HOSTS
Node + ExpressNODE_ENV=production, নিজের error handler

Login-এ error-এর আরেকটি ফাঁদ আছে। “এই email নেই” আর “password ভুল” আলাদা বললে account enumeration-এর পথ খোলে। তাই বার্তা একটিই রাখুন, “email বা password সঠিক নয়” (পর্ব ১৫)।

এবার একটি কাল্পনিক ঘটনা। সব মান বানানো, কোনো secret আসল নয়। shop.example-এর checkout পাতায় বৃহস্পতিবার রাতে নতুন release গেছে।

checkout, 500 errorক্রেতার screenshot (fake)
Exception: connection refused
at /var/www/shop/app/Checkout.php:88
DB_HOST=db.internal.example
DB_USER=shop_app
DB_PASSWORD=example-token
APP_DEBUG=true
checkout, 500 errorসংশোধনের পরে
দুঃখিত, একটি সমস্যা হয়েছে।
Reference: E-002a
deploy-এর আগের ধাপউদাহরণ, নিজের pipeline
grep -qx 'APP_DEBUG=false' .env.production \
  || { echo 'debug is on: stop'; exit 1; }
অবস্থা
APP_DEBUG
DB password
Log পর্যালোচনা
স্বয়ংক্রিয় বাধা
  1. ধরা পড়ল। একজন ক্রেতা checkout-এর screenshot পাঠালেন। Database একটু সময়ের জন্য বন্ধ ছিল, আর পাতায় ভেসে উঠল ফাইলের পথ, user আর password. Error rate-এর alert একই সময়ে বেজেছিল, কিন্তু ফাঁসের খবর দিলেন ক্রেতাই।
  2. নিশ্চিত করা। Release-এর config দেখে পাওয়া গেল APP_DEBUG=true, staging-এর ফাইল ভুলে production-এ এসেছে। এটি এখন একটি incident. একজন দায়িত্ব নিলেন, সময় লেখা শুরু হলো। Screenshot-এর গোপন অংশ ঢেকে রাখা হলো।
  3. থামানো। APP_DEBUG=false করে config cache নতুন করে তৈরি হলো। পাতা এখন শুধু সাধারণ বার্তা আর reference দেখায়। ফাঁস থেমেছে, কিন্তু password এখনো পুরোনো। সারণির সেই ঘরটি খেয়াল করুন।
  4. Secret বদল। যে password পাতায় দেখা গেছে, সেটি ফাঁস হওয়া ধরে নেওয়া হলো। নতুন password বসানো হলো, পুরোনোটি database থেকে বাতিল। Debug বন্ধ করাই যথেষ্ট ভাবা এখানকার সবচেয়ে বড় ভুল হতো। একই পাতার অন্য key থাকলে সেগুলোও বদলাতে হবে।
  5. Log দেখা। Access log থেকে বের হলো কতক্ষণ ধরে কারা ওই error পাতা পেয়েছেন, আর পুরোনো user দিয়ে database-এ অস্বাভাবিক login হয়েছে কি না। প্রমাণ না পেলে সেটি “হয়নি” নয়, “প্রমাণ পাইনি”। গ্রাহকের ডেটা ক্ষতিগ্রস্ত হয়ে থাকলে জানানোর দায়িত্বের প্রশ্ন আসে।
  6. আর যেন না হয়। Pipeline-এ একটি বাধা বসল, যা debug চালু দেখলে deploy থামায়। Staging আর production-এর config আলাদা হলো। দোষ খোঁজার বদলে প্রক্রিয়া বদলানো হলো, এটিই post-incident review-এর মূল কথা।

৬ · WAF — ঢাল হিসেবে ভালো, চিকিৎসা হিসেবে নয়

WAF (Web Application Firewall) HTTP অনুরোধের ভেতরটা পড়ে নিয়ম ধরে সিদ্ধান্ত নেয়। Rule-এর ধরন, count ও block action আর cloud-এ বসানোর পদ্ধতি বিস্তারিত আছে CDN, WAF আর edge-এর পর্বে। সেগুলো এখানে আর বলছি না। এখানে নিরাপত্তার প্রশ্ন তিনটি: কোন মডেলে চলে, ভুল করলে কী হয়, আর কোথায় থামে।

Negative আর positive security

Negative security

Negative security model: “যা খারাপ বলে চেনা, তা আটকে যাবে, বাকি সব যাবে।” চোর-পুলিশের ছবির তালিকা হাতে দারোয়ানের মতো। শুরু করা সহজ, কিন্তু অচেনা জিনিস পার হতে পারে।

উদাহরণ: managed rule set

Positive security

Positive security model: “যা বৈধ বলে জানা, শুধু সেটুকু আসবে, বাকি সব আটকে যাবে।” অতিথির তালিকা হাতে দারোয়ানের মতো। শক্ত, কিন্তু app বদলালেই তালিকাও বদলাতে হয়।

উদাহরণ: API schema যাচাই

এখানে সেই denylist আর allowlist-এর পুরোনো হিসাবই চলে। বাস্তবে দুটি মেলানো হয়। বাইরে সাধারণ rule set, আর সংবেদনশীল API-তে schema ধরে positive নিয়ম (পর্ব ৪৩)।

False positive আর তার tuning

WAF শব্দের ধরন মেলায়, অর্থ বোঝে না। তাই নিরীহ লেখাও সন্দেহজনক মনে হতে পারে। নিচের ডায়াগ্রামে একটি খেলনা rule আছে, যা একই ক্ষেত্রে select আর from শব্দ পেলে সন্দেহ করে। আসল rule আরও জটিল হয়, কিন্তু সমস্যাটি একই। সংখ্যাগুলো কাল্পনিক।

  1. অনুরোধ/support/message
  2. Toy ruleselect + from
  3. Log পড়াসত্য না ভুল
  4. সরু ছাড়একটি path, একটি field
WAF log (toy)ক্রেতার আসল বার্তা
path=/support/message  field=message
text="please select a blue size from the list"
action=COUNT  rule=toy-sql-words
Exception (toy)মালিক আর মেয়াদসহ
rule=toy-sql-words  skip when
  path == /support/message AND field == message
owner=web-team  review=2027-01-31
এক সপ্তাহের হিসাব
Mode
মোট hit
False positive
True positive
Precision
  1. Count mode-এ rule. নতুন rule বসল, কিন্তু কিছু আটকাচ্ছে না, শুধু গুনছে। সপ্তাহ সবে শুরু, hit ০।
  2. এক সপ্তাহ পরে। ১২০টি hit. সংখ্যাটি দেখে মনে হতে পারে সাইট আক্রমণের মুখে, বা rule দারুণ কাজ করছে। কিন্তু log না পড়ে দুটির কোনোটিই বলা যায় না।
  3. Log পড়া। ১১৪টি ক্রেতার সাধারণ বার্তা, যেমন “please select a blue size from the list”. শুধু ৬টি সত্যিই সন্দেহজনক। Precision = ৬ ÷ ১২০ = ৫%. অর্থাৎ ২০টি সতর্কতার ১৯টিই ভুল।
  4. Block করলে কী হতো? সপ্তাহে ১১৪ জন ক্রেতা অভিযোগ পাঠাতে গিয়ে 403 পেতেন। আর যে ক্রেতা অভিযোগ করতে পারেন না, তিনি শুধু চলে যান। False positive-এর দাম এখানে সরাসরি ব্যবসায়।
  5. সরু rule exclusion. পুরো rule বন্ধ করা হয়নি। শুধু এই path-এর এই field-এ এই rule বাদ। আগে যাচাই হলো যে ওই field-এর কোড parameterized query ব্যবহার করে (পর্ব ৩৯)। ছাড়ের একজন মালিক আর পর্যালোচনার তারিখ আছে।
  6. আবার মাপা। Hit ১২০ থেকে ৮: ১১২টি false positive গেল। সত্য ৬টি এখনো ধরা পড়ছে। Precision = ৬ ÷ ৮ = ৭৫%. বাকি ২টি ভুলের জন্য তাড়াহুড়ো নয়, আরও দেখা হবে।
  7. Block আর alert. এবার block চালু। Block-এর হার হঠাৎ বাড়লে alert যাবে, কারণ app-এর নতুন release আবার false positive আনতে পারে। মনে রাখুন, ছাড় মানে ওই field-এ WAF আর পাহারা দিচ্ছে না, সেখানে ভরসা কোডের ওপরই।

WAF কোড সারানোর বদলি কেন নয়

  • যুক্তির ভুল সে দেখে না। অন্য গ্রাহকের order id চাওয়ার অনুরোধ দেখতে একদম স্বাভাবিক। Broken access control-এর (পর্ব ৩৮) সামনে WAF প্রায় অন্ধ, কারণ তার জানা নেই কোন order কার।
  • Pattern-এর সীমা আছে। যে ধরন rule-এর অচেনা, তা পার হতে পারে। তাই একে শেষ দেয়াল ধরা বিপজ্জনক।
  • ভেতরের পথ বাদ পড়ে। Origin সরাসরি খোলা থাকলে WAF-কে পাশ কাটিয়ে অনুরোধ পৌঁছায়। Origin রক্ষার কথা আছে cloud কোর্সের ওই পর্বেই।

WAF-এর সত্যিকারের ভালো ব্যবহার virtual patch: নতুন ত্রুটি প্রকাশের পর আসল patch না হওয়া পর্যন্ত একটি সরু rule দিয়ে সময় কেনা (পর্ব ৩২)। মেয়াদ দিন, আর আসল সমাধান শেষ হলে rule-টি পর্যালোচনা করুন। ঝুঁকি কমানোর এই অস্থায়ী উপায়কে বলে compensating control.

৭ · Dependency আর third-party script — অন্যের কোডে বিশ্বাসের হিসাব

রেস্টুরেন্টের রান্নাঘর পরিষ্কার হতে পারে, কিন্তু বাইরের মসলা ভেজাল হলে খাবার তবু খারাপ। আপনার সাইটের বড় অংশ অন্যের লেখা কোড। Transitive dependency ধরলে তালিকা আরও লম্বা। পুরো supply chain আলাদা করে আছে পর্ব ৪৮-এ। এখানে শুধু দৈনন্দিন hardening.

অভ্যাসকেনউদাহরণ
Lockfile commit করাপ্রতিটি build-এ হুবহু একই version আর hashpackage-lock.json, composer.lock
Lockfile মেনে installProduction-এ হঠাৎ নতুন version ঢোকে নাnpm ci, composer install
নিয়মিত audit আর updateপরিচিত ত্রুটি দ্রুত ধরা পড়েnpm audit, composer audit, pip-audit
দরকার নেই, তা বাদপ্রতিটি package একটি বাড়তি বিশ্বাসঅব্যবহৃত library মোছা

Third-party script আরও সরাসরি ঝুঁকি। Analytics, chat, বিজ্ঞাপন বা CDN-এর একটি <script src> আপনার পাতায় আপনার কোডের সমান ক্ষমতা পায়। সেটি form-এর লেখাও পড়তে পারে। ২০২৪ সালে polyfill.io-র ঘটনায় Sansec-এর প্রকাশিত প্রতিবেদন বলছে, domain-এর মালিকানা বদলের পরে এক লাখের বেশি সাইটে embed করা সেই script থেকে কিছু মোবাইল ব্যবহারকারীকে অন্য সাইটে পাঠানো হচ্ছিল। সাইট মালিকরা নিজের কোড একটুও বদলাননি।

Subresource Integrity (SRI, W3C Recommendation, ২০১৬) এর একটি উত্তর। পোস্ট অফিসের সিলমোহর করা খামের মতো: খাম খুলে সিল না মিললে জিনিসটি নেওয়া হয় না। আপনি HTML-এ ফাইলের প্রত্যাশিত hash লিখে দেন। Browser ফাইল এনে hash মেলায়, না মিললে চালায় না (hash-এর ভিত্তি পর্ব ৯)।

# SRI value for a fake script held in memory. Nothing is downloaded.
import base64
import hashlib


def sri(data: bytes, algorithm: str = "sha384") -> str:
    digest = hashlib.new(algorithm, data).digest()
    return algorithm + "-" + base64.b64encode(digest).decode("ascii")


pinned_file = b"function greet(){return 'hello from example CDN';}\n"
changed_file = b"function greet(){return 'hello from example CDN!';}\n"

expected = sri(pinned_file)
print("integrity value:", expected)

digest_bytes = hashlib.sha384(pinned_file).digest_size
base64_chars = len(expected.split("-", 1)[1])
print("digest bytes:", digest_bytes, "base64 chars:", base64_chars)

for label, delivered in [("same file", pinned_file), ("one char added", changed_file)]:
    actual = sri(delivered)
    decision = "RUN" if actual == expected else "BLOCK (network error)"
    print(f"{label:15} {actual[:25]}... {decision}")

আউটপুট:

integrity value: sha384-QJjojmQzbULHbKbJK7ioD/QFXaHq5lR+mCoWZ7yJtCBTotebeWjt8oytUhpoWdhH
digest bytes: 48 base64 chars: 64
same file       sha384-QJjojmQzbULHbKbJK7... RUN
one char added  sha384-CrooZN9QULXst6+jOj... BLOCK (network error)

SHA-384 দেয় ৩৮৪ ÷ ৮ = ৪৮ byte. Base64-এ প্রতি ৩ byte হয় ৪ অক্ষর, তাই ৪৮ ÷ ৩ × ৪ = ৬৪ অক্ষর। একটি মাত্র ! যোগ করলেই hash পুরো বদলে গেল, এটিই avalanche effect. Browser-এ না মিললে ফল network error, script চলে না। আসল hash আসে আসল ফাইলের byte থেকে, তাই ওপরের মান শুধু এই খেলনা লেখার জন্য।

HTML-এ এটি এভাবে বসে। অন্য origin-এর ফাইলে crossorigin আর সেই server-এর CORS অনুমতিও লাগে।

<script src="https://cdn.example.com/lib-1.2.3.min.js"
        integrity="sha384-QJjojmQzbULHbKbJK7ioD/QFXaHq5lR+mCoWZ7yJtCBTotebeWjt8oytUhpoWdhH"
        crossorigin="anonymous"></script>

SRI-র সীমা: এটি শুধু নির্দিষ্ট version-এর স্থির ফাইলে কাজ করে। যে script প্রতিদিন বা ব্যবহারকারী-ভেদে বদলায়, যেমন অনেক analytics আর বিজ্ঞাপনের script, সেখানে SRI দেওয়া যায় না, দিলে সাইটই ভাঙবে। সেখানে দরকার সংখ্যা কমানো, CSP-র সীমা, সম্ভব হলে নিজের সার্ভারে রাখা, আর vendor যাচাই (পর্ব ৭১)। SRI ফাইলটি বদলেছে কি না বলে। ফাইলটি শুরু থেকেই নিরাপদ কি না, তা বলে না।

৮ · CMS, plugin আর shared hosting — ছোট সাইটের বাস্তবতা

বাংলাদেশের বহু ছোট ব্যবসা, স্কুল, পত্রিকা আর ব্যক্তিগত সাইট চলে একটি CMS আর সস্তা shared hosting-এ, প্রায়ই cPanel-জাতীয় panel দিয়ে। এখানে CDN বা WAF-এর বাজেট নাও থাকতে পারে। কিন্তু সবচেয়ে কাজের hardening-এর বেশিরভাগই বিনা খরচে করা যায়।

CMS আর plugin

WordPress উদাহরণ হিসেবে নিচ্ছি, কারণ এটি বহুল ব্যবহৃত, এটি অন্যগুলোর চেয়ে দুর্বল বলে নয়। অন্য CMS-এও একই নীতি চলে। অভিজ্ঞতায় অনেক সমস্যা আসে মূল software থেকে নয়, plugin আর theme থেকে।

  • Update চালু রাখুন: Core, theme আর plugin. স্বয়ংক্রিয় update সম্ভব হলে নিন, তবে আগে backup নিশ্চিত করুন।
  • কম plugin: অব্যবহৃত plugin শুধু বন্ধ নয়, মুছে ফেলুন। দীর্ঘদিন আপডেট না পাওয়া plugin বদল করুন।
  • Crack করা paid plugin বা theme নয়: অজানা উৎসের “free” কপিতে লুকানো কোড থাকতে পারে। সস্তা দেখালেও এটি সবচেয়ে দামি ভুলগুলোর একটি।
  • Admin account: MFA, শক্ত password, আলাদা ব্যবহারকারী, admin নাম নয়। Login-এ rate limit (পর্ব ১৬)।
  • Panel থেকে ফাইল সম্পাদনা বন্ধ: WordPress-এ DISALLOW_FILE_EDIT-এর মতো setting আছে। Admin account দখল হলেও ক্ষতি কিছুটা কমে।

File permission

Shared hosting-এ একই machine-এ বহু গ্রাহক থাকেন, যেন একই ভবনের ভিন্ন ভাড়াটিয়া। তাই file permission গুরুত্বপূর্ণ। সংখ্যার তিনটি ঘর হলো মালিক, group আর অন্যরা। ৪ মানে পড়া, ২ মানে লেখা, ১ মানে চালানো বা ফোল্ডারে ঢোকা। মূল তত্ত্ব আছে OS কোর্সে আর পর্ব ৩০-এ।

মানঅর্থকোথায়
755 বা 750মালিক সব, বাকিরা পড়া ও ঢোকা (৭৫০-এ অন্যরা কিছুই নয়)ফোল্ডার
644 বা 640মালিক লেখা ও পড়া, বাকিরা পড়া (৬৪০-এ অন্যরা কিছুই নয়)সাধারণ ফাইল
600, 440 বা 400শুধু মালিক পড়তে পারেন (৪৪০-এ group-ও), ৪৪০ আর ৪০০-তে লেখা বন্ধwp-config.php, config ফাইল (host-এর ধরন দেখে)
777যে কেউ পড়া, লেখা, চালানোকোথাও নয়

“Upload কাজ করছে না, chmod 777 দিন” পরিচিত পরামর্শ, প্রায়ই AI-ও এটি দেয়। সেটি সমস্যা লুকায়, সারায় না। আসল কারণ সাধারণত ফাইলের মালিক ঠিক না থাকা। আর host যেভাবে PHP চালায় (suexec বা অন্য ধরন), সেটির ওপর সঠিক মান কিছুটা নির্ভর করে। নিজের host-এর docs দেখুন, আর upload ফোল্ডারে script চালানো বন্ধ রাখুন (পর্ব ৪২)।

cPanel-জাতীয় hosting-এ আরও কিছু কাজ: panel-এ MFA চালু করুন, FTP-র বদলে SFTP নিন, পুরোনো PHP version বদলান, একই account-এ অনেক সাইট গাদাগাদি না রাখুন। কারণ একটি অবহেলিত পুরোনো সাইট দিয়ে পুরো account ঝুঁকিতে পড়তে পারে। এটি blast radius-এর সহজ উদাহরণ। ভুলে থাকা subdomain আর পুরোনো staging সাইটও খুঁজে সরিয়ে ফেলুন।

৯ · Backup, monitoring আর uptime — খারাপ দিনের প্রস্তুতি

Hardening ঝুঁকি কমায়, শূন্য করে না। সাইট একদিন ভাঙতে পারে, ভুল update-এ বা হামলায়। তখন প্রশ্ন দুটি: আপনি কত দ্রুত জানবেন, আর কতটা ফিরিয়ে আনতে পারবেন।

Backup

  • সার্ভারের বাইরে কপি: Backup একই account-এ থাকলে হ্যাক হলে সেটিও যাবে। 3-2-1-1-0 নিয়মের ভিত্তি আছে পর্ব ৩৪-এ।
  • Host-এর backup-কে নিজের ভাববেন না: মেয়াদ, শর্ত আর ফেরত দেওয়ার নিয়ম host-ভেদে আলাদা। নিজের একটি কপি রাখুন।
  • ফাইল আর database দুটোই: শুধু ফাইল ফেরালে order আর গ্রাহকের তথ্য থাকবে না। প্রতিদিন একবার backup মানে সর্বোচ্চ প্রায় ২৪ ঘণ্টার ডেটা হারানোর ঝুঁকি, যা RPO.
  • পুরোনো কপিও রাখুন: হামলা হয়তো ২ সপ্তাহ আগে হয়েছে। মাত্র ৭ দিনের backup তখন সবগুলোই দূষিত।
  • Restore করে দেখুন: Restore test ছাড়া backup শুধু একটি আশা। বিস্তারিত আছে Backup আর DR-এর পর্বে।

Monitoring আর uptime

কী দেখবেনকেন
Uptime check সার্ভারের বাইরে থেকেসার্ভার নিজেই বন্ধ হলে ভেতরের check কিছু জানাতে পারে না
Certificate আর domain-এর মেয়াদভুলে যাওয়া মেয়াদ নিজের হাতে বানানো outage
5xx আর 403-এর হারনতুন release, WAF-এর ভুল বা হামলার প্রথম ইঙ্গিত
Admin login, নতুন admin accountদখলের সবচেয়ে স্পষ্ট চিহ্নগুলোর একটি
ফাইলের পরিবর্তনFile integrity monitoring অচেনা PHP ফাইল ধরে
Disk আর backup jobDisk ভরলে log আর backup দুটোই থামে

Alert এমন জায়গায় যাওয়া দরকার, যা কেউ দেখেন। কে দেখবেন, রাতে কে ফোন ধরবেন, সেটি একটি ছোট runbook-এ লিখুন। Alert-এর নকশা আছে SLO আর alert-এর পর্বে। আর সাইটে /.well-known/security.txt রাখলে (security.txt) কোনো সৎ গবেষক ত্রুটি পেলে কোথায় জানাবেন, তা সহজে খুঁজে পান।

আরও গভীরে: CSP প্রথম প্রস্তাব করেন Mozilla-র গবেষকরা (Stamm ও সহলেখকেরা, WWW ২০১০)। Google-এর “CSP Is Dead, Long Live CSP!” (Weichselbaum ও সহলেখকেরা, ACM CCS ২০১৬) দেখায়, বড় মাপের ডেটায় বেশিরভাগ allowlist-ভিত্তিক নীতি প্রত্যাশিত সুরক্ষা দিত না, তাই nonce আর strict-dynamic-ভিত্তিক “strict CSP”-এর প্রস্তাব আসে। WAF-এ OWASP Core Rule Set-এর “anomaly scoring” একটি rule-এ না থেমে একাধিক সন্দেহের নম্বর যোগ করে, আর “paranoia level” বাড়ালে সুরক্ষা ও false positive দুটোই বাড়ে। Header-এর সর্বশেষ তালিকার জন্য OWASP Secure Headers Project আর MDN দেখুন।

পরীক্ষা ও ইন্টারভিউতে:

  • CSP-র Report-Only কেন? কিছু না ভেঙে নীতি পরীক্ষার জন্য। এটি আটকায় না, শুধু জানায়।
  • HSTS-এর দুর্বলতম মুহূর্ত কখন? প্রথম ভিজিট, যখন browser header দেখেনি। Preload এর সমাধান।
  • X-XSS-Protection কেন বাদ? Filter অবসরে গেছে, আর নিজেই ঝুঁকি তৈরি করতে পারত। CSP আর encoding এর জায়গা নিয়েছে।
  • Negative আর positive model? প্রথমটি চেনা খারাপ আটকায়, দ্বিতীয়টি শুধু চেনা ভালো ঢুকতে দেয়।
  • WAF থাকলে SQL injection সারানো লাগবে কি? হ্যাঁ। WAF সময় কেনে, সমাধান parameterized query.

১০ · AI-এর যুগে web hardening

AI খুব দ্রুত একটি header block লিখে দিতে পারে। সমস্যা হলো, সে আপনার সাইট দেখতে পায় না। সে জানে না আপনার payment script কোন domain থেকে আসে, বা কোন subdomain এখনো HTTP-তে আছে। আর বহু পুরোনো tutorial থেকে শেখা অভ্যাসও সে নিয়ে আসে।

AI-এর পরিচিত ভুলঝুঁকিযাচাই
X-XSS-Protection: 1; mode=block দেয়বাতিল পরামর্শMDN আর OWASP-এর সর্বশেষ পাতা দেখা
সাইট ভাঙলে CSP-তে unsafe-inline যোগ করেCSP-র মূল সুরক্ষা হারায়Inline কোড সরানো, nonce ব্যবহার
শুরুতেই ২ বছরের HSTS আর preloadফেরার পথ বন্ধ৩০০ সেকেন্ড দিয়ে শুরু করা
chmod 777, Access-Control-Allow-Origin: *সমস্যা ঢেকে দেয়, ঝুঁকি বাড়ায়মালিক আর আসল কারণ খোঁজা
False positive-এ পুরো WAF rule বন্ধ করতে বলেসব path অরক্ষিতসরু exclusion, মালিক আর মেয়াদসহ
পরিচিত শোনায় এমন CVE নম্বর বা directive বানিয়ে দেয়ভুল সিদ্ধান্তNVD আর আসল docs-এ মিলিয়ে দেখা

AI-কে config দিতে বলার সময় তাকে আপনার সিস্টেমের বিবরণ দিন, আর পর্যালোচক হিসেবে ব্যবহার করুন। কোনো secret, আসল domain-এর log বা গ্রাহকের তথ্য পেস্ট করবেন না।

তুমি একজন সতর্ক web security পর্যালোচক। আমার নিজের সাইটের জন্য নিচের header config পর্যালোচনা করো। সাইটটি Apache shared hosting-এ চলে, আর বাইরের একটি payment script ও একটি analytics script আছে। আসল domain ও secret সরিয়ে দিয়েছি। [এখানে .htaccess-এর header অংশ পেস্ট করুন] ১. প্রতিটি header কী করছে, সহজ বাংলায় বলো। ২. কোন header বাতিল বা অকার্যকর? MDN বা OWASP-এর কোন পাতায় মিলিয়ে দেখব, বলো। ৩. কোন পরিবর্তনে সাইট ভাঙতে পারে? সেটি Report-Only বা ছোট max-age দিয়ে কীভাবে ধাপে ধাপে চালাব? ৪. কোথাও unsafe-inline, 777 বা * প্রস্তাব করবে না, যদি না কারণ ও ঝুঁকি স্পষ্ট করে বলো। ৫. যা নিশ্চিত নও, সেটি স্পষ্ট করে “নিশ্চিত নই” বলো। CVE নম্বর বানাবে না।

AI-এর পরামর্শ চালু করার আগে

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোর পেছনে বাস্তব ঘটনা আছে।

প্র ০১HSTS টিকে গেল, কিন্তু একই ধরনের header HPKP বাতিল হলো। কেন?

দুটি header-এরই যুক্তি একই: browser-কে কিছু একটা মনে রাখতে বলা। ২০০৯ সালে Moxie Marlinspike-এর sslstrip গবেষণা দেখায়, ব্যবহারকারী HTTP-তে শুরু করলে পথের মাঝের কেউ তাঁকে HTTP-তেই আটকে রাখতে পারে। উত্তর হিসেবে এলো HSTS, RFC 6797 (২০১২): “এই সাইটে সবসময় HTTPS”.

HPKP (RFC 7469, ২০১৫) আরও এক ধাপ এগোল: “এই সাইটে শুধু এই নির্দিষ্ট key-র certificate চলবে।” উদ্দেশ্য ছিল ভুল CA থেকে জারি হওয়া certificate ঠেকানো। ধারণাটি শক্ত, কিন্তু ঝুঁকি ছিল বিশাল। Key হারালে বা ভুল key pin করলে browser মেয়াদ শেষ না হওয়া পর্যন্ত সাইটটি খুলতেই দেবে না। মালিকের নিজের কোনো সমাধান থাকত না। HPKP-র আরও একটি ভয়ের দিক ছিল: সাইট সাময়িকভাবে দখলকারী কেউ নিজের key pin করে আসল মালিককেই বাইরে রাখতে পারতেন। ফলে খুব কম সাইট এটি ব্যবহার করল, আর browser-গুলো সমর্থন তুলে নিল।

HSTS-এর ভুলও কষ্টদায়ক, কিন্তু সমাধানযোগ্য। HTTPS ঠিক করলেই সাইট আবার খোলে, যেকোনো বৈধ certificate দিয়ে। আর Certificate Transparency ভুল certificate ধরার কাজ করল অন্য পথে: browser-এ আটকানোর বদলে সবার দেখার জন্য প্রকাশ করে। এটি ব্যর্থ হলেও সাইট বন্ধ হয় না।

এই গল্প থেকেই এই পর্বের rollout-এর যুক্তি আসে। Header-এর মান শুধু “কতটা শক্ত” দিয়ে মাপা যায় না। মাপতে হয় “ভুল হলে কতটা ফেরা যায়” দিয়েও। তাই HSTS-এর ৫ মিনিট থেকে শুরু করা ভীরুতা নয়, ভালো প্রকৌশল। বাড়ির নতুন তালা লাগানোর আগে অতিরিক্ত চাবিটি হাতে আছে কি না দেখে নেওয়ার মতো।

মূল উপলব্ধি: যে নিরাপত্তা-ব্যবস্থার ভুল মালিককেই স্থায়ীভাবে বাইরে রাখে, সেটি যত শক্তই হোক, মানুষ ব্যবহার করবে না। ফেরার পথ নকশারই অংশ।

প্র ০২একটি সুরক্ষা-ব্যবস্থা কীভাবে নিজেই ঝুঁকি হয়ে উঠতে পারে? X-XSS-Protection-এর গল্প কী শেখায়?

২০০০-এর দশকের শেষে reflected XSS খুব সাধারণ ছিল। Browser-নির্মাতারা একটি বুদ্ধিমান কৌশল আনলেন। Internet Explorer 8-এ (২০০৮-০৯) XSS Filter আসে, পরে WebKit আর Chrome-এ আসে XSS Auditor. ধারণাটি ছিল: অনুরোধের লেখা যদি উত্তরের script-এর সাথে মিলে যায়, তবে সেটি আটকে দেওয়া হবে। X-XSS-Protection header ছিল এই filter-কে নিয়ন্ত্রণের উপায়।

দীর্ঘদিনে দুটি সমস্যা স্পষ্ট হলো। প্রথমত, filter সব কিছু ধরতে পারত না, কারণ এটি বাইরে থেকে অনুমান করত। দ্বিতীয়টি আরও আশ্চর্য। MDN-এর ব্যাখ্যা মতে filter কখনো একটি নিরীহ script-কে ভুল করে “ঢোকানো” ভেবে সরিয়ে দিতে পারত, আর সেই script-এর ওপর নির্ভর করা পাতার আচরণ বদলে যেত। অর্থাৎ যে সাইটে ত্রুটি ছিল না, সেখানেও ঝুঁকি তৈরি হতে পারত। কিছু ক্ষেত্রে block হওয়া আর না হওয়ার পার্থক্য থেকে তথ্য ফাঁসের প্রশ্নও ওঠে।

Chrome ২০১৯ সালে নিজের XSS Auditor সরিয়ে নেয়। Firefox এটি কখনো গ্রহণ করেনি। ফলে header-টি আজ মূলত ইতিহাস, যদিও পুরোনো tutorial আর AI-র উত্তরে এখনো ভেসে আসে।

এর উল্টো পথ CSP: ব্রাউজার অনুমান করে না, সাইট মালিক নিজে বলেন কী অনুমোদিত। Positive model আগের negative অনুমানের জায়গা নিল। এই পর্বের WAF-এর আলোচনার সাথে মিলটি লক্ষ করুন। অনুমান-ভিত্তিক filter দরকারি হতে পারে, কিন্তু সেটি নিজের ভুলও নিয়ে আসে। যে নিয়ম মালিক নিজে লেখেন, সেটি পড়া যায়, পরীক্ষা করা যায়, আর ভুল হলে কোথায় ভুল তা খুঁজে পাওয়া যায়।

মূল উপলব্ধি: নিরাপত্তা-ব্যবস্থাও কোড, তাই তারও bug আর attack surface আছে। কোনো control যোগ করার আগে জিজ্ঞেস করুন: এটি ভুল করলে কী হবে? আর পুরোনো পরামর্শ অন্ধভাবে কপি করবেন না।

প্র ০৩Equifax ২০১৭-এর প্রকাশিত প্রতিবেদন থেকে “WAF আছে, patch পরে হবে” ভাবনা সম্পর্কে কী শেখা যায়?

২০১৭ সালে Equifax-এর ঘটনায় প্রায় ১৪.৫ কোটি মানুষের তথ্য ক্ষতিগ্রস্ত হয় বলে প্রকাশিত হয়েছে। মার্কিন GAO আর House Oversight Committee-র প্রতিবেদনে কিছু প্রতিরক্ষামূলক ব্যর্থতা উঠে আসে, যা এই পর্বের সাথে সরাসরি মেলে।

  • Patch পৌঁছায়নি: Apache Struts-এর ত্রুটির patch ২০১৭ সালের মার্চে প্রকাশিত হয়। নিয়মে দ্রুত patch করার কথা থাকলেও একটি পুরোনো সিস্টেমে তা হয়নি, আর scan-ও সেটি ধরেনি। অর্থাৎ asset inventory অসম্পূর্ণ ছিল।
  • নজরদারি অন্ধ ছিল: Traffic দেখার একটি device-এর certificate-এর মেয়াদ বহুদিন আগেই শেষ হয়েছিল। মেয়াদের হিসাব উৎসভেদে আলাদা। Certificate বদলানোর পরপরই সন্দেহজনক traffic চোখে পড়ে।
  • ভেতরে ঢোকার পরে বাধা কম ছিল: GAO-র বিবরণে দুর্বল segmentation আর secret রাখার সমস্যার কথা আছে।

এখান থেকে তিনটি শিক্ষা। এক, সুরক্ষা-যন্ত্র কাজ করছে কি না, সেটিও monitor করতে হয়। বন্ধ WAF বা অন্ধ যন্ত্র কোনো alert দেয় না, চুপ করে থাকে। দুই, virtual patch সময় কেনার জন্য, আর সেই সময়ের একটি মালিক আর শেষ তারিখ থাকা দরকার। তিন, দায়িত্ব অস্পষ্ট থাকলে “কেউ নিশ্চয়ই করছেন” ধারণা সবচেয়ে বিপজ্জনক। House-এর প্রতিবেদন এই ঘটনাকে প্রতিরোধযোগ্য বলে বর্ণনা করেছে।

আপনার ছোট সাইটের ভাষায় এর মানে: certificate-এর মেয়াদের alert, plugin update-এর দিন, আর WAF rule-এর পর্যালোচনার তারিখ কারও ক্যালেন্ডারে থাকা চাই। সেই ক্যালেন্ডারের মালিক একজন নির্দিষ্ট মানুষ হওয়া দরকার, “পুরো দল” নয়। আর মাসে একবার নিজের হাতে দেখে নিন, alert সত্যিই আসে কি না।

মূল উপলব্ধি: বড় ঘটনার পেছনে প্রায়ই সাধারণ কাজের ফাঁক থাকে, রহস্যময় কিছু নয়। Control বসানো শুরু, সেটি চালু আছে কি না নিয়মিত যাচাই করাই আসল কাজ।

প্র ০৪polyfill.io-র ঘটনায় SRI কি বাঁচাতে পারত? তৃতীয় পক্ষের script-এ বিশ্বাসের সীমা কোথায়?

Polyfill হলো পুরোনো browser-এ নতুন feature যোগ করার ছোট কোড। polyfill.io বহু বছর একটি সুবিধাজনক সেবা ছিল। পাতায় একটি লাইন, আর script আসত। Sansec-এর প্রতিবেদন অনুযায়ী ২০২৪ সালের ফেব্রুয়ারিতে domain আর GitHub account-এর মালিকানা বদলায়, আর পরে দেখা যায় script নির্দিষ্ট কিছু মোবাইল ব্যবহারকারীকে অন্য সাইটে পাঠাচ্ছে। এই কোড admin-দের এড়িয়ে যেত বলেও প্রতিবেদনে বলা হয়েছে। ফলে সাইটের মালিকদের চোখে সহজে পড়ত না।

প্রথম নজরে মনে হয়, SRI থাকলেই হতো। কিন্তু প্রশ্নটি জটিল। এই সেবার script অনুরোধকারী browser অনুযায়ী বদলাত বলেই জানা যায়, ফলে একটি স্থির hash সেখানে বসানো কঠিন ছিল। তাই এই নির্দিষ্ট ক্ষেত্রে SRI-র প্রয়োগ কতটা বাস্তবসম্মত ছিল, তা নিয়ে বিশ্লেষকদের মধ্যেও সতর্ক ভাষা দেখা যায়। স্থির, version-করা ফাইলের ক্ষেত্রে SRI সরাসরি কাজের। পরিবর্তনশীল সেবায় নয়।

তাহলে কী করা যেত? এক, প্রয়োজন যাচাই: মূল লেখকও জানিয়েছিলেন আধুনিক সাইটে এর দরকার নেই বললেই চলে। দুই, নিজের সার্ভারে একটি নির্দিষ্ট version রাখা। তিন, CSP দিয়ে কোন domain-এ script যেতে ও আসতে পারবে, তা সীমিত করা। চার, domain-এর মালিকানা বদলের খবর পেলে পর্যালোচনা। পাঁচ, সাইটে কোন বাইরের script আছে, তার তালিকা রাখা, যাতে খবর পেলেই খুঁজে পাওয়া যায়।

Supply chain-এর এই সমস্যা শুধু বড় সাইটের নয়। ছোট সাইটের theme বা plugin-ও বাইরের script টেনে আনতে পারে, যা মালিক জানেনই না।

মূল উপলব্ধি: বাইরের script মানে আপনার সাইটে অন্য কারও লেখার অধিকার, আর সেই অধিকার মালিকানা বদলের সাথেও হস্তান্তর হতে পারে। প্রযুক্তিগত বাধার আগে প্রয়োজনের প্রশ্নটি করুন।

প্র ০৫“আমার ছোট সাইট, কে হামলা করবে?” — এই ভাবনা ভুল কেন?

এই ভাবনা ধরে নেয় হামলা মানেই কেউ একজন আপনাকে বেছে নিয়েছেন। কিন্তু ইন্টারনেটের বড় অংশের হামলা ব্যক্তিগত নয়, স্বয়ংক্রিয়। প্রোগ্রাম গোটা ইন্টারনেটের ঠিকানা ঘুরে পরিচিত দুর্বলতা খোঁজে, যেমন মাছ ধরার বিশাল জাল। জাল জানে না মাছটি কার। আপনার সাইটের নতুন certificate প্রকাশ্য CT log-এ ওঠার সাথে সাথে স্বয়ংক্রিয় অনুরোধ আসা শুরু হতে পারে। যেকোনো সার্ভারের access log দেখলেই এই পটভূমি শব্দ চোখে পড়ে।

ছোট সাইটের মূল্য তার তথ্যে সীমিত নয়। দখল হওয়া সাইট দিয়ে phishing পাতা বসানো যায়, spam পাঠানো যায়, search result-এ অন্য সাইটের লিংক গোঁজা যায়, বা malware ছড়ানো যায়। দর্শকের ক্ষতি হয়, আর আপনার domain-এর সুনাম নষ্ট হয়। সাইটকে search engine বা browser সতর্কতার তালিকায় তুললে ব্যবসা হঠাৎ থেমে যেতে পারে। Host পুরো account স্থগিতও করতে পারে।

অর্থনীতি এখানে স্পষ্ট। একটি পুরোনো plugin-এ ত্রুটি প্রকাশ হলে আক্রমণকারীর এক হাজার সাইট পরীক্ষার খরচ প্রায় শূন্য। পর্ব ৩২-এর KEV তালিকার পেছনের ধারণাও এটিই: যা সক্রিয়ভাবে ব্যবহৃত হচ্ছে, তা আগে সারান। তাই ছোট সাইটের প্রতিরক্ষার লক্ষ্য অপরাজেয় হওয়া নয়, “সহজ মাছ” না হওয়া।

ভালো খবর হলো, এই পর্বের checklist-এর বড় অংশই বিনা খরচে হয়: update, MFA, অপ্রয়োজনীয় plugin মোছা, ফাইলের অনুমতি ঠিক করা আর সার্ভারের বাইরে backup. বাজেট কম হলে এগুলোই আগে করুন, তারপর CDN বা WAF ভাবুন।

মূল উপলব্ধি: স্বয়ংক্রিয় ঝুঁকি আপনার পরিচয় দেখে না, শুধু আপনার দুর্বলতা দেখে। তাই “আমি ছোট” নিরাপত্তা নয়। সস্তা, নিয়মিত অভ্যাসই ছোট সাইটের সবচেয়ে বড় প্রতিরক্ষা।

অনুশীলন

সব কাজ নিজের সাইট, নিজের lab বা নিচের কাল্পনিক ডেটার ওপর করুন।

  1. হিসাব: (ক) max-age=15552000 কত দিন? (খ) একটি WAF rule-এ এক সপ্তাহে ২০০ hit, যার ৩০টি সত্যিকারের। Precision কত? ১৬০টি false positive একটি সরু ছাড়ে বাদ দিলে নতুন precision কত? (গ) SHA-256 আর SHA-512-এর SRI মানে padding-সহ কত base64 অক্ষর?

    (ক) ১৫,৫৫২,০০০ ÷ ৮৬,৪০০ = ১৮০ দিন।

    (খ) ৩০ ÷ ২০০ = ১৫%. ছাড়ের পরে মোট hit ২০০ − ১৬০ = ৪০, সত্য ৩০টি অপরিবর্তিত, তাই ৩০ ÷ ৪০ = ৭৫%.

    (গ) Base64-এ প্রতি ৩ byte-এর দলে ৪ অক্ষর, শেষ দল অসম্পূর্ণ হলেও ৪ অক্ষর (padding = সহ)। SHA-256 = ৩২ byte, ⌈৩২ ÷ ৩⌉ = ১১ দল, ১১ × ৪ = ৪৪ অক্ষর। SHA-512 = ৬৪ byte, ⌈৬৪ ÷ ৩⌉ = ২২ দল, ২২ × ৪ = ৮৮ অক্ষর। সাথে sha256- বা sha512- prefix আলাদা।

  2. নিজের সাইটের নিরীক্ষা (command): নিজের hosting account বা একটি পরীক্ষার ফোল্ডারে অংশ ৪-এর দুটি find লাইন চালান। ফল দেখে একটি তালিকা বানান: কোনটি সরাবেন, কোনটির permission বদলাবেন। সাথে নিজের সাইটের জন্য একটি ফাইলের SRI মান openssl দিয়ে বের করুন।

    প্রথম find-এ কিছু পেলে web root থেকে সরান। পড়ে দেখুন ফাইলে secret ছিল কি না। থাকলে সেগুলো বদলান। দ্বিতীয় find-এ অন্যের লেখার অধিকার পেলে ফোল্ডারে chmod 755, ফাইলে chmod 644 দিন। আগে দেখে নিন host কী সুপারিশ করে। কোনো সাইটের আসল ফাইল মুছে ফেলার আগে backup নিন। SRI-র জন্য MDN-এর পদ্ধতি:

    openssl dgst -sha384 -binary lib.min.js | openssl base64 -A

    ফলের আগে sha384- বসালেই integrity-র মান পাবেন। ফাইলের এক byte বদলে আবার চালিয়ে দেখুন মানটি পুরো বদলে যায়।

  3. সমালোচনা করুন: একটি কাল্পনিক নতুন সাইট প্রথম দিনেই নিচের header দিয়ে চালু হচ্ছে। সাইটের old.shop.example subdomain এখনো শুধু HTTP-তে চলে। কী কী বদলাবেন, আর কেন?
    Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
    Content-Security-Policy: default-src * 'unsafe-inline' 'unsafe-eval'
    X-XSS-Protection: 1; mode=block
    Access-Control-Allow-Origin: *
    Server: Apache/2.4.41 (Ubuntu)
    • HSTS: includeSubDomains দিলে old subdomain ভাঙবে, কারণ সেটি শুধু HTTP. Preload আরও বেশি বাঁধে। আগে subdomain-টি HTTPS-এ আনুন বা বন্ধ করুন। শুরু করুন max-age=300 দিয়ে।
    • CSP: নীতিটি প্রায় সব খুলে রেখেছে, তাই নামেই CSP. Report-Only-তে একটি কড়া নীতি দিয়ে শুরু করুন।
    • X-XSS-Protection: বাদ দিন।
    • CORS: * কেন লাগছে, জিজ্ঞেস করুন। Public ফাইলের জন্য ঠিক হতে পারে, কিন্তু সব পাতায় ছড়ানো উচিত নয় (পর্ব ৩৬)।
    • Server: Version লুকান, আর পুরোনো software patch করুন।
    • যা নেই: nosniff, Referrer-Policy, Permissions-Policy.
  4. আউটপুট অনুমান করুন: ধরুন add_header-এর default inheritance চলছে। GET /index.html আর GET /app/page.html, দুটিরই 200 উত্তরে কোন কোন header থাকবে?
    server {
        add_header X-Content-Type-Options "nosniff" always;
        add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    
        location / { }
    
        location /app/ {
            add_header Cache-Control "no-store";
        }
    }

    /index.html: X-Content-Type-Options আর Referrer-Policy, কারণ location /-এ নিজের কোনো add_header নেই, তাই ওপরের দুটি আসে।

    /app/page.html: শুধু Cache-Control: no-store. ওই location-এ একটি add_header থাকায় ওপরের দুটি আর উত্তরাধিকারে আসে না। অর্থাৎ অ্যাপের সবচেয়ে সংবেদনশীল পাতাটিই নীরবে header হারাল। সমাধান: location-এ দরকারি header আবার লিখুন, একটি include ফাইল ব্যবহার করুন, আর deploy-এর পরে আসল response যাচাই করুন।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি চালু সাইটে HSTS ছাড়ার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. সব host-এ HTTPS আর HTTP থেকে 301 redirect যাচাই
  2. max-age=300 পাঠিয়ে সমস্যা দেখা
  3. মেয়াদ বাড়িয়ে ১ দিন, তারপর ৭ দিন
  4. পূর্ণ মেয়াদ, ৩১,৫৩৬,০০০ সেকেন্ড
  5. সব subdomain যাচাইয়ের পরে includeSubDomains

একটি সাইট HTTP আর HTTPS, দুই ধরনের উত্তরেই Strict-Transport-Security পাঠায়। Browser HTTP উত্তরের header নিয়ে কী করে?

  • সেটি মেনে পরের ভিজিটে HTTPS ব্যবহার করে
  • উপেক্ষা করে
  • পাতা লোড করা বন্ধ করে দেয়
  • Domain-টি preload তালিকায় যোগ করে

Content-Security-Policy-Report-Only header-এর আচরণ কোনটি?

  • নীতি ভাঙা script আটকে দেয়, আর রিপোর্টও পাঠায়
  • শুধু HTML-এর meta tag-এ কাজ করে
  • কিছু আটকায় না, নীতি ভাঙলে শুধু জানায়
  • WAF-কে count mode-এ পাঠিয়ে দেয়

WAF-এর positive security model বলতে কী বোঝায়?

  • যা বৈধ বলে জানা, শুধু সেটুকু আসতে দেওয়া, বাকি সব আটকানো
  • চেনা খারাপ pattern আটকানো, বাকি সব আসতে দেওয়া
  • সব অনুরোধ যেতে দিয়ে শুধু log রাখা
  • প্রতি IP-র অনুরোধের সংখ্যা সীমিত করা

নিজের নতুন সাইটের header ঠিক করার সময় X-XSS-Protection নিয়ে সবচেয়ে ভালো সিদ্ধান্ত কোনটি?

  • সবসময় 1; mode=block পাঠানো
  • CSP বাদ দিয়ে শুধু এটি রাখা
  • HSTS চালু থাকলে এটিও চালু করা
  • না পাঠানো বা 0 দেওয়া, আর XSS-এর জন্য encoding ও CSP ব্যবহার করা

নিজের সাইটের web root-এ একটি public .env ফাইল পেলেন, যেখানে database password আছে। সবচেয়ে সঠিক সাড়া কোনটি?

  • ফাইলটি মুছে ফেলা, এটুকুই যথেষ্ট
  • ফাইল সরানো, সব secret বদলানো, access log দেখা আর deploy প্রক্রিয়া ঠিক করা
  • ফাইলের নাম বদলে দেওয়া
  • শুধু একটি WAF rule বসানো

একটি script tag-এ integrity দেওয়া আছে। CDN থেকে আসা ফাইলের hash মিলল না। Browser কী করে?

  • সতর্কতা দেখিয়ে ফাইলটি চালায়
  • অন্য CDN থেকে একই ফাইল খুঁজে আনে
  • ফাইলটি প্রত্যাখ্যান করে, script চলে না
  • নতুন hash হিসাব করে integrity মান বদলে নেয়

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. Hardening মানে স্তর ধরে checklist, আর যাচাই শুধু নিজের বা অনুমোদিত সিস্টেমে।
  2. Header browser-কে নির্দেশ দেয়, কোড সারায় না। CSP আগে Report-Only, HSTS আগে ছোট max-age. X-XSS-Protection বাদ।
  3. Web root-এ .git, .env, backup নয়। ফাঁস হলে secret বদলান, শুধু মুছলে চলে না।
  4. ব্যবহারকারীকে সাধারণ বার্তা আর reference, log-এ বিবরণ, secret বাদ। Production-এ debug বন্ধ।
  5. WAF সময় কেনে, কোড সারায় না। Dependency-তে lockfile আর update, স্থির script-এ SRI, plugin কম, 777 নয়, আর restore-করা backup ও বাইরের monitoring.

পরের পর্বে: এই মডিউল শেষ। এতক্ষণ দেখলেন চালু সাইট কীভাবে শক্ত করবেন। এবার পেছনে যাব: নকশা থেকে release পর্যন্ত নিরাপত্তা কীভাবে শুরু থেকেই কাজের অংশ হয়, সেই Secure SDLC-তে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান