Threat Modeling ও STRIDE — কী রক্ষা করছি, কার থেকে
Threat Modeling and the STRIDE Framework
Security মানে 'সবকিছু রক্ষা করা' নয় — আগে জানতে হয় কী রক্ষা করছি, কার থেকে, আর কতটা রক্ষা করার মূল্য আছে; STRIDE সেই প্রশ্নগুলোকে ছয়টা structured category-তে ভেঙে দেয়, আর least privilege ও defense in depth সারা module জুড়ে ফিরে-ফিরে আসা দুই নীতি এখান থেকেই শুরু।
আগে এটা বুঝি
এই 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 | এক লাইনে প্রশ্ন |
|---|---|---|---|
| S | Spoofing | Authentication | কেউ কি অন্য কারো পরিচয় ধার করতে পারবে? |
| T | Tampering | Integrity | কেউ কি অনুমতি ছাড়া ডেটা বদলাতে পারবে? |
| R | Repudiation | Non-repudiation | কেউ কি নিজের কাজ অস্বীকার করতে পারবে, প্রমাণ ছাড়া? |
| I | Information Disclosure | Confidentiality | কেউ কি এমন ডেটা দেখবে যা তার দেখার কথা না? |
| D | Denial of Service | Availability | কেউ কি system-কে বৈধ user-দের জন্য অকেজো করে দিতে পারবে? |
| E | Elevation of Privilege | Authorization | কেউ কি কম-অধিকার থেকে বেশি-অধিকারে পৌঁছাতে পারবে? |
প্রতিটা অক্ষর একটা নির্দিষ্ট 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 জুড়ে বারবার ফিরে আসবে।
- network edgeTLS — transport-এ কেউ শুনতে/বদলাতে না পারে
- authentication middlewaresession token বৈধ? কেউ একজন login করা আছে তো?
- authorization checkএই user কি *এই নির্দিষ্ট* note-এর owner/admin?
- business logicdelete-এর actual কাজ — এখানে পৌঁছানোর আগেই দুই স্তর পার হতে হয়েছে
- 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 / Component | STRIDE | নির্দিষ্ট threat | Mitigation |
|---|---|---|---|
| Session / login | Spoofing | Predictable token বা credential-stuffing দিয়ে impersonation | Random session id, rate-limit, MFA |
| Note content (write path) | Tampering | IDOR — note_id বদলে অন্যের note edit | Per-object ownership check প্রতিটা write-এ |
| Note content (write path) | Tampering | String-concat query দিয়ে SQL injection | সবসময় parameterized query |
| Delete/upload action | Repudiation | কে করল প্রমাণ নেই, অস্বীকার করা যায় | Append-only audit log, আলাদা storage |
| File storage | Information Disclosure | Sequential path enumerate করে অন্যের ফাইল দেখা | Random storage key + per-access permission check |
| Upload endpoint | Denial of Service | Size-limit-হীন upload দিয়ে disk/CPU নিঃশেষ | Size limit, rate limit, processing timeout |
| Uploaded file serving | Elevation of Privilege | Webroot-এ script হিসেবে চালানো | Webroot-বাইরে storage, fixed content-type |
| Admin panel | Elevation 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
└─────────────┘ └──────────────┘ attacker request
│
▼
┌───────────┐ fail ──► request dropped, কিছুই এগোয় না
│ TLS/edge │
└─────┬─────┘
│ pass
▼
┌───────────┐ fail ──► 401, কিন্তু business logic-এ পৌঁছায়নি
│ AuthN │
└─────┬─────┘
│ pass
▼
┌───────────┐ fail ──► 403, ownership-check-এই থেমে গেল
│ AuthZ │
└─────┬─────┘
│ pass
▼
┌───────────┐
│ business │──► audit log (সফল হোক ব্যর্থ হোক, ঘটনা রেকর্ড থাকে)
│ logic │
└───────────┘নিজে চালিয়ে দেখুন
Attack surface score — exposure সংখ্যায় মাপা, আর কমানোর প্রভাব দেখা
# 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-এর সস্তা প্রথম পদক্ষেপ কতটা কার্যকর, এটাই তার প্রমাণ।
নিজে বানান
TrustBoundaryLinter — unguarded trust-boundary crossing স্বয়ংক্রিয়ভাবে খুঁজে বের করা
- System-কে component + trust-level হিসেবে model করুন (internet=0 সবচেয়ে কম বিশ্বাস, admin-panel=সবচেয়ে বেশি)
- প্রতিটা data flow-কে (source, dest, protected, description) হিসেবে সংজ্ঞায়িত করুন
- একটা লিন্টার লিখুন যা trust-level-এ ফারাক-থাকা কিন্তু protected=False এমন flow flag করে
- NoteShare-এর সাতটা flow চালিয়ে দেখুন কয়টা flag হয়
- দুইটা 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 খুঁজে দেয় না, প্রশ্নটা জিজ্ঞেস করতে ভোলে
না।
নিজে বাড়ান:
- একটা নতুন flow যোগ করুন —
app-frontend -> file-storageসরাসরি (backend বাইপাস করে) — linter কী বলে, আর বাস্তবে এমন shortcut কেন বিপজ্জনক? protectedবুলিয়ানের বদলে একটাcontrols: list[str](যেমন["auth", "rate-limit"]) ব্যবহার করুন — নির্দিষ্ট trust-gap-এর জন্য কোন control বাধ্যতামূলক তা policy হিসেবে লিখুন- একই 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 হয়।
বুঝেছেন কি না দেখুন
1STRIDE-এর ছয়টা অক্ষরের পূর্ণরূপ আর প্রতিটা ঠিক কোন security property
ভাঙে তা মিলিয়ে বলুন।
স্মরণ
Spoofing (Authentication), Tampering (Integrity), Repudiation (Non-repudiation), Information Disclosure (Confidentiality), Denial of Service (Availability), Elevation of Privilege (Authorization) — প্রতিটা অক্ষর তার সংশ্লিষ্ট property-র সরাসরি বিপরীত ঘটনা বর্ণনা করে।
2Threat modeling-এর চার-প্রশ্ন framework-এ চতুর্থ প্রশ্ন (“কাজটা ভালো
হলো কিনা”) ইচ্ছাকৃতভাবে প্রথম প্রশ্নে ফিরিয়ে দেয়। এই loop-structure কেন
জরুরি, একবারের linear checklist যথেষ্ট না কেন?
যুক্তি
Threat model system-এর architecture-নির্ভর, আর architecture স্থির থাকে না — নতুন feature মানেই নতুন data flow ও সম্ভাব্য নতুন threat। Linear checklist একবার শেষ হয়ে গেলে ধরে নেয় কাজ শেষ; loop স্বীকার করে যে প্রতিটা architecture-বদলে threat-surface নতুন করে বদলে যায়, তাই পর্যায়ক্রমে ফিরে আসা লাগবে।
3NoteShare-এ নতুন একটা 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-ক্ষমতা আলাদা রাখা লাগবে।
4Least privilege আর defense in depth — দুইটাই “একটা ভুলে সব শেষ না
হোক” এই লক্ষ্যে কাজ করে, তবু আলাদা নীতি হিসেবে গণ্য হয় কেন? Build
section-এর linter-এর file-storage trust-level ১ রাখার সিদ্ধান্তটা
কোন নীতির সাথে বেশি সম্পর্কিত?
যুক্তি
file-storage trust-level ১ রাখার সিদ্ধান্তটা
কোন নীতির সাথে বেশি সম্পর্কিত?Least privilege প্রশ্ন করে “এই component/user-এর ঠিক কতটুকু access
দরকার” — একটা single-point সিদ্ধান্ত। Defense in depth প্রশ্ন করে
“যদি সেই সিদ্ধান্তটাই ভুল হয়, তাহলে দ্বিতীয় কোন স্তর ঠেকাবে” — একাধিক
স্বাধীন স্তরের বিন্যাস। file-storage-কে trust-level ১ দেওয়া মূলত
least-privilege-এর প্রশ্ন: এই component-এ আসলে কতটা বিশ্বাস প্রাপ্য,
সেটা তার প্রকৃত exposure অনুযায়ী নির্ধারণ করা, formal hierarchy-র
ভরসায় না।
5NoteShare-এ একটা নতুন “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 যথেষ্ট না।
6Experiment-এ দেখা গেল /admin/users-কে শুধু public=False করে দিলেই
স্কোর ৬ থেকে ৩-এ, অর্ধেক নেমে গেল — অথচ permission_level (৩,
সবচেয়ে বেশি ঝুঁকিপূর্ণ) অপরিবর্তিতই রইল। Score-formula-র multiplicative
গঠন থেকে এই ফলাফল কেন স্বাভাবিক?
যুক্তি
/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-গাইড