Foundationপ্রথম নীতি থেকে
LEVEL 10লেসন ১/২০মাঝারি৪৫ মিনিট

Threat Modeling ও STRIDE — কী রক্ষা করছি, কার থেকে

Threat Modeling and the STRIDE Framework

Security মানে 'সবকিছু রক্ষা করা' নয় — আগে জানতে হয় কী রক্ষা করছি, কার থেকে, আর কতটা রক্ষা করার মূল্য আছে; STRIDE সেই প্রশ্নগুলোকে ছয়টা structured category-তে ভেঙে দেয়, আর least privilege ও defense in depth সারা module জুড়ে ফিরে-ফিরে আসা দুই নীতি এখান থেকেই শুরু।

এই লেসন শেষে আপনি পারবেন

  • Threat modeling কেন এবং কীভাবে 'security'-কে একটা scope-করা সমস্যায় বদলে দেয় তা বুঝে, একটা system-এর asset, entry point, আর adversary capability আলাদাভাবে চিহ্নিত করতে পারবেন
  • STRIDE-এর ছয়টা threat category — Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, Elevation of Privilege — সংজ্ঞায়িত করে একটা running example system-এর ওপর প্রতিটা প্রয়োগ করে দেখাতে পারবেন
  • Attack surface-এর সংজ্ঞা বুঝে কোড চালিয়ে একটা toy attack-surface score গণনা করে দেখাতে পারবেন কেন exposure কমানো সবচেয়ে সস্তা security-বিনিয়োগ
  • Least privilege আর defense in depth — এই দুই core security principle ব্যাখ্যা করে একটা trust-boundary linter বানিয়ে বাস্তবে প্রয়োগ করতে পারবেন
  • একটা সম্পূর্ণ STRIDE threat-modeling pass (asset × category × threat × mitigation টেবিল) স্বাধীনভাবে চালিয়ে নতুন কোনো feature-এর ঝুঁকি বিশ্লেষণ করতে পারবেন

আগে এটা বুঝি

এই module-এর প্রথম লেসন — তাই একটা প্রশ্ন দিয়ে শুরু করা যাক যেটা বাকি সব লেসনের নিচে বসে থাকবে: “correct” আর “secure” — এই দুইটা কি একই জিনিস?

ধরুন একটা login form লিখলেন। Unit test পাস, integration test পাস, QA সাইন-অফ করল — সঠিক username আর password দিলে ঠিক login হয়, ভুল দিলে ঠিক reject হয়। স্পেসিফিকেশন অনুযায়ী প্রতিটা ইনপুটে প্রতিটা আউটপুট মিলছে — correctness-এর সংজ্ঞা অনুযায়ী প্রোগ্রামটা নিখুঁত। তারপরও সেই একই ফর্মে কেউ ' OR '1'='1 টাইপ করে যেকোনো user হিসেবে ঢুকে পড়তে পারে — কারণ কেউ কখনো এই ইনপুটটা টেস্ট করেনি। Correctness প্রশ্ন করে: “প্রত্যাশিত ইনপুটে সিস্টেম কি প্রত্যাশিত আউটপুট দেয়?” Security প্রশ্ন করে সম্পূর্ণ ভিন্ন কিছু: “কোনো বুদ্ধিমান, লক্ষ্যপ্রণোদিত adversary যদি ইচ্ছাকৃতভাবে সবচেয়ে খারাপ ইনপুট বেছে নেয়, তাহলে কী হয়?” টেস্ট স্যুট সহযোগী user-দের কল্পনা করে; adversary সহযোগী নয় — সে আপনার প্রতিটা assumption ভাঙতে চেষ্টা করবে।

এই পার্থক্যটাই security-কে কঠিন করে তোলে একটা বিশেষ কারণে: correctness-এর জন্য শেষ একটা bounded input space থাকে (spec যা বলেছে), কিন্তু security-র জন্য “সবকিছু রক্ষা করুন” বাস্তবিক কোনো নির্দেশনাই না। Budget সসীম, সময় সসীম, প্রতিটা feature-এর নিজস্ব ঝুঁকি আছে — তাই আসল প্রথম প্রশ্ন কখনোই “সব ঠেকাব কীভাবে” না, বরং: কোথায় সবচেয়ে বেশি ঝুঁকি, আর সেখানে টাকা-সময় ঢালা উচিত কেন? এই প্রশ্নের নিয়মতান্ত্রিক উত্তর দেওয়ার শৃঙ্খলার নাম threat modeling — আজকের লেসনের মূল বিষয়।

পুরো module জুড়ে একটা কাল্পনিক system ব্যবহার করব — নাম NoteShare: একটা সাধারণ note-taking web app। User login করে, note লেখে, note-এর সাথে ফাইল (ছবি, PDF) attach করতে পারে, আর চাইলে একটা note অন্য কাউকে share করতে পারে একটা link দিয়ে। একটা admin panel আছে যেখান থেকে অপারেটর user-account manage করে। ছোট, চেনা, বাস্তব-জগতের প্রায় প্রতিটা web app-এর মতোই — ঠিক এই কারণেই আজকের threat-modeling exercise-এর জন্য আদর্শ।

আজকের যাত্রা তিন ধাপে: প্রথমে threat modeling-কে একটা discipline হিসেবে দাঁড় করাব — asset, entry point, adversary capability। তারপর STRIDE — ছয় অক্ষরের একটা mnemonic যেটা “কী ভুল হতে পারে” প্রশ্নটাকে এলোমেলো brainstorm থেকে structured checklist-এ বদলে দেয়। শেষে দুইটা নীতি বের করে আনব যেগুলো এই module-এর প্রতিটা পরের লেসনে ফিরে ফিরে আসবে: least privilege (কাউকে দরকারের বেশি access দেবেন না) আর defense in depth (একটা স্তরের ওপর ভরসা করবেন না)।

মূল ধারণা

Threat modeling — চারটা প্রশ্ন

Adam Shostack-এর ফ্রেমওয়ার্ক অনুযায়ী threat modeling আসলে চারটা প্রশ্নের নিয়মতান্ত্রিক উত্তর:

#প্রশ্নমানে
১কী বানাচ্ছি?System-টার architecture, data flow, trust boundary — একটা ছবি বা diagram
২কী ভুল হতে পারে?Threat enumeration — STRIDE এখানেই কাজে লাগে
৩কী করব এটা নিয়ে?প্রতিটা threat-এর mitigation, বা সচেতনভাবে accept করা residual risk
৪কাজটা ভালো হলো কিনা?Review, test, আর architecture বদলালে আবার প্রথম প্রশ্নে ফেরত যাওয়া

লক্ষ্য করুন — চতুর্থ প্রশ্নটা প্রথম প্রশ্নে ফিরিয়ে নিয়ে যায়। Threat modeling একবারের কাজ না, একটা loop — নতুন feature মানেই নতুন data flow, নতুন data flow মানেই সম্ভাব্য নতুন threat।

Asset, entry point, adversary — তিনটা বিল্ডিং ব্লক

প্রথম প্রশ্নের (“কী বানাচ্ছি”) উত্তর দিতে তিনটা জিনিস স্পষ্ট করা লাগে:

Asset — যা রক্ষা করার মতো মূল্যবান। NoteShare-এ: user credential, session token, note-এর content, uploaded file, admin-এর access, আর নিজেই — availability/uptime।

Entry point (attack surface) — যেখান দিয়ে বাইরের কেউ system-কে touch করতে পারে। NoteShare-এ: login form, registration form, upload endpoint, note-share link, password-reset flow, admin panel, আর health-check endpoint। এই পুরো সেট-টাকেই বলা হয় attack surface — নিচের experiment অংশে এটাকেই সংখ্যায় মাপব।

Adversary model — কে আক্রমণ করতে পারে, তার ক্ষমতা কতটুকু। তিন স্তরের adversary বিবেচনা করা স্বাভাবিক: (১) anonymous external — internet থেকে যে কেউ, কোনো account নেই; (২) authenticated user — একটা বৈধ account আছে, কিন্তু অন্য user-এর ডেটা বা admin-এর ক্ষমতা চায়; (৩) insider/compromised component — একটা internal service বা employee-account নিজেই আক্রমণের উৎস হয়ে গেছে। প্রতিটা threat কোন adversary-র কাছে সম্ভব সেটা আলাদাভাবে ভাবতে হয় — anonymous user-এর জন্য যেটা কঠিন, authenticated user-এর জন্য সেটা তুচ্ছ হতে পারে।

এই তিনটার মাঝে যে সীমারেখা — কোন দিকে কম বিশ্বাস, কোন দিকে বেশি — তাকে বলে trust boundary। NoteShare-এ অন্তত দুইটা স্পষ্ট trust boundary: internet ↔ application (anonymous থেকে authenticated), আর application ↔ admin panel (সাধারণ user থেকে privileged operator)। diagram section-এ এটাই ছবি হয়ে আসবে।

STRIDE — “কী ভুল হতে পারে” প্রশ্নের ছয়টা lens

দ্বিতীয় প্রশ্নের (“কী ভুল হতে পারে”) জন্য এলোমেলো brainstorming করলে সহজেই কিছু বাদ পড়ে যায় — মানুষ যা চেনে সেটাই ভাবে, নতুন কিছু মিস করে। STRIDE (Loren Kohnfelder ও Praerit Garg, Microsoft, ১৯৯৯) এই ফাঁক বন্ধ করে একটা checklist দিয়ে — প্রতিটা component-এ ছয়টা প্রশ্নই জিজ্ঞেস করুন, একটাও বাদ না দিয়ে:

অক্ষরCategoryভাঙে যে security propertyএক লাইনে প্রশ্ন
SSpoofingAuthenticationকেউ কি অন্য কারো পরিচয় ধার করতে পারবে?
TTamperingIntegrityকেউ কি অনুমতি ছাড়া ডেটা বদলাতে পারবে?
RRepudiationNon-repudiationকেউ কি নিজের কাজ অস্বীকার করতে পারবে, প্রমাণ ছাড়া?
IInformation DisclosureConfidentialityকেউ কি এমন ডেটা দেখবে যা তার দেখার কথা না?
DDenial of ServiceAvailabilityকেউ কি system-কে বৈধ user-দের জন্য অকেজো করে দিতে পারবে?
EElevation of PrivilegeAuthorizationকেউ কি কম-অধিকার থেকে বেশি-অধিকারে পৌঁছাতে পারবে?

প্রতিটা অক্ষর একটা নির্দিষ্ট security property-র সরাসরি বিপরীত — এইজন্যই STRIDE শুধু মুখস্থ-করা mnemonic না, নিচের অংশে দেখব প্রতিটা category NoteShare-এর একটা নির্দিষ্ট, বাস্তব দুর্বলতায় রূপ নেয়।

ভেতরে কী ঘটছে

NoteShare-এর ওপর STRIDE — ছয়বার একই প্রশ্ন, ছয়টা ভিন্ন উত্তর

এবার সেই প্রশ্নগুলো সত্যিই জিজ্ঞেস করি, একটা একটা করে।

Spoofing। NoteShare-এর session token যদি predictable হয় (যেমন ক্রমবর্ধমান integer, বা সহজ hash), বা login endpoint-এ কোনো rate-limit/MFA না থাকে, তাহলে attacker হয় token guess করে বা credential-stuffing (leaked password-list দিয়ে বারবার চেষ্টা) করে অন্য user হিসেবে ঢুকে পড়তে পারে। Mitigation: cryptographically random session id, secure/httpOnly cookie flag, login attempt-এ rate limiting, sensitive action-এ MFA।

Tampering। ধরুন note edit করার API PUT /notes/id নেয়, আর সার্ভার শুধু চেক করে user login করেছে কিনা — কিন্তু চেক করে না note-টা তার কিনা। তাহলে যেকোনো logged-in user নিজের id বদলে অন্য কারো note edit করে ফেলতে পারে — এই ক্লাসের bug-এর নাম IDOR (Insecure Direct Object Reference)। আরেকটা পথ: note-এর content database-এ লেখার সময় যদি raw string concatenation দিয়ে SQL query বানানো হয়, SQL injection দিয়ে সরাসরি ডেটা বদলে ফেলা সম্ভব। Mitigation: প্রতিটা write-এ per-object ownership check, সবসময় parameterized query।

Repudiation। কেউ একটা malicious ফাইল upload করল বা অন্য user-এর note ডিলিট করে দিল — কিন্তু কোনো audit log নেই যা কে-কখন-কী করল তা প্রমাণ করে। User পরে অস্বীকার করলে (বা attacker নিজের ট্র্যাক মুছে দিলে) কিছুই যাচাই করা যায় না। Mitigation: append-only, tamper-evident audit log — প্রতিটা sensitive action (login, delete, share, admin action) কে-কখন করল তা রেকর্ড করা, আদর্শভাবে আলাদা storage-এ যাতে compromised app-server নিজের ট্র্যাক মুছতে না পারে।

Information disclosure। ফাইল storage যদি predictable path ব্যবহার করে (/uploads/1023.png, sequential id), attacker শুধু সংখ্যা বদলে বদলে অন্য user-দের সব ফাইল দেখে ফেলতে পারে — কোনো authorization bypass লাগেই না, শুধু enumerate করলেই হয়। Mitigation: unguessable random storage key (UUID) এবং প্রতিটা access-এ owner/permission check — দুইটাই লাগে, শুধু random নাম যথেষ্ট না যদি access-control পুরোপুরি অনুপস্থিত থাকে।

Denial of service। Upload endpoint যদি file-size limit বা rate-limit ছাড়া থাকে, attacker বারবার বড় ফাইল পাঠিয়ে ডিস্ক ভরিয়ে ফেলতে পারে, বা image-processing pipeline-কে বারবার ট্রিগার করে CPU নিঃশেষ করে দিতে পারে (একটাই crafted image দিয়ে বহুবার request)। Mitigation: request-size limit, per-user/per-IP rate limiting, processing-এর জন্য timeout ও resource quota।

Elevation of privilege। যদি uploaded file webroot-এর ভেতরে সংরক্ষিত হয় আর server ভুল content-type সহ সেটা serve করে, একটা .php/.js-জাতীয় ফাইল আপলোড করে সরাসরি সার্ভারে code চালানো সম্ভব হতে পারে — অথবা যদি admin panel শুধু “URL অনুমান করা কঠিন” এই ভরসায় রক্ষিত থাকে, কোনো role-check ছাড়াই, তাহলে সেই URL কেউ খুঁজে পেলেই সরাসরি admin ক্ষমতা পেয়ে যায়। এই দুইটাই একই প্যাটার্নের রূপ — কোনো এক জায়গায় “কম-অধিকার” থেকে “বেশি-অধিকার”-এ লাফ দেওয়ার রাস্তা খোলা রয়ে গেছে (উল্লেখ্য, একই পরিবারের আরেকটা ক্লাসিক উদাহরণ memory-safety জগতে — buffer overflow দিয়ে নিজের প্রসেসের ওপরই বেশি নিয়ন্ত্রণ নেওয়া, যদিও NoteShare-এর মতো ওয়েব-অ্যাপে এই নির্দিষ্ট রাস্তাটা কম প্রাসঙ্গিক)। Mitigation: uploaded file webroot-এর বাইরে রাখা, fixed/whitelisted content-type-এ serve করা, আর প্রতিটা privileged endpoint-এ URL-secrecy না, explicit role-check।

লক্ষ্য করার মতো একটা প্যাটার্ন: প্রতিটা mitigation-ই আসলে দুইটা নীতির কোনো একটার—বা দুইটারই—প্রয়োগ। “Note-টা তার কিনা চেক করুন” মানে সেই component-কে শুধু-দরকারি access দেওয়া — least privilege। “শুধু random নাম না, তার সাথে access-check-ও লাগবে” মানে একটা স্তরে ভরসা না করে একাধিক independent স্তর রাখা — defense in depth। এই দুইটা থ্রেড বাকি module জুড়ে বারবার ফিরে আসবে।

একটা privileged action-এ (যেমন note delete) কতগুলো স্বাধীন check বসতে পারে
  1. network edgeTLS — transport-এ কেউ শুনতে/বদলাতে না পারে
  2. authentication middlewaresession token বৈধ? কেউ একজন login করা আছে তো?
  3. authorization checkএই user কি *এই নির্দিষ্ট* note-এর owner/admin?
  4. business logicdelete-এর actual কাজ — এখানে পৌঁছানোর আগেই দুই স্তর পার হতে হয়েছে
  5. audit logকাজ সফল হোক বা ব্যর্থ, ঘটনাটা রেকর্ড হলো — repudiation ঠেকাতে

পাঁচটা স্তর, প্রতিটা স্বাধীন — এইটাই defense in depth-এর সংজ্ঞা বাস্তবে: authentication middleware-এ bug থাকলেও authorization check এখনো আটকাতে পারে; দুইটাতেই bug থাকলেও audit log অন্তত প্রমাণ রেখে দেয়। একটা স্তর ব্যর্থ হলে গল্প শেষ হয় না।

উদাহরণ

Worked STRIDE pass — NoteShare-এর পূর্ণ টেবিল

আগের অংশের ছয়টা অনুচ্ছেদকে এক জায়গায় এনে দাঁড় করালে এটাই সেই “asset × STRIDE category × threat × mitigation” pass — threat modeling-এর deliverable ঠিক এই আকৃতির হয়:

Asset / ComponentSTRIDEনির্দিষ্ট threatMitigation
Session / loginSpoofingPredictable token বা credential-stuffing দিয়ে impersonationRandom session id, rate-limit, MFA
Note content (write path)TamperingIDOR — note_id বদলে অন্যের note editPer-object ownership check প্রতিটা write-এ
Note content (write path)TamperingString-concat query দিয়ে SQL injectionসবসময় parameterized query
Delete/upload actionRepudiationকে করল প্রমাণ নেই, অস্বীকার করা যায়Append-only audit log, আলাদা storage
File storageInformation DisclosureSequential path enumerate করে অন্যের ফাইল দেখাRandom storage key + per-access permission check
Upload endpointDenial of ServiceSize-limit-হীন upload দিয়ে disk/CPU নিঃশেষSize limit, rate limit, processing timeout
Uploaded file servingElevation of PrivilegeWebroot-এ script হিসেবে চালানোWebroot-বাইরে storage, fixed content-type
Admin panelElevation of Privilegeশুধু URL-secrecy, কোনো role-check নেইExplicit role-check middleware

আটটা row, একটা ছোট app, আর প্রতিটার একটা নির্দিষ্ট mitigation — এটাই threat modeling-এর আসল output: গল্প নয়, একটা actionable checklist যা engineer-রা সরাসরি ticket বানিয়ে কাজে লাগাতে পারে।

ছবিতে

   ┌─────────────┐   trust boundary #1 (internet ↔ app)
   │  Browser    │   ═══════════════════════════════════
   │ (anonymous  │──login/upload/share──►┌──────────────┐
   │  or user)   │◄──response────────────│  Web App /   │
   └─────────────┘                       │  Backend API │
                                          └──────┬───────┘
                                                 │        ┌────────────┐
                                                 ├───────►│  Database  │
                                                 │        └────────────┘
                                                 │        ┌────────────┐
                                                 └───────►│File Storage│
                                                          └────────────┘
   trust boundary #2 (app ↔ admin)
   ═══════════════════════════════
   ┌─────────────┐
   │   Admin     │──manage users───────►┌──────────────┐
   │  (operator) │◄──response────────────│ Admin Panel  │──►Database
   └─────────────┘                       └──────────────┘
NoteShare-এর data flow diagram — দুইটা trust boundary (internet↔app, app↔admin) স্পষ্ট করে দেখানো; প্রতিটা boundary-পার-হওয়া arrow-ই একটা সম্ভাব্য STRIDE-target।
  attacker request
        │
        ▼
  ┌───────────┐   fail ──► request dropped, কিছুই এগোয় না
  │ TLS/edge  │
  └─────┬─────┘
        │ pass
        ▼
  ┌───────────┐   fail ──► 401, কিন্তু business logic-এ পৌঁছায়নি
  │ AuthN     │
  └─────┬─────┘
        │ pass
        ▼
  ┌───────────┐   fail ──► 403, ownership-check-এই থেমে গেল
  │ AuthZ     │
  └─────┬─────┘
        │ pass
        ▼
  ┌───────────┐
  │ business  │──► audit log (সফল হোক ব্যর্থ হোক, ঘটনা রেকর্ড থাকে)
  │ logic     │
  └───────────┘
Defense in depth — একই privileged action-এর পথে পরপর কয়েকটা স্বাধীন প্রতিরক্ষা-স্তর; কোনো একটা স্তর একা 'পুরো নিরাপত্তা' না, প্রতিটাই একটা backup।

নিজে চালিয়ে দেখুন

EXPERIMENT

Attack surface score — exposure সংখ্যায় মাপা, আর কমানোর প্রভাব দেখা

macOS / Linux / Windows (Python 3)· ১৫ মিনিট
# attack_surface_score.py — toy "attack surface score" for a set of exposed endpoints
ENDPOINTS = [
    # name, public(bool), requires_auth(bool), permission_level(1..3), accepts_file_input(bool)
    ("POST /login",           True,  False, 1, False),
    ("POST /register",        True,  False, 1, False),
    ("POST /upload",          True,  True,  2, True),
    ("GET  /files/<id>",      True,  True,  2, False),
    ("GET  /admin/users",     True,  True,  3, False),
    ("POST /password-reset",  True,  False, 1, False),
    ("GET  /health",          True,  False, 1, False),
]

def endpoint_score(public, requires_auth, permission_level, accepts_file_input):
    exposure = 2 if public else 1
    auth_gap = 3 if not requires_auth else 1
    file_risk = 2 if accepts_file_input else 1
    return exposure * auth_gap * permission_level * file_risk

def attack_surface_score(endpoints):
    return sum(endpoint_score(p, a, lvl, f) for _, p, a, lvl, f in endpoints)

print("প্রতিটা endpoint-এর score:")
total = 0
for name, public, auth, lvl, file_in in ENDPOINTS:
    s = endpoint_score(public, auth, lvl, file_in)
    total += s
    print(f"  {name:<22} public={str(public):<5} auth={str(auth):<5} perm={lvl} file={str(file_in):<5} -> score={s}")
print(f"মোট attack surface score: {total}")

print("\n— দুইটা mitigation apply করার পরে —")
MITIGATED = [
    ("POST /login",           True,  False, 1, False),
    ("POST /register",        True,  False, 1, False),
    ("POST /upload",          True,  True,  2, True),
    ("GET  /files/<id>",      True,  True,  2, False),
    ("GET  /admin/users",     False, True,  3, False),   # internal-only now
    ("POST /password-reset",  True,  False, 1, False),
    ("GET  /health",          False, False, 1, False),   # internal monitoring only
]
total2 = 0
for name, public, auth, lvl, file_in in MITIGATED:
    s = endpoint_score(public, auth, lvl, file_in)
    total2 += s
    print(f"  {name:<22} public={str(public):<5} auth={str(auth):<5} perm={lvl} file={str(file_in):<5} -> score={s}")
print(f"মোট attack surface score (mitigated): {total2}")
reduction = 100 * (total - total2) / total
print(f"হ্রাস: {total - total2} পয়েন্ট ({reduction:.1f}%)")

প্রত্যাশিত output (deterministic — হুবহু এটাই আসবে):

প্রতিটা endpoint-এর score:
  POST /login            public=True  auth=False perm=1 file=False -> score=6
  POST /register         public=True  auth=False perm=1 file=False -> score=6
  POST /upload           public=True  auth=True  perm=2 file=True  -> score=8
  GET  /files/<id>       public=True  auth=True  perm=2 file=False -> score=4
  GET  /admin/users      public=True  auth=True  perm=3 file=False -> score=6
  POST /password-reset   public=True  auth=False perm=1 file=False -> score=6
  GET  /health           public=True  auth=False perm=1 file=False -> score=6
মোট attack surface score: 42

— দুইটা mitigation apply করার পরে —
  POST /login            public=True  auth=False perm=1 file=False -> score=6
  POST /register         public=True  auth=False perm=1 file=False -> score=6
  POST /upload           public=True  auth=True  perm=2 file=True  -> score=8
  GET  /files/<id>       public=True  auth=True  perm=2 file=False -> score=4
  GET  /admin/users      public=False auth=True  perm=3 file=False -> score=3
  POST /password-reset   public=True  auth=False perm=1 file=False -> score=6
  GET  /health           public=False auth=False perm=1 file=False -> score=3
মোট attack surface score (mitigated): 36
হ্রাস: 6 পয়েন্ট (14.3%)

তিনটা লক্ষ্য করার মতো জিনিস: (১) /upload-এর score সবচেয়ে বেশি (৮) — কারণ এতে auth এবং file-input দুইটাই ঝুঁকি একসাথে; (২) /admin/users-কে শুধু internal বানিয়ে দিলেই (public থেকে non-public) স্কোর ৬ থেকে ৩-এ নেমে গেল — অর্ধেক, কোনো feature না হারিয়েই; (৩) মোট system-score ১৪.৩% কমল মাত্র দুইটা config-বদল দিয়ে — নতুন কোনো কোড না লিখেই, শুধু “এই endpoint-টা কি সত্যিই public হওয়া দরকার” প্রশ্নটা জিজ্ঞেস করেই। এটাই attack surface কমানো কেন সবচেয়ে সস্তা security-বিনিয়োগ তার সংখ্যাগত প্রমাণ।

এটা কী প্রমাণ করে

Attack surface একটা abstract ধারণা নয় — প্রতিটা endpoint-কে exposure (public/internal), auth-gap, permission-level, আর input-risk অনুযায়ী স্কোর দিলে পুরো system-এর জন্য একটা তুলনাযোগ্য সংখ্যা পাওয়া যায়; আর দুইটা ছোট mitigation (admin panel internal করা, health-check public না রাখা) প্রয়োগ করলেই সেই সংখ্যা মাপযোগ্যভাবে কমে — least-privilege-এর সস্তা প্রথম পদক্ষেপ কতটা কার্যকর, এটাই তার প্রমাণ।

নিজে বানান

BUILD IT

TrustBoundaryLinter — unguarded trust-boundary crossing স্বয়ংক্রিয়ভাবে খুঁজে বের করা

Python · ●●○○○
  1. System-কে component + trust-level হিসেবে model করুন (internet=0 সবচেয়ে কম বিশ্বাস, admin-panel=সবচেয়ে বেশি)
  2. প্রতিটা data flow-কে (source, dest, protected, description) হিসেবে সংজ্ঞায়িত করুন
  3. একটা লিন্টার লিখুন যা trust-level-এ ফারাক-থাকা কিন্তু protected=False এমন flow flag করে
  4. NoteShare-এর সাতটা flow চালিয়ে দেখুন কয়টা flag হয়
  5. দুইটা fix apply করুন (file-storage write আর admin-panel access) — flag-সংখ্যা শূন্যে নামা দেখান

লক্ষ্য: “least privilege ঠিকমতো প্রয়োগ হয়েছে কিনা” প্রশ্নটা manual review-এর বদলে একটা ছোট, স্বয়ংক্রিয় check-এ পরিণত করা — বাস্তব জগতে এই ধরনের linter CI pipeline-এ বসিয়ে প্রতিটা architecture-বদলে চালানো যায়।

# trust_boundary_linter.py — unguarded trust-boundary crossing খুঁজে বের করা
TRUST = {
    "internet":      0,  # anonymous external user/attacker
    "app-frontend":  1,  # authenticated request handling
    "app-backend":   2,  # business logic, session-validated
    "admin-panel":   3,  # privileged operator
    "database":      2,  # data store
    "file-storage":  1,  # attacker-influenced content lands here directly
}

# flows: (source, dest, protected, description)
FLOWS = [
    ("internet", "app-frontend", True,  "login form POST — TLS + credential check"),
    ("internet", "app-frontend", True,  "upload form POST — TLS, session cookie required"),
    ("app-frontend", "app-backend", True, "session-validated API call"),
    ("app-backend", "database", True,  "parameterized query, backend-only credentials"),
    ("app-backend", "file-storage", False, "file write — path constructed from user filename"),
    ("internet", "admin-panel", False, "admin URL guessable, no separate auth check found"),
    ("admin-panel", "database", True, "admin ORM query, role-checked"),
]

def trust_gap(a, b):
    return abs(TRUST[a] - TRUST[b])

def lint(flows):
    findings = []
    for src, dst, protected, desc in flows:
        gap = trust_gap(src, dst)
        if gap >= 1 and not protected:
            findings.append((src, dst, gap, desc))
    return findings

print("— NoteShare data flow লিন্ট —")
findings = lint(FLOWS)
for src, dst, gap, desc in findings:
    print(f"  FLAG  {src:<12} -> {dst:<12} (trust gap={gap})  {desc}")
print(f"\nমোট unguarded trust-boundary crossing: {len(findings)}")

print("\n— fix apply করে আবার —")
FIXED = list(FLOWS)
FIXED[4] = ("app-backend", "file-storage", True, "file write — filename sanitized, random storage key, no path from user input")
FIXED[5] = ("internet", "admin-panel", True, "admin URL + separate admin-role auth middleware added")

findings2 = lint(FIXED)
for src, dst, gap, desc in findings2:
    print(f"  FLAG  {src:<12} -> {dst:<12} (trust gap={gap})  {desc}")
print(f"fix-এর পরে unguarded crossing: {len(findings2)}")

প্রত্যাশিত output (deterministic — হুবহু এটাই আসবে):

— NoteShare data flow লিন্ট —
  FLAG  app-backend  -> file-storage (trust gap=1)  file write — path constructed from user filename
  FLAG  internet     -> admin-panel  (trust gap=3)  admin URL guessable, no separate auth check found

মোট unguarded trust-boundary crossing: 2

— fix apply করে আবার —
fix-এর পরে unguarded crossing: 0

দুইটা জিনিস লক্ষ্য করুন: database-এর trust-level app-backend-এর সমান (২) বলে সেই flow কখনোই flag হয়নি — কারণ trust gap শূন্য, কোনো boundary পার হয়নি। কিন্তু file-storage-কে ইচ্ছাকৃতভাবে trust-level ১ দেওয়া হয়েছে (২ না) — কারণ ওখানে সরাসরি attacker-নিয়ন্ত্রিত filename পৌঁছাচ্ছে, formal component-hierarchy যাই বলুক না কেন। এটাই মূল শিক্ষা: trust-level বসানো নিজেই একটা judgment call, linter শুধু সেই judgment-কে consistently প্রয়োগ করে — bug খুঁজে দেয় না, প্রশ্নটা জিজ্ঞেস করতে ভোলে না।

নিজে বাড়ান:

  1. একটা নতুন flow যোগ করুন — app-frontend -> file-storage সরাসরি (backend বাইপাস করে) — linter কী বলে, আর বাস্তবে এমন shortcut কেন বিপজ্জনক?
  2. protected বুলিয়ানের বদলে একটা controls: list[str] (যেমন ["auth", "rate-limit"]) ব্যবহার করুন — নির্দিষ্ট trust-gap-এর জন্য কোন control বাধ্যতামূলক তা policy হিসেবে লিখুন
  3. একই linter অন্য কোনো system-এ (যেমন আপনার নিজের কোনো side-project) প্রয়োগ করে দেখুন — কয়টা flag আসে?

বাস্তব সিস্টেমে

বাস্তব জগতে threat modeling ও এর অভাব

Microsoft SDL। STRIDE ১৯৯৯ সালে তৈরি হওয়ার পর ২০০২-এ Bill Gates-এর বিখ্যাত “Trustworthy Computing” memo-র পরে পুরো Microsoft Security Development Lifecycle (SDL)-এর কেন্দ্রীয় হাতিয়ার হয়ে ওঠে — আজও Microsoft-এর প্রতিটা নতুন feature design-review-এ STRIDE pass বাধ্যতামূলক।

Equifax breach (2017)। একটা unpatched Apache Struts vulnerability (CVE-2017-5638) মাসের পর মাস production-এ থেকে গিয়েছিল — classic attack-surface mismanagement: known vulnerability patch করা হয়নি, ফলাফল ১৪ কোটির বেশি মানুষের personal data leak (Information Disclosure, সরাসরি tampering-ও ছিল data-access-এ)। যেটা মূলত একটা patch-management ব্যর্থতা, সেটাই attack-surface-এর একটা component (পুরনো library) কতদিন exposed থাকতে দেওয়া হচ্ছে তার প্রশ্ন।

Capital One breach (2019)। একটা misconfigured WAF-কে ব্যবহার করে Server-Side Request Forgery (SSRF) দিয়ে attacker AWS metadata service থেকে অতিরিক্ত-ক্ষমতাসম্পন্ন IAM role-এর credential হাতিয়ে নেয় — সেই role-টার প্রয়োজনের চেয়ে অনেক বেশি S3-access permission ছিল। এটা প্রায় পাঠ্যপুস্তক-উদাহরণ একসাথে দুইটা ভুলের: Elevation of Privilege (SSRF দিয়ে মেটাডেটা service-এ পৌঁছানো) এবং least-privilege লঙ্ঘন (IAM role-এর দরকারের চেয়ে বেশি অধিকার) — দুইটা সমস্যা একসাথে না থাকলে ক্ষতি এত বড় হতো না।

Target breach (2013)। Attacker ঢুকেছিল একটা third-party HVAC (heating/cooling) vendor-এর credential দিয়ে — সেই vendor-এর access থাকার কথা ছিল শুধু billing system-এ, কিন্তু network segmentation দুর্বল থাকায় সেখান থেকে payment-processing system পর্যন্ত পৌঁছানো গিয়েছিল। Defense-in-depth-এর অনুপস্থিতির ক্লাসিক উদাহরণ: একটা মাত্র compromised vendor-credential-ই পুরো payment infrastructure-এ পৌঁছানোর জন্য যথেষ্ট ছিল, মাঝে কোনো দ্বিতীয় স্বাধীন স্তর ছিল না।

যে ভুলগুলো সবাই করে

“Threat modeling মানে সব সম্ভাব্য আক্রমণ ঠেকিয়ে দেওয়া — 'perfect security' অর্জন করা।”

Perfect security বাস্তবিক লক্ষ্য না — সময়-বাজেট সসীম, আর প্রতিটা mitigation-এর নিজস্ব খরচ আছে (development time, latency, user-experience friction)। থ্রেট মডেলিং-এর আসল কাজ prioritization: কোন threat-এর impact আর likelihood সবচেয়ে বেশি, সেখানে আগে বিনিয়োগ করা — আর যেগুলো কম-ঝুঁকিপূর্ণ সেগুলো সচেতনভাবে “residual risk” হিসেবে accept করা, না ঠেকিয়ে ভুলে যাওয়া না।

“STRIDE একবার launch-এর আগে করলেই যথেষ্ট — এরপর আর দরকার নেই।”

Threat model system-এর architecture-এর সাথে বাঁধা, আর architecture বদলায় — নতুন feature মানেই নতুন data flow, নতুন data flow মানেই সম্ভাব্য নতুন threat। Shostack-এর চার-প্রশ্নের চতুর্থটা (“কাজটা ভালো হলো কিনা”) ইচ্ছাকৃতভাবে প্রথম প্রশ্নে ফিরিয়ে নিয়ে যায় — এটা একবারের checklist না, একটা চলমান অভ্যাস।

“Attack surface কমানো মানে feature কমানো বা functionality বলি দেওয়া।”

Experiment-এ ঠিক উল্টোটা দেখা গেছে — /admin/users আর /health-এর কোনো functionality হারায়নি, শুধু তাদের unnecessary exposure কমানো হয়েছে (public থেকে internal-only)। Attack surface কমানো সাধারণত মানে default-deny, অব্যবহৃত endpoint সরিয়ে ফেলা, বা প্রয়োজনের চেয়ে বেশি-খোলা config বন্ধ করা — user-এর কাছে দরকারি কোনো feature হারানো না।

“Least privilege শুধু human user-দের জন্য প্রযোজ্য — service/process/API key-র ক্ষেত্রে না।”

Capital One-এর ঘটনাটাই এর সরাসরি বিপরীত প্রমাণ: ক্ষতিগ্রস্ত হয়েছিল একটা IAM role — কোনো মানুষ না, একটা automated service-এর ক্ষমতা। বাস্তবে service-account আর automated process-গুলোই প্রায়শই সবচেয়ে বেশি over-privileged থাকে, কারণ কেউ নিয়মিত সেগুলোর access review করে না — মানুষের access সাধারণত অন্তত মাঝে মাঝে audit হয়।

বুঝেছেন কি না দেখুন

1

STRIDE-এর ছয়টা অক্ষরের পূর্ণরূপ আর প্রতিটা ঠিক কোন security property ভাঙে তা মিলিয়ে বলুন।

স্মরণ

Spoofing (Authentication), Tampering (Integrity), Repudiation (Non-repudiation), Information Disclosure (Confidentiality), Denial of Service (Availability), Elevation of Privilege (Authorization) — প্রতিটা অক্ষর তার সংশ্লিষ্ট property-র সরাসরি বিপরীত ঘটনা বর্ণনা করে।

2

Threat modeling-এর চার-প্রশ্ন framework-এ চতুর্থ প্রশ্ন (“কাজটা ভালো হলো কিনা”) ইচ্ছাকৃতভাবে প্রথম প্রশ্নে ফিরিয়ে দেয়। এই loop-structure কেন জরুরি, একবারের linear checklist যথেষ্ট না কেন?

যুক্তি

Threat model system-এর architecture-নির্ভর, আর architecture স্থির থাকে না — নতুন feature মানেই নতুন data flow ও সম্ভাব্য নতুন threat। Linear checklist একবার শেষ হয়ে গেলে ধরে নেয় কাজ শেষ; loop স্বীকার করে যে প্রতিটা architecture-বদলে threat-surface নতুন করে বদলে যায়, তাই পর্যায়ক্রমে ফিরে আসা লাগবে।

3

NoteShare-এ নতুন একটা feature যোগ হচ্ছে — “public share link” (কোনো login ছাড়াই একটা link দিয়ে যে কেউ একটা note পড়তে পারবে)। এই feature-এ কোন STRIDE category-গুলো নতুনভাবে প্রাসঙ্গিক হয়ে ওঠে, আর একটা মূল mitigation কী হতে পারে?

প্রয়োগ

প্রধানত Information Disclosure (link leak/guess হলে private note-ও প্রকাশ্য হয়ে যায় — তাই link-টা cryptographically random ও যথেষ্ট লম্বা হতে হবে, sequential id না) এবং Denial of Service (unauthenticated public endpoint হওয়ায় rate-limit ছাড়া বারবার request পাঠিয়ে সহজেই flood করা যায়)। Elevation of Privilege-ও প্রাসঙ্গিক যদি link-access থেকে ভুলবশত edit-ক্ষমতা পাওয়া যায় — read-only link আর edit-ক্ষমতা আলাদা রাখা লাগবে।

4

Least privilege আর defense in depth — দুইটাই “একটা ভুলে সব শেষ না হোক” এই লক্ষ্যে কাজ করে, তবু আলাদা নীতি হিসেবে গণ্য হয় কেন? Build section-এর linter-এর file-storage trust-level ১ রাখার সিদ্ধান্তটা কোন নীতির সাথে বেশি সম্পর্কিত?

যুক্তি

Least privilege প্রশ্ন করে “এই component/user-এর ঠিক কতটুকু access দরকার” — একটা single-point সিদ্ধান্ত। Defense in depth প্রশ্ন করে “যদি সেই সিদ্ধান্তটাই ভুল হয়, তাহলে দ্বিতীয় কোন স্তর ঠেকাবে” — একাধিক স্বাধীন স্তরের বিন্যাস। file-storage-কে trust-level ১ দেওয়া মূলত least-privilege-এর প্রশ্ন: এই component-এ আসলে কতটা বিশ্বাস প্রাপ্য, সেটা তার প্রকৃত exposure অনুযায়ী নির্ধারণ করা, formal hierarchy-র ভরসায় না।

5

NoteShare-এ একটা নতুন “bulk export” admin feature ডিজাইন করছেন — একজন admin এক ক্লিকে সব user-এর সব note একটা ZIP ফাইলে ডাউনলোড করতে পারবে। STRIDE pass চালিয়ে সবচেয়ে গুরুত্বপূর্ণ দুইটা threat আর তাদের mitigation প্রস্তাব করুন।

ডিজাইন

সবচেয়ে বড় ঝুঁকি Information Disclosure: একটা মাত্র compromised-admin account বা একটা bug এখন সব user-এর সব ডেটায় একসাথে পৌঁছাতে পারে — blast radius বিশাল। Mitigation: এই নির্দিষ্ট action-এর জন্য আলাদা, বাড়তি-শক্ত auth (re-authentication/MFA-প্রম্পট), আর বাধ্যতামূলক audit log যাতে কে-কখন export করল রেকর্ড থাকে (Repudiation ঠেকাতে)। দ্বিতীয় ঝুঁকি Elevation of Privilege/Denial of Service একসাথে: export-endpoint যদি rate-limit ছাড়া থাকে, একটা compromised admin-token দিয়েই বারবার পুরো ডেটাবেস export করে সার্ভার overload করা সম্ভব — তাই এখানেও rate-limit ও resource-quota লাগবে, শুধু role-check যথেষ্ট না।

6

Experiment-এ দেখা গেল /admin/users-কে শুধু public=False করে দিলেই স্কোর ৬ থেকে ৩-এ, অর্ধেক নেমে গেল — অথচ permission_level (৩, সবচেয়ে বেশি ঝুঁকিপূর্ণ) অপরিবর্তিতই রইল। Score-formula-র multiplicative গঠন থেকে এই ফলাফল কেন স্বাভাবিক?

যুক্তি

Score-টা factor-গুলোর গুণফল (exposure × auth_gap × permission_level × file_risk) — কোনো একটা factor অর্ধেক করলেই পুরো গুণফল সেই অনুপাতে কমে, বাকি factor যাই হোক না কেন। এটাই বোঝায় কেন “শুধু exposure কমানো” (এখনো high-permission থাকা সত্ত্বেও) একটা দ্রুত, সস্তা প্রথম পদক্ষেপ: পুরো root-cause (permission_level) ঠিক না করেও measurable ঝুঁকি-হ্রাস পাওয়া যায় — যদিও চূড়ান্ত সমাধান দুইটা factor-ই কমানো, শুধু একটা না।

এরপর কী

এরপর কী

আজ দুইটা জিনিস হাতে এলো: কোথায় defend করতে হবে (asset, entry point, trust boundary — আর STRIDE দিয়ে সেই জায়গাগুলোয় ঠিক কী ধরনের threat সম্ভব), আর কোন নীতি সেই defense-কে গাইড করবে (least privilege, defense in depth)। কিন্তু এখনো একটা বড় প্রশ্ন অনুত্তরিত: এই mitigation-গুলো বাস্তবে বানাব কীভাবে? “Session token predictable না হওয়া উচিত” বলাই যথেষ্ট না — predictable-না-হওয়া মানে ঠিক কী, কীভাবে মাপা যায়, কীভাবে নিশ্চিত করা যায়?

পরের লেসনে সেই ভিত্তি: cryptography fundamentals। এতক্ষণ যা যা mitigation হিসেবে বলা হলো — random token, tamper-detection, secure storage — এগুলোর প্রতিটার পেছনে একটা নির্দিষ্ট গাণিতিক হাতিয়ার আছে। Randomness আসলে কতটা “random”, hash function কীভাবে tampering ধরে, encryption কীভাবে confidentiality দেয় — এই module-এর বাকি লেসনগুলো আজকের STRIDE টেবিলের প্রতিটা mitigation-কে একটা একটা করে বাস্তব primitive-এ রূপান্তর করবে।

আরও পড়ুন

  • The STRIDE Threat Model — Microsoft — Microsoft Learn, Security Development Lifecycle documentation · STRIDE-এর মূল উৎস — ১৯৯৯ সালে Loren Kohnfelder ও Praerit Garg-এর তৈরি, পরে পুরো Microsoft SDL-এর ভিত্তি হয়ে ওঠে
  • The Protection of Information in Computer Systems — Jerome H. Saltzer, Michael D. Schroeder — Proceedings of the IEEE, 63(9), 1975 · Least privilege-সহ আটটা design principle-এর জন্মপত্র — আজও সবচেয়ে বেশি উদ্ধৃত security paper-দের একটা
  • Threat Modeling: Designing for Security — Adam Shostack — Wiley, 2014 · চার-প্রশ্নের framework (কী বানাচ্ছি? কী ভুল হতে পারে? কী করব সেটা নিয়ে? কাজটা ভালো হলো কিনা?) এই বইয়েরই কাঠামো
  • Attack Surface Analysis Cheat Sheet — OWASP Foundation · Attack surface কীভাবে map করা ও কমানো যায় তার practitioner-গাইড