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

Authorization — কে কী করতে পারবে, RBAC থেকে ABAC পর্যন্ত

Authorization: RBAC, ABAC, and IDOR

Authentication বলে দেয় আপনি কে, authorization বলে দেয় আপনি কী করতে পারবেন — RBAC সেই সিদ্ধান্ত role-এর মাধ্যমে সহজ-অডিটেবল কিন্তু coarse-grained রাখে, ABAC policy দিয়ে attribute-ভিত্তিক সূক্ষ্ম সিদ্ধান্ত দেয় কিন্তু audit করা কঠিন করে, আর IDOR — শুধু ID বদলে অন্যের রিসোর্স পাওয়া — থেকে যায় বাস্তব জগতের সবচেয়ে সাধারণ authorization bug।

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

  • Authentication ও authorization-এর সংজ্ঞাগত পার্থক্য concrete উদাহরণ দিয়ে ব্যাখ্যা করতে পারবেন, যেখানে একটা ঠিক থাকলেও অন্যটা ভুল হতে পারে
  • RBAC মডেল (user → role → permission) বাস্তবায়ন করে একটা ছোট engine চালিয়ে দেখাতে পারবেন, আর এর coarse-grained সীমাবদ্ধতা সংখ্যায় identify করতে পারবেন
  • ABAC-এর policy-based decision model ব্যাখ্যা করে এমন একটা access-control case দেখাতে পারবেন যা RBAC সহজে প্রকাশ করতে পারে না
  • Least privilege নীতি authorization design-এ কীভাবে প্রয়োগ হয় (default-deny, explicit grant, প্রতি-request check) তা যুক্তি দিয়ে বলতে পারবেন
  • IDOR vulnerability সনাক্ত করে একটা naive endpoint-কে ownership-check-সহ fix করে exploit ব্যর্থ করে দেখাতে পারবেন, আর confused deputy প্যাটার্নের সাথে সম্পর্ক ব্যাখ্যা করতে পারবেন

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

আগের লেসনে authentication ঠিক করেছে একটা প্রশ্নের উত্তর — আপনি কে? Password, OTP, session token, MFA — সবকিছু মিলিয়ে সিস্টেম একটা identity নিয়ে নিশ্চিত হয়। আজকের প্রশ্নটা সম্পূর্ণ আলাদা, আর identity নিশ্চিত হওয়ার পরেই শুরু হয়: সেই identity-টা ধরে নিয়ে, সে কী করতে পারবে?

Authentication : আপনি কে?          (identity verification)
Authorization  : আপনি কী করতে পারবেন? (permission decision, identity ধরে নিয়ে)

দুইটা প্রশ্ন এতটাই কাছাকাছি বসে যে বাস্তব bug-এ প্রায়ই গুলিয়ে যায় — অথচ একটা ঠিক থেকেও অন্যটা ভেঙে যেতে পারে, স্বাধীনভাবে। দুইটা concrete উদাহরণ দিয়ে পার্থক্যটা এখনই স্পষ্ট করে নেওয়া যাক।

উদাহরণ ১ — authentication ঠিক, authorization ভুল। Alice ব্যাংকের app-এ password আর OTP দিয়ে সঠিকভাবে লগ-ইন করেছে — সিস্টেম নিশ্চিত, সে সত্যিই Alice। কিন্তু “আমার ইনভয়েস দেখুন” বাটনে ক্লিক করলে browser যে request পাঠায় তাতে থাকে GET /invoice/1001 — আর Alice যদি URL-এর সংখ্যাটা হাতে বদলে GET /invoice/1002 করে ফেলেন, আর সার্ভার শুধু “session valid কিনা” চেক করে ডেটা ফেরত দেয় — Alice-কে Bob-এর ইনভয়েস দেখিয়ে দেবে। Authentication নিখুঁত (Alice সত্যিই Alice), কিন্তু authorization পুরোপুরি অনুপস্থিত (কেউ চেক করেনি ইনভয়েস ১০০২ আসলে Alice-এর কিনা)। আজকের লেসনের শেষ অংশে এই ঠিক এই bug-টাই — নাম IDOR — হাতে-কলমে exploit ও fix করা হবে।

উদাহরণ ২ — authorization-লজিক ঠিক, authentication ভাঙা। ধরুন একটা API সাবধানে লেখা হয়েছে: প্রতিটা request-এর token থেকে role পড়ে, role == "admin" হলেই delete-endpoint চালায় — authorization-লজিক নিজে নিখুঁত, কোনো ফাঁক নেই। কিন্তু যদি token-এর signature ঠিকভাবে verify না হয় (যেমন কুখ্যাত "alg": "none" JWT bug), attacker নিজেই একটা token বানিয়ে তাতে লিখে দিতে পারে role: "admin" — সিস্টেম সেই forged claim বিশ্বাস করে ফেলে, আর authorization check “সঠিকভাবে” True বলে দেয়, কারণ তার কাছে যে identity এসেছে (forged হলেও) সেটাই admin বলছে। এখানে authorization-লজিক এক বিন্দুও ভুল করেনি — সমস্যাটা authentication-স্তরে, identity-টাই মিথ্যা।

এই দুইটা উদাহরণের মূল শিক্ষা: authorization সবসময় ধরে নেয় যে authentication ইতিমধ্যে সঠিক identity প্রতিষ্ঠা করে দিয়েছে — সেই ধারণার ওপর দাঁড়িয়েই আজকের সব মডেল কাজ করে। আজ দুইটা প্রধান মডেল দেখব — RBAC আর ABAC — যেগুলো ঠিক করে “এই identity-টার জন্য কোন request allowed”, আর দেখব বাস্তবে এই সিদ্ধান্ত বাস্তবায়নে সবচেয়ে সাধারণ যে ভুলটা হয় — IDOR — সেটা কীভাবে ঘটে, ধরা পড়ে, আর ঠেকানো যায়।

মূল ধারণা

RBAC — role-কে মধ্যস্থতাকারী বানানো

Role-Based Access Control (RBAC)-এর ধারণাটা প্রায় প্রতিষ্ঠান-চালনার মতোই স্বাভাবিক: প্রতিটা user-কে সরাসরি permission না দিয়ে, একটা role (যেমন employee, manager, finance)-এ assign করা হয়, আর সেই role-কে দেওয়া হয় একগুচ্ছ permission। User-এর প্রকৃত অধিকার হলো তার সব role-এর permission-এর union।

user  --assigned to-->  role  --granted-->  permission

can(user, permission)  =  exists role in roles(user) : permission in permissions(role)

এই এক-ধাপ indirection-টাই RBAC-এর আসল শক্তি: নতুন কর্মচারী এলে তাকে শুধু সঠিক role-এ assign করলেই চলে, প্রতিটা individual permission হাতে বসাতে হয় না; কারো দায়িত্ব বদলালে তার role বদলে দিলেই পুরো permission-সেট বদলে যায়; আর audit করা সহজ — “কে কে approve_expense করতে পারে” প্রশ্নের উত্তর একটাই role-list দেখলেই বেরিয়ে আসে, প্রতিটা user আলাদা করে চেক করতে হয় না।

ABAC — role নয়, policy

Attribute-Based Access Control (ABAC) ভিন্ন প্রশ্ন থেকে শুরু করে: role একটা সিদ্ধান্তকে মাত্র এক ধরনের তথ্যে (কোন role-এ আছি) বেঁধে ফেলে। বাস্তব সিদ্ধান্ত প্রায়ই আরও তথ্যের ওপর নির্ভর করে — এই request-এর amount কত, resource-টা কোন department-এর, এখন সময় কত, subject আর resource-এর department মিলছে কিনা। ABAC এই সবকিছুকে চারটা attribute-ক্যাটাগরিতে ভাগ করে, আর সিদ্ধান্ত নেয় একটা policy — একটা বুলিয়ান এক্সপ্রেশন — evaluate করে:

subject     : কে request করছে (role, department, approval_limit, ...)
resource    : কীসের ওপর request (owner, department, amount, sensitivity, ...)
action      : কী করতে চাইছে (view, approve, delete, ...)
environment : প্রসঙ্গ (সময়, IP, device-trust-level, ...)

allow(subject, resource, action, environment)  =  policy(subject, resource, action, environment)  -->  true/false

RBAC-এ সিদ্ধান্ত একটা lookup — role-এ permission আছে কিনা। ABAC-এ সিদ্ধান্ত একটা evaluation — policy-এক্সপ্রেশনে চারটা attribute-সেট বসিয়ে সত্য/মিথ্যা বের করা। এই পার্থক্যটাই পরের দুই সেকশনে সংখ্যায় দেখানো হবে — একই scenario, দুইটা মডেল, ভিন্ন সিদ্ধান্ত।

RBACABAC
সিদ্ধান্তের ভিত্তিকোন role-এ আছিsubject+resource+action+environment-এর attribute
Audit করাসহজ — role-list দেখলেই চলেকঠিন — policy simulate করতে হয়
GranularityCoarse — role-স্তরেFine — প্রতিটা request-এর context অনুযায়ী
নতুন নিয়ম যোগনতুন role বানাতে হয়, কখনো role-বিস্ফোরণpolicy-তে একটা শর্ত যোগ, role লাগে না
সবচেয়ে ভালো কখনদায়িত্ব সংখ্যায় সীমিত, স্থিতিশীল সংগঠনসিদ্ধান্ত context-নির্ভর, dynamic

ভেতরে কী ঘটছে

RBAC engine — আসলেই বানিয়ে চালানো

নিচের কোডটা একটা ছোট কিন্তু আসল RBAC engine — কোনো library নয়, নিয়ম তিনটা: role-এ permission যোগ করা, user-কে role assign করা, আর can(user, permission) জিজ্ঞেস করা। Scenario: একটা কোম্পানির expense-approval সিস্টেম — bob engineering দলের manager (তার approval-limit 500),carolsalesদলেরmanager(তারapproval−limit500), carol sales দলের manager (তার approval-limit 50,000)।

# rbac_engine.py — একটা ছোট কিন্তু আসল RBAC engine: user -> role -> permission
class RBACEngine:
    def __init__(self):
        self.role_permissions = {}   # role -> set(permission)
        self.user_roles = {}         # user -> set(role)

    def add_role(self, role, permissions):
        self.role_permissions[role] = set(permissions)

    def assign_role(self, user, role):
        self.user_roles.setdefault(user, set()).add(role)

    def permissions_of(self, user):
        perms = set()
        for role in self.user_roles.get(user, ()):
            perms |= self.role_permissions.get(role, set())
        return perms

    def can(self, user, permission):
        return permission in self.permissions_of(user)


rbac = RBACEngine()
rbac.add_role("employee", ["view_own_expense", "submit_expense"])
rbac.add_role("manager", ["view_own_expense", "submit_expense", "approve_expense"])
rbac.add_role("finance", ["view_own_expense", "submit_expense", "approve_expense", "export_ledger"])

rbac.assign_role("alice", "employee")
rbac.assign_role("bob", "manager")       # bob: engineering দলের manager, approval limit $500
rbac.assign_role("carol", "manager")     # carol: sales দলের manager, approval limit $50,000
rbac.assign_role("dave", "finance")

requests = [
    ("alice", "view_own_expense"),
    ("alice", "approve_expense"),
    ("bob", "approve_expense"),
    ("carol", "approve_expense"),
    ("dave", "export_ledger"),
]

print("RBAC decisions (role-level permission — amount/department কিছুই দেখছে না):")
for user, perm in requests:
    decision = rbac.can(user, perm)
    print(f"  can({user:6}, {perm:20}) = {decision}")

print()
print("সমস্যা: bob (limit $500) আর carol (limit $50,000) — RBAC-এর চোখে দুইজনই")
print("শুধু 'manager', তাই 'approve_expense' permission-এ কোনো তফাত নেই:")
for user in ("bob", "carol"):
    print(f"  can({user:6}, approve_expense) = {rbac.can(user, 'approve_expense')}  "
          f"(amount/department-নির্বিশেষে সবসময় True)")

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

RBAC decisions (role-level permission — amount/department কিছুই দেখছে না):
  can(alice , view_own_expense    ) = True
  can(alice , approve_expense     ) = False
  can(bob   , approve_expense     ) = True
  can(carol , approve_expense     ) = True
  can(dave  , export_ledger       ) = True

সমস্যা: bob (limit $500) আর carol (limit $50,000) — RBAC-এর চোখে দুইজনই
শুধু 'manager', তাই 'approve_expense' permission-এ কোনো তফাত নেই:
  can(bob   , approve_expense) = True  (amount/department-নির্বিশেষে সবসময় True)
  can(carol , approve_expense) = True  (amount/department-নির্বিশেষে সবসময় True)

এই output-ই RBAC-এর coarse-grained সীমাবদ্ধতা নিজেই বলে দিচ্ছে: can() ফাংশনটা bob-কে $50,000-এর expense approve করতে দেবে কিনা জিজ্ঞেস করলেও একই True ফেরত দেবে — কারণ RBAC-এর role/permission মডেলে amount নামে কোনো ধারণাই নেই। Permission হয় আছে, নয় নেই — মাঝামাঝি কোনো শর্ত এখানে বসানোর জায়গা নেই।

ABAC evaluator — একই scenario, এবার policy দিয়ে

এখন একই scenario-য় একটা ABAC policy লিখি — একটাই ফাংশন, চারটা attribute-গ্রুপ (এখানে environment খালি, আজ দরকার নেই) বিবেচনা করে:

# abac_engine.py — একই expense-approval scenario, এবার ABAC দিয়ে
def abac_allow(subject, resource, action, environment):
    # policy: manager নিজের department-এর expense approve করতে পারবে,
    # কিন্তু শুধু নিজের approval_limit-এর মধ্যে থাকলে
    if action != "approve_expense":
        return False
    if subject["role"] not in ("manager", "finance"):
        return False
    if subject["role"] == "finance":
        return True  # finance role-এর কোনো limit/department restriction নেই
    same_department = subject["department"] == resource["department"]
    within_limit = resource["amount"] <= subject["approval_limit"]
    return same_department and within_limit


subjects = {
    "bob":   {"role": "manager", "department": "engineering", "approval_limit": 500},
    "carol": {"role": "manager", "department": "sales",       "approval_limit": 50000},
    "dave":  {"role": "finance", "department": "finance",     "approval_limit": None},
}

# প্রতিটা resource-এর নিজস্ব amount আর department থাকে — RBAC এই দুইটা কখনো দেখেইনি
resources = {
    "exp-101": {"department": "engineering", "amount": 300},     # bob-এর limit-এর ভেতরে
    "exp-102": {"department": "engineering", "amount": 4000},    # bob-এর limit ছাড়িয়ে গেছে
    "exp-103": {"department": "sales",       "amount": 4000},    # carol-এর department, limit-এর ভেতরে
    "exp-104": {"department": "sales",       "amount": 4000},    # bob-এর department না — bob-এর নয়
}

test_cases = [
    ("bob",   "exp-101", "approve_expense"),  # bob নিজ department, limit-এর ভেতরে -> allow
    ("bob",   "exp-102", "approve_expense"),  # bob নিজ department, কিন্তু limit ছাড়িয়ে -> deny
    ("bob",   "exp-104", "approve_expense"),  # bob-এর department না -> deny
    ("carol", "exp-103", "approve_expense"),  # carol নিজ department, limit-এর ভেতরে -> allow
    ("dave",  "exp-102", "approve_expense"),  # finance -> সবসময় allow
]

print("ABAC decisions (policy attribute-ভিত্তিক — role + department + amount + limit):")
for user, res_id, action in test_cases:
    subject = subjects[user]
    resource = resources[res_id]
    decision = abac_allow(subject, resource, action, environment={})
    print(f"  allow({user:6}, {res_id}, amount=${resource['amount']:<6}"
          f"dept={resource['department']:11}) = {decision}")

print()
print("তুলনা — একই দুইটা request, RBAC আর ABAC-এর সিদ্ধান্ত পাশাপাশি:")
print(f"  RBAC  can(bob, approve_expense)                         = True   (সবসময়, blind)")
print(f"  ABAC  allow(bob, exp-102 [$4000, engineering])           = "
      f"{abac_allow(subjects['bob'], resources['exp-102'], 'approve_expense', {})}"
      f"  (limit ছাড়িয়ে গেছে বলে deny)")
print(f"  ABAC  allow(bob, exp-104 [$4000, sales])                 = "
      f"{abac_allow(subjects['bob'], resources['exp-104'], 'approve_expense', {})}"
      f"  (bob-এর department না বলে deny)")

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

ABAC decisions (policy attribute-ভিত্তিক — role + department + amount + limit):
  allow(bob   , exp-101, amount=$300   dept=engineering) = True
  allow(bob   , exp-102, amount=$4000  dept=engineering) = False
  allow(bob   , exp-104, amount=$4000  dept=sales      ) = False
  allow(carol , exp-103, amount=$4000  dept=sales      ) = True
  allow(dave  , exp-102, amount=$4000  dept=engineering) = True

তুলনা — একই দুইটা request, RBAC আর ABAC-এর সিদ্ধান্ত পাশাপাশি:
  RBAC  can(bob, approve_expense)                         = True   (সবসময়, blind)
  ABAC  allow(bob, exp-102 [$4000, engineering])           = False  (limit ছাড়িয়ে গেছে বলে deny)
  ABAC  allow(bob, exp-104 [$4000, sales])                 = False  (bob-এর department না বলে deny)

এই তুলনাটাই আজকের কেন্দ্রীয় প্রমাণ: exp-102 (bob-এর নিজের department, কিন্তু limit ছাড়িয়ে যাওয়া) আর exp-104 (bob-এর limit-এর ভেতরে হলেও ভিন্ন department) — দুইটা ক্ষেত্রেই RBAC কিছুই বলতে পারে না, কারণ তার কাছে amount বা department নামে কোনো ধারণাই নেই — শুধু approve_expense permission আছে কিনা, যেটা bob-এর জন্য সবসময় True। ABAC ঠিক এই দুইটা ক্ষেত্রেই সঠিকভাবে False বলে দিচ্ছে, কারণ তার policy সরাসরি resource-এর attribute (amount, department) subject-এর attribute-এর (approval_limit, department) সাথে তুলনা করছে। এটাই ABAC-এর expressiveness-এর সংজ্ঞাগত প্রমাণ — কোনো hand-wave নয়, একই কোড, একই input, ভিন্ন সিদ্ধান্ত-প্রক্রিয়া।

উদাহরণ

Worked comparison — একই request-সেট, দুই মডেল

আগের দুই কোড-ব্লকের ফলাফল একসাথে সাজালে পার্থক্যটা এক নজরে দেখা যায়:

RequestRBAC সিদ্ধান্তABAC সিদ্ধান্তকেন আলাদা
bob → exp-101 ($300, engineering)allow (role আছে)allowদুইটাই সঠিক এখানে
bob → exp-102 ($4000, engineering)allow (role আছে)denyRBAC amount দেখে না, limit ছাড়িয়েছে
bob → exp-104 ($4000, sales)allow (role আছে)denyRBAC department দেখে না, ভিন্ন দল
carol → exp-103 ($4000, sales)allow (role আছে)allowদুইজনেই সঠিক
dave → exp-102 (finance role)allowallowদুইজনেই সঠিক

দুইটা ঘরে RBAC আর ABAC-এর সিদ্ধান্ত মিলছে না — আর দুইটাতেই RBAC ভুল দিকে ভুল করছে: over-permissive। এই প্যাটার্নটাই বাস্তব incident-এ বারবার দেখা যায় — RBAC নিজেই কোনো বাগ না, কিন্তু যে নিয়মগুলো role দিয়ে প্রকাশ করা যায় না, সেগুলো role-based সিস্টেমে নীরবে অনুপস্থিত থেকে যায়, ধরা পড়ে না যতক্ষণ না কেউ manually audit করে বা exploit করে বসে।

Least privilege — authorization design-এ তিনটা নিয়ম

আগের লেসনগুলোতে least privilege নীতির পরিচয় হয়েছে সাধারণভাবে — আজ সেটা authorization design-এ কংক্রিটভাবে তিনটা নিয়মে নামিয়ে আনা যাক:

১. Default-deny। সিস্টেমের শুরুর অবস্থান হওয়া উচিত “কিছুই allowed না”, তারপর explicit grant যোগ করা — উল্টোটা না। উপরের RBACEngine-এ লক্ষ্য করুন: can() ফাংশন শুরু করে খালি permissions_of(user) সেট দিয়ে, permission in perms মিথ্যা হলে ফলাফল False — কোনো role-এ assign না থাকা user স্বয়ংক্রিয়ভাবে সবকিছুতে বাদ পড়ে। যদি ডিজাইনটা উল্টো হতো (“blocklist”-স্টাইল — সব allowed, শুধু নিষিদ্ধগুলো বাদ), একটা ভুলে-বাদ-পড়া rule মানেই একটা নতুন open door।

২. Explicit grant, ইমপ্লিসিট না। কোনো permission “প্রায়ই এভাবেই হয়” বা “সবাই তো manager, তাই থাকবেই” ধরনের অনুমানে আসা উচিত না — প্রতিটা role-permission বা policy-rule কোডে/config-এ স্পষ্টভাবে লেখা থাকা উচিত, যাতে audit করার সময় “কেন এই access আছে” প্রশ্নের একটা সরাসরি উত্তর পাওয়া যায়।

৩. প্রতি-request check, শুধু লগ-ইনে না। একটা সাধারণ ভুল ডিজাইন: লগ-ইনের সময় user-এর role/permission একটা token-এ বসিয়ে দেওয়া, তারপর প্রতিটা পরবর্তী request-এ শুধু সেই token-ই বিশ্বাস করা। সমস্যা: role পরিবর্তন (কাউকে demote করা, বরখাস্ত করা) হলে সেই পরিবর্তন token expire না হওয়া পর্যন্ত কার্যকর হয় না — আর resource-নির্দিষ্ট decision (এই নির্দিষ্ট invoice, এই নির্দিষ্ট amount) লগ-ইনের সময় জানাই সম্ভব না, কারণ resource-টা তখনো request করা হয়নি। সঠিক ডিজাইন: প্রতিটা request-এ, resource-এর প্রকৃত attribute (owner, amount, department) নিয়ে, তখন-তখনই সিদ্ধান্ত নেওয়া — ঠিক যেভাবে উপরের abac_allow() ফাংশনটা প্রতিবার resource dictionary থেকে fresh ডেটা পড়ছে, কোনো cached token থেকে না।

ছবিতে

Authorization check কোথায় কোথায় হওয়া উচিত
  1. client / UIবাটন লুকানো, disabled state — শুধু UX, কোনো নিরাপত্তা নয় (client-side কোড attacker সম্পূর্ণ নিয়ন্ত্রণ করতে পারে)
  2. API gateway / middlewarecoarse-grained: authenticated কিনা, কোন role আছে — প্রথম ফিল্টার, যথেষ্ট নয়
  3. business logic (handler)resource-নির্দিষ্ট check এখানেই হতে হবে — ownership, amount, department, সব context
  4. data-access layerdefense-in-depth হিসেবে query নিজেই owner-scoped রাখা যায় (WHERE owner_id = ?) — একটা দ্বিতীয় সুরক্ষা-স্তর

সবচেয়ে গুরুত্বপূর্ণ layer হলো business logic, কারণ শুধু সেখানেই resource-এর প্রকৃত attribute (কে owner, amount কত) আর request-কারী identity-র attribute একসাথে পাওয়া যায়। উপরের IDOR উদাহরণে (এই লেসনের build অংশে হাতে-কলমে দেখানো হবে) ঠিক এই layer-টাই বাদ পড়েছিল — middleware বলেছে “authenticated”, কিন্তু business logic কখনো জিজ্ঞেস করেনি “এই invoice-টা কি সত্যিই এই ব্যবহারকারীর?”

RBAC:
  user ──► [role lookup] ──► permission-set ──► permission in set? ──► allow/deny

ABAC:
  subject attrs   ─┐
  resource attrs   ─┤
  action           ─┼──► [policy evaluation] ──► allow/deny
  environment attrs─┘
RBAC বনাম ABAC — সিদ্ধান্তের পথ। RBAC-এ পথ সোজা এক-লাইন lookup; ABAC-এ পথ চারটা attribute-source একত্র করে policy-evaluation-এ যায়।

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

EXPERIMENT

Role explosion — সংখ্যায়: একটা ABAC policy নকল করতে RBAC-এ কতগুলো role লাগে?

macOS / Linux / Windows (Python 3)· ১০ মিনিট
# role_explosion.py — RBAC-এ ABAC-এর একটা policy নকল করতে কতগুলো role লাগে?
# policy: "manager নিজের department-এর expense approve করবে, নিজের approval-tier limit-এর মধ্যে"
# RBAC-এ এই একই নিয়ম আনতে হলে প্রতিটা (department, tier) combo-র জন্য আলাদা role লাগবে।

departments = ["engineering", "sales", "marketing", "finance", "operations"]
tiers = [500, 2000, 10000, 50000]   # approval-limit tier গুলো

print(f"departments = {len(departments)}, approval-tiers = {len(tiers)}")
print()
print("RBAC-এ প্রতিটা (department x tier) combo-র জন্য আলাদা role বানাতে হবে:")
roles_needed = []
for dept in departments:
    for tier in tiers:
        role_name = f"manager_{dept}_upto_{tier}"
        roles_needed.append(role_name)

for r in roles_needed[:6]:
    print(f"  {r}")
print(f"  ... (মোট {len(roles_needed)}টা role)")

print()
print("ABAC-এ লাগে ঠিক ১টা policy — attribute-expression-এ department আর tier variable:")
print("  allow if subject.department == resource.department and resource.amount <= subject.approval_limit")

print()
print("সাধারণ সূত্র: RBAC role-সংখ্যা = product(প্রতিটা independent attribute-এর সম্ভাব্য মান-সংখ্যা)")
for n_attrs, vals_per_attr in [(1, 5), (2, 5), (3, 5), (4, 5)]:
    combos = vals_per_attr ** n_attrs
    print(f"  {n_attrs} attribute, প্রতিটায় {vals_per_attr}টা সম্ভাব্য মান -> {vals_per_attr}^{n_attrs} = {combos} role লাগবে RBAC-এ")

print()
print(f"এই scenario-তে (department={len(departments)}, tier={len(tiers)}): "
      f"RBAC role লাগবে {len(departments)}*{len(tiers)} = {len(departments)*len(tiers)}টা, "
      f"ABAC policy লাগবে ১টা।")

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

departments = 5, approval-tiers = 4

RBAC-এ প্রতিটা (department x tier) combo-র জন্য আলাদা role বানাতে হবে:
  manager_engineering_upto_500
  manager_engineering_upto_2000
  manager_engineering_upto_10000
  manager_engineering_upto_50000
  manager_sales_upto_500
  manager_sales_upto_2000
  ... (মোট 20টা role)

ABAC-এ লাগে ঠিক ১টা policy — attribute-expression-এ department আর tier variable:
  allow if subject.department == resource.department and resource.amount <= subject.approval_limit

সাধারণ সূত্র: RBAC role-সংখ্যা = product(প্রতিটা independent attribute-এর সম্ভাব্য মান-সংখ্যা)
  1 attribute, প্রতিটায় 5টা সম্ভাব্য মান -> 5^1 = 5 role লাগবে RBAC-এ
  2 attribute, প্রতিটায় 5টা সম্ভাব্য মান -> 5^2 = 25 role লাগবে RBAC-এ
  3 attribute, প্রতিটায় 5টা সম্ভাব্য মান -> 5^3 = 125 role লাগবে RBAC-এ
  4 attribute, প্রতিটায় 5টা সম্ভাব্য মান -> 5^4 = 625 role লাগবে RBAC-এ

এই scenario-তে (department=5, tier=4): RBAC role লাগবে 5*4 = 20টা, ABAC policy লাগবে ১টা।

সংখ্যাগুলো নিজেই RBAC-এর সীমা বলে দেয়: মাত্র চারটা attribute, প্রতিটায় পাঁচটা সম্ভাব্য মান হলেই RBAC-এ ৬২৫টা role লাগে — বাস্তবে এত role কেউ ব্যবহারিকভাবে maintain করে না, তাই টিমগুলো হয় সেই সূক্ষ্মতা ছেড়ে দেয় (over-permissive হয়ে যায়, ঠিক আগের experiment-এর bob-carol সমস্যা) নয়তো কোডের ভেতর ছোট ছোট if-এ hardcode করে ফেলে (যেটা আর সত্যিকারের RBAC থাকে না, hidden ABAC হয়ে যায়, কিন্তু audit-friendly RBAC-এর সুবিধা ছাড়াই)। Role-সংখ্যা attribute-সংখ্যার সাথে গুণিতক হারে (k^n) বাড়ে — ঠিক এই কারণেই বড়, context-নির্ভর সিস্টেম একটা point-এ ABAC-এর দিকে ঝোঁকে।

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

ABAC-এর একটা policy (department-match + amount-limit) RBAC-এ হুবহু নকল করতে হলে department-সংখ্যা × tier-সংখ্যার সমান আলাদা role লাগে — attribute-সংখ্যা বাড়লে role-সংখ্যা গুণিতক হারে (combinatorial) বাড়ে, যেখানে ABAC-এর policy-সংখ্যা একই থাকে।

নিজে বানান

BUILD IT

IDOR — naive lookup বনাম ownership-checked lookup

Python · ●●○○○
  1. একটা INVOICES ডেটাস্টোর বানান — প্রতিটা invoice-এর owner field আছে
  2. ভালনারেবল endpoint লিখুন — শুধু invoice_id দিয়ে lookup, owner check নেই
  3. exploit চালান — alice নিজের session দিয়েই bob-এর invoice চেয়ে নিন, দেখুন সফল হয়
  4. fixed endpoint লিখুন — session_user আর invoice-এর owner মেলে কিনা চেক করুন
  5. একই exploit fixed endpoint-এর বিরুদ্ধে চালান, ৪০৩ পান তা নিশ্চিত করুন
  6. ব্যাচ-টেস্টে ৬টা (user, invoice) জোড়া চালিয়ে naive/fixed-এর পার্থক্য টেবিলে দেখুন

লক্ষ্য: Insecure Direct Object Reference (IDOR) — সার্ভার একটা resource-identifier (এখানে invoice_id) সরাসরি বিশ্বাস করে ডেটা ফেরত দেয়, resource-টা request-কারীর নিজের কিনা যাচাই না করেই। এটা authorization bug-এর সবচেয়ে সাধারণ রূপ কারণ exploit করা সবচেয়ে সহজ — attacker-এর কোনো বিশেষ টুল লাগে না, শুধু URL-এর একটা সংখ্যা বদলানো যথেষ্ট।

# idor_demo.py — IDOR: naive lookup-by-id বনাম ownership-checked lookup
INVOICES = {
    1001: {"owner": "alice", "amount": 250.00, "note": "Alice-এর নিজের ইনভয়েস"},
    1002: {"owner": "bob",   "amount": 9999.00, "note": "Bob-এর ইনভয়েস — CFO salary breakdown সহ"},
    1003: {"owner": "alice", "amount": 80.00,  "note": "Alice-এর আরেকটা ইনভয়েস"},
}


# ---------- ভালনারেবল (naive) endpoint — শুধু ID দিয়ে lookup, owner check নেই ----------
def get_invoice_naive(session_user, invoice_id):
    # bug: authenticated হলেই যথেষ্ট মনে করা হয়েছে — এই invoice-টা
    # আসলে session_user-এর কিনা সেটা কখনো যাচাই হয়নি
    if invoice_id not in INVOICES:
        return {"status": 404}
    return {"status": 200, "data": INVOICES[invoice_id]}


# ---------- ফিক্সড endpoint — ownership check করে তারপর ডেটা দেয় ----------
def get_invoice_fixed(session_user, invoice_id):
    if invoice_id not in INVOICES:
        return {"status": 404}
    invoice = INVOICES[invoice_id]
    if invoice["owner"] != session_user:
        return {"status": 403, "error": "forbidden: এই invoice আপনার নয়"}
    return {"status": 200, "data": invoice}


print("--- naive endpoint: alice নিজের invoice চাইছে ---")
r = get_invoice_naive("alice", 1001)
print(f"  get_invoice_naive('alice', 1001) -> status={r['status']}, data={r.get('data')}")

print()
print("--- EXPLOIT: alice, URL-এর id বদলে bob-এর invoice চাইছে (session তার নিজেরই) ---")
r = get_invoice_naive("alice", 1002)
print(f"  get_invoice_naive('alice', 1002) -> status={r['status']}, data={r.get('data')}")
print(f"  exploit সফল হলো: {r['status'] == 200 and r['data']['owner'] != 'alice'}")

print()
print("--- একই exploit, fixed endpoint-এর বিরুদ্ধে ---")
r = get_invoice_fixed("alice", 1002)
print(f"  get_invoice_fixed('alice', 1002) -> status={r['status']}, error={r.get('error')}")
print(f"  exploit ব্লক হলো: {r['status'] == 403}")

print()
print("--- sanity check: fixed endpoint নিজের invoice-এ ঠিকই কাজ করে ---")
r = get_invoice_fixed("alice", 1001)
print(f"  get_invoice_fixed('alice', 1001) -> status={r['status']}, data={r.get('data')}")
r = get_invoice_fixed("alice", 1003)
print(f"  get_invoice_fixed('alice', 1003) -> status={r['status']}, data={r.get('data')}")

print()
print("--- ব্যাচ টেস্ট: ৬টা (user, invoice_id) জোড়া, naive বনাম fixed ---")
tests = [
    ("alice", 1001), ("alice", 1002), ("alice", 1003),
    ("bob", 1001), ("bob", 1002), ("bob", 1003),
]
print(f"  {'user':6} {'inv_id':7} {'naive-status':13} {'fixed-status':13} {'IDOR leak?'}")
for user, inv_id in tests:
    rn = get_invoice_naive(user, inv_id)
    rf = get_invoice_fixed(user, inv_id)
    leaked = rn["status"] == 200 and INVOICES[inv_id]["owner"] != user
    print(f"  {user:6} {inv_id:<7} {rn['status']:<13} {rf['status']:<13} {leaked}")

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

--- naive endpoint: alice নিজের invoice চাইছে ---
  get_invoice_naive('alice', 1001) -> status=200, data={'owner': 'alice', 'amount': 250.0, 'note': 'Alice-এর নিজের ইনভয়েস'}

--- EXPLOIT: alice, URL-এর id বদলে bob-এর invoice চাইছে (session তার নিজেরই) ---
  get_invoice_naive('alice', 1002) -> status=200, data={'owner': 'bob', 'amount': 9999.0, 'note': 'Bob-এর ইনভয়েস — CFO salary breakdown সহ'}
  exploit সফল হলো: True

--- একই exploit, fixed endpoint-এর বিরুদ্ধে ---
  get_invoice_fixed('alice', 1002) -> status=403, error=forbidden: এই invoice আপনার নয়
  exploit ব্লক হলো: True

--- sanity check: fixed endpoint নিজের invoice-এ ঠিকই কাজ করে ---
  get_invoice_fixed('alice', 1001) -> status=200, data={'owner': 'alice', 'amount': 250.0, 'note': 'Alice-এর নিজের ইনভয়েস'}
  get_invoice_fixed('alice', 1003) -> status=200, data={'owner': 'alice', 'amount': 80.0, 'note': 'Alice-এর আরেকটা ইনভয়েস'}

--- ব্যাচ টেস্ট: ৬টা (user, invoice_id) জোড়া, naive বনাম fixed ---
  user   inv_id  naive-status  fixed-status  IDOR leak?
  alice  1001    200           200           False
  alice  1002    200           403           True
  alice  1003    200           200           False
  bob    1001    200           403           True
  bob    1002    200           200           False
  bob    1003    200           403           True

মূল মুহূর্ত exploit সফল হলো: True — alice কোনো hacking-tool ছাড়াই, শুধু URL-এর একটা সংখ্যা বদলে, CFO salary breakdown-সহ Bob-এর পুরো invoice দেখে ফেলল। Fixed endpoint-এ ঠিক একই request 403 দিচ্ছে, কারণ একটাই নতুন লাইন যোগ হয়েছে — if invoice["owner"] != session_user। ব্যাচ-টেস্টের টেবিলে লক্ষ্য করুন: IDOR leak? = True ঠিক সেই সারিগুলোতে যেখানে naive-status = 200 কিন্তু invoice-টা আসলে সেই user-এর নয় — আর প্রতিটা এমন সারিতেই fixed-endpoint সঠিকভাবে 403 ফেরত দিচ্ছে।

Confused deputy — এই bug-এর সাধারণ নাম। IDOR আসলে একটা বিস্তৃত প্যাটার্নের নির্দিষ্ট রূপ, যাকে বলে confused deputy problem: একটা প্রোগ্রাম (এখানে get_invoice_naive) নিজের authority (database-এর সব invoice পড়ার ক্ষমতা) ব্যবহার করে কাজ করছে, কিন্তু caller-এর intent (শুধু নিজের invoice দেখা চায়) যাচাই না করেই সেই পুরো authority caller-কে ধার দিয়ে দিচ্ছে। “Deputy” (এই ফাংশন) নিজের ক্ষমতা আর caller-এর অনুরোধ গুলিয়ে ফেলেছে — attacker নিজে কোনো privilege escalation করেনি, সে শুধু deputy-কে ভুল কাজে “confuse” করিয়েছে। CSRF (deputy = browser, যে attacker-এর জন্য অজান্তে authenticated request পাঠায়), print-spooler-এর ধ্রুপদী উদাহরণ (Hardy, ১৯৮৮), আর IDOR — সবগুলোই একই কাঠামোর ভিন্ন রূপ।

নিজে বাড়ান:

  1. get_invoice_fixed-এ যদি session_user কোনোভাবে spoof করা যায় (যেমন আগের লেসনের JWT-forgery bug), এই fix কি তখনও যথেষ্ট? কেন/কেন না?
  2. একটা admin role যোগ করুন যে যেকোনো invoice দেখতে পারবে — শুধু owner != session_user চেকটাকে কীভাবে বদলাবেন যাতে ownership অথবা admin-role দুইটার একটা থাকলেই allow হয়?
  3. Batch-টেস্টের ফলাফল থেকে, “IDOR leak” column-টাকে যদি automated security-test-এ রূপান্তর করেন (প্রতিটা user-invoice জোড়ার জন্য assert করা যে non-owner সবসময় 403/404 পাবে), সেটা কীভাবে লিখবেন?

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

বাস্তব জগতে authorization — কেন IDOR এত সাধারণ

OWASP Top 10 (2021) — Broken Access Control, এখন #১। ২০২১ সংস্করণে “Broken Access Control” ক্যাটাগরি সব ক্যাটাগরির মধ্যে সবচেয়ে বেশি রিপোর্টেড হয়েছে — টেস্ট করা অ্যাপ্লিকেশনগুলোর প্রায় ৯৪%-এ কোনো-না-কোনো ধরনের access-control ফাঁক পাওয়া গেছে। এই ক্যাটাগরির ভেতরে সবচেয়ে সাধারণ উপ-প্যাটার্নই IDOR — কারণ, আজকের build অংশেই দেখা গেছে, এটা exploit করতে কোনো বিশেষ দক্ষতা লাগে না, শুধু একটা URL-parameter বদলানো যথেষ্ট।

USPS Informed Visibility API (2017-2018)। US Postal Service-এর একটা API endpoint-এ যেকোনো authenticated user (অ্যাকাউন্ট বানানো নিজেই trivial ছিল) অন্য যেকোনো ব্যবহারকারীর account-তথ্য (ইমেইল, ফোন নম্বর, ঠিকানা, real-time mail-tracking তথ্য) query করতে পারত শুধু user-id বদলে — প্রায় ৬ কোটি অ্যাকাউন্ট এর আওতায় পড়েছিল। এখানেও একই প্যাটার্ন: authentication ঠিক ছিল (যে কেউ নিজের অ্যাকাউন্ট দিয়ে সঠিকভাবে লগ-ইন করত), কিন্তু authorization-এ resource-নির্দিষ্ট ownership check অনুপস্থিত ছিল।

Least privilege বাস্তবে — API design-এর সাধারণ প্যাটার্ন। এই breach-গুলো থেকে প্র্যাকটিক্যাল শিক্ষা: sequential, predictable identifier (1001, 1002, 1003...) নিজেই ঝুঁকি বাড়ায়, কারণ এটা attacker-কে বিনামূল্যে একটা “স্ক্যান করার তালিকা” দিয়ে দেয় — যদিও এটা সমস্যার মূল কারণ না (আসল bug ownership-check-এর অনুপস্থিতি), UUID-জাতীয় non-sequential identifier একটা বাড়তি defense-in-depth স্তর যোগ করে। আসল, non-negotiable সমাধান একটাই: প্রতিটা resource-access-এ, প্রতিবার, business-logic layer-এ ownership/permission যাচাই করা — ঠিক এই লেসনের build অংশে যা দেখানো হয়েছে।

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

“Authorization মানেই RBAC — role check ছাড়া 'সঠিক' authorization হয় না।”

RBAC একটা জনপ্রিয় মডেল, একমাত্র মডেল না। ABAC, ACL (access-control list — প্রতিটা resource-এ সরাসরি allowed-user-এর তালিকা), আর এমনকি এই লেসনের IDOR-fix-এর মতো সরাসরি ownership-check — সবগুলোই বৈধ authorization কৌশল। প্রশ্নটা “role ব্যবহার করছি কিনা” না, প্রশ্নটা “প্রতিটা request-এ সঠিক subject-resource সম্পর্ক যাচাই হচ্ছে কিনা”।

“লগ-ইনের সময় একবার permission চেক করে token-এ বসিয়ে দিলেই যথেষ্ট, প্রতি request-এ আবার চেক করার দরকার নেই।”

Example সেকশনের তিন নিয়মের তৃতীয়টা ঠিক এই ভুল ধারণা ভাঙে। Role পরিবর্তন (demote, বরখাস্ত) হলে পুরনো token-এর ভেতরের বাসি permission কার্যকর থেকে যায় যতক্ষণ না expire করে। আরও গুরুত্বপূর্ণ: resource-নির্দিষ্ট decision (এই নির্দিষ্ট invoice কার) লগ-ইনের সময় জানাই সম্ভব না, কারণ resource-টা তখনো request হয়নি — এটা জানা যায় শুধু request-এর মুহূর্তেই। IDOR bug-টা মূলত এই ভুল ধারণারই একটা রূপ: “user authenticated, তাই ধরে নিচ্ছি সব ঠিক আছে” — resource-নির্দিষ্ট check বাদ পড়ে যায়।

“ID-টা UUID বা hash-ভিত্তিক (guess করা কঠিন) হলেই IDOR থেকে নিরাপদ।”

Realworld সেকশনের Callout-এই এই ভুল ধারণা বিস্তারিত ভাঙা হয়েছে — সংক্ষেপে: unpredictable ID শুধু “random scanning” আক্রমণ কঠিন করে, কিন্তু attacker-এর কাছে যদি কোনোভাবে একটা বৈধ ID পৌঁছায় (leaked link, log, আরেকটা API response), ownership-check না থাকলে bug ঠিক একইভাবে কাজ করে। ID-র predictability কমানো একটা mitigation স্তর, আসল fix না।

“ABAC সবসময় RBAC-এর চেয়ে ভালো পছন্দ, যেহেতু বেশি expressive।”

Experiment সেকশনের role-explosion সংখ্যাগুলো ABAC-এর সুবিধা দেখিয়েছে, কিন্তু concept সেকশনের টেবিলে RBAC-এর নিজস্ব সুবিধাও স্পষ্ট: সরলতা আর auditability। “কে কে সংবেদনশীল ডেটা এক্সেস করতে পারে” প্রশ্নের উত্তর RBAC-এ একটা role-list দেখলেই বেরিয়ে আসে; ABAC-এ policy-এক্সপ্রেশনগুলো সব সম্ভাব্য attribute-combination-এর জন্য simulate করে দেখতে হয়, যেটা জটিল policy-সেটে ব্যবহারিকভাবে কঠিন। সংগঠন যত ছোট ও স্থিতিশীল, RBAC তত বেশি যথেষ্ট; সিদ্ধান্ত যত context-নির্ভর ও dynamic, ABAC তত বেশি দরকারি — কোনোটাই সার্বজনীনভাবে “ভালো” না।

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

1

Authentication আর authorization-এর সংজ্ঞাগত পার্থক্য এক বাক্যে কী, আর কোন প্রশ্নটা কার আওতায় পড়ে?

স্মরণ

Authentication উত্তর দেয় “আপনি কে” — identity verification। Authorization উত্তর দেয় “আপনি কী করতে পারবেন” — identity ইতিমধ্যে নিশ্চিত ধরে নিয়ে permission-এর সিদ্ধান্ত। একটা সঠিক থাকলেও অন্যটা স্বাধীনভাবে ভুল হতে পারে — যেমন intro-এর দুই উদাহরণে (IDOR: authn ঠিক authz ভুল; forged-token: authz-লজিক ঠিক authn ভাঙা)।

2

RBAC engine-এর experiment-এ bob (limit 500)আরcarol(limit500) আর carol (limit 50,000) দুইজনেই approve_expense-এ True পেয়েছে, amount-নির্বিশেষে। কোন mechanical কারণে RBAC এই পার্থক্য ধরতে পারছে না?

যুক্তি

RBACEngine-এর can() ফাংশন শুধু জিজ্ঞেস করে “এই permission কি user-এর কোনো role-এর permission-set-এ আছে” — একটা বুলিয়ান membership-test। Role আর permission-এর সংজ্ঞায় amount নামে কোনো attribute-ই নেই, তাই amount ভিন্ন হলেও মডেলের কাছে সেই তথ্য পৌঁছানোরই কোনো পথ নেই — manager role থাকা মানেই approve_expense = True, সংখ্যা-নির্বিশেষে।

3

একটা কোম্পানিতে ৬টা department আর ৫টা approval-tier আছে। role-explosion সূত্র (departments × tiers) অনুযায়ী, ABAC-এর একটা policy-কে RBAC-এ হুবহু নকল করতে কতগুলো role লাগবে?

প্রয়োগ

6 × 5 = 30টা role লাগবে — প্রতিটা (department, tier) সমন্বয়ের জন্য একটা করে, যেমন manager_engineering_upto_500, manager_engineering_upto_2000, … ইত্যাদি। ABAC-এ এই একই নিয়ম প্রকাশ করতে policy-সংখ্যা থাকবে ১, যেখানে department আর tier দুইটাই variable হিসেবে policy-এক্সপ্রেশনে বসে।

4

Build অংশের ব্যাচ-টেস্ট টেবিলে bob, 1001 সারিতে naive-status 200 কিন্তু fixed-status 403। কেন — invoice 1001 তো ঠিকই ডেটাবেসে আছে?

যুক্তি

Invoice 1001-এর owner alice, কিন্তু request করছে bob। Naive endpoint শুধু invoice_id ডেটাবেসে আছে কিনা দেখে ডেটা ফেরত দেয় — owner কে সেটা কখনো তুলনা করে না, তাই যেকোনো authenticated user যেকোনো ID চাইলেই পেয়ে যায়। Fixed endpoint অতিরিক্ত একটা চেক করে — invoice["owner"] != session_user — bob invoice ১০০১-এর owner না বলে 403 ফেরত দেয়, IDOR ব্লক হয়ে যায়।

5

ব্যাচ-টেস্ট টেবিলে “IDOR leak?” column ঠিক কী শর্তে True হয়, আর সেই শর্তটা naive আর fixed endpoint-এর মধ্যে কীভাবে পার্থক্য তৈরি করে?

প্রয়োগ

কোডে শর্তটা: naive-status == 200 এবং invoice-এর প্রকৃত owner request-কারী user-এর সমান না — অর্থাৎ, naive endpoint এমন ডেটা ফেরত দিয়েছে যেটা request-কারীর নিজের নয়। এই শর্ত সত্যি হওয়া মানেই একটা IDOR ফাঁস ঘটেছে। Fixed endpoint-এ ঠিক এই একই শর্তের ক্ষেত্রে ownership-check owner != session_user-কে ধরে ফেলে 403 দেয় — তাই fixed-endpoint-এ কোনো সারিতেই সফল লিক ঘটে না, শুধু naive-এ ঘটে।

6

একটা মাল্টি-টেন্যান্ট SaaS প্রজেক্ট-ম্যানেজমেন্ট অ্যাপ ডিজাইন করছেন — প্রতিটা কোম্পানি (tenant) তার নিজের project/task দেখবে, কোম্পানির ভেতরে কিছু user “viewer” (শুধু দেখতে পারে) আর কিছু “editor” (এডিট করতে পারে), আর একটা বিশেষ নিয়ম: কোনো editor একটা task শুধুই edit করতে পারবে যদি সেই task-এর due-date এখনো না পেরিয়ে থাকে (overdue task শুধু admin এডিট করতে পারবে)। RBAC, ABAC, নাকি দুইটার মিশ্রণ বাছবেন, আর least-privilege-এর কোন নিয়মগুলো কোথায় প্রয়োগ করবেন?

ডিজাইন

মিশ্রণ সবচেয়ে উপযুক্ত: বড় কাঠামো (tenant-isolation, viewer/editor/admin role) RBAC দিয়ে — সহজ, auditable, আর tenant-boundary নিজেই একটা default-deny filter (প্রতিটা query tenant-scoped হওয়া উচিত, প্রতিটা request-এ যাচাই করে — শুধু লগ-ইনে না)। কিন্তু “due-date পেরিয়েছে কিনা” শর্তটা resource-এর একটা dynamic attribute (task.due_date বনাম environment.current_time) — এটা RBAC-এ নতুন role (editor_overdue, editor_active) বানিয়ে করা যায়, কিন্তু সেটা সময়ের সাথে সাথে role-এর অর্থ বদলাতে থাকবে (আজকের active task কাল overdue হয়ে যাবে), যা RBAC-এর স্থিতিশীল-role অনুমানের সাথে মেলে না — এখানে ABAC policy (allow if role == "editor" and resource.due_date >= environment.now) স্বাভাবিক ফিট। Least-privilege-এর তিনটা নিয়মই দরকার: default-deny (কোনো role না থাকলে কিছুই access নেই), explicit grant (viewer/editor/admin-এর permission কোডে স্পষ্ট), আর প্রতি-request check (due-date-নির্ভর সিদ্ধান্ত লগ-ইনের সময় জানাই সম্ভব না, request-এর মুহূর্তে জানতে হবে)।

এরপর কী

এরপর কী

আজ পর্যন্ত সবকিছুই ছিল application logic স্তরে — কে কী করতে পারবে, সেই সিদ্ধান্ত কীভাবে সংজ্ঞায়িত (RBAC/ABAC) আর কীভাবে ভুল বাস্তবায়িত হলে ফাঁস হয় (IDOR)। এই পুরো আলোচনাটা ধরে নিয়েছে যে প্রোগ্রামটা নিজে ঠিকঠাক চলছে — যা চেক করার কথা তা-ই চেক করছে, memory-তে যা লেখার কথা তা-ই লিখছে।

পরের লেসনে সেই ধারণাটাই ভেঙে দেওয়া হবে: memory safety আর buffer overflow। এমনকি একটা নিখুঁতভাবে authorized request-ও বিপর্যয়ে পরিণত হতে পারে, যদি সেই request handle করা প্রোগ্রামটা memory mismanage করে — একটা oversized input একটা fixed-size buffer-এর সীমানা ছাড়িয়ে গিয়ে পাশের memory-তে লিখে ফেলতে পারে, আর ঠিক সেভাবেই একজন সম্পূর্ণ বৈধ, সম্পূর্ণ authorized user-এর request প্রোগ্রামের নিয়ন্ত্রণ নিজের হাতে তুলে নিতে পারে attacker-এর জন্য। আজ যা শিখেছি তার একটা স্তর নিচে নামছি — application-logic-এর “কে কী করতে পারবে” থেকে সরাসরি মেশিন-মেমরির “কোথায় কী লেখা হচ্ছে”।

আরও পড়ুন

  • Role-Based Access Controls — David F. Ferraiolo, D. Richard Kuhn — 15th National Computer Security Conference, 1992 · RBAC মডেলের ভিত্তিপ্রস্তর paper — user-role-permission তিন-স্তর কাঠামোর মূল সংজ্ঞা এখান থেকেই, পরে NIST/ANSI INCITS 359 standard-এ formalize হয়েছে
  • Guide to Attribute Based Access Control (ABAC) Definition and Considerations — SP 800-162 — Vincent C. Hu, David Ferraiolo, Rick Kuhn, Adam Schnitzer, Kenneth Sandlin, Robert Miller, Karen Scarfone — NIST · ABAC-এর আনুষ্ঠানিক NIST সংজ্ঞা — subject/resource/action/environment attribute আর policy-evaluation model এই ডকুমেন্ট থেকেই
  • A01:2021 — Broken Access Control — OWASP Top 10 project · ২০২১ সংস্করণে সবচেয়ে বেশি-রিপোর্টেড ক্যাটাগরি (৯৪% application-এ কোনো-না-কোনো broken-access-control টেস্ট ফেল করেছে) — IDOR এই ক্যাটাগরির সবচেয়ে সাধারণ উপ-প্যাটার্ন
  • The Confused Deputy: (or why capabilities might have been invented) — Norman Hardy — ACM SIGOPS Operating Systems Review, 1988 · Confused-deputy সমস্যার মূল বর্ণনা — একটা প্রোগ্রাম নিজের authority আর caller-এর intent গুলিয়ে ফেললে কী ঘটে, তার ধ্রুপদী কেস স্টাডি