এই পর্বে যা শিখবেন
- OWASP API Security Top 10 (২০২৩)-এর দশ ঝুঁকি আর প্রতিটির প্রতিকার
- BOLA, BOPLA আর BFLA চেনা এবং কোড দিয়ে ঠিক করা
- Schema যাচাই, response filter, rate limit, quota আর pagination-এর নকশা
- API gateway-র কাজ, API key বনাম OAuth token, webhook-এর signature আর replay সুরক্ষা
- GraphQL-এর সীমা, shadow ও zombie API খোঁজা, log আর contract test
১ · API আলাদা কেন — দরজাটি সোজা ভাণ্ডারঘরে খোলে
একটি কাল্পনিক অনলাইন বাজারের কথা ভাবুন। ওয়েবসাইটে রিনা শুধু নিজের অর্ডার দেখেন। পর্দায় অন্য কারও অর্ডার দেখার কোনো বোতাম নেই। ডেভেলপার ভাবলেন, কাজ শেষ। কিন্তু রিনার ফোনের app যে পথে ডেটা আনে, সেই API কি এই সীমা জানে?
ব্যাংকের কাউন্টারের কথা ভাবুন। কর্মকর্তা আপনার পরিচয়পত্র দেখেন, তারপর শুধু আপনার হিসাবের কাগজ বের করে দেন। ওয়েবসাইট অনেকটা এই কাউন্টার। API হলো ভেতরের লকার-কক্ষের দরজা, যেখানে আপনি নিজে লকারের নম্বর বলে দেন। দারোয়ান যদি শুধু দেখেন “এই লোক ব্যাংকের গ্রাহক”, আর নম্বরটি তাঁর কি না না মেলান, তাহলে যে কোনো গ্রাহক যে কোনো লকারের কাছে পৌঁছে যাবেন।
API-র ঝুঁকি তিন কারণে আলাদা:
- সরাসরি object-এ হাত।
/orders/1042-এর মতো ঠিকানায় id নিজেই অনুরোধের অংশ। Client যেকোনো সংখ্যা পাঠাতে পারে। - ওপাশে যন্ত্র, মানুষ নয়। Partner-এর server, script বা bot সেকেন্ডে বহু অনুরোধ পাঠাতে পারে। CAPTCHA বা “মানুষের গতি” ধরে নেওয়া এখানে চলে না।
- Mobile app খোলা বই। App ফোনে থাকে, তাই তার ভেতরের endpoint আর গোপন বলে লুকানো মান বের করা সম্ভব। App-এর ভেতরের যাচাই তাই বাড়তি সুবিধা, সুরক্ষা নয় (পর্ব ৩৫).
API-র trust boundary আর ঝুঁকির জায়গা
Client-এর হাতের সবকিছু অবিশ্বস্ত; সিদ্ধান্ত হয় boundary-র ভেতরে
এই ছবির মূল বার্তা: gateway জানে “কে এসেছেন”, কিন্তু order ১০৪২ কার, সেটি জানে শুধু service. তাই gateway থাকলেই API নিরাপদ, এই ধারণা ভুল।
অন্যের API-তে id বদলে “শুধু দেখা”ও অনুমতি ছাড়া প্রবেশের সমান হতে পারে, উদ্দেশ্য ভালো হলেও। পরীক্ষা করুন শুধু নিজের সিস্টেমে, অথবা লিখিত অনুমতি ও ঘোষিত scope-এর ভেতরে। এই পর্বের সব কোড নকল ডেটা নিয়ে memory-তে চলে। আইনি সীমা দেখুন পর্ব ৬-এ.
২ · OWASP API Security Top 10 — দশ ঝুঁকি, দশ প্রতিকার
OWASP-এর সাধারণ web তালিকা পাবেন পর্ব ৩৭-এ। API-র জন্য তাদের আলাদা তালিকা আছে: OWASP API Security Top 10. প্রথম সংস্করণ ২০১৯ সালে, দ্বিতীয়টি ২০২৩ সালে। ২০২৬ সালের অক্টোবরে প্রকল্পের নিজের site দেখে নতুন কোনো সংস্করণ পাইনি, তবে পড়ার সময় আবার মিলিয়ে নেবেন।
| নম্বর | ঝুঁকি | সহজ ভাষায় | মূল প্রতিকার |
|---|---|---|---|
| API1 | Broken Object Level Authorization | id বদলালে অন্যের ডেটা আসে | প্রতিটি object ধরে মালিকানা বা অনুমতি যাচাই |
| API2 | Broken Authentication | Login বা token-এর দুর্বলতায় অন্যের পরিচয় নেওয়া যায় | পরীক্ষিত authentication ব্যবস্থা, login-এ কড়া সীমা, token-এর মেয়াদ, audience ও signature যাচাই |
| API3 | Broken Object Property Level Authorization | Object-এর ভুল ঘর পড়া বা বদলানো যায় | ইনপুট আর আউটপুট দুদিকেই ঘরের allowlist |
| API4 | Unrestricted Resource Consumption | CPU, memory, ব্যান্ডউইথ বা পয়সার খরচে সীমা নেই | Rate limit, quota, আকার ও সময়ের সীমা, খরচের সতর্কতা |
| API5 | Broken Function Level Authorization | সাধারণ ব্যবহারকারী admin-এর কাজ ডাকতে পারেন | Default deny, প্রতিটি function-এ ভূমিকা যাচাই |
| API6 | Unrestricted Access to Sensitive Business Flows | বৈধ কাজটিই যন্ত্র দিয়ে বিপুল পরিমাণে করা যায় | কাজ ধরে সীমা, অস্বাভাবিক আচরণ ধরা, দরকারে বাড়তি যাচাই |
| API7 | Server Side Request Forgery (SSRF) | আপনার server ভুল ঠিকানায় অনুরোধ পাঠায় | গন্তব্যের allowlist, ভেতরের network-এ egress নিয়ন্ত্রণ (পর্ব ৪২) |
| API8 | Security Misconfiguration | Debug চালু, দুর্বল TLS, ঢালাও CORS, ফাঁস হওয়া error | Hardening baseline, স্বয়ংক্রিয় পরীক্ষা, নিরাপদ error |
| API9 | Improper Inventory Management | পুরোনো version আর ভুলে যাওয়া host চলছে | হালনাগাদ inventory, মালিক, বন্ধ করার পরিকল্পনা |
| API10 | Unsafe Consumption of APIs | বাইরের API-র উত্তর অন্ধভাবে বিশ্বাস | বাইরের ডেটাও যাচাই, TLS, timeout, redirect-এ সতর্কতা |
২০১৯ থেকে ২০২৩-এ কয়েকটি বদল লক্ষ্য করুন। SSRF, business flow আর unsafe consumption নতুন। ২০১৯-এর excessive data exposure আর mass assignment মিলে হয়েছে API3. Injection আর logging আলাদা item হিসেবে নেই, তবে এর মানে এগুলো গুরুত্বহীন নয়। Injection-এর নিয়ম পর্ব ৩৯-এ দেখবেন, আর log-এর কথা এই পর্বের অংশ ৯-এ।
৩ · Authorization-এর তিন স্তর — কাজ, object আর ঘর
তালিকার API1, API3 আর API5 আসলে একই প্রশ্নের তিন রূপ: “এই ব্যক্তি কি এটি করতে পারেন?” Authentication বলে আপনি কে। Authorization বলে আপনি কী পারেন। মডেলগুলো দেখেছেন পর্ব ১৯-এ, আর ওয়েবের দিক থেকে IDOR পাবেন পর্ব ৩৮-এ (Broken access control). API-তে এটি তিন স্তরে ভাঙে:
| স্তর | প্রশ্ন | দারোয়ানের ভাষায় | ভাঙলে |
|---|---|---|---|
| Function (BFLA) | এই কাজটি কি আপনার ভূমিকায় আছে? | গ্রাহক কি ম্যানেজারের ঘরে ঢুকতে পারবেন? | সাধারণ ব্যবহারকারী refund চালান |
| Object (BOLA) | এই নির্দিষ্ট জিনিসটি কি আপনার? | এই লকার কি আপনার নামে? | রিনা করিমের অর্ডার দেখেন |
| Property (BOPLA) | এই ঘরটি কি আপনি দেখতে বা বদলাতে পারেন? | লকারের রেজিস্টারে কোন লাইন আপনি ছুঁতে পারেন? | গ্রাহক নিজের role বদলান |
নিচের খেলনা API-তে দুটি handler আছে। unsafeGet শুধু দেখে token বৈধ কি না। safeGet মালিকানাও মেলায়। সবই memory-তে, কোনো network নেই।
// Toy in-memory API. No network, fake data only.
const orders = {
1042: { owner: "rina", item: "book", amount: 500 },
1043: { owner: "karim", item: "phone", amount: 9000 },
};
const tokens = { "example-token-rina": "rina", "example-token-karim": "karim" };
function unsafeGet(req) {
const user = tokens[req.token];
if (!user) return { status: 401 }; // checks WHO you are
const order = orders[req.id];
return order ? { status: 200, order } : { status: 404 };
}
function safeGet(req) {
const user = tokens[req.token];
if (!user) return { status: 401 };
const order = orders[req.id];
// Same answer for "missing" and "not yours", so ids stay unconfirmed.
if (!order || order.owner !== user) return { status: 404 };
return { status: 200, order };
}
const tests = [
{ who: "rina, own order ", token: "example-token-rina", id: 1042 },
{ who: "rina, karim order", token: "example-token-rina", id: 1043 },
{ who: "rina, no order ", token: "example-token-rina", id: 9999 },
{ who: "no token ", token: "", id: 1042 },
];
const show = r => r.status + (r.order ? " amount=" + r.order.amount : "");
for (const t of tests) {
console.log(t.who, "| unsafe:", show(unsafeGet(t)).padEnd(15), "| safe:", show(safeGet(t)));
}দ্বিতীয় লাইনটিই পুরো গল্প: rina, karim order | unsafe: 200 amount=9000 | safe: 404. Token দুই ক্ষেত্রেই বৈধ, তাই authentication-এ কিছু ধরা পড়েনি। পার্থক্য একটি শর্তে, order.owner !== user. safeGet-এ “অন্যের” আর “নেই” দুটোতেই 404, তাই বাইরে থেকে বোঝা যায় না id-টি আছে কি না। কেউ কেউ “অন্যের” ক্ষেত্রে 403 পছন্দ করেন; দুটো পথই চলে, তবে id অনুমান করা গেলে 404 তথ্য কম দেয়।
তিনটি অভ্যাস এই শর্ত ভুলে যাওয়া ঠেকায়। প্রথমত, মালিকানা ধরা থাকুক ডেটা আনার query-তেই, যেমন “এই id এবং এই owner”. দ্বিতীয়ত, user-এর পরিচয় নিন token থেকে, অনুরোধের body বা URL থেকে নয়। তৃতীয়ত, প্রতিটি endpoint-এ দুই ব্যবহারকারী দিয়ে স্বয়ংক্রিয় test লিখুন: A-এর token দিয়ে B-এর জিনিস চাইলে ব্যর্থ হতেই হবে।
UUID-এর মতো অনুমান-কঠিন id ব্যবহার করা ভালো, তবে সেটি প্রতিকার নয়। id ফাঁস হয় log, URL, শেয়ার করা লিঙ্ক আর অন্য API-র উত্তর থেকে। অনুমতি যাচাই না থাকলে একটি ফাঁস হওয়া id-ই যথেষ্ট।
BFLA ঠেকায় default deny: নতুন endpoint শুরুতে সবার জন্য বন্ধ, নির্দিষ্ট ভূমিকা খুললে তবেই খোলা। URL-এ /admin নেই বলে কোনো কাজ সাধারণ হয়ে যায় না। আর পর্দায় বোতাম লুকানো শুধু সাজসজ্জা; যাচাই হতে হবে server-এ।
৪ · Schema যাচাই আর response ফিল্টার — দুই দিকের চুক্তি
সরকারি ফরমে নির্দিষ্ট ঘর থাকে। কেউ ফরমের পাশে “আমার বেতন দ্বিগুণ করা হোক” লিখে দিলে কেরানি সেটি নথিতে তোলেন না। Schema validation ঠিক এই কেরানি। OpenAPI ফাইলে আপনি লিখে রাখেন কোন endpoint কী নেবে, কী দেবে। ভেতরের গঠনটি লেখা হয় JSON Schema-য়। এটিই positive security model: যা ঘোষিত, শুধু তা-ই চলবে।
openapi: 3.1.0
info: { title: Orders API, version: 2.0.0 }
paths:
/v2/orders:
post:
security:
- bearerAuth: [orders:write]
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/NewOrder' }
responses:
'201': { description: created }
'400': { description: invalid input }
components:
securitySchemes:
bearerAuth: { type: http, scheme: bearer }
schemas:
NewOrder:
type: object
required: [item, qty]
properties:
item: { type: string, maxLength: 20 }
qty: { type: integer, minimum: 1, maximum: 10 }
additionalProperties: falseশেষের additionalProperties: false লাইনটিই সবচেয়ে দামি। এটি না থাকলে অজানা ঘর চুপচাপ ঢুকে পড়ে। আর কোড যদি পুরো body সরাসরি object-এ বসিয়ে দেয়, তখনই হয় mass assignment: ক্রেতা নিজেই price ঠিক করে দেন। নিচে JSON Schema-র ছোট একটি অংশ হাতে বানানো হয়েছে শুধু বোঝার জন্য। বাস্তবে পরীক্ষিত library ব্যবহার করবেন।
// A tiny subset of JSON Schema, for learning. Real services use a tested library.
const schema = {
required: ["item", "qty"],
additionalProperties: false,
properties: {
item: { type: "string", maxLength: 20 },
qty: { type: "number", minimum: 1, maximum: 10 },
note: { type: "string", maxLength: 50 },
},
};
function validate(data, s) {
const errs = [];
for (const k of s.required) if (!(k in data)) errs.push("missing " + k);
for (const [k, v] of Object.entries(data)) {
const rule = s.properties[k];
if (!rule) { errs.push("unknown field " + k); continue; }
if (typeof v !== rule.type) { errs.push(k + " must be " + rule.type); continue; }
if (rule.maxLength && v.length > rule.maxLength) errs.push(k + " too long");
if (rule.minimum !== undefined && v < rule.minimum) errs.push(k + " below minimum");
if (rule.maximum !== undefined && v > rule.maximum) errs.push(k + " above maximum");
}
return errs;
}
const payloads = [
{ item: "book", qty: 2 },
{ item: "book", qty: 2, price: 1 },
{ item: "book", qty: "2" },
{ item: "book", qty: 500 },
{ qty: 1, note: "x".repeat(60) },
];
for (const p of payloads) {
const errs = validate(p, schema);
const shown = JSON.stringify(p).slice(0, 34);
console.log(errs.length ? "400 " : "OK ", shown, errs.join("; "));
}পাঁচটির মধ্যে একটিই OK. দ্বিতীয়টির ফল unknown field price: দাম client ঠিক করতে পারবেন না। তৃতীয়টি qty must be number, কারণ "2" লেখা, সংখ্যা নয়। চতুর্থটি qty above maximum. পঞ্চমটিতে দুটি ভুল একসঙ্গে: missing item; note too long. সব ভুল একবারে জানানো client-এর জন্য সুবিধাজনক। তবে ত্রুটির বার্তায় ভেতরের table বা class-এর নাম যেন না থাকে।
Schema যা পারে না সেটিও জানা দরকার। qty-এর গঠন ঠিক থাকলেও অর্ডারটি রিনার কি না, schema বলতে পারে না। Injection-ও এতে পুরো ঠেকে না; তার জন্য parameterized query লাগে (পর্ব ৩৯). Schema প্রথম ছাঁকনি, শেষ সিদ্ধান্ত নয়।
উত্তরের দিকের ছাঁকনি
ইনপুট আটকালে অর্ধেক কাজ হয়। Excessive data exposure ঘটে উল্টো পথে: ডেটাবেসের পুরো row পাঠিয়ে ভাবা হয় app শুধু নাম দেখাবে। কিন্তু network-এর উত্তর যে কেউ নিজের ফোনেই পড়তে পারেন। তাই response filtering করতে হবে server-এ, allowlist দিয়ে:
// Name each field that may leave the server. Never send the raw row.
function toOrderView(row) {
return { id: row.id, item: row.item, amount: row.amount };
}
// row also holds owner, cost_price and supplier_note: none of them go out.নতুন ঘর ডেটাবেসে যোগ হলে এই পদ্ধতিতে সেটি আপনাআপনি বাইরে যায় না, কেউ ভেবে যোগ করলে তবেই যায়। সন্দেহ থাকলে উত্তরের schema-ও test-এ যাচাই করুন।
৫ · Rate limit, quota আর pagination — গতি, মোট আর আকার
ঈদের ট্রেনের টিকিটের ঘর ভাবুন। “একজন একবারে চারটির বেশি নয়” হলো কাজের সীমা। “জানালায় মিনিটে কতজন” হলো গতির সীমা। “আজ মোট কত” হলো quota. API-তেও তিনটি আলাদা জিনিস। Rate limit দেখে গতি, quota দেখে মোট পরিমাণ, আর pagination দেখে এক উত্তরের আকার। Algorithm-গুলোর গভীর আলোচনা আছে System Design-এর rate limiting পর্বে; এখানে দেখছি নিরাপত্তার দিক।
| যা ধরে গোনা | যা ঠেকায় | যেখানে দুর্বল |
|---|---|---|
| IP | Login-এর আগের অনুরোধ, সরল bot | অনেক মোবাইল ব্যবহারকারী CGNAT-এ এক IP ভাগ করেন, আবার ছড়ানো botnet-এর IP অনেক |
| API key | একটি app বা partner-এর মোট ব্যবহার | Key ফাঁস হলে সব ব্যবহারকারীর ভাগ একজন খেয়ে ফেলেন |
| User বা account | একজনের অস্বাভাবিক পড়া | Login-এর আগে কাজ করে না; অনেক অ্যাকাউন্ট থাকলে ভাগ হয়ে যায় |
| Tenant | এক গ্রাহক প্রতিষ্ঠান যেন বাকিদের ধীর না করে | প্রতিষ্ঠানের ভেতরের অপব্যবহার দেখে না |
| নির্দিষ্ট কাজ | OTP পাঠানো, password reset, টিকিট কেনা | প্রতিটি কাজের সীমা আলাদা করে ভাবতে হয় |
একটি স্তরে ভরসা নয়, কয়েকটি একসঙ্গে। Token bucket-এর ধারণা সহজ: বালতিতে কয়েকটি token, প্রতি অনুরোধে একটি খরচ, সময় গেলে ধীরে ধীরে ভরে। নিচের সিমুলেটরে প্রতি API key-র জন্য আলাদা বালতি, ধারণক্ষমতা ৩, প্রতি ১০ সেকেন্ডে একটি token, আর দিনের quota ৬. ঘড়ি নকল।
// Token bucket per API key, plus a daily quota. Simulated clock, no real time.
const CAPACITY = 3, REFILL_EVERY = 10, DAILY_QUOTA = 6; // tokens, seconds, calls
const state = {};
function allow(key, now) {
const b = state[key] ??= { tokens: CAPACITY, last: 0, used: 0 };
const gained = Math.floor((now - b.last) / REFILL_EVERY);
if (gained > 0) {
b.tokens = Math.min(CAPACITY, b.tokens + gained);
b.last += gained * REFILL_EVERY;
}
if (b.used >= DAILY_QUOTA) return "429 quota";
if (b.tokens === 0) return "429 retry-after=" + (b.last + REFILL_EVERY - now) + "s";
b.tokens--;
b.used++;
return "200";
}
const calls = [
[0, "key-A"], [0, "key-A"], [1, "key-A"], [2, "key-A"], [2, "key-B"],
[10, "key-A"], [14, "key-A"], [45, "key-A"], [46, "key-A"], [47, "key-A"],
[90, "key-A"], [90, "key-B"],
];
for (const [t, key] of calls) {
const r = allow(key, t);
const b = state[key];
console.log("t=" + String(t).padStart(2), key, r.padEnd(19), "tokens=" + b.tokens, "used=" + b.used);
}t=2-এ key-A পায় 429 retry-after=8s, কারণ বালতি খালি আর পরের token আসবে t=10-এ। একই মুহূর্তে key-B পায় 200: একজনের অতিরিক্ত ব্যবহার অন্যকে আটকায় না। t=45-এ জমে থাকা সময়ে বালতি আবার ভরে যায়, ফলে tokens=2. t=47-এ বালতিতে token থাকলেও উত্তর 429 quota, কারণ used=6. t=90-এ বালতি ভরা (tokens=3), তবু quota শেষ। গতি আর মোট, দুটো আলাদা হিসাব।
Sliding window আরেকটি পথ: প্রতি মুহূর্তে ঠিক আগের ৬০ সেকেন্ডের অনুরোধ গোনা। এতে burst কম যায়, তবে হিসাব রাখার খরচ বেশি। বেছে নেওয়ার বিষয়, ঠিক-ভুলের নয়।
HTTP 429 (Too Many Requests, RFC 6585) ফেরানো আর Retry-After জানানো ভালো ব্যবহার। ভদ্র client এতে থামে। যিনি নিয়ম মানেন না, তাঁর জন্য সীমাটি server-এই বসানো।
হিসাব করে দেখুন
ধারণক্ষমতা b, প্রতি সেকেন্ডে r token, শুরুতে বালতি ভরা। তাহলে T সেকেন্ডে সর্বোচ্চ অনুরোধ = b + rT. যেমন b = ৬০, r = ১ হলে ৬০ সেকেন্ডে ৬০ + ৬০ = ১২০টি। তাই “মিনিটে ৬০” লিখলেও প্রথম মিনিটে দ্বিগুণ যেতে পারে।
Rate limit BOLA সারায় না। ধরুন ১০,০০,০০০টি অর্ডার, আর API-তে BOLA আছে। প্রতি key-তে মিনিটে ৬০ অনুরোধ মানে সেকেন্ডে ১টি। সব পড়তে লাগে ১০,০০,০০০ ÷ ৮৬,৪০০ ≈ ১১.৬ দিন। ২০টি key থাকলে প্রায় ১৩.৯ ঘণ্টা। সীমা গতি কমায় আর সময় কেনে, ফুটো বন্ধ করে না।
Pagination-ও সীমা। ধরুন প্রতি row ২ KB. limit-এর উপরসীমা না থাকলে ১,০০,০০০ row-এর এক উত্তর ২০০ MB. উপরসীমা ১০০ হলে উত্তর ২০০ KB, মানে এক হাজার ভাগের এক ভাগ। তাই client যা-ই চান, server-এ limit কেটে দিন, আর পৃষ্ঠার গভীরে না যেতে cursor ব্যবহার করুন।
Sensitive business flow আলাদা করে ভাবতে হয়, কারণ এখানে প্রতিটি অনুরোধ বৈধ। ঈদের টিকিট যন্ত্র দিয়ে মুহূর্তে কিনে নেওয়া, বা নকল অ্যাকাউন্ট খুলে referral বোনাস তোলা, কোনোটিতেই কোড ভাঙে না। প্রতিকার ব্যবসার ভাষায়: এক ব্যক্তির কেনার সীমা, লাইন বা অপেক্ষার ব্যবস্থা, অস্বাভাবিক গতিতে বাড়তি যাচাই। আর পয়সার খরচে সীমা দিন: প্রতিটি SMS OTP-র জন্য টাকা লাগলে সীমাহীন endpoint সরাসরি বিলে আঘাত করে (economic denial of sustainability).
৬ · Gateway আর পরিচয় — API key বনাম OAuth token
API gateway হলো অফিসের রিসেপশন। সবাই এখানে দিয়েই ঢোকেন। পরিচয়পত্র দেখা, খাতায় নাম তোলা, ঠিক কক্ষে পাঠানো, ভিড় সামলানো, এসব একই জায়গায় হয়। প্রতিটি কক্ষে আলাদা করে করতে হয় না। মূল প্রযুক্তিটি System Design-এর gateway পর্বে আছে।
Gateway-র ভালো কাজগুলো হলো TLS termination, token যাচাই, rate limit, আকারের সীমা, schema যাচাই, routing আর অভিন্ন log. কিন্তু object-এর মালিকানা ব্যবসার ডেটা, তাই সে সিদ্ধান্ত service-এর। Service-কে সরাসরি internet থেকে আসা অনুরোধও নিতে দেবেন না, নইলে gateway এড়িয়ে যাওয়া যায়। ভেতরে mTLS বা network নিয়ম দিয়ে এটি নিশ্চিত করা হয়, আর service নিজেও token-এর মূল দাবি আবার দেখে নেয় (zero trust, পর্ব ৭৪)।
নিচে রিনার একটি বৈধ অনুরোধ ধাপে ধাপে দেখুন। প্রতিটি ধাপে কী মেলানো হলো আর ব্যর্থ হলে কী উত্তর যেত, দুটোই খেয়াল করুন।
- App
requestরিনার ফোন - Gateway
৪টি যাচাইপরিচয়, সীমা, গঠন - Service
২টি যাচাইকাজ, object - উত্তর
200ছাঁকা ঘর, log
GET /v2/orders/1042
Authorization: Bearer
example-token-rina
X-Request-Id: req-7f31
TLS 1.3 ok header size 1.2 KB <= 8 KB pass body size 0 <= 1 MB pass fail -> 431 or 413
signature valid
exp not expired
aud orders-api
scope orders:read
sub rina
fail -> 401
limit 60 per minute
used 2
now 3 <= 60 pass
fail -> 429 + Retry-After
route /v2/orders/{id} declared
id 1042 integer pass
query none pass
fail -> 400 or 404
role customer
action order.read
policy customer may read pass
fail -> 403
order 1042 owner = rina
token sub = rina
equal pass
fail -> 404
db row: id item amount owner
cost_price supplier_note
reply : id item amount
log : req-7f31 rina order=1042
200 (token not logged)
- অনুরোধ বের হলো। রিনার app order ১০৪২ চাইছে। URL, header আর token, সবই client-এর পাঠানো, তাই অবিশ্বস্ত।
X-Request-Idপরে সব log জোড়া লাগাতে সাহায্য করবে। - প্রবেশপথের সস্তা যাচাই। TLS শেষ হয়, আকার মাপা হয়। আগে সস্তা যাচাই, যাতে বিশাল অনুরোধে দামি কাজ শুরুই না হয়। Header বেশি বড় হলে
431, body বড় হলে413. - আপনি কে? Gateway token-এর signature, মেয়াদ, audience আর scope মেলায়।
audমেলানো জরুরি, নইলে অন্য service-এর জন্য দেওয়া token এখানেও চলে। ফল:sub = rina. ব্যর্থ হলে401. - গতি ঠিক আছে? পরিচয় জানা গেছে, তাই চাবি হলো
user:rina. এই মিনিটে আগে ২টি ছিল, এটি ৩ নম্বর, সীমা ৬০. বেশি হলে429আরRetry-After. - অনুরোধটি চুক্তিতে আছে? Route spec-এ ঘোষিত, id পূর্ণসংখ্যা, অজানা query নেই। Spec-এ না থাকা route এখানেই থামে। এ পর্যন্ত যাচাই gateway-তে সেরে নেওয়া যায়।
- এই কাজ কি আপনার ভূমিকায়? Service দেখে
customerভূমিকায়order.readআছে। এটি BFLA ঠেকানোর ধাপ। না থাকলে403. - এই object কি আপনার? ডেটাবেসে order ১০৪২-এর মালিক
rina, token-এরsub-ওrina. এটি BOLA ঠেকানোর ধাপ, এবং শুধু service-ই এটি করতে পারে। অন্যের হলে404. - ছেঁকে উত্তর, তারপর log. Row-এ ৬টি ঘর, রিনা পান ৩টি: এটি API3-এর প্রতিরক্ষা। Log-এ অনুরোধের id, user, object আর ফল থাকে, কিন্তু token থাকে না।
API key নাকি OAuth token
দুটোকে অনেকে একই ভাবেন, কিন্তু প্রশ্ন আলাদা। API key বলে “কোন app বা প্রকল্প ডাকছে”. Access token বলে “কোন app, কার হয়ে, কী করার অনুমতি নিয়ে, কতক্ষণের জন্য”. Session আর token-এর ভেতরের কথা আছে পর্ব ১৭-এ, আর OAuth পর্ব ১৮-এ.
| দিক | API key | OAuth access token |
|---|---|---|
| যা বোঝায় | App বা প্রকল্প | App, ব্যবহারকারী আর scope |
| আয়ু | সাধারণত দীর্ঘ, হাতে বদলাতে হয় | স্বল্প, নিজে থেকে মেয়াদ শেষ |
| সীমা বসানো | Key ধরে অনুমতি আর quota | Scope আর audience ধরে সূক্ষ্ম |
| ভালো মানায় | Server থেকে server, সরল ব্যবহার ও billing | ব্যবহারকারীর ডেটা, তৃতীয় পক্ষের app |
| বড় ভুল | Mobile app বা browser-এ বসানো | Audience না মেলানো, scope ঢালাও রাখা |
Key-তেও ভালো অভ্যাস চলে: একটি key শুধু একটি কাজ, দরকারি অনুমতির বেশি নয়, server-এ শুধু hash রেখে দেওয়া, চিনতে সুবিধার জন্য স্পষ্ট prefix, আর নিয়মিত rotation. Machine-to-machine-এ client credentials বা workload identity থাকলে স্বল্পায়ু token পাওয়া যায়, তাই দীর্ঘস্থায়ী key-এর দরকার কমে।
৭ · Webhook আর বাইরের API — অন্যের কথাও যাচাই করুন
এতক্ষণ অনুরোধ আসছিল ব্যবহারকারীর কাছ থেকে। Webhook-এ উল্টো: বাইরের কোম্পানি আপনার URL ডেকে জানায় “টাকা এসেছে”. URL জানলে যে কেউ এই ঠিকানায় অনুরোধ পাঠাতে পারেন। পর্ব ৯-এ আপনি HMAC দিয়ে এই বার্তা যাচাই আর constant-time তুলনা দেখেছেন। এখানে তিনটি বাস্তব সমস্যা যোগ হচ্ছে: সময়ের মধ্যে একই বার্তা আবার আসা, বৈধ পুনঃপ্রেরণ, আর key বদলের দিন দুটি key.
পোস্ট অফিসের রেজিস্টার্ড চিঠি ভাবুন। সিলমোহর বলে কে পাঠিয়েছে আর খাম খোলা হয়নি। তারিখ বলে চিঠিটি পুরোনো কি না। আর রেজিস্টার খাতার নম্বর বলে এই চিঠি আগে জমা হয়েছে কি না। তিনটি আলাদা প্রশ্ন, একটি অন্যটির কাজ করে না।
Timestamp একা replay ঠেকায় না, ৫ মিনিটের মধ্যে একই বার্তা আবার পাঠানো যায়। তাই signature-এর ভেতরে event id রাখা হয়, আর গ্রহণকারী দেখা id-গুলো জমা রাখেন। নিচের কোডে Python-এর আসল hmac চলছে। Key নকল, ঘড়ি নকল, network নেই।
import hashlib, hmac
KEYS = [b"example-new-key", b"example-old-key"] # fake; two live during rotation
MAX_AGE = 300 # seconds
NOW = 1767225700 # simulated clock
seen = set() # real systems: shared store with expiry
def sign(event_id, ts, body, key):
msg = f"{event_id}.{ts}.".encode() + body
return hmac.new(key, msg, hashlib.sha256).hexdigest()
def receive(event_id, ts, body, tag):
if abs(NOW - ts) > MAX_AGE:
return "400 outside time window"
if not any(hmac.compare_digest(sign(event_id, ts, body, k), tag) for k in KEYS):
return "401 bad signature"
if event_id in seen:
return "200 duplicate, ignored" # say OK so the sender stops retrying
seen.add(event_id)
return "200 processed"
ts = 1767225600
body = b'{"order":1042,"amount":500,"status":"paid"}'
tag = sign("evt_1", ts, body, KEYS[0])
print("tag :", tag[:16] + "...")
print("first :", receive("evt_1", ts, body, tag))
print("again :", receive("evt_1", ts, body, tag))
print("edited :", receive("evt_2", ts, body.replace(b"500", b"900"), sign("evt_2", ts, body, KEYS[0])))
print("new id :", receive("evt_3", ts, body, tag))
print("old key :", receive("evt_4", ts, body, sign("evt_4", ts, body, KEYS[1])))
print("wrong key:", receive("evt_5", ts, body, sign("evt_5", ts, body, b"other-key")))
old = ts - 3600
print("too old :", receive("evt_6", old, body, sign("evt_6", old, body, KEYS[0])))
print("seen :", sorted(seen))first হলো 200 processed, আর হুবহু একই বার্তা again-এ 200 duplicate, ignored: টাকা দুবার জমা হয় না। 200 ফেরানো হলো যাতে প্রেরক বারবার না পাঠান। edited আর new id দুটোই 401 bad signature, কারণ body আর id দুটোই সই করা অংশে। old key হলো 200 processed: rotation-এর সময় দুটি key-ই চলছে। too old ধরা পড়ে সময়ের যাচাইয়ে। শেষে seen-এ আছে শুধু evt_1 আর evt_4.
- প্রেরক
signসিলমোহর - Raw body
43 bytesঅপরিবর্তিত - যাচাই
time, tag, idতিন প্রশ্ন - কাজ
order 1042একবারই
id = evt_1
ts = 1767225600
msg = id + "." + ts + "." + body
tag = HMAC-SHA256(key, msg)
= a9abb62054af5d3d...
header id : evt_1
header ts : 1767225600
header tag : a9abb62054af5d3d...
raw body : 43 bytes, untouched
clock NOW : 1767225700
NOW - ts
= 1767225700 - 1767225600
= 100 seconds
100 <= 300 pass
computed = a9abb62054af5d3d... received = a9abb62054af5d3d... compare_digest -> equal
seen before: { }
evt_1 not in seen new
seen after : { evt_1 }
action : mark order 1042 paid
reply : 200 processed
seen : { evt_1 }
evt_1 in seen duplicate
action : none
reply : 200 duplicate, ignored
- সই করা। প্রেরক id, সময় আর body জুড়ে HMAC-SHA256 বানান। Tag-এর শুরু
a9abb62054af5d3d. সময় আর id সই করা অংশে থাকায় পরে এগুলো বদলানো যায় না। - Raw byte ধরে রাখা। ৪৩ byte body যেমন এসেছে, তেমনই রাখা হলো। আগে JSON parse করে আবার বানালে ফাঁকা জায়গা বা ক্রম বদলে যেতে পারে, তখন বৈধ tag-ও মিলবে না।
- সময় মেলানো। ব্যবধান ১০০ সেকেন্ড, সীমা ৩০০, তাই পাস। ভবিষ্যতের সময়ও আটকাতে পরম মান নেওয়া হয়। সীমা খুব ছোট হলে ঘড়ির অমিল আর বৈধ পুনঃপ্রেরণ আটকে যাবে।
- সিলমোহর মেলানো। গ্রহণকারী নিজের key দিয়ে আবার হিসাব করেন। দুই মান সমান। তুলনা হয়
compare_digestদিয়ে, যাতে সময় দেখে কিছু বোঝা না যায়। সইয়ের বিশ্বাস আগে, তারপরই body-র ভেতরের মান পড়া। - রেজিস্টারে তোলা।
evt_1আগে দেখা হয়নি, তাই খাতায় উঠল, অর্ডার ১০৪২ পরিশোধিত হলো, উত্তর200 processed. বাস্তবে id রাখার কাজ আর ব্যবসায়িক কাজ একই transaction-এ হলে ভালো। - আবার এল একই বার্তা। নেটওয়ার্কের কারণে প্রেরক হয়তো আবার পাঠিয়েছেন, অথবা কেউ ধরে রেখে পাঠিয়েছেন। Signature আর সময় দুটোই পাস করে। শুধু রেজিস্টার ধরে: কোনো কাজ নয়, উত্তর
200 duplicate, ignored. এটিই idempotency.
বাইরের API-কে কতটা বিশ্বাস
API10-এর মূল কথা সহজ: partner-এর API থেকে আসা ডেটাও ইনপুট। সেই কোম্পানি হ্যাক হতে পারে, ভুল ডেটা পাঠাতে পারে, বা অন্য ঠিকানায় redirect করতে পারে। তাই উত্তরের schema যাচাই করুন, আকারের সীমা ও timeout দিন, redirect অন্ধভাবে অনুসরণ করবেন না, আর ব্যবহারকারী-দেওয়া URL fetch করতে হলে SSRF-এর নিয়ম মেনে চলুন (পর্ব ৪২, SSRF আর file handling).
৮ · GraphQL — এক দরজায় অসংখ্য অনুরোধ
REST-এ প্রতিটি কাজের আলাদা URL থাকে। GraphQL-এ সাধারণত একটিই endpoint, আর client query লিখে বলে দেয় ঠিক কী চাই। রেস্তোরাঁর নির্দিষ্ট মেনু বনাম “যা খুশি বলুন” ধরনের রান্নাঘর ভাবুন। সুবিধা বিশাল, তবে নিরাপত্তার হিসাব বদলে যায়: এক অনুরোধের কাজের পরিমাণ আর আগে থেকে জানা নেই। তাই “অনুরোধ গোনা” rate limit এখানে যথেষ্ট নয়।
হিসাবটি দেখুন। Query-র তিন স্তরে তালিকা: ১০ জন ক্রেতা, প্রত্যেকের ১০টি অর্ডার, প্রতি অর্ডারে ১০টি পণ্য। সর্বোচ্চ node = ১০ + ১০ × ১০ + ১০ × ১০ × ১০ = ১০ + ১০০ + ১,০০০ = ১,১১০. প্রতিটি সীমা ১০০ হলে একই গঠনে ১০০ + ১০,০০০ + ১০,০০,০০০ = ১০,১০,১০০. এক অনুরোধ, অথচ কাজ এক হাজার গুণের কাছাকাছি।
| প্রতিরক্ষা | যা করে | সীমা |
|---|---|---|
| Depth limit | কত স্তর গভীর যাওয়া যাবে তা বেঁধে দেয় | অগভীর কিন্তু চওড়া query ধরে না |
| Cost analysis | চালানোর আগে কাজের হিসাব করে বাতিল করে | নকশা ও ওজন ঠিক করা কঠিন |
| তালিকার সীমা | প্রতিটি list-এ বাধ্যতামূলক pagination | সব resolver-এ রাখতে হয় |
| Batch-এর সীমা | এক HTTP অনুরোধে কত operation, সেটি সীমিত | গোনা হওয়া দরকার HTTP নয়, operation ধরে |
| Persisted query | নিবন্ধিত query ছাড়া কিছু চলে না | নিজের app-এর জন্য ভালো, public API-তে কঠিন |
| Introspection বন্ধ | Public production-এ পুরো schema খুলে দেয় না | নাম অনুমান ঠেকায় না; এটি গভীর প্রতিরক্ষার সামান্য অংশ |
OWASP-এর GraphQL cheat sheet introspection আর development-এর playground public production-এ বন্ধ রাখতে বলে, আর সেই সঙ্গে সতর্ক করে যে এতে schema পুরো লুকায় না। সবচেয়ে বড় কথা, authorization থাকতে হবে প্রতিটি field-এর resolver বা business layer-এ। একটি পথ দিয়ে order সুরক্ষিত, অন্য nested পথে খোলা, GraphQL-এ এই ভুল সহজেই হয়।
৯ · Inventory, version, log আর contract test
পুরোনো বাড়ির কথা ভাবুন। সামনের গেটে দারোয়ান, ক্যামেরা, খাতা সব আছে। কিন্তু পেছনে একটি গলির গেট, বহু বছর আগে বসানো, যার কথা নতুন কেয়ারটেকার জানেনই না। Shadow API হলো সেই গেট, যা চলছে কিন্তু তালিকায় নেই। Zombie API হলো যে গেট “বন্ধ” বলে ঘোষণা হয়েছিল, অথচ তালা আসলে কেউ লাগায়নি।
Versioning দরকার, কারণ পুরোনো mobile app সবাই তখনই বদলান না। কিন্তু প্রতিটি পুরোনো version একটি বাড়তি দরজা, আর নতুন প্রতিকার সেখানে প্রায়ই পৌঁছায় না। তাই inventory-তে প্রতিটি API-র জন্য লিখে রাখুন: মালিক, version, পরিবেশ, কারা ব্যবহার করেন, কোন ডেটা, আর কবে বন্ধ হবে। Spec-কে সত্য ধরবেন না; মিলিয়ে দেখবেন বাস্তব প্রমাণের সঙ্গে।
GET /v2/orders/{id}
POST /v2/orders
GET /v2/profile
POST /v2/webhooks/payment
total: 4 routes
/v2/* -> orders-service
/v1/* -> legacy-service
/internal/* -> blocked
note: /v1 has no owner tag
GET /v2/orders/{id} 812,040
POST /v2/orders 96,115
GET /v2/profile 230,772
POST /v2/webhooks/pay.. 4,018
GET /v1/customers/{id} 1,940
in spec, in log : 4
in spec, no log : 0
no spec, in log : 1
GET /v1/customers/{id}
owner: unknown
auth shared API key
object check none
rate limit none
reply full row, NID field
callers 1 old partner key
today : temporary limit + alert
week 1: owner named, partner told
week 2: Deprecation + Sunset headers
week 6: 410 Gone, route removed
CI : route not in spec = fail
- দলের বিশ্বাস। Spec বলছে ৪টি route. সবাই ভাবছেন API এটুকুই। এটি একটি দাবি, প্রমাণ নয়।
- Gateway-র সত্য। Configuration-এ
/v1/*এখনো পুরোনো service-এ যাচ্ছে, আর তার কোনো মালিকের নাম নেই। প্রথম সন্দেহ এখানেই। - Log-এর সত্য। ৩০ দিনের log-এ path-এর সংখ্যা
{id}-তে বদলে গোনা হলো। মূল চারটির পাশে আছেGET /v1/customers/{id}, ১,৯৪০ বার। মানে কেউ এখনো এটি ডাকছেন। - তিন তালিকার তুলনা। ৪টি মিলেছে, ১টি spec-এ নেই। “মালিক নেই, তালিকায় নেই, কিন্তু চলছে”, এটিই খোঁজা হচ্ছিল।
- ঝুঁকি মাপা। নিজের staging-এ পরীক্ষায় দেখা গেল key সবার জন্য একটিই, object যাচাই নেই, সীমা নেই, উত্তরে জাতীয় পরিচয়পত্রের ঘরও আছে। অর্থাৎ API1, API3, API4 আর API9 এক জায়গায়। এটি incident কি না জানতে পুরোনো log-ও দেখতে হবে (পর্ব ৬১).
- সুশৃঙ্খল বিদায়। সঙ্গে সঙ্গে সীমা আর alert, তারপর মালিক ঠিক করা, partner-কে জানানো, deprecation-এর সংকেত, আর নির্দিষ্ট দিনে
410 Gone. শেষ ধাপটিই আসল শিক্ষা: CI-তে নিয়ম, যাতে spec-এর বাইরে route আর মুক্তি না পায়।
Log আর anomaly alert
Authorization-এর ফুটো প্রায়ই error দেখায় না; উত্তর 200-ই আসে। তাই সাধারণ error alert এটি ধরে না। API-র log-এ প্রতি অনুরোধে রাখুন: সময়, request id, user, key-এর id (key নয়), route, object id, status, উত্তরের আকার আর সময়। Token, password আর ব্যক্তিগত ডেটা log-এ নয় (পর্ব ৫৫).
যে আচরণ দেখতে হয়: একজন user অল্প সময়ে অনেক আলাদা object ছুঁচ্ছেন, 403 বা 404-এর হঠাৎ বৃদ্ধি, অস্বাভাবিক বড় উত্তর, পুরোনো version-এ হঠাৎ চলাচল, রাতের বেলা একটি key-র ব্যবহার। নিচে নিজের API log-এর জন্য একটি Sigma-ধাঁচের নিয়ম।
# Sigma-style idea for your own API logs; field names are examples
title: Denied order lookups
name: orders_denied
status: experimental
logsource:
product: orders-api
category: application
detection:
selection:
route: '/v2/orders/{id}'
status:
- 403
- 404
condition: selection
level: low
---
title: One user denied on many different orders
correlation:
type: value_count
rules:
- orders_denied
group-by:
- user_id
timespan: 10m
condition:
field: object_id
gte: 50
level: mediumসীমা ৫০ এখানে অনুমান; নিজের ট্র্যাফিক দেখে ঠিক করবেন, নইলে false positive বাড়বে। ব্যর্থতার পাশাপাশি “একজন user-এর সফল object-সংখ্যা”ও বেসলাইন ধরে দেখুন, কারণ BOLA থাকলে ব্যর্থতা হয়তো আসবেই না।
Contract test
Contract test spec আর বাস্তবকে একসঙ্গে রাখে। CI-তে নকল ডেটা নিয়ে আসল service-কে ডাকা হয়, আর দেখা হয় উত্তর spec মানছে কি না। নিরাপত্তার জন্য এতে কয়েকটি নেতিবাচক test যোগ করুন: অজানা ঘর দিলে 400, token ছাড়া 401, অন্যের object-এ 403 বা 404, অতিরিক্ত limit কেটে যাওয়া, উত্তরে ঘোষণার বাইরের ঘর না থাকা। শুধু “সফল পথ” পরীক্ষা করলে নিরাপত্তার কিছুই প্রমাণ হয় না। আরও পাবেন পর্ব ৪৯-এ।
আরও গভীরে: OpenAPI ৩.১ JSON Schema-র ২০২০-১২ dialect-এর সঙ্গে মিলেছে। allOf দিয়ে schema জোড়া দিলে additionalProperties: false অনেক সময় বৈধ ঘরও আটকায়; তখন unevaluatedProperties দেখুন। Deprecation header-এর মান RFC 9745 (২০২৫), আর বন্ধের দিন জানানোর Sunset header RFC 8594 (২০১৯). Token চুরি হলে কাজে না লাগাতে sender-constrained token যেমন DPoP (RFC 9449) বা mTLS-বাঁধা token ব্যবহার হয়।
পরীক্ষা ও ইন্টারভিউতে: এ ধরনের প্রশ্ন আসতে পারে।
BOLA আর BFLA-র পার্থক্য? BOLA নির্দিষ্ট object-এর মালিকানায় ভুল, BFLA কাজ বা ভূমিকার অনুমতিতে ভুল।
Gateway কেন BOLA ঠেকাতে পারে না? Object কার, সেই ব্যবসায়িক ডেটা gateway-র কাছে সাধারণত থাকে না।
Rate limit আর quota-র পার্থক্য? একটি গতি মাপে, অন্যটি লম্বা সময়ের মোট পরিমাণ।
Webhook-এ timestamp থাকলেও event id কেন? সময়ের জানালার ভেতরের replay আর বৈধ পুনঃপ্রেরণ ধরতে।
Shadow আর zombie API-র পার্থক্য? Shadow তালিকায় কখনো ওঠেনি, zombie বন্ধ বলে ধরা হয়েছিল কিন্তু চলছে।
১০ · AI-এর যুগে API নিরাপত্তা
AI কয়েক সেকেন্ডে পুরো CRUD API লিখে দেয়। দেখতে সুন্দর, test-ও চলে। কিন্তু তার সাধারণ ভুলগুলো ঠিক এই পর্বের ঝুঁকিগুলোই: id দিয়ে সরাসরি row আনা, পুরো body ডেটাবেসে বসানো, পুরো row উত্তরে পাঠানো, আর CORS-এ সব খুলে দেওয়া। কোড “চলছে” দেখে এগুলো ধরা পড়ে না, কারণ সুখী পথে কোনো ব্যর্থতা নেই।
| AI যা করতে পারে | ঝুঁকি | কীভাবে যাচাই |
|---|---|---|
| id ধরে row আনে, owner মেলায় না | API1 | দুই ব্যবহারকারীর নেতিবাচক test |
| পুরো body model-এ বসায় | API3 | অজানা ঘর পাঠিয়ে test, allowlist পড়া |
| Signature যাচাই “error এড়াতে” বন্ধ করতে বলে | API2, webhook জাল | Diff পড়া, verify=False-এর মতো সন্ধান |
Webhook-এ == দিয়ে tag তুলনা, parse করা body-তে HMAC | দুর্বল যাচাই | Provider-এর মূল নথি মিলিয়ে দেখা |
| OWASP-এর ভুল নম্বর বা অস্তিত্বহীন RFC বা CVE বলে | ভুল সিদ্ধান্ত | মূল উৎস খুলে পড়া |
নিজের API-র spec বা কোড পর্যালোচনায় AI-কে এভাবে কাজে লাগান। Token, key বা গ্রাহকের ডেটা আগে সরিয়ে নিন।
AI-এর লেখা API মেনে নেওয়ার আগে
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১BOLA ২০১৯ আর ২০২৩, দুই সংস্করণেই এক নম্বরে কেন? এত সহজ একটি শর্ত ডেভেলপাররা ভোলেন কেন?
BOLA কঠিন বলে নয়, বরং ছড়ানো বলে টিকে আছে। একটি বড় API-তে শত শত endpoint থাকে। প্রতিটিতে id আসে, প্রতিটিতে আলাদা করে মালিকানার নিয়ম লাগে। Authentication একবার middleware-এ বসালে সব জায়গায় চলে। কিন্তু “এই invoice কি এই প্রতিষ্ঠানের, আর এই কর্মী কি সেটি দেখতে পারেন” প্রশ্নের উত্তর ব্যবসা অনুযায়ী বদলায়। একটি শর্ত ভুললেই ফুটো।
সরঞ্জামও এখানে সাহায্য করে কম। Static analysis-এর tool ব্যবসার নিয়ম জানে না। সাধারণ scanner একটি token নিয়ে চলে, ফলে দেখে সব 200, সব ঠিক। ধরতে হলে দুই ব্যবহারকারীর দৃষ্টিকোণ লাগে। WAF-ও দেখে একটি বৈধ অনুরোধ, কোনো অদ্ভুত অক্ষর নেই।
একটি প্রকাশিত উদাহরণ: ২০১৮ সালে KrebsOnSecurity-র প্রতিবেদনে জানা যায়, যুক্তরাষ্ট্রের ডাক বিভাগের একটি API-তে দুর্বলতার কারণে লগইন করা ব্যবহারকারী অন্যদের অ্যাকাউন্টের তথ্য চাইতে পারতেন। ঝুঁকিতে থাকা অ্যাকাউন্টের অনুমান ছিল প্রায় ছয় কোটি। প্রতিবেদন অনুযায়ী একজন গবেষক এক বছরেরও বেশি আগে জানিয়েছিলেন, আর খবর প্রকাশের পর দ্রুত ঠিক করা হয়। সংস্থাটি তখন বলেছিল, তথ্য ব্যবহারের প্রমাণ তাদের কাছে নেই।
প্রতিকার তাই কাঠামোগত। অনুমতির সিদ্ধান্ত একটি কেন্দ্রীয় policy layer-এ আনা, ডেটা-আনার কোডে tenant আর owner বাধ্যতামূলক করা, যেখানে সম্ভব ডেটাবেসে row-level security, প্রতিটি route-এর জন্য দুই-ব্যবহারকারীর test, আর বিশেষজ্ঞের অনুমোদিত penetration test (পর্ব ৫০). Bug bounty বা vulnerability disclosure নীতিও কাজে আসে, কারণ বাইরের গবেষকরা বৈধ পথে জানাতে পারেন।
মূল উপলব্ধি: যে নিয়ম প্রতিটি endpoint-এ হাতে লিখতে হয়, একদিন একটিতে সেটি বাদ পড়বেই। তাই নিয়মটি কাঠামোতে বসান, মানুষের স্মৃতিতে নয়।
প্র ০২একটি API থেকে কোটি রেকর্ড চলে গেল, কিন্তু ধরা পড়ল ছয় সপ্তাহ পরে। এত দেরি কেন হয়, আর রক্ষক কী করতে পারেন?
২০২৩ সালের জানুয়ারিতে T-Mobile যুক্তরাষ্ট্রের SEC-তে জমা দেওয়া ফাইলিংয়ে জানায়, একজন আক্রমণকারী একটি API দিয়ে অনুমতি ছাড়াই গ্রাহকের তথ্য নিচ্ছিলেন। প্রাথমিক হিসাবে প্রায় ৩.৭ কোটি অ্যাকাউন্টের ডেটা, যদিও অনেকগুলোতে সব ঘর ছিল না। ফাইলিং অনুযায়ী এটি শুরু ২০২২ সালের নভেম্বরের ২৫ তারিখ নাগাদ, আর ধরা পড়ে ২০২৩-এর জানুয়ারির ৫ তারিখে, প্রায় ৪১ দিন পরে। ঠিক কোন ত্রুটি ছিল, তা প্রকাশিত নথিতে বিস্তারিত নেই, তাই আমরা অনুমান করব না।
ধীর ধরা পড়ার সাধারণ কারণগুলো বোঝা যায়। প্রথমত, API চুরি দেখতে সাধারণ ট্র্যাফিকের মতো। Status 200, বৈধ পথ, কোনো ক্ষতিকর অক্ষর নেই। দ্বিতীয়ত, নজর থাকে uptime আর error-এ, “কে কত আলাদা জিনিস পড়ছে” তাতে নয়। তৃতীয়ত, ধীরে পড়লে গড় ব্যবহারে এটি হারিয়ে যায়। মিনিটে ৬০ অনুরোধ মানে দিনে ৮৬,৪০০; ৪১ দিনে একটি key-ই প্রায় ৩৫ লাখ রেকর্ড পড়তে পারে। কয়েকটি key মিলে সংখ্যা আরও বাড়ে।
রক্ষকের হাতে কয়েকটি পথ আছে। Object-স্তরের log রাখা, শুধু URL-এর নয়। প্রতি ব্যবহারকারী বা key ধরে “আলাদা object-এর সংখ্যা” আর “উত্তরের মোট আকার” মাপা। লম্বা সময়ের quota বসানো, যাতে ধীর চুরিও একসময় থামে। উত্তরে ঘর কম রাখা, যাতে চুরি হলেও ক্ষতি কম হয়। Incident-এর জন্য key বা route দ্রুত বন্ধ করার প্রস্তুতি রাখা। সংবেদনশীল এলাকায় canary রেকর্ডও কাজের: কোনো আসল ব্যবহারকারী যে id ছোঁবেন না, সেটি ছুঁলেই alert.
মূল উপলব্ধি: API-তে চুরি প্রায়ই error নয়, পরিমাণ। আপনি ধরতে পারবেন শুধু তা-ই, যা আগে থেকে মাপছেন।
প্র ০৩Optus-এর ২০২২ সালের ঘটনার অভিযোগ থেকে API inventory নিয়ে কী শেখার আছে?
২০২২ সালে অস্ট্রেলিয়ার টেলিকম Optus-এর প্রায় ৯৫ লাখ বর্তমান ও সাবেক গ্রাহকের তথ্য ফাঁসের কথা প্রকাশ পায়। ২০২৪ সালে সেখানকার নিয়ন্ত্রক ACMA আদালতে মামলা করে। প্রকাশিত প্রতিবেদন অনুযায়ী তাদের অভিযোগ: একটি নিষ্ক্রিয়, internet-মুখী API-র access control-এ কোডের ভুল ছিল। মূল domain-এ ২০২১ সালে ভুলটি সারানো হয়, কিন্তু আরেকটি domain-এ একই ভুল থেকে যায়। Optus কোডের ভুলটি স্বীকার করলেও কিছু ব্যাখ্যা মানেনি। এগুলো আদালতের অভিযোগ, চূড়ান্ত রায় বলে ধরবেন না।
রক্ষকের চোখে তিনটি শিক্ষা। প্রথমটি: নিষ্ক্রিয় মানে নিরাপদ নয়। যে API “ব্যবহার হচ্ছে না” তাতে কারও নজর থাকে না, মালিক থাকেন না, পরীক্ষা হয় না, অথচ সেটি সাড়া দেয়। ব্যবহার না হলে বন্ধ করুন। চালু রাখলে সুরক্ষার সব শর্ত রাখুন।
দ্বিতীয়টি: প্রতিকার এক জায়গায় নয়, সব জায়গায়। একই কোড একাধিক domain বা version-এ চললে একটি সারানো মানে কাজ অর্ধেক। তাই বাগ ঠিক করার সময় প্রশ্ন করুন: “এই একই ধরন আর কোথায় আছে?” Inventory ছাড়া এই প্রশ্নের উত্তর নেই।
তৃতীয়টি: পরিবর্তনের সময়ই ঝুঁকি ঢোকে। কোনো API ভেতর থেকে internet-মুখী হলে সেটি নতুন ঝুঁকি-মূল্যায়নের ঘটনা হওয়া উচিত। স্বয়ংক্রিয়ভাবে external attack surface-এর তালিকা, DNS রেকর্ড পর্যালোচনা আর gateway-র বাইরের সব host বন্ধ করলে এসব ধরা সহজ হয়। পরিচালনায় স্পষ্ট মালিক দরকার: যে API-র মালিক নেই, তার মেয়াদও নেই।
মূল উপলব্ধি: আক্রমণকারী আপনার সেরা দরজায় আসেন না, আসেন ভুলে যাওয়া দরজায়। Inventory তাই কাগজের কাজ নয়, প্রতিরক্ষার প্রথম ধাপ।
প্র ০৪“গ্রহণে উদার থাকুন” নীতি ইন্টারনেট গড়েছে। তাহলে API-তে অজানা ঘর বাতিল করা কি সেই নীতির বিরোধী?
১৯৮০ সালে Jon Postel-এর IP নথিতে (RFC 760) একটি নীতি আসে, যা পরে Robustness Principle নামে পরিচিত হয়: নিজে পাঠানোর সময় সতর্ক, গ্রহণের সময় উদার। শুরুর ইন্টারনেটে নানা ত্রুটিপূর্ণ বাস্তবায়নকে একসঙ্গে চলতে দিতে এটি অসাধারণ কাজ করেছিল।
তবে সময়ের সঙ্গে দাম ধরা পড়ে। উদার গ্রহণে ভুলগুলো স্থায়ী হয়ে যায়, আর একই বার্তা দুই সিস্টেম দুভাবে বোঝে। IAB-এর RFC 9413 (২০২৩) ঠিক এই বিষয়ে: সহনশীলতা প্রোটোকলকে ধীরে ধীরে ক্ষয় করে; ভালো পথ হলো সক্রিয় রক্ষণাবেক্ষণ আর স্পষ্ট error. নিরাপত্তায় এই অমিল বিশেষ বিপজ্জনক, কারণ gateway এক অর্থ বুঝল আর service আরেক অর্থ, তখন যাচাই এড়িয়ে যাওয়ার পথ তৈরি হয়।
Mass assignment এর একটি পরিচিত দৃষ্টান্ত। ২০১২ সালে GitHub-এ এমন একটি ত্রুটি প্রকাশ্যে আসে, যেখানে অনুরোধের অতিরিক্ত ঘর গ্রহণের ফলে অনুমতি ছাড়া public key যোগ করা গিয়েছিল। GitHub ঠিক করে নিজেদের ব্লগে জানায়। Ruby on Rails-ও পরে strong parameters-এর দিকে যায়, যেখানে ডেভেলপারকে অনুমোদিত ঘর নিজে ঘোষণা করতে হয়।
তাহলে সমাধান কী? দুই দিকের নীতি আলাদা। Server অনুরোধ গ্রহণে কড়া: অজানা ঘর বাতিল, কারণ এখানে ভুল মানে নিরাপত্তা। Client উত্তর পড়ায় সহনশীল: নতুন ঘর এলে উপেক্ষা করে, যাতে API বাড়লেই পুরোনো app না ভাঙে। আর অজানা ঘর বাদ দিতে হলে চুপচাপ বাদ নাকি ৪০০, তা নিয়েও তর্ক আছে। চুপচাপ বাদ দিলে client-এর বানান ভুল ধরা পড়ে না; বাতিল করলে ভুল সামনে আসে। বেশিরভাগ নিরাপত্তা-সংবেদনশীল API-তে বাতিল করাই স্পষ্ট পথ।
মূল উপলব্ধি: নীতি প্রসঙ্গ-নির্ভর। সহনশীলতা যেখানে বিবর্তন বাঁচায়, সেখানে রাখুন; যেখানে সিদ্ধান্ত হয়, সেখানে কড়া থাকুন।
প্র ০৫GraphQL কি REST-এর চেয়ে কম নিরাপদ? নাকি প্রশ্নটিই ভুল?
প্রশ্নটি অর্ধেক ভুল। GraphQL নিজে কোনো ফুটো নয়; ঝুঁকিগুলো এক জায়গা থেকে অন্য জায়গায় সরে। Facebook ভেতরে এটি ২০১২ সালে ব্যবহার শুরু করে আর ২০১৫ সালে প্রকাশ করে, মূলত ধীর mobile network-এ অল্প ডেটা আনতে। সেই সুবিধার দামই নিরাপত্তার হিসাবে বদল আনে।
REST-এ যা সহজ: প্রতিটি URL-এর আলাদা log, আলাদা rate limit, আলাদা cache. WAF বা gateway route দেখে নিয়ম বসাতে পারে। একটি অনুরোধের খরচ মোটামুটি জানা।
GraphQL-এ যা কঠিন: সব যায় একই POST /graphql-এ, তাই URL দেখে কিছু বোঝা যায় না। এক অনুরোধে অনেক কাজ থাকতে পারে, তাই OWASP সতর্ক করে যে batching অনুরোধ-গোনা সীমাকে পাশ কাটাতে পারে। আগের হিসাবে ১০ × ১০ × ১০ থেকে ১০০ × ১০০ × ১০০-তে গেলে node ১,১১০ থেকে ১০,১০,১০০. একই object বহু পথে পৌঁছানো যায়, তাই route-ভিত্তিক অনুমতি যথেষ্ট নয়।
GraphQL-এ যা ভালো: একটি typed schema আছে, তাই ইনপুটের ধরন আপনাআপনি যাচাই হয়। Client যা চায় শুধু তা-ই আসে, ফলে অতিরিক্ত ঘর কম যায়, যদিও schema-তে ঘোষিত ঘর authorization ছাড়া রক্ষিত নয়। নিজের app-এ persisted query ব্যবহার করলে আক্রমণের জায়গা অনেক কমে, প্রায় REST-এর মতো নির্দিষ্ট তালিকা হয়ে যায়।
সিদ্ধান্ত কীভাবে নেবেন? দলের কি query-র খরচ মাপার, field ধরে authorization আর operation ধরে log-এর দক্ষতা আছে? Public partner API হলে REST প্রায়ই সহজ পরিচালনা দেয়। নিজের mobile আর web client-এর জন্য GraphQL আর persisted query ভালো জোড়া হতে পারে। অনেকে দুটোই রাখেন।
মূল উপলব্ধি: প্রযুক্তি ঝুঁকি মুছে না, ঝুঁকির ঠিকানা বদলায়। প্রশ্ন “কোনটি নিরাপদ” নয়, “এর ঝুঁকি সামলানোর সরঞ্জাম আর দক্ষতা কি আমাদের আছে”.
অনুশীলন
হিসাব, পর্যালোচনা, ফল অনুমান আর নিজের সিস্টেমের log. সবই নিরাপদ, আর কাজ শুধু নিজের সিস্টেমে।
-
হিসাব: (ক) বালতির ধারণক্ষমতা ১০০, প্রতি সেকেন্ডে ১ token, শুরুতে ভরা। ৬০ সেকেন্ডে সর্বোচ্চ কত অনুরোধ? (খ) ৫,০০,০০০ রেকর্ড মিনিটে ১২০ অনুরোধে একটি একটি করে পড়তে কত সময়? প্রতি উত্তরে ১০০ রেকর্ড এলে? (গ) GraphQL query-তে তিন স্তরের তালিকা, সীমা ২০, ১০, ৫. সর্বোচ্চ node কত?
(ক) b + rT = ১০০ + ১ × ৬০ = ১৬০টি।
(খ) ৫,০০,০০০ ÷ ১২০ ≈ ৪,১৬৬.৭ মিনিট, মানে প্রায় ৬৯.৪ ঘণ্টা বা ২.৯ দিন। প্রতি উত্তরে ১০০টি হলে দরকার ৫,০০০ অনুরোধ, ৫,০০০ ÷ ১২০ ≈ ৪১.৭ মিনিট। শিক্ষা:
limit-এর উপরসীমা আর অনুরোধের সীমা একসঙ্গে ভাবতে হয়, শুধু অনুরোধ গুনলে চলে না।(গ) ২০ + ২০ × ১০ + ২০ × ১০ × ৫ = ২০ + ২০০ + ১,০০০ = ১,২২০.
-
নকশা সমালোচনা: একটি কাল্পনিক অনলাইন বাজারের API:
GET /v1/users/{id}পুরো user row ফেরায়, যাতে password hash-ও আছে।PATCH /v1/users/{id}যেকোনো ঘর নেয়।POST /v1/refunds-এর বোতাম শুধু admin পর্দায় দেখা যায়।GET /v1/orders-এlimit-এর উপরসীমা নেই। ফোন app-এ একটি API key বসানো। কী কী সমস্যা, আর প্রতিকার কী?- API1:
{id}-তে মালিকানা যাচাইয়ের কথা নেই। Token-এরsubমেলান, বা/users/meব্যবহার করুন। - API3 (পড়া): পুরো row পাঠানো। উত্তরের allowlist বানান। Password hash কখনো উত্তরের model-এ নয়।
- API3 (লেখা): যেকোনো ঘর নেওয়া মানে mass assignment. Schema-য়
additionalProperties: falseআর ঘরের allowlist দিন। - API5: লুকানো বোতাম সুরক্ষা নয়। Server-এ ভূমিকা যাচাই করুন, আর refund-এর মতো কাজে দুই-ব্যক্তির অনুমোদন (maker-checker) ভাবুন।
- API4:
limit-এর উপরসীমা, pagination আর rate limit দিন। - API2: App-এর ভেতরের key গোপন থাকে না। ব্যবহারকারীর স্বল্পায়ু OAuth token ব্যবহার করুন, PKCE সহ।
- API1:
-
ফল অনুমান করুন: Token bucket-এর ধারণক্ষমতা ৩, প্রতি ১০ সেকেন্ডে ১ token, শুরুতে ভরা। একই key থেকে অনুরোধ আসে
t = 0, 0, 0, 0, 10, 30সেকেন্ডে। প্রতিটির উত্তর (200না429) কী? শেষে বালতিতে কত token?200, 200, 200, 429, 200, 200, শেষে ১টি token. প্রথম তিনটিতে ৩ → ০. চতুর্থটিতে বালতি খালি।t=10-এ ১টি জমে, সেটি খরচ হয়ে ০.t=30-এ আরও ২০ সেকেন্ড গেছে, তাই ২টি জমে, ১টি খরচ হয়ে থাকে ১. আমরা একটি ছোট script চালিয়ে এই ফল মিলিয়েছি। -
নিজের API-র inventory: আপনার নিজের প্রকল্পের access log আর spec-এর route তালিকা থেকে শুধু log-এ আছে এমন route বের করুন। একটি শর্টকাট: আগে হাতে
spec-routes.txtবানান, যেখানে প্রতি লাইনেGET /v2/orders/{id}-এর মতো route.নিচের কমান্ড শুধু আপনার নিজের log ফাইল পড়ে; কোনো network ডাক নেই। এটি সাধারণ combined log format ধরে নিয়েছে, আপনার format আলাদা হলে field নম্বর বদলাবেন।
# Your own log: method and path, ids and query strings normalised awk '{gsub(/"/, "", $6); print $6, $7}' access.log \ | sed -E 's/\?.*$//; s#/[0-9]+#/{id}#g' \ | sort -u > log-routes.txt sort -u spec-routes.txt -o spec-routes.txt comm -13 spec-routes.txt log-routes.txtযা বের হবে, সেগুলো হয় spec-এ যোগ হবে, নয় বন্ধ হবে। সেই সঙ্গে দেখুন
/v1পথে কে আসছেন। আমরা ছোট নমুনা log-এ কমান্ডটি চালিয়ে দেখেছি।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি API অনুরোধের যাচাইগুলো সাধারণ ক্রমে সাজান (▲▼ চেপে সরান)
- TLS শেষ করা আর আকারের সীমা দেখা
- Token যাচাই করে পরিচয় জানা
- পরিচয় ধরে rate limit দেখা
- Schema-র সঙ্গে অনুরোধ মেলানো
- Function আর object-স্তরের অনুমতি যাচাই
- উত্তর ছেঁকে পাঠানো আর log রাখা
একটি API-তে বৈধ token নিয়ে GET /orders/1043 ডাকলে অন্য ব্যবহারকারীর অর্ডার পাওয়া যায়। OWASP API Top 10 (২০২৩) অনুযায়ী এটি কোন ঝুঁকি?
- API2: Broken Authentication
- API1: Broken Object Level Authorization
- API4: Unrestricted Resource Consumption
- API9: Improper Inventory Management
PATCH /users/me অনুরোধের body-তে অতিরিক্ত "role" ঘর পাঠালে সেটি ডেটাবেসে বসে যায়। সবচেয়ে কার্যকর প্রতিকার কোনটি?
- Rate limit কমিয়ে দেওয়া
- App-এর পর্দা থেকে role ঘর সরিয়ে দেওয়া
- Schema-য় অনুমোদিত ঘর বেঁধে দিয়ে অজানা ঘর বাতিল করা
- উত্তর থেকে role ঘর বাদ দেওয়া
Token bucket-এর ধারণক্ষমতা ২০, প্রতি সেকেন্ডে ২ token, শুরুতে বালতি ভরা। ৬০ সেকেন্ডে সর্বোচ্চ কতটি অনুরোধ যেতে পারে?
- ১৪০টি
- ১২০টি
- ২০টি
- ১,২০০টি
একটি GraphQL query-তে তিন স্তরের তালিকা আছে, সীমা ৫০, ২০ আর ৫. প্রতিটি স্তরের node যোগ করলে সর্বোচ্চ কত node?
- ৭৫
- ৫,০০০
- ১,০০০
- ৬,০৫০
একটি webhook-এর signature বৈধ, সময়ও সীমার মধ্যে, কিন্তু একই event id আগেই প্রক্রিয়া হয়েছে। গ্রহণকারীর কী করা উচিত?
- আবার প্রক্রিয়া করা, কারণ signature বৈধ
- কাজ না করে সফল উত্তর ফেরানো
- সঙ্গে সঙ্গে signing key বদলানো
- 500 ফিরিয়ে প্রেরককে আবার পাঠাতে বলা
পুরোনো /v1 API “বন্ধ” বলে ঘোষণা হয়েছিল, কিন্তু gateway-তে route রয়ে গেছে এবং সেটি এখনো সাড়া দেয়। একে কী বলে, আর কোন পদক্ষেপ সবচেয়ে কার্যকর?
- GraphQL introspection; schema লুকানো
- Sensitive business flow; CAPTCHA বসানো
- Zombie API; মালিক ঠিক করে ব্যবহারকারী সরিয়ে route বন্ধ করা
- Security misconfiguration; শুধু TLS version বাড়ানো
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- API-তে client-এর সবকিছু অবিশ্বস্ত। সিদ্ধান্ত হবে server-এ, প্রতিটি অনুরোধে।
- Authorization তিন স্তরে: function, object, property. শেষের দুটি gateway একা পারে না।
- ইনপুটে schema, আউটপুটে allowlist; অজানা ঘর বাতিল।
- সীমা বহুস্তরে: rate limit, quota, pagination, আর ব্যবসায়িক কাজের আলাদা সীমা। এগুলো গতি কমায়, BOLA সারায় না।
- Webhook-এ signature, timestamp আর event id; সঙ্গে inventory, log আর contract test, যাতে ভুলে যাওয়া দরজা না থাকে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৪৪ · Web hardening — security header, WAF, dependency আর নিরাপদ errorপরবর্তী পর্বSecurity header, WAF, dependency আর নিরাপদ error.
- সাইবার নিরাপত্তা · কোর্স হোম৮০টি পর্বপুরো কোর্সের রোডম্যাপ।
- Rate limiting — token bucket, leaky bucket আর sliding windowগভীরে যানAlgorithm-এর তুলনা আর distributed limiter.
- API gateway আর proxyপাশের ট্র্যাকGateway-র নকশা আর ভেতরের কাজ।
- REST API designপাশের ট্র্যাকResource, version আর ভালো API-র নিয়ম।