ফুল-স্ট্যাক অ্যাপ সুরক্ষিত করা — CSRF, CORS ও XSS বেসিক
এই পাঠে যা শিখবেন
- CSRF, CORS, ও XSS — তিনটি সাধারণ ওয়েব-নিরাপত্তা সমস্যার সংক্ষিপ্ত ধারণা
- একটি সত্যিকারের HTML-এস্কেপিং ফাংশন লিখে XSS ইনজেকশন নিষ্ক্রিয় করা
- একটি সত্যিকারের allow-list-ভিত্তিক CORS origin-checking ফাংশন লেখা
- কেন এই সুরক্ষাগুলো ফ্রেমওয়ার্ক-স্তরে ডিফল্ট হিসেবে থাকা উচিত, প্রতিবার হাতে লেখার বদলে
১ · তিনটি সাধারণ সমস্যা — সংক্ষেপে
গভীর নিরাপত্তা তত্ত্ব ও আক্রমণ-প্রতিরোধের প্রতিটি খুঁটিনাটি ../cybersecurity/ কোর্সের বিষয়;
এখানে শুধু ফুল-স্ট্যাক অ্যাপ তৈরির সময় এই তিনটি নাম কেন গুরুত্বপূর্ণ তা সংক্ষেপে দেখা যাক।
একজন লগ-ইন-করা ইউজারকে না জানিয়ে তার ব্রাউজার দিয়ে একটি অনাকাঙ্ক্ষিত রিকোয়েস্ট পাঠানো — CSRF টোকেন যাচাই করে ঠেকানো হয়।
ব্রাউজার ডিফল্টভাবে একটি origin-এর JavaScript-কে আরেকটি origin-এর ডেটা পড়া থেকে আটকায় — সার্ভার নির্দিষ্ট origin-কে অনুমতি দিলেই তা সম্ভব হয়।
ইউজারের দেওয়া ইনপুট (যেমন কমেন্ট) escape না করে সরাসরি HTML-এ বসালে সেটি স্ক্রিপ্ট হিসেবে চলে যেতে পারে।
২ · XSS ঠেকানো — একটি সত্যিকারের HTML-এস্কেপিং ফাংশন
নিচের কোডে একটি বাস্তব html_escape() ফাংশন পাঁচটি বিশেষ ক্যারেক্টার
(&, <, >, ", ') তাদের HTML
entity সমতুল্যে রূপান্তর করে। একটি ক্ষতিকর-দেখতে ইনপুট স্ট্রিং র (raw) ও escape-করা — দুই অবস্থায় পাশাপাশি
দেখানো হয়েছে।
def html_escape(text: str) -> str:
"""HTML-এ বিশেষ অর্থ বহনকারী ৫টি ক্যারেক্টারকে entity-তে রূপান্তর করে,
যাতে ব্রাউজার এগুলোকে মার্কআপ/স্ক্রিপ্ট হিসেবে না বুঝে সাধারণ টেক্সট হিসেবে দেখায়"""
replacements = [
("&", "&"), # সবার আগে -- নাহলে পরের entity-গুলোর '&' আবার এস্কেপ হয়ে যাবে
("<", "<"),
(">", ">"),
('"', """),
("'", "'"),
]
escaped = text
for char, entity in replacements:
escaped = escaped.replace(char, entity)
return escaped
# ব্যবহারকারীর কমেন্ট বক্সে টাইপ করা একটি ক্ষতিকর ইনপুট
user_comment = "<script>alert(1)</script>"
print("র (raw) ইনপুট -- সরাসরি পেজে বসালে ব্রাউজার একে <script> ট্যাগ হিসেবে চালাত:")
print(user_comment)
safe_comment = html_escape(user_comment)
print("\nescape করা ইনপুট -- এখন নিরাপদে প্লেইন টেক্সট হিসেবে দেখানো যায়:")
print(safe_comment)
print("\nরাউ ইনপুট আসলেই <script> দিয়ে শুরু কি না:", user_comment.startswith("<script>"))
print("escape করার পর কোনো '<' অক্ষর অবশিষ্ট নেই:", "<" not in safe_comment)
replacements তালিকায় &-কে সবার প্রথমে রূপান্তর করা হয়েছে। যদি
এটি শেষে করা হতো, তাহলে "<"-কে "<" লেখার সময় তৈরি হওয়া নতুন
&-টিও আবার এস্কেপ হয়ে যেত (&lt;-এর মতো ভুল ফলাফল) — এই ক্রম-নির্ভরতা
একটি বাস্তব, সাধারণ বাগ যা এস্কেপিং ফাংশন লেখার সময় খেয়াল রাখতে হয়।
৩ · CORS — কোন origin-কে বিশ্বাস করা যাবে
ব্রাউজার ডিফল্টভাবে ধরে নেয় ভিন্ন origin থেকে আসা JavaScript আপনার API-এর রেসপন্স পড়তে পারবে না, যতক্ষণ না
সার্ভার স্পষ্টভাবে সেই origin-কে অনুমতি দেয়। নিচের কোডে একটি allow-list ও একটি origin-checking ফাংশন
বাস্তবায়ন করে বিভিন্ন Origin হেডার মান পরীক্ষা করা হয়েছে।
allowed_origins = {
"https://abcltech.com",
"https://app.abcltech.com",
"http://localhost:3000", # ডেভেলপমেন্ট origin
}
def is_origin_allowed(origin_header: str) -> bool:
"""CORS: শুধুমাত্র allow-list-এ থাকা origin থেকে আসা রিকোয়েস্টকে
ব্রাউজারকে ক্রস-অরিজিন রেসপন্স পড়ার অনুমতি দেওয়া হয়"""
return origin_header in allowed_origins
test_origins = [
"https://abcltech.com",
"https://app.abcltech.com",
"http://localhost:3000",
"https://evil-attacker.com",
"https://abcltech.com.evil-attacker.com", # সাবডোমেইন-সদৃশ প্রতারণামূলক origin
]
print(f"{'Origin':40s} | সিদ্ধান্ত")
print("-" * 58)
for origin in test_origins:
decision = "Allow" if is_origin_allowed(origin) else "Deny"
print(f"{origin:40s} | {decision}")
"https://abcltech.com.evil-attacker.com" দেখতে abcltech.com-এর মতো
মনে হলেও এটি আসলে সম্পূর্ণ ভিন্ন একটি origin (evil-attacker.com-এর একটি সাবডোমেইন) — কারণ
is_origin_allowed() সরাসরি সেট-মেম্বারশিপ (exact string match) চেক করে, স্ট্রিং-এর মধ্যে
"abcltech.com" আছে কি না তা নয়। কখনো "abcltech.com" in origin_header-এর মতো
আংশিক-মিল (substring) চেক দিয়ে CORS বাস্তবায়ন করা উচিত নয় — এটিই একটি বাস্তব, সাধারণ নিরাপত্তা ভুল।
XSS, CORS, ও CSRF তিনটিই "কে কী পড়তে/চালাতে পারে" সংক্রান্ত সমস্যা, ঠিক যেমন L36-এর RBAC "কে কী করতে
পারে" সংক্রান্ত। বাস্তব ফ্রেমওয়ার্কগুলো (Django, Rails, Express-এর cors মিডলওয়্যার) এই
সুরক্ষাগুলো বিল্ট-ইন হিসেবে দেয়, যাতে প্রতিটি প্রজেক্টে নতুন করে হাতে-কলমে লিখতে না হয় — L01-এ শেখা
"সমাধান হওয়া সমস্যা পুনরায় সমাধান না করা" নীতিরই একটি প্রয়োগ।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
যদি একজন ডেভেলপার ভুলে html_escape() কল করতে ভুলে যান, তাহলে কী ঘটতে পারে?
যদি ইউজারের র (raw) ইনপুট সরাসরি পেজে বসানো হয়, এবং সেই ইনপুটে <script>...</script>
-এর মতো কিছু থাকে, ব্রাউজার সেটিকে সত্যিকারের চালযোগ্য JavaScript হিসেবে মেনে নেবে — এটিই Cross-Site
Scripting (XSS)। এই স্ক্রিপ্ট তখন ভুক্তভোগী ইউজারের সেশন কুকি চুরি করতে, ফর্ম ডেটা পাঠিয়ে দিতে, বা তার
পক্ষে অ্যাকশন চালাতে পারে — এই কারণেই আউটপুটে বসানোর আগে ইউজার-ইনপুট সবসময় এস্কেপ করা জরুরি।
প্র ০২ CORS আসলে সার্ভারকে সুরক্ষা দেয়, নাকি ব্রাউজার-ব্যবহারকারীকে? পার্থক্যটা কী?
CORS মূলত ব্রাউজারকে সুরক্ষা দেয় — এটি একটি ব্রাউজার-প্রয়োগিত নীতি যা নির্ধারণ করে কোন origin-এর JavaScript আরেকটি origin-এর রেসপন্স পড়তে পারবে। এটি সার্ভারকে সরাসরি "আক্রমণ" থেকে রক্ষা করে না (একজন আক্রমণকারী `curl` দিয়ে সরাসরি API কল করলে CORS কোনো বাধা দেয় না, কারণ CORS ব্রাউজার-নির্দিষ্ট নিয়ম) — বরং এটি রক্ষা করে সেই ইউজারকে, যার ব্রাউজারে ভিন্ন একটি ক্ষতিকর সাইট খোলা থাকতে পারে এবং যেটি ইউজারের অজান্তে তার লগ-ইন-করা সেশন ব্যবহার করে আপনার API-এর ডেটা পড়ার চেষ্টা করতে পারে।
প্র ০৩
উপরের কোডে is_origin_allowed("http://localhost:3000") কেন সঠিকভাবে Allow
রিটার্ন করে, অথচ এটি একটি লোকাল ডেভেলপমেন্ট ঠিকানা?
কারণ "http://localhost:3000" স্ট্রিংটি হুবহু allowed_origins সেটে যোগ করা
আছে — এটি একটি বাস্তব প্র্যাকটিসও প্রতিফলিত করে, যেখানে ডেভেলপমেন্ট পরিবেশের জন্য আলাদা করে
localhost-ভিত্তিক origin allow-list-এ রাখা হয় (এবং প্রোডাকশন ডিপ্লয়মেন্টে সেটি সাধারণত
বাদ দেওয়া হয়, L51-এর এনভায়রনমেন্ট-নির্দিষ্ট কনফিগারেশনের ধারণার সাথে সামঞ্জস্যপূর্ণ)।
অনুশীলন
-
চিন্তা করুন: L34-এর JWT ও L37-এর CSRF-এর মধ্যে একটি সম্পর্ক আছে — কেন টোকেন-ভিত্তিক
(JWT, সাধারণত একটি
Authorizationহেডারে পাঠানো) API-গুলো সাধারণত কুকি-ভিত্তিক সেশনের চেয়ে CSRF-এর প্রতি কম ঝুঁকিপূর্ণ বলে বিবেচিত হয়?CSRF আক্রমণ কাজ করে কারণ ব্রাউজার স্বয়ংক্রিয়ভাবে কুকি সংযুক্ত করে পাঠায়, এমনকি একটি ক্ষতিকর সাইট থেকে শুরু-হওয়া রিকোয়েস্টেও। কিন্তু JWT সাধারণত JavaScript কোড স্পষ্টভাবে
Authorization: Bearer <token>হেডারে যোগ করে — ব্রাউজার এটি স্বয়ংক্রিয়ভাবে সংযুক্ত করে না, তাই একটি ক্ষতিকর সাইট সহজে ব্যবহারকারীর টোকেন "চুরি করে" তার পক্ষে অনুরোধ করাতে পারে না, যদি না সেই ক্ষতিকর সাইট আসলেই টোকেনটির মান জানে। -
পরীক্ষা করুন: উপরের CORS কোড সেলে
allowed_origins-এ"https://staging.abcltech.com"যোগ করুন এবংtest_origins-এ সেই একই স্ট্রিং যোগ করে Run চেপে দেখুন সিদ্ধান্তটিDenyথেকেAllow-এ বদলায় কি না।allowed_origins-এ নতুন এন্ট্রি যোগ করার আগেis_origin_allowed("https://staging.abcltech.com")False(Deny) রিটার্ন করত, কারণ সেই origin সেটে ছিল না। যোগ করার পর সেটে"https://staging.abcltech.com"থাকায় সেট-মেম্বারশিপ চেক সত্য হয়ে যায়, তাই ফাংশন এখনTrue(Allow) রিটার্ন করবে — এটাই দেখায় allow-list একটি জীবন্ত কনফিগারেশন, প্রতিটি নতুন বৈধ ফ্রন্ট-এন্ড ডিপ্লয়মেন্টের জন্য স্পষ্টভাবে আপডেট করতে হয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ আর্কিটেকচার প্যাটার্ন, ফ্রন্ট-এন্ড/ব্যাক-এন্ড ফ্রেমওয়ার্ক ফান্ডামেন্টাল, স্টেট ম্যানেজমেন্ট, REST API, ORM, অথেন্টিকেশন, রেন্ডারিং স্ট্র্যাটেজি ও ডিপ্লয়মেন্ট।
- Cybersecurity কোর্স সহোদর কোর্স CSRF টোকেন বাস্তবায়ন, কনটেন্ট সিকিউরিটি পলিসি (CSP), ও XSS-এর গভীর প্রকারভেদ সেই কোর্সে বিস্তারিত আলোচিত।
- JavaScript Programming কোর্স সহোদর কোর্স ব্রাউজারের ফেচ/XHR ও origin-সম্পর্কিত আচরণের ভাষাগত ভিত্তি সেই কোর্সে তৈরি হয়েছে।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics ও Full-Stack Web Frameworks — সব এক জায়গায়।