এই পর্বে যা শিখবেন
- Browser কেন cookie নিজে থেকে পাঠায়, আর সেখান থেকে CSRF কীভাবে জন্মায়
- Synchronizer token আর signed double-submit, HMAC-সহ
SameSite-এর সিদ্ধান্ত, আর site ও origin-এর পার্থক্যOrigin,Referer, Fetch Metadata আর custom header-এর ব্যবহার- XSS, CORS, subdomain আর cookie prefix-এর সঙ্গে CSRF-এর সম্পর্ক
- Clickjacking,
frame-ancestors, নিজের app পরীক্ষা আর log দেখে ধরা
সীমারেখা: এই পর্ব রক্ষকের জন্য। এখানে কোনো কার্যকর forged form বা আক্রমণের ধাপ নেই। সব কোড খেলনা ডেটায় memory-র ভেতরে চলে, কোনো real request যায় না। পরীক্ষা শুধু নিজের app-এ, বা লিখিত অনুমতি থাকা system-এ করা যাবে। অনুমতি ছাড়া অন্যের system পরীক্ষা করা অপরাধ হতে পারে। বিস্তারিত পর্ব ৬-এ।
১ · Cookie নিজে থেকেই যায়: CSRF-এর মূল কারণ
রহিমা একটি কাল্পনিক অনলাইন বাজার shop.example.com-এ login করলেন। Tab বন্ধ করেননি। তারপর আরেকটি tab-এ একটি অচেনা ব্লগ খুললেন। ব্লগটি দেখতে সাধারণ। অথচ পরদিন দেখলেন, তাঁর account-এর delivery ঠিকানা বদলে গেছে। তিনি নিজে কিছু করেননি। Password চুরি হয়নি, server-এও কেউ ঢোকেনি। তাহলে হলো কী?
এর নাম CSRF, পুরো নাম Cross-Site Request Forgery. বাংলায় বলা যায় “অন্য site থেকে জাল অনুরোধ”। এখানে অন্য একটি পাতা রহিমার browser-কে দিয়ে shop-এ একটি request পাঠিয়েছে। Browser সেই request-এ shop-এর cookie নিজে থেকে জুড়ে দিয়েছে। Server cookie দেখে ভেবেছে, রহিমা নিজেই চেয়েছেন।
একটি অফিসের পিয়নের কথা ভাবুন। তাঁর গলায় আপনার দেওয়া পরিচয়পত্র ঝোলে। ব্যাংকের দারোয়ান সেই পরিচয়পত্র দেখলেই ভেতরে যেতে দেন। এখন রাস্তার এক অচেনা লোক পিয়নের হাতে একটি চিরকুট দিল: “ব্যাংকে গিয়ে এটি জমা দিন”। পিয়ন সরল মনে গেলেন। দারোয়ান পরিচয়পত্র দেখলেন, ক্যাশিয়ার চিরকুট মেনে কাজ করলেন। পরিচয়পত্র আসল, কিন্তু নির্দেশটি আপনার নয়।
এখানে পিয়ন হলো browser, পরিচয়পত্র cookie, আর চিরকুট অন্য site-এর পাঠানো request.
নিরাপত্তার ভাষায় এই সুবিধার নাম ambient authority: অধিকার পরিবেশেই আছে, আলাদা করে দেখাতে হয় না। আর browser এখানে একজন confused deputy: নিজের অধিকার দিয়ে অন্যের নির্দেশ পালন করছে। Cookie-র মতো একই সমস্যা হয় HTTP Basic auth, TLS client certificate আর “অফিসের network থেকে এলেই বিশ্বাস” ধরনের নিয়মে। এগুলোও browser বা network নিজে থেকে যোগ করে।
Cookie বলে “কে”, কিন্তু বলে না “কে চেয়েছে”
Ambient authority: both tabs reach the same cookie jar
CSRF হতে তিনটি শর্ত একসঙ্গে লাগে: ১) একটি কাজ আছে যা server-এর অবস্থা বদলায়, ২) পরিচয় আসে শুধু নিজে থেকে যাওয়া cookie বা সমতুল্য কিছু থেকে, ৩) request-এ এমন কিছু নেই যা অন্য site জানে না। প্রতিরক্ষা মানে এর অন্তত একটি শর্ত ভাঙা।
আক্রমণকারী এখানে উত্তরটি সাধারণত পড়তে পারে না। Same-origin policy অন্য origin-এর response পড়া আটকায় (পর্ব ৩৬)। কিন্তু CSRF-এর উত্তর পড়ার দরকার নেই। কাজ হয়ে গেলেই ক্ষতি। তাই ক্ষতি নির্ভর করে কাজটির ওপর: ঠিকানা বা email বদল, টাকা পাঠানো, role বদল, এমনকি admin-এর নতুন user তৈরি। ব্যবহারকারী admin হলে পুরো system ঝুঁকিতে পড়ে।
আরেকটি রূপ আছে, login CSRF. এখানে ভুক্তভোগীকে আক্রমণকারীর নিজের account-এ login করিয়ে দেওয়া হয়। ভুক্তভোগী যা খোঁজেন বা যে কার্ড যোগ করেন, তা ওই account-এ জমে। তাই login form-ও CSRF প্রতিরক্ষার বাইরে নয়। Login-এর আগে একটি প্রাথমিক session-এ token বাঁধা যায়, আর login সফল হলে session বদলে নিতে হয় (পর্ব ১৭).
২ · প্রথম নিয়ম: GET কখনো কিছু বদলাবে না
HTTP method-গুলোর দুটি দল আছে। Safe method, যেমন GET, HEAD আর OPTIONS, শুধু পড়ার জন্য। POST, PUT, PATCH আর DELETE হলো state-changing request, অর্থাৎ এগুলো কিছু বদলায়। RFC 9110 এই অর্থই দেয়। প্রায় সব CSRF প্রতিরক্ষা এই বিভাজনের ওপর দাঁড়িয়ে।
| যে নকশা | সমস্যা | সমাধান |
|---|---|---|
GET /order/42/cancel | ছবির লিংক, preview bot বা prefetch-ও কাজটি ঘটাতে পারে | POST, সঙ্গে CSRF প্রতিরক্ষা |
| GET-এ logout | অন্য পাতা ব্যবহারকারীকে বের করে দিতে পারে | Logout-ও POST |
| Token URL-এ | Log, history আর Referer-এ ফাঁস হয় | Token শুধু form field বা header-এ |
এই নিয়ম ভাঙলে আরও একটি বিপদ আসে। SameSite=Lax বাইরের লিংকে ক্লিক করে আসা GET-এ cookie পাঠায়। তাই GET কিছু বদলালে Lax আর আপনাকে বাঁচাবে না। আরেকটি পরিচিত ফাঁদ হলো framework-এর method override. কিছু framework লুকানো field বা header দেখে request-এর method বদলে ধরে। আপনার route যে method-এ কাজ করে, প্রতিরক্ষাও সেই method-এ আছে কি না যাচাই করুন।
৩ · CSRF token: যা অন্য site জানতে পারে না
সমাধানের মূল চিন্তা সহজ। Request-এ এমন একটি মান চাই যা browser নিজে থেকে দেয় না, আর অন্য site পড়তেও পারে না। এই মানের নাম CSRF token. Same-origin policy-র কারণে অন্য site আপনার পাতার ভেতরের token পড়তে পারে না।
ব্যাংকের চেকবইয়ের কথা ভাবুন। শুধু পরিচয়পত্র দেখালে টাকা ওঠে না। চেকের পাতায় ব্যাংকের ছাপা serial নম্বর লাগে, আর সেই পাতা আছে শুধু আপনার কাছে। রাস্তার লোকের চিরকুটে সেই নম্বর থাকতে পারে না। CSRF token ঠিক সেই serial নম্বর।
Synchronizer token
Synchronizer token pattern-এ server একটি এলোমেলো মান বানিয়ে session-এর সঙ্গে রাখে। মানটি CSPRNG থেকে আসে। Form-এর ভেতরে লুকানো field-এ, বা SPA-তে একটি custom header-এ সেটি ফেরত আসে। Server দুটি মান মিলিয়ে দেখে। নিচের ডায়াগ্রামে একটি সৎ form submit ধাপে ধাপে দেখুন।
- পাতা চাওয়া
GET /transferbrowser - Token তৈরি
tok-a1server - Form-এ token
hidden fieldresponse - Submit
POST /transfercookie + token - মিল
200 OKকাজ সম্পন্ন
GET /transfer HTTP/1.1
Host: shop.example.com
Cookie: __Host-sid=sid-rahima
sid-rahima → { user: "rahima",
csrf: "tok-a1" }
খেলনা মান; real token ২৫৬ bit এলোমেলো
<form method="post" action="/transfer">
<input type="hidden" name="csrf" value="tok-a1">
<input name="amount">
<button>পাঠান</button>
</form>
POST /transfer HTTP/1.1
Host: shop.example.com
Origin: https://shop.example.com
Sec-Fetch-Site: same-origin
Cookie: __Host-sid=sid-rahima
amount=500&csrf=tok-a1
session("sid-rahima").csrf = tok-a1
request field = tok-a1
মিলেছে → balance 5000 → 4500
- ধাপ ১ — পাতা চাওয়া। রহিমা
/transferখুললেন। Browser cookie সঙ্গে নিল। এটিGET, তাই কিছু বদলায় না, token লাগে না। - ধাপ ২ — Token তৈরি। Server রহিমার session
sid-rahima-র সঙ্গেtok-a1মানটি রাখল। অন্য session-এ অন্য মান থাকে। - ধাপ ৩ — Form-এ token. মানটি HTML-এর লুকানো field-এ বসল। পাতাটি শুধু রহিমার browser-ই পেল। অন্য origin-এর পাতা এটি পড়তে পারে না।
- ধাপ ৪ — Submit. Form পাঠালে cookie নিজে থেকে গেল, আর
csrf=tok-a1গেল পাতার ভেতর থেকে। দ্বিতীয়টিই অন্য site-এর নাগালের বাইরে। - ধাপ ৫ — মিল। Session-এর মান আর field-এর মান দুটোই
tok-a1. কাজ হলো, balance 5000 থেকে 4500 হলো। মান না মিললে 403 ফিরত, balance বদলাত না।
কয়েকটি বাস্তব সিদ্ধান্ত মনে রাখুন। Session-প্রতি একটি token সাধারণত যথেষ্ট। Request-প্রতি নতুন token একটু বেশি শক্ত, কিন্তু browser-এর back বাটনে ব্যবহারকারী আটকে যেতে পারেন। Token তুলনা হবে constant-time পদ্ধতিতে। Token কখনো URL-এ রাখা চলবে না।
Signed double-submit cookie
Server-এ session store না থাকলে কী হবে? তখন double-submit cookie কাজে আসে। Token একবার cookie-তে যায়, আরেকবার form বা header-এ। Server দুটি মিলিয়ে দেখে। অন্য site cookie পড়তে পারে না, তাই সে দ্বিতীয় জায়গায় একই মান বসাতে পারে না।
কিন্তু সাধারণ (naive) রূপটিতে ফাঁক আছে। কেউ কোনোভাবে cookie বসাতে পারলে, যেমন একটি দখল হওয়া subdomain থেকে, সে দুই জায়গাতেই নিজের জানা মান বসাতে পারে। OWASP তাই naive রূপকে DISCOURAGED বলে। সমাধান signed double-submit: token-কে HMAC দিয়ে session-এর সঙ্গে বাঁধা। Key server ছাড়া কারও কাছে নেই, তাই বাইরে থেকে বৈধ token বানানো যায় না। HMAC-এর ভিত্তি পর্ব ৯-এ।
tag = HMAC-SHA256(key, len(sid) ‖ sid ‖ len(rand) ‖ rand)token = tag.randনিচে Python-এর আসল hmac module দিয়ে পুরোটি চালানো হয়েছে। Key নকল, session খেলনা। সূত্রে দৈর্ঘ্য বসানো হয়েছে যাতে দুই field-এর সীমানা গুলিয়ে না যায়।
import hashlib, hmac
KEY = b"example-key-not-real" # নকল key; আসল key থাকে secret store-এ
def message(session_id, rand):
s, r = session_id.encode(), rand.encode()
return len(s).to_bytes(4, "big") + s + len(r).to_bytes(4, "big") + r
def tag_of(session_id, rand):
return hmac.new(KEY, message(session_id, rand), hashlib.sha256).hexdigest()
def issue(session_id, rand): # real কোডে rand = secrets.token_hex(32)
return tag_of(session_id, rand) + "." + rand
def verify(session_id, cookie_tok, sent_tok):
if not hmac.compare_digest(cookie_tok.encode(), sent_tok.encode()):
return "DENY cookie and field differ"
tag, dot, rand = sent_tok.partition(".")
if not dot:
return "DENY bad format"
good = tag_of(session_id, rand)
return "ALLOW" if hmac.compare_digest(tag.encode(), good.encode()) else "DENY bad HMAC"
def naive(cookie_tok, sent_tok): # শুধু দুই মান মেলায়, স্বাক্ষর দেখে না
return "ALLOW" if cookie_tok == sent_tok else "DENY"
tok = issue("sid-rahima", "r-7f3a")
other = issue("sid-karim", "r-91bc")
bad = "0" * 64 + ".r-7f3a"
print("token:", tok[:20] + "..." + tok[-7:], "length", len(tok))
print("1 correct :", verify("sid-rahima", tok, tok))
print("2 no field :", verify("sid-rahima", tok, ""))
print("3 other session:", verify("sid-rahima", other, other))
print("4 planted value:", verify("sid-rahima", bad, bad))
print("5 naive, same :", naive(bad, bad))আউটপুট: প্রথম লাইনে token: 43433d2e6622c0acaabc....r-7f3a length 71. ৬৪ অক্ষরের tag, একটি বিন্দু, আর ৬ অক্ষরের খেলনা rand. 1 correct : ALLOW সৎ request. 2 no field : DENY cookie and field differ হলো অন্য site-এর সাধারণ অবস্থা: cookie গেছে, field নেই। 3 other session: DENY bad HMAC সবচেয়ে শিক্ষণীয়। Karim-এর বৈধ token রহিমার session-এ চলে না, কারণ tag-এ session ID ঢোকানো। 4 planted value: DENY bad HMAC: কেউ cookie আর field দুই জায়গায় একই বানানো মান বসালেও key ছাড়া tag মেলে না। আর শেষ লাইন 5 naive, same : ALLOW: একই মান naive যাচাই পেরিয়ে যায়। এটিই signed রূপের কারণ।
OWASP একটি কথা জোর দিয়ে বলে। Session-এর সঙ্গে না বেঁধে শুধু সই করলে লাভ সামান্য, কারণ আক্রমণকারী নিজের জন্য পাওয়া বৈধ token বসাতে পারে। Email বা user ID-র মতো স্থির মানও না, এমন মান নিন যা প্রতি login-এ বদলায়।
৪ · SameSite: browser-এর দরজায় বাছাই
পর্ব ১৭-এ cookie-র flag দেখেছেন। এখানে একটি flag-ই গভীরে দেখব। SameSite browser-কে বলে, অন্য site থেকে শুরু হওয়া request-এ cookie পাঠানো হবে কি না। এটি server-এ নয়, browser-এ কাজ করে।
| মান | Browser কী করে | কোথায় মানায় |
|---|---|---|
Strict | শুধু same-site request-এ cookie পাঠায় | সংবেদনশীল app; তবে বাইরের লিংকে এলে login দেখায় না |
Lax | Same-site-এ পাঠায়, আর বাইরের top-level navigation-এ যদি method safe হয় | সাধারণ session cookie-র ভালো শুরু |
None | সব জায়গায় পাঠায়, Secure আবশ্যক | সত্যিই অন্য site-এ embed হওয়া সেবা |
এবার registrable domain নিয়ে একটি জরুরি কথা। Origin মানে scheme, host আর port, তিনটিই হুবহু এক। Site অনেক ঢিলা: scheme আর registrable domain এক হলেই চলে। Registrable domain ঠিক হয় Public Suffix List দেখে। ফলে shop.example.com আর blog.example.com আলাদা origin, কিন্তু একই site.
Origin ছোট ঘর, site বড় বাড়ি
Same-site is wider than same-origin
নিচের খেলনা টেবিলে একই request তিনটি মানে কেমন ফল দেয় দেখুন। এখানে সরলতার জন্য site মানে domain-এর শেষ দুই অংশ।
// খেলনা SameSite টেবিল: real cookie বা request নেই
// খেলনা site = শেষ দুই label; real browser Public Suffix List দেখে
const site = h => h.split(".").slice(-2).join(".");
const SAFE = ["GET", "HEAD"];
function sends(mode, q) {
if (site(q.from) === site(q.to)) return true;
if (mode === "None") return true; // Secure-ও লাগবে
if (mode === "Strict") return false;
return q.kind === "navigate" && SAFE.includes(q.method); // Lax
}
const TO = "shop.example.com";
const rows = [
["same page POST", "shop.example.com", "navigate", "POST"],
["sibling fetch", "blog.example.com", "fetch", "POST"],
["link click", "other.example.net", "navigate", "GET"],
["cross form POST", "other.example.net", "navigate", "POST"],
["cross image", "other.example.net", "image", "GET"],
["cross iframe", "other.example.net", "iframe", "GET"],
["cross fetch", "other.example.net", "fetch", "POST"],
];
const yn = b => (b ? "send" : "-").padEnd(8);
console.log("request".padEnd(17), "Strict ", "Lax ", "None");
for (const [name, from, kind, method] of rows) {
const q = { from, to: TO, kind, method };
console.log(name.padEnd(17), yn(sends("Strict", q)), yn(sends("Lax", q)),
yn(sends("None", q)));
}আউটপুটে same page POST আর sibling fetch, দুই সারিতেই তিন কলামে send. দ্বিতীয়টিই সতর্কবার্তা: পাশের subdomain থেকে POST গেলে তিন মানই cookie পাঠায়। link click সারিতে Strict -, Lax send. এটিই Lax-এর সুবিধা, বাইরের লিংকে এলেও ব্যবহারকারী লগইন থাকেন। cross form POST, cross image, cross iframe আর cross fetch-এ Strict ও Lax দুটোই -, শুধু None send. অর্থাৎ Lax বেশিরভাগ cross-site কাজ থামায়, যতক্ষণ আপনার GET কিছু বদলায় না।
SameSite-এর সীমা
- Subdomain: পাশের subdomain same-site. সেখানে XSS বা দখল হলে SameSite কিছু করে না।
- Lax-এ GET: GET কিছু বদলালে বাইরের লিংকেই কাজ হয়ে যাবে।
- Default-এ ভরসা: Chromium-ভিত্তিক browser ২০২০ সাল থেকে ধাপে ধাপে flag না থাকলে Lax ধরে। সব browser তা করে না। MDN আরও বলে, এই default-এর অবস্থায় সদ্য বসানো cookie প্রায় দুই মিনিট
POST-এও যেতে পারে। তাই flag নিজে লিখুন। - Client-side CSRF: আপনার নিজের পাতার JavaScript যদি URL থেকে পাওয়া মান দিয়ে request পাঠায়, সেটি same-site. Cookie চলে যায়।
- পুরোনো বা embedded browser: সব জায়গায় এই নিয়ম মানা হয় না।
তাই OWASP SameSite-কে স্তর হিসেবে রাখে, একমাত্র প্রতিরক্ষা হিসেবে নয়। এটি defense in depth-এর একটি চমৎকার উদাহরণ।
৫ · Origin, Referer আর Fetch Metadata: browser-এর সাক্ষ্য
Browser request-এর সঙ্গে কিছু header যোগ করে, যা পাতার JavaScript ইচ্ছামতো বদলাতে পারে না। একে forbidden request header বলে। Server এগুলোকে সাক্ষী হিসেবে ব্যবহার করতে পারে। তবে এই সুরক্ষা browser-এর জন্যই। curl-এর মতো tool এসব যা খুশি পাঠাতে পারে, কিন্তু সেখানে ভুক্তভোগীর cookie থাকে না, তাই CSRF-এর প্রশ্নও ওঠে না।
| Header | কী জানায় | সাবধানতা |
|---|---|---|
Sec-Fetch-Site | same-origin, same-site, cross-site বা none | শুধু HTTPS-এ আসে; না থাকলে fallback লাগে |
Origin | Request কোন origin থেকে শুরু হলো | পুরো origin মেলান; null বা অনুপস্থিত মানকে বিশ্বাস নয় |
Referer | আগের পাতার URL | Privacy নীতিতে বাদ পড়তে পারে; শুধু fallback |
| Custom header | API-র নিজের header, যেমন X-CSRF-Token | Preflight আর সঠিক CORS-এর ওপর নির্ভর করে |
Fetch Metadata হলো Sec-Fetch- দিয়ে শুরু হওয়া একদল header. Sec-Fetch-Site: none মানে ব্যবহারকারী নিজে address bar বা bookmark থেকে এসেছেন। OWASP-এর বর্তমান cheat sheet বলে, শুধু আধুনিক browser-এর জন্য বানানো software-এ এর ওপর নির্ভর করা যায়। শর্ত দুটি: পুরো সাইট HTTPS, আর header না এলে Origin বা Referer-এ fallback বাধ্যতামূলক।
Origin মেলানোর সময় পুরো মান তুলনা করুন। শুধু “শুরুটা মিলেছে কি না” দেখলে shop.example.com.-এর মতো নামও পাস করতে পারে। আপনার নিজের origin আসবে config থেকে। Proxy-র header-এ অন্ধ বিশ্বাস নয়।
মত ভিন্ন: দুটি header-ই না থাকলে কী হবে? OWASP বলে, বিশ্বস্ত উৎস প্রমাণ না হলে বাতিল করুন। Go 1.25-এর CrossOriginProtection এমন request-কে same-origin বা non-browser ধরে যেতে দেয়। দুটি দৃষ্টিভঙ্গিই যুক্তিসঙ্গত। Library-র নিয়ম পড়ে নিজের ঝুঁকি অনুযায়ী ঠিক করুন।
এবার সব মিলিয়ে একটি খেলনা server. সে cookie, Sec-Fetch-Site, Origin আর token দেখে সিদ্ধান্ত নেয়। নয়টি পরিস্থিতি চালানো হলো।
// খেলনা server: কোনো real request নয়, সব ডেটা memory-তে
const ME = "https://shop.example.com";
const SESSIONS = { "sid-rahima": { user: "rahima", csrf: "tok-a1" } };
const SAFE = ["GET", "HEAD", "OPTIONS"];
function decide(r) {
if (SAFE.includes(r.method)) return "ALLOW safe method, no change";
const s = SESSIONS[r.cookie];
if (!s) return "DENY 401 no session";
if (r.site) { // Fetch Metadata
if (r.site !== "same-origin" && r.site !== "none")
return "DENY 403 Sec-Fetch-Site=" + r.site;
} else if (r.origin !== ME) { // পুরোনো browser: fallback
return "DENY 403 Origin=" + (r.origin || "missing");
}
if (r.token !== s.csrf) return "DENY 403 bad token"; // real কোডে constant-time
return "ALLOW change as " + s.user;
}
const C = "sid-rahima", P = "POST";
const cases = [
["own form", { method: P, cookie: C, site: "same-origin", token: "tok-a1" }],
["page view", { method: "GET", cookie: C, site: "cross-site" }],
["no login", { method: P, site: "same-origin", token: "tok-a1" }],
["other site", { method: P, cookie: C, site: "cross-site" }],
["sibling subdomain",{ method: P, cookie: C, site: "same-site" }],
["old browser", { method: P, cookie: C, origin: "https://other.example.net" }],
["no headers", { method: P, cookie: C, token: "tok-a1" }],
["own page, no tok", { method: P, cookie: C, site: "same-origin" }],
["XSS on own page", { method: P, cookie: C, site: "same-origin", token: "tok-a1" }],
];
for (const [name, req] of cases) console.log(name.padEnd(18), decide(req));আউটপুটের সারিগুলো পড়ুন। own form ALLOW change as rahima সৎ কাজ। page view ALLOW safe method, no change: বাইরের লিংকে এসে পাতা দেখা স্বাভাবিক, GET কিছু বদলায় না। other site DENY 403 Sec-Fetch-Site=cross-site আর sibling subdomain DENY 403 Sec-Fetch-Site=same-site দেখায় Fetch Metadata পাশের subdomain-ও আলাদা করে, যা SameSite পারে না। তবে আপনার পাশের subdomain সত্যিই request পাঠালে নির্দিষ্ট ব্যতিক্রম লাগবে। old browser DENY 403 Origin= হলো fallback, আর no headers DENY 403 Origin=missing OWASP-এর কঠোর নিয়ম। own page, no tok DENY 403 bad token দেখায় same-origin হলেও token লাগে।
শেষ সারিটি মন দিয়ে দেখুন: XSS on own page ALLOW change as rahima. নিজের পাতায় XSS চললে request ঠিকঠাক same-origin, token-ও সঠিক। Server-এর আর আলাদা করার কিছু নেই। পরের অংশে এর কারণ দেখব।
এবার একই যুক্তি একটি cross-site request-এর ওপর ধাপে ধাপে দেখুন। সব স্তর দেখাতে ধরে নিচ্ছি cookie-টি SameSite=None, তাই cookie পৌঁছেছে। Lax হলে শুরুতেই cookie বাদ পড়ত।
| যাচাই | |
| পাওয়া গেছে | |
| প্রত্যাশিত | |
| ফল | |
| balance |
- Request এল। রহিমার browser-এ অন্য site-এর পাতা খোলা, আর সেই পাতা shop-এ
POST /transferপাঠাতে চাইল। Balance এখন 5000. - Cookie. Cookie বৈধ, session
sid-rahima. Server জানল এটি রহিমার browser. কিন্তু আগেই দেখেছেন, এটি শুধু “কে”, “কে চাইল” নয়। - Sec-Fetch-Site. Browser জানাল
cross-site. অনুমোদিত মান শুধুsame-originবাnone. Server এখানেই 403 ঠিক করে ফেলে। - Origin. পুরোনো browser-এ আগের header না থাকলেও এই fallback ধরত:
https://other.example.netআরhttps://shop.example.comএক নয়। - CSRF token. দুই header কোনোভাবে পার হলেও token নেই। অন্য site সেটি পড়তে পারেনি। প্রত্যাশিত
tok-a1মেলেনি। এটিই defense in depth. - Log আর সাড়া। 403 ফিরল,
csrf_deniedঘটনা log-এ উঠল। Balance আগের মতোই 5000. একটি মাত্র স্তরও একা এই request থামাতে পারত।
API আর custom header
AJAX বা API-তে form নেই। তাই token যায় X-CSRF-Token ধরনের custom header-এ। Cross-origin পাতা custom header পাঠাতে চাইলে browser আগে একটি preflight (OPTIONS) পাঠিয়ে অনুমতি চায়। Server অনুমতি না দিলে আসল request যায় না। মান খালি হলেও কিছুটা কাজ দেয়, কিন্তু সেটি এই একটি শর্তের ওপরই দাঁড়িয়ে। তাই token-সহ header ভালো। API-র বিস্তারিত পর্ব ৪৩-এ।
৬ · যেখানে প্রতিরক্ষা ভাঙে: XSS, CORS আর subdomain
XSS থাকলে CSRF প্রতিরক্ষা শেষ
OWASP-এর সতর্কবার্তা সরাসরি: XSS সব CSRF প্রতিরক্ষা হারাতে পারে। CSRF প্রতিরক্ষার ভিত্তি হলো “অন্য site আপনার পাতার ভেতরটা পড়তে পারে না”। XSS মানে আক্রমণকারীর script আপনার origin-এর ভেতরেই চলছে। সে পাতা থেকে token পড়তে পারে, same-origin request পাঠাতে পারে। Browser-এর সাক্ষ্যও বলবে same-origin, কারণ সেটি সত্য। তাই আগে XSS বন্ধ করুন (পর্ব ৪০)। HttpOnly শুধু cookie পড়া আটকায়, cookie-র ব্যবহার আটকায় না।
CORS CSRF প্রতিরক্ষা নয়
CORS নিয়ে সবচেয়ে প্রচলিত ভুল ধারণা এটি। CORS ঠিক করে অন্য origin-এর পাতা উত্তর পড়তে পারবে কি না। সে request পাঠানো সবসময় থামায় না। সাধারণ form-ধরনের POST-এ preflight হয়ই না, request server-এ পৌঁছে কাজও হয়ে যেতে পারে। Browser শুধু উত্তরটি পাতাকে দেখায় না। CSRF-এ উত্তরের দরকার নেই।
উল্টো ঢিলা CORS ক্ষতি বাড়ায়। Credential-সহ যেকোনো origin ফিরিয়ে দিলে অন্য site token-ও পড়তে পারে, preflight-ও পার হয়। তাই শুধু নির্দিষ্ট, নিজের origin-এর তালিকা রাখুন। JSON API-তে Content-Type: application/json বাধ্য করুন, ঢিলা parser সরিয়ে দিন। না হলে preflight ছাড়া যে content type যায়, সেগুলো JSON হিসেবে পড়া হতে পারে। CORS-এর ভিত্তি পর্ব ৩৬-এ।
Token-ভিত্তিক API বনাম cookie
কোন পরিচয়-ব্যবস্থা CSRF-এর ঝুঁকিতে পড়ে? উত্তর একটি প্রশ্নে: browser কি পরিচয়টি নিজে থেকে পাঠায়?
| ব্যবস্থা | CSRF ঝুঁকি | প্রধান ঝুঁকি | যা দরকার |
|---|---|---|---|
| Session cookie | আছে | CSRF | Token, SameSite, Origin |
| Bearer token header-এ, JavaScript বসায় | প্রায় নেই | XSS-এ token চুরি | ছোট মেয়াদ, CSP, নিরাপদ জায়গা |
| JWT, কিন্তু cookie-তে | আছে | CSRF | Cookie-র মতোই সব |
| Webhook, service-to-service | নেই, browser নেই | Key ফাঁস | Signature, mTLS |
লক্ষ করুন তৃতীয় সারি। “JWT ব্যবহার করি, তাই CSRF নেই” ভুল। CSRF token-এর ধরনে নয়, পরিবহনে। কোথায় token রাখবেন, সেই আলোচনা পর্ব ১৭-এ।
Cookie prefix আর scoping
Cookie যত কম জায়গায় যায়, ঝুঁকি তত কম। Cookie prefix নামের ভেতরেই নিয়ম বসায়, আর browser সেটি মানে।
| বৈশিষ্ট্য | নিয়ম | লাভ |
|---|---|---|
__Secure- | Secure flag আর HTTPS আবশ্যক | HTTP থেকে এই নামে বসানো যায় না |
__Host- | সঙ্গে Domain নেই, Path=/ | শুধু এই host-এ বাঁধা; subdomain এই নামে বসাতে পারে না |
Domain না দেওয়া | Host-only cookie | Subdomain-এ যায় না |
Domain=example.com | সব subdomain-এ যায় | লাভ নয়, ঝুঁকি বাড়ে; শুধু দরকারে |
Path | কোন path-এ পাঠাবে | নিরাপত্তার সীমা নয়, একই origin path পেরিয়ে যেতে পারে |
একটি ভালো শুরু, শুধু আপনার নিজের app-এর জন্য:
Set-Cookie: __Host-sid=example-token; Path=/; Secure; HttpOnly; SameSite=Lax
MDN এখন __Http- আর __Host-Http- prefix-ও তালিকায় রেখেছে। এগুলো নতুন, সমর্থন ভিন্ন হতে পারে। আর পুরোনো browser prefix না বুঝলে নিয়ম ছাড়াই cookie নিয়ে নেয়, তাই prefix বাড়তি স্তর, একমাত্র ভরসা নয়।
Subdomain মানেই বিশ্বাসের সীমা
একই registrable domain-এর যেকোনো subdomain সাধারণত parent domain-এ cookie বসাতে পারে। একে cookie injection বা cookie tossing বলে। একটি ভুলে যাওয়া marketing পাতা, একটি পুরোনো test server, বা subdomain takeover-এর শিকার কোনো নাম তখন মূল app-এর দুর্বলতা হয়ে ওঠে। Django-র ডকুমেন্টেশনও সরাসরি বলে, parent domain-এ cookie বসাতে পারে এমন subdomain তার CSRF সুরক্ষা পাশ কাটাতে পারে।
- ব্যবহারকারীর লেখা HTML বা অন্যের কোড চললে সেটি আলাদা registrable domain-এ রাখুন।
- অচল DNS record নিয়মিত সরান (পর্ব ২৬)।
- Session আর CSRF cookie-তে
__Host-ব্যবহার করুন। - Token-কে session-এর সঙ্গে বাঁধুন, যাতে বসানো মান কাজে না লাগে।
৭ · Clickjacking: ক্লিক আপনার, লক্ষ্য অন্যের
Clickjacking-এ request জাল করা হয় না। ব্যবহারকারী নিজেই আসল পাতায় আসল ক্লিক করেন, শুধু জানেন না কোথায় করছেন। অন্য একটি পাতা আপনার পাতাকে iframe-এ লুকিয়ে রাখে আর ওপরে একটি ভিন্ন ছবি দেখায়। Token এখানে কোনো কাজ করে না, কারণ ক্লিক আসল পাতা থেকেই হচ্ছে।
দোকানে একটি কাগজে সই করতে বলা হলো। ওপরে আরেকটি কাগজ চাপা, শুধু সই-এর ছোট ঘরটি কাটা। আপনি ভাবছেন এটি ডেলিভারির রসিদ। নিচে আসলে অন্য দলিল। সইটি আসল, হাতও আপনার, শুধু কাগজের ওপরের অংশ আপনি দেখেননি।
প্রতিরক্ষা সহজ: পাতাটি জানিয়ে দেবে কে তাকে ফ্রেমে রাখতে পারবে। Content Security Policy-র frame-ancestors এই কাজ করে। পুরোনো পথ হলো X-Frame-Options.
# নিজের server-এর জন্য উদাহরণ: শুধু নিজের site ফ্রেম করতে পারবে
add_header Content-Security-Policy "frame-ancestors 'self'" always;
add_header X-Frame-Options "SAMEORIGIN" always;| মান | অর্থ | মনে রাখুন |
|---|---|---|
frame-ancestors 'none' | কেউ ফ্রেম করতে পারবে না | Login আর payment পাতার ভালো default |
frame-ancestors 'self' | শুধু নিজের origin | একাধিক partner-ও তালিকায় রাখা যায় |
X-Frame-Options: DENY | কেউ না | পুরোনো browser-এর জন্য |
X-Frame-Options: SAMEORIGIN | নিজের site | একটিমাত্র মান দেওয়া যায় |
ALLOW-FROM | পুরোনো, এখন অচল | আধুনিক browser উপেক্ষা করে, ফলে সুরক্ষাই থাকে না |
দুটি header একসঙ্গে থাকলে CSP-র নিয়মে frame-ancestors জেতে, X-Frame-Options উপেক্ষিত হয়। আর frame-ancestors HTTP header হিসেবেই পাঠাতে হবে, <meta> tag-এ এটি কাজ করে না। JavaScript দিয়ে ফ্রেম ভাঙার পুরোনো কৌশল, যাকে frame busting বলে, নির্ভরযোগ্য নয়।
- অন্য পাতা
other.example.netআকর্ষণীয় বাটন - Iframe চাওয়া
/settingsshop-এর পাতা - Response
frame-ancestorsনীতি এল - তুলনা
ancestor ≠ selfbrowser - বন্ধ
blockedপাতা দেখায় না
ব্যবহারকারী লগইন আছেন: shop.example.com
খোলা পাতা: other.example.net
লক্ষ্য: তাঁর ক্লিক অন্য পাতায় ফেলা
GET /settings HTTP/1.1
Host: shop.example.com
Sec-Fetch-Dest: iframe
Sec-Fetch-Site: cross-site
HTTP/1.1 200 OK
Content-Security-Policy: frame-ancestors 'self'
X-Frame-Options: SAMEORIGIN
অনুমোদিত : https://shop.example.com
ancestor : https://other.example.net
তালিকায় নেই
ফ্রেমে shop-এর পাতা আসেনি
ক্লিক করার মতো কিছু নেই
settings অপরিবর্তিত
- অন্য পাতা। ব্যবহারকারী shop-এ লগইন অবস্থায়
other.example.netখুললেন। পাতাটি তাঁর ক্লিক shop-এর ওপর ফেলতে চায়। - Iframe চাওয়া। পাতাটি shop-এর
/settingsফ্রেমে আনতে চাইল। Browser request পাঠাল,Sec-Fetch-Dest: iframeআরSec-Fetch-Site: cross-siteসহ। - Response. Server পাতার সঙ্গে নীতি পাঠাল:
frame-ancestors 'self', পুরোনো browser-এর জন্যX-Frame-Options: SAMEORIGIN-ও। - তুলনা। Browser দেখল অনুমোদিত শুধু
https://shop.example.com, আর ancestor হলোhttps://other.example.net. তালিকায় নেই। - বন্ধ। Browser পাতাটি দেখাল না। ক্লিক করার কিছুই রইল না, settings অপরিবর্তিত। লক্ষ করুন, সিদ্ধান্ত browser নিল, নীতি দিল server.
নিচের খেলনা ফাংশনটি browser-এর এই সিদ্ধান্ত সরলভাবে অনুকরণ করে। chain মানে বাইরের সব পাতার origin.
// খেলনা framing নীতি: browser-এর সিদ্ধান্তের সরল রূপ
const ME = "https://shop.example.com", PARTNER = "https://partner.example.org";
const OTHER = "https://other.example.net";
function mayFrame(h, chain) { // chain = বাইরের সব page
if (h.csp) { // CSP থাকলে XFO উপেক্ষিত
if (h.csp.includes("'none'")) return "BLOCK (csp none)";
const list = h.csp.map(s => (s === "'self'" ? ME : s));
return chain.every(a => list.includes(a)) ? "ALLOW (csp)" : "BLOCK (csp)";
}
if (h.xfo === "DENY") return "BLOCK (xfo)";
if (h.xfo === "SAMEORIGIN")
return chain.every(a => a === ME) ? "ALLOW (xfo)" : "BLOCK (xfo)";
return "ALLOW (no valid policy)";
}
const tests = [
["no header", {}, [OTHER]],
["XFO DENY, self", { xfo: "DENY" }, [ME]],
["XFO SAMEORIGIN", { xfo: "SAMEORIGIN" }, [ME]],
["XFO ALLOW-FROM", { xfo: "ALLOW-FROM " + PARTNER }, [OTHER]],
["csp none", { csp: ["'none'"] }, [ME]],
["csp self+partner", { csp: ["'self'", PARTNER] }, [PARTNER]],
["same, other site", { csp: ["'self'", PARTNER] }, [OTHER]],
["double framing", { csp: ["'self'"] }, [ME, OTHER]],
["csp beats xfo", { csp: ["'self'"], xfo: "DENY" }, [ME]],
];
for (const [name, headers, chain] of tests)
console.log(name.padEnd(18), mayFrame(headers, chain));আউটপুটের দুটি সারি বিপজ্জনক: no header ALLOW (no valid policy) আর XFO ALLOW-FROM ALLOW (no valid policy). দ্বিতীয়টি বোঝায়, অচল মান দিয়ে নিশ্চিন্ত থাকলে পাতা আসলে খোলাই থাকে। XFO DENY, self BLOCK (xfo): DENY নিজের ফ্রেমও আটকায়। csp self+partner ALLOW (csp) আর same, other site BLOCK (csp) তালিকার কাজ দেখায়। double framing BLOCK (csp): নিকটতম ফ্রেম নিজের হলেও বাইরে অন্য site থাকায় বন্ধ, কারণ সব ancestor দেখা হয়। আর csp beats xfo ALLOW (csp): X-Frame-Options: DENY থাকলেও CSP-র নিয়মই চলল।
SameSite=Lax বা Strict এখানেও সাহায্য করে। Cross-site iframe-এ cookie না গেলে পাতা লগইন-করা অবস্থায় আসে না। তবে পাশের subdomain ফ্রেম করতে পারে, আর login ছাড়াই যে কাজ হয় সেখানে এটি কিছু করে না। তাই header-ই মূল প্রতিরক্ষা।
UI confirmation আর re-authentication
“আপনি কি নিশ্চিত?” বাক্সটি ভুল ক্লিক থামায়। কিন্তু এটি নিরাপত্তার সীমা নয়। একই পাতা ফ্রেমে থাকলে দ্বিতীয় ক্লিকও ফাঁদে ফেলা যায়, আর XSS হলে script নিজেই সেটি পার হতে পারে। ভালো confirmation-এ কী হচ্ছে তা স্পষ্ট লেখা থাকে: কত টাকা, কাকে, কোন account. Confirmation পাতাটিও ফ্রেমে আসতে পারবে না।
বড় কাজে এক ধাপ এগোন। Password বা email বদল, টাকা পাঠানো, payout বদল আর MFA সরানোর আগে re-authentication চান। মোবাইল ব্যাংকিংয়ে প্রতিবার PIN চাওয়া ঠিক এই কাজই করে। PIN cookie-র মতো নিজে থেকে যায় না, আর অন্য site সেটি জানে না। এজন্য CSRF, clickjacking, এমনকি অনেক ক্ষেত্রে XSS-এর ক্ষতিও কমে। Passkey হলে phishing-ও কঠিন হয় (পর্ব ১৬)। OWASP CSRF-এর জন্য CAPTCHA-র ওপর ভরসা না করতে বলে।
৮ · Framework, নিজের app পরীক্ষা আর detection
OWASP-এর প্রথম পরামর্শ হলো নিজে না বানিয়ে framework-এর ব্যবস্থা ব্যবহার করা। নিচের তথ্য লেখার সময়ের ডকুমেন্টেশন থেকে নেওয়া। Version-ভেদে বদলাতে পারে, তাই নিজের version-এর মূল পাতা দেখুন। এগুলো উদাহরণ, কোনো পণ্যের সুপারিশ নয়।
| Framework | ব্যবস্থা | সতর্কতা |
|---|---|---|
| Django | CsrfViewMiddleware, template-এ {% csrf_token %}; Origin আর HTTPS-এ Referer যাচাই | csrf_exempt ঢালাও নয়; CSRF_TRUSTED_ORIGINS ছোট রাখুন |
| Laravel | @csrf আর session token. ১৩.x ডকুমেন্টেশনে আগে Sec-Fetch-Site, তারপর token fallback | বাদ দেওয়া route-এ নিজের signature; test-এ middleware নিজে থেকে বন্ধ থাকে |
| Express | নিজের ভেতরে নেই। পুরোনো csurf বাতিল; csrf-sync বা csrf-csrf-এর মতো community library আছে | Library-র রক্ষণাবেক্ষণ আর নকশা নিজে যাচাই করুন |
| Go (১.২৫ থেকে) | net/http-এর CrossOriginProtection, Fetch Metadata আর Origin দেখে | দুটি header না থাকলে request যেতে দেয় |
নিজের app পরীক্ষা
“Framework আছে” মানে “সুরক্ষিত” নয়। একটি route বাদ পড়তে পারে, একটি middleware বন্ধ থাকতে পারে। তাই নিজের staging পরিবেশে নিচের তালিকা ধরে যাচাই করুন। কাজটি শুধু নিজের app-এ, অথবা লিখিত অনুমতিতে। পদ্ধতিগত পরীক্ষা পর্ব ৪৯-এ, আর অনুমোদিত penetration test পর্ব ৫০-এ।
স্বয়ংক্রিয় test-এ একটি ফাঁদ আছে। অনেক framework test-এ CSRF যাচাই নিজে থেকে বন্ধ রাখে। Django-র test client-এ সেটি স্পষ্ট করে চালু করতে হয়। নিচের উদাহরণে self.user setUp-এ তৈরি, আর /transfer/ আপনার নিজের route.
from django.test import Client, TestCase
class TransferCsrfTests(TestCase):
def test_post_without_token_is_refused(self):
client = Client(enforce_csrf_checks=True)
client.force_login(self.user)
resp = client.post("/transfer/", {"amount": "100"})
self.assertEqual(resp.status_code, 403)Detection আর সাড়া
CSRF ব্যর্থতা log করুন, তবে token বা cookie-র মান নয়। Route, user ID, Origin, Sec-Fetch-Site আর কারণ রাখুন। Deploy-এর পরে হঠাৎ অনেক ব্যর্থতা সাধারণত কোডের ভুল। একই route-এ অনেক user-এর, একই বাইরের origin থেকে ব্যর্থতা সন্দেহজনক। নিচের নিয়মটি একটি Sigma-ধরনের উদাহরণ, field-এর নাম আপনার নিজের log অনুযায়ী বদলাবেন। ধারণাটি পর্ব ৫৬-এ বিস্তারিত।
title: Cross-site request refused on a sensitive route
status: experimental
logsource:
product: example-web-app
category: application
detection:
selection:
event_action: csrf_denied
fetch_site: cross-site
route|startswith:
- /transfer
- /account/email
condition: selection
level: mediumCSRF-এর একটি কঠিন দিক হলো, সফল হলে log-এ সব স্বাভাবিক দেখায়। বৈধ session, বৈধ route. তাই সংবেদনশীল কাজের পরে ব্যবহারকারীকে email বা SMS-এ জানান। অভিযোগ এলে Origin আর Sec-Fetch-Site log থেকে দেখা যাবে কাজটি কোথা থেকে শুরু। সাড়ার ধাপ: প্রতিরক্ষা-হীন route বন্ধ বা ঠিক করা, ক্ষতিগ্রস্ত account-এর বদল ফেরানো, session বাতিল, আর একই ধরনের route খোঁজা (পর্ব ৬১)।
আরও গভীরে: Barth, Jackson আর Mitchell-এর “Robust Defenses for Cross-Site Request Forgery” (ACM CCS ২০০৮) login CSRF-কে সামনে আনে আর Origin header-এর প্রস্তাব দেয়। BREACH ধরনের compression দুর্বলতার কারণে Django প্রতিটি response-এ token-এর মুখোশ বদলায়। Same-site-এর বর্তমান সংজ্ঞায় scheme-ও ধরা হয়, যা RFC 6265-এর হালনাগাদ কাজ (6265bis)-এ আছে; এর চূড়ান্ত অবস্থা IETF-এ দেখে নিন। OWASP Top 10:2025-এ CSRF-এর CWE-352 আছে A01 Broken Access Control-এর অধীনে।
পরীক্ষা ও ইন্টারভিউতে:
- CSRF আর XSS-এর পার্থক্য? CSRF ব্যবহারকারীর ওপর server-এর বিশ্বাসকে কাজে লাগায়; XSS ব্যবহারকারীর browser-এ site-এর ওপর বিশ্বাসকে।
- CORS কি CSRF থামায়? না। CORS মূলত উত্তর পড়া নিয়ন্ত্রণ করে, সাধারণ request পাঠানো নয়।
- Bearer header-এ CSRF হয় না কেন? Browser সেটি নিজে থেকে জোড়ে না, JavaScript বসায়।
- SameSite=Lax কেন যথেষ্ট নয়? Subdomain same-site, আর top-level GET-এ cookie যায়।
- Clickjacking-এ CSRF token কাজ করে না কেন? ক্লিক আসল পাতা থেকে হয়, তাই token-ও ঠিকমতো যায়।
৯ · AI-এর যুগে CSRF আর cookie যাচাই
AI-কে “এই CORS বা 403 error ঠিক করে দিন” বললে সে প্রায়ই error সরিয়ে দেয়, সমস্যা নয়। CSRF token-এর 403 দেখলে সবচেয়ে সহজ উত্তর হলো যাচাই বন্ধ করা। সেই কোড চলবে, আর আপনি দরজা খোলা রেখে দেবেন।
| AI যা লিখতে পারে | কেন বিপজ্জনক | যা চাইবেন |
|---|---|---|
csrf_exempt বা CSRF middleware বন্ধ | পুরো route খোলা | কেন 403 হলো তার কারণ আর সমাধান |
| CORS-এ সব origin, credential-সহ | অন্য site token পড়তে পারে | নিজের origin-এর ছোট তালিকা |
| “SameSite দিলেই CSRF শেষ” | Subdomain আর GET-এর ফাঁক | একাধিক স্তর |
| “JWT-তে CSRF হয় না” | JWT cookie-তে থাকলে কথাটি ভুল | পরিবহন দেখে সিদ্ধান্ত |
Naive double-submit, বা == তুলনা | Cookie বসালেই পার; timing ঝুঁকি | HMAC, session-বাঁধা, constant-time |
X-Frame-Options: ALLOW-FROM | অচল মান | frame-ancestors |
Secure সরিয়ে local-এ চালু করা | পরে production-এ চলে যায় | Local আর production-এর config আলাদা |
| বানানো CVE, RFC বা ভুল framework নাম | ভুল আত্মবিশ্বাস | মূল ডকুমেন্টেশন নিজে পড়া |
AI-কে পর্যালোচক হিসেবে ব্যবহার করুন। আসল key, cookie, token বা গ্রাহকের ডেটা কখনো দেবেন না। নকল মান বসিয়ে দিন। উত্তরকে সূত্র হিসেবে নয়, যাচাইয়ের তালিকা হিসেবে নিন।
AI-এর পরামর্শ হাতে পেলে
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১Browser প্রতিটি request-এ cookie নিজে থেকে পাঠায় কেন? এই নকশা আজ নতুন করে বানানো হলে কী হতো?
Cookie এসেছিল ১৯৯৪ সালের দিকে Netscape-এ, একটি খুব সাধারণ সমস্যার সমাধানে। HTTP আগের কথা মনে রাখে না, অথচ অনলাইন কেনাকাটার ঝুড়ি মনে রাখা দরকার ছিল। তখন একটি পাতা বলতে ছিল একটি server-এর পাতা। অন্য site-এর ছবি বা form থেকে request যাবে, আর সেটি ঝুঁকি হবে, এমন চিন্তা সামনে ছিল না। তাই নিয়ম হলো সহজ: যে host-এর cookie, সেই host-এ যাওয়া প্রতিটি request-এ cookie যাবে, কে পাঠাচ্ছে তা না দেখেই।
১৯৮৮ সালে Norm Hardy “The Confused Deputy” প্রবন্ধে ঠিক এই শ্রেণির সমস্যা লিখেছিলেন: একটি প্রোগ্রাম নিজের অধিকার ব্যবহার করে অন্যের নির্দেশ পালন করে। তাঁর সমাধানের দিক ছিল capability, অর্থাৎ অধিকার পরিবেশ থেকে নয়, হাতে করে দেখাতে হবে। ২০০১ সালে Peter Watkins-কে “cross-site request forgery” নামটির উৎস হিসেবে ধরা হয়। আজকের CSRF token আসলে সেই capability-র একটি রূপ: প্রতিটি কাজে হাতে করে একটি প্রমাণ দেখাতে হয়।
নতুন করে বানালে সম্ভবত default হতো “নিজের site থেকে শুরু হওয়া request-এই cookie”, আর cross-site পাঠাতে আলাদা অনুমতি লাগত। SameSite ধীরে ধীরে সেই দিকেই যাচ্ছে। কিন্তু ওয়েব পেছনের দিকে সামঞ্জস্য রাখে। Payment redirect, single sign-on আর embed হওয়া সেবা পুরোনো আচরণের ওপর দাঁড়িয়ে। তাই পরিবর্তন আসে ধাপে ধাপে, আর প্রতিটি browser একই গতিতে যায় না।
Bearer token এই ধারণার অন্য রূপ। সেখানে JavaScript নিজে header বসায়, তাই ambient authority নেই। কিন্তু তখন token-কে পাতার script-এর নাগালে রাখতে হয়, আর XSS-এর দাম বাড়ে। কোনো পক্ষই বিনামূল্যে নয়।
মূল উপলব্ধি: নিরাপত্তার অনেক দুর্বলতা আসলে পুরোনো সুবিধার দীর্ঘ ছায়া। সুবিধা যত স্বয়ংক্রিয়, যাচাই তত স্পষ্টভাবে আলাদা করে রাখতে হয়।
প্র ০২OWASP Top 10-এর আলাদা তালিকা থেকে CSRF ২০১৭ সালে বাদ পড়েছিল। তাহলে কি সমস্যাটি শেষ?
২০০৭ আর ২০১০ সালের OWASP তালিকায় CSRF ছিল প্রথম পাঁচের ভেতরে, ২০১৩ সালে নেমে আসে A8-এ। ২০১৭ সালে আলাদা ধাপ হিসেবে এটি বাদ পড়ে। OWASP-এর কারণ ছিল, অনেক framework CSRF প্রতিরক্ষা নিজে দিচ্ছে, আর পরীক্ষায় এটি তুলনামূলক কম পাওয়া যাচ্ছে। এটি একটি সত্যিকারের সাফল্য। যে সমস্যা প্রতিটি ডেভেলপারকে মনে রাখতে হতো, সেটি নিরাপদ default হয়ে গেল।
কিন্তু বাদ পড়া মানে মৃত্যু নয়। OWASP Top 10:2025-এ CWE-352 (CSRF) A01 Broken Access Control-এর ভেতরে তালিকাভুক্ত। সমস্যাটি অন্য রূপে ফেরে, কারণ প্রতিরক্ষা শুধু default পথেই থাকে। কিছু সাধারণ ফাঁক: কেউ একটি route-এ যাচাই বন্ধ করলেন। SPA আর cookie-র মিশ্র নকশায় API-তে framework-এর সুরক্ষা লাগানো হলো না। Mobile-এর জন্য বানানো endpoint পরে web থেকেও ব্যবহার হলো। Webhook-এর ব্যতিক্রম বেশি চওড়া হলো। আবার router বা local admin পাতার মতো ছোট embedded web server-এ প্রায়ই framework থাকেই না।
২০২০ সালের পরে Chromium-ভিত্তিক browser-এর Lax default ঝুঁকি আরও কমিয়েছে। কিন্তু সব browser একই নিয়ম মানে না, আর এটি subdomain বা GET-এর সমস্যা মেটায় না। তাই “browser করে দেবে” ভাবা ঝুঁকিপূর্ণ।
ইতিহাসের শিক্ষা হলো, তালিকার স্থান দুর্বলতার গুরুত্ব নয়, তার প্রচলন আর তথ্যের ফল। OWASP তালিকা নিজেও পদ্ধতি বদলায়, শ্রেণি একত্র করে। তাই নিরাপত্তা-পরিকল্পনা শুধু তালিকা মেলানোর কাজ হলে অনেক কিছু বাদ পড়বে। পর্ব ৩৭-এ তালিকাটি নিজেই আলোচিত।
মূল উপলব্ধি: সেরা নিরাপত্তা হলো যা ডেভেলপারকে মনে রাখতে হয় না। কিন্তু default কাজ করছে কি না, সেটি নিয়মিত test করে দেখতে হয়।
প্র ০৩XSS থাকলে CSRF token অর্থহীন হয় কেন? ২০০৫ সালের Samy ঘটনা এখানে কী শেখায়?
২০০৫ সালে তখনকার জনপ্রিয় সামাজিক সাইট MySpace-এ Samy নামে পরিচিত একটি XSS worm ছড়িয়ে পড়ে। প্রকাশিত বিবরণ অনুযায়ী প্রায় এক দিনের মধ্যে দশ লক্ষের বেশি profile আক্রান্ত হয়। MySpace-এ CSRF-ধরনের token ছিল। কিন্তু worm-টি ব্যবহারকারীর পাতার ভেতরেই চলছিল, তাই same-origin হিসেবেই পাতা পড়ে token সংগ্রহ করতে পারত। এই ঘটনা ইতিহাসে দেখায়, XSS থাকলে CSRF token দেয়াল হিসেবে দাঁড়ায় না।
কারণটি যুক্তিতে খুব সরল। CSRF token একটি মাত্র ধারণার ওপর দাঁড়িয়ে: same-origin policy অন্য origin-কে আপনার পাতা পড়তে দেয় না। XSS সেই সীমাটিই পার হয়, কারণ script এখন আপনার origin-এরই অংশ। তখন Sec-Fetch-Site বলবে same-origin, Origin মিলবে, cookie যাবে, token-ও সঠিক। আপনার যাচাই টেবিলের সব ঘর সবুজ, অথচ নির্দেশ অন্যের।
HttpOnly এখানে আংশিক রক্ষা দেয়। Script cookie পড়তে পারে না, তাই cookie চুরি করে অন্য জায়গা থেকে ব্যবহার করা কঠিন। কিন্তু ভুক্তভোগীর browser থেকেই request পাঠানো আটকায় না। তাই ক্ষতি সীমিত করতে লাগে অন্য স্তর। CSP script চলার সুযোগ কমায় (পর্ব ৪০)। সংবেদনশীল কাজে re-authentication এমন একটি মান চায় যা পাতায় থাকে না, তবে পাতার ভেতরের script ব্যবহারকারীকে ভুল দেখিয়ে PIN চাইতে পারে। তাই মুখ্য প্রতিরক্ষা XSS না থাকা।
একটি প্রকৌশল শিক্ষা আছে। দুর্বলতাগুলো আলাদা ঘরে থাকে না। একটি ছোট stored XSS, যাকে কেউ “মাঝারি” বলে ফেলে রেখেছেন, পুরো CSRF প্রতিরক্ষাকে অকার্যকর করে। ঝুঁকি মাপার সময় একটি দুর্বলতা অন্য কোন নিয়ন্ত্রণ ভাঙে, সেটিও দেখুন।
মূল উপলব্ধি: প্রতিটি প্রতিরক্ষার একটি অনুমান থাকে। সেই অনুমান যা ভাঙে, তার গুরুত্ব প্রতিরক্ষার সমান।
প্র ০৪Fetch Metadata থাকলে কি CSRF token আর লাগবে না? Token-হীন CSRF প্রতিরক্ষা কি ভবিষ্যৎ?
Token সুরক্ষার সমস্যা হলো খরচ। প্রতিটি form-এ field, প্রতিটি AJAX-এ header, cache-এর সঙ্গে ঝামেলা, মেয়াদ শেষে ব্যবহারকারীর বিরক্তি। ২০০৮ সালে Barth, Jackson আর Mitchell Origin header-এর প্রস্তাব দেন, যাতে browser নিজেই উৎস বলে দেয়, আর Referer-এর privacy-সমস্যা না থাকে। পরে W3C-র Fetch Metadata আরও সরাসরি প্রশ্ন করল: এই request same-origin, same-site, না cross-site? ২০২৩ সালের দিকে প্রধান browser-গুলোতে Sec-Fetch-Site পৌঁছায়।
এখন কিছু framework এই পথে হাঁটছে। Go ১.২৫ CrossOriginProtection যোগ করেছে, যা token ছাড়াই কাজ করে। Laravel-এর ১৩.x ডকুমেন্টেশন প্রথমে এই header দেখে, ব্যর্থ হলে token-এ ফেরে, আর চাইলে শুধু origin যাচাইয়ের option দেয়। OWASP-ও শুধু আধুনিক browser-এর software-এ Fetch Metadata-কে নির্ভর করার মতো বলে, তবে fallback বাধ্যতামূলক রেখে।
লাভ পরিষ্কার: কোনো গোপন মান রাখতে হয় না, ফাঁসেরও প্রশ্ন নেই, পাতা cache করা সহজ, আর পাশের subdomain-ও আলাদা করা যায়, যা SameSite পারে না। শর্তও পরিষ্কার: পুরো সাইট HTTPS হতে হবে, কারণ header শুধু নিরাপদ সংযোগে আসে। পুরোনো browser, কিছু embedded WebView, আর header মুছে দেওয়া proxy নিয়ে ভাবতে হয়। একই site-এর ভেতরে আপনার নিজের অন্য subdomain থেকে আসা সৎ request-এর জন্য নির্দিষ্ট ব্যতিক্রম লাগে।
সিদ্ধান্ত তাই ব্যবহারকারী ভিত্তির ওপর। নতুন, HTTPS-only app, আধুনিক browser: Fetch Metadata আর Origin fallback যুক্তিসঙ্গত প্রধান প্রতিরক্ষা। বড় app, পুরোনো যন্ত্রের ব্যবহারকারী, বা টাকার লেনদেন: token রাখুন, Fetch Metadata স্তর হিসেবে যোগ করুন। বাংলাদেশের মতো বাজারে পুরোনো Android আর কম আপডেট হওয়া WebView বিবেচনায় রাখা দরকার, নিজের log দেখে মেপে সিদ্ধান্ত নিন।
মূল উপলব্ধি: নিরাপত্তা ধীরে ধীরে application-এর কোড থেকে platform-এ সরে। কিন্তু সরানোর সিদ্ধান্ত অনুমানে নয়, নিজের ব্যবহারকারীর তথ্য দেখে নিতে হয়।
প্র ০৫Frame-busting JavaScript কেন ব্যর্থ হলো? এই ঘটনা client আর server-এর দায় নিয়ে কী শেখায়?
২০০৮ সালে Robert Hansen আর Jeremiah Grossman “clickjacking” নামটি জনপ্রিয় করেন। তখন ওয়েবের প্রথম প্রতিক্রিয়া ছিল সহজ: পাতার ভেতরে একটি ছোট script, যা দেখে পাতা ফ্রেমে আছে কি না, থাকলে বেরিয়ে আসে। একে frame busting বলে। ২০১০ সালে Rydstedt, Bursztein, Boneh আর Jackson-এর “Busting Frame Busting” গবেষণা জনপ্রিয় সাইটের এই ধরনের কোড পরীক্ষা করে দেখায়, সাধারণ কৌশলগুলো নানা পথে অকার্যকর হয়। OWASP-এর clickjacking cheat sheet-ও এটিকে নির্ভরযোগ্য বলে না।
মূল কারণটি কাঠামোগত, কোনো একটি bug নয়। Frame-busting script চলে সেই পরিবেশে, যার বাইরের পাতা আক্রমণকারীর হাতে। বাইরের পাতা script-এর চলার পরিবেশ বদলাতে পারে। অর্থাৎ যাকে থামানো দরকার, রক্ষকের কোড তারই ঘরে চলছে। এটি অনেকটা চোরের ঘরে পাহারাদার পাঠানোর মতো।
২০০৯ সালে Microsoft Internet Explorer 8-এ X-Frame-Options আনে, পরে অন্য browser-ও সমর্থন দেয়, আর RFC 7034 (২০১৩) এর আচরণ নথিবদ্ধ করে। পরিবর্তনটি ছোট দেখায়, কিন্তু গভীর। এখন সিদ্ধান্ত পাতার script নেয় না। Server একটি header-এ নীতি ঘোষণা করে, আর browser নিজে পাতা দেখানোর আগেই তা কার্যকর করে। আক্রমণকারী সেই ধাপে হাত দিতে পারে না। পরে CSP-র frame-ancestors এসে একাধিক origin, wildcard আর সব ancestor পরীক্ষার সুবিধা দিল, যা X-Frame-Options দিতে পারত না।
একই নমুনা বারবার দেখা যায়। CSRF-এ সিদ্ধান্ত গেল server-এর token থেকে browser-এর SameSite আর Fetch Metadata-র সাহায্যে। XSS-এ CSP. সব ক্ষেত্রেই ভালো নকশা হলো: নীতি ঘোষণা করে server, কার্যকর করে এমন একটি পক্ষ যাকে অন্য পাতা প্রভাবিত করতে পারে না।
মূল উপলব্ধি: আক্রমণকারীর নিয়ন্ত্রণে থাকা পরিবেশে প্রতিরক্ষা বসালে সেটি দুর্বল। প্রতিরক্ষা বসান trust boundary-র যে পাশে আপনার নিয়ন্ত্রণ আছে।
অনুশীলন
সব অনুশীলন খাতায়, নিজের কম্পিউটারে, বা নিজের test app-এ। অন্যের system-এ নয়।
-
হিসাব: একটি দুর্বল app-এ CSRF token ৩২ bit. অন্য একটি app-এ ১২৮ bit. ধরে নিন আক্রমণকারী প্রতি সেকেন্ডে ১০০০টি অনুমান যাচাই করতে পারে (ধরে নেওয়া মান)। সফল হতে গড়ে অর্ধেক token-space লাগে। দুটি ক্ষেত্রে সময় কত? আর
secrets.token_hex(32)-এর rand আর ৬৪ অক্ষরের HMAC tag মিলিয়েtag.randtoken কত অক্ষরের?৩২ bit: 231 / 1000 = 2,147,483.6 সেকেন্ড, মানে প্রায় ২৪.৯ দিন।
১২৮ bit: 2127 / 1000 ≈ 1.70 × 1035 সেকেন্ড। বছরে প্রায় 3.156 × 107 সেকেন্ড ধরলে প্রায় 5.39 × 1027 বছর।
দৈর্ঘ্য:
token_hex(32)দেয় ৩২ byte, মানে ৬৪ hex অক্ষর। তাই ৬৪ + ১ + ৬৪ = ১২৯ অক্ষর। অর্থাৎ ছোট token-এ দিন গোনা যায়, ১২৮ bit-এ নয়। আর ব্যর্থ অনুমান log আর rate limit-এ ধরা পড়ে। -
নকশা পর্যালোচনা: একটি কাল্পনিক অনলাইন বাজারের নকশা: ১)
GET /address/delete?id=7২) token URL-এর query-তে ৩) সব subdomain-এ cookie:Domain=example.com; SameSite=Lax, আরpromo.example.com-এ অন্য কোম্পানির campaign পাতা ৪) CORS-এ যেকোনো origin ফিরিয়ে দেওয়া, credential-সহ ৫)X-Frame-Options: ALLOW-FROM https://partner.example.org৬) Payout বদলে শুধু একটি “নিশ্চিত?” বাক্স। প্রতিটির সমস্যা আর সমাধান লিখুন।- ১: GET কিছু বদলাচ্ছে, Lax-ও রক্ষা করবে না। সমাধান:
POSTবাDELETE, সঙ্গে token. - ২: URL log, history আর Referer-এ ফাঁস হয়। সমাধান: form field বা header.
- ৩: সব subdomain cookie পায়, আর promo পাতা same-site. সমাধান:
__Host-cookie, promo আলাদা registrable domain-এ। - ৪: অন্য site token পড়তে পারে, preflight-ও পার হয়। সমাধান: নির্দিষ্ট origin-এর তালিকা।
- ৫: অচল মান, আধুনিক browser উপেক্ষা করে, পাতা অরক্ষিত থাকে। সমাধান:
frame-ancestors 'self' https://partner.example.org. - ৬: Confirmation নিরাপত্তার সীমা নয়। সমাধান: re-authentication, বার্তায় বদলের বিবরণ, আর ব্যবহারকারীকে আলাদা বার্তা।
- ১: GET কিছু বদলাচ্ছে, Lax-ও রক্ষা করবে না। সমাধান:
-
ফল অনুমান: একটি খেলনা server-এ balance 5000. রহিমার session-এর token
tok-a1. নিয়ম: GET কিছু বদলায় না; POST হলে site হতে হবে same-origin, আর token মিলতে হবে; মিললে amount কাটা হবে। পরপর পাঁচটি request: ক) POST, same-origin, tok-a1, amount 500. খ) POST, cross-site, token নেই, amount 1000. গ) GET, same-origin, amount 1000. ঘ) POST, same-origin, token tok-b9, amount 700. ঙ) POST, same-origin, tok-a1, amount 200. প্রতিটির ফল আর শেষ balance কত?ক) ALLOW, 5000 − 500 = 4500. খ) DENY, cross-site, 4500. গ) শুধু পড়া, 4500. ঘ) DENY, token মেলেনি, 4500. ঙ) ALLOW, 4500 − 200 = 4300. শেষ balance 4300. নিজে যাচাই করতে চাইলে
csrf-decide.js-এ একটি balance চলক যোগ করে দেখুন। -
নিজের app দেখুন: নিজের বানানো বা অনুমতি পাওয়া একটি app খুলুন। DevTools-এর Network ট্যাবে একটি নিজের form submit দেখুন।
OriginআরSec-Fetch-Site-এর মান, session cookie-র flag, আর response-এরContent-Security-Policyলিখে রাখুন। কোন স্তরগুলো আছে, কোনটি নেই?HTTPS-এ নিজের পাতার form-এ সাধারণত
Originআপনার নিজের origin, আরSec-Fetch-Site: same-origin. Local HTTP-তেSec-Fetch-Siteসাধারণত থাকে না, এটি স্বাভাবিক, তাই production-ও দেখুন। Form-এ লুকানো token আছে কি না Elements ট্যাবে দেখুন। একটি ছোট টেবিল বানান: স্তর, আছে কি না, প্রমাণ, পরবর্তী কাজ। যে স্তর নেই, সেটির জন্য framework-এর ডকুমেন্টেশন দেখে একটি কাজ তালিকায় তুলুন। প্রোডাকশনের আসল token বা cookie-র মান কোথাও লিখবেন না।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি state-changing POST request-এ server-এর যাচাই সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- Method দেখা: safe নয়, তাই যাচাই লাগবে
- Cookie থেকে session আর ব্যবহারকারী খুঁজে পাওয়া
Sec-Fetch-Site, না থাকলেOriginযাচাই- CSRF token constant-time তুলনা
- Authorization দেখে কাজ করা, আর log রাখা
CSRF সম্ভব হওয়ার মূল কারণ কোনটি?
- আক্রমণকারী ব্যবহারকারীর password জানে
- Browser request-এ cookie নিজে থেকে পাঠায়, আর server শুধু cookie দেখে কাজ করে
- Same-origin policy অন্য origin-কে response পড়তে দেয়
- Site-এ HTTPS নেই
একটি session cookie-তে SameSite=Lax আছে। other.example.net-এর পাতা থেকে shop.example.com-এ কোন ক্ষেত্রে cookie যাবে?
- Cross-site form POST-এ
- Cross-site পাতার ছবির request-এ
- Cross-site লিংকে ক্লিক করে top-level GET navigation-এ
- Cross-site iframe-এর ভেতরের GET-এ
Signed double-submit cookie-তে HMAC-এর বার্তায় session-নির্ভর মান রাখা হয় কেন?
- Token encrypt করে লুকিয়ে রাখতে
- Token-এর দৈর্ঘ্য বাড়াতে
- Browser যাতে cookie নিজে থেকে পাঠায়
- অন্য session-এর বৈধ token যাতে এই session-এ না চলে
“আমাদের API-তে CORS ঠিকঠাক সেট করা, তাই CSRF নিয়ে ভাবতে হবে না।” এই কথার মূল্যায়ন কোনটি?
- ভুল, কারণ CORS মূলত উত্তর পড়া নিয়ন্ত্রণ করে; সাধারণ request পৌঁছাতে পারে
- ঠিক, কারণ CORS সব cross-origin request থামায়
- ঠিক, তবে শুধু JWT ব্যবহার করলে
- ঠিক, যদি সাইটটি HTTPS-এ চলে
একটি পাতার response-এ Content-Security-Policy: frame-ancestors 'self' আর X-Frame-Options: DENY, দুটিই আছে। পাতাটি একই origin-এর আরেকটি পাতার iframe-এ আসতে চায়। আধুনিক browser কী করবে?
- আটকাবে, কারণ DENY সবসময় জেতে
- দেখাবে, কারণ frame-ancestors থাকলে X-Frame-Options উপেক্ষিত হয়
- ব্যবহারকারীকে জিজ্ঞেস করবে
- দুটি header-ই বাতিল ধরে সব origin-কে অনুমতি দেবে
একটি app-এর session cookie Domain=example.com; SameSite=Strict. promo.example.com-এ XSS পাওয়া গেল। ঝুঁকি সম্পর্কে কোন কথা ঠিক?
- Strict আছে, তাই promo পাতা থেকে মূল app-এ cookie যাবে না
- আলাদা origin, তাই কোনো ঝুঁকি নেই
- Subdomain same-site, তাই SameSite বাধা দেয় না; Domain চওড়া হওয়ায় ঝুঁকি আরও বাড়ে
- HTTPS চালু থাকলে এই ঝুঁকি থাকে না
একটি ব্যাংকিং app-এ টাকা পাঠানোর ঠিক আগে আবার PIN চাওয়া হলো। Cookie-ভিত্তিক session-এ এটি কোন ঝুঁকি সবচেয়ে সরাসরি কমায়?
- SQL injection
- Network-এ ডেটা পড়ে ফেলা
- DDoS
- ব্যবহারকারীর অজান্তে পাঠানো request-এ কাজ হয়ে যাওয়া
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব শব্দ এখানে একসাথে:
মনে রাখুন
- Browser cookie নিজে থেকে পাঠায়। তাই cookie “কে” বলে, “কে চাইল” বলে না।
- GET কিছু বদলাবে না; state-changing request-এ framework-এর সুরক্ষা, session-বাঁধা token.
SameSite,OriginআরSec-Fetch-Siteস্তর; site origin-এর চেয়ে চওড়া, subdomain-ও বিশ্বাসের সীমা।- CORS CSRF প্রতিরক্ষা নয়, আর XSS থাকলে CSRF প্রতিরক্ষা ভেঙে পড়ে।
- Clickjacking থামায়
frame-ancestors; বড় কাজে re-authentication, আর সব যাচাই নিজের app-এ test করুন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৪২ · SSRF আর file handlingপরবর্তী পর্বURL fetch, upload আর path-এর নিরাপত্তা।
- সাইবার নিরাপত্তা · কোর্স হোম৮০টি পর্বপুরো কোর্সের রোডম্যাপ।
- পর্ব ১৭ · Session আর tokenআগের ভিত্তিSession ID, cookie flag, JWT আর নিরাপদ logout.
- JavaScript · Web securityগভীরে যানBrowser-এর দিক থেকে XSS, CSRF আর cookie.
- Computer Networks · HTTPপাশের ট্র্যাকMethod, header আর status code-এর ভিত্তি।
- System Design · Security designপাশের ট্র্যাকবড় system-এ নিরাপত্তার নকশা।