সরাসরি পর্বে যান
পর্ব ৪৯ · ৮০-এর মধ্যে · মডিউল ৭

নিরাপত্তা পরীক্ষা — SAST, DAST, fuzzing আর code review

Security Testing — SAST, DAST, Fuzzing & Code Review

Taught in Bangla, this lesson maps which test finds which flaw and where it fits: SAST and taint analysis, DAST on your own staging, IAST, RASP, SCA and secret scanning. It includes a mini coverage-guided fuzzer, sanitizers, property-based testing, turning a finding into a regression test, manual secure code review, triage, and measuring what your tools miss. All testing stays on your own code or with written permission.

এই পর্বে যা শিখবেন

  • কোন পরীক্ষা কোন ত্রুটি ধরে, আর software-এর জীবনের কোন ধাপে তার জায়গা
  • Pattern matching, data-flow আর taint analysis-এর পার্থক্য, আর একটি SAST rule পড়া
  • নিজের staging-এ অনুমতি নিয়ে DAST, আর IAST, RASP, SCA ও secret scanning-এর ভূমিকা
  • Coverage-guided fuzzing, harness, sanitizer, seed corpus আর property-based testing
  • একটি finding-কে regression test বানানো, misuse-case test আর হাতে secure code review
  • Triage, deduplication, false positive-এর হিসাব, CI-র জন্য tool বাছা আর tool-এর ফাঁক মাপা

১ · পরীক্ষার মানচিত্র — কে কী ধরে, কোথায় বসে

একটি নতুন ভবন হস্তান্তরের আগে কী হয় ভাবুন। প্রকৌশলী নকশা মিলিয়ে দেখেন। মিস্ত্রি পানির লাইনে চাপ দিয়ে দেখেন ফুটো আছে কি না। ফায়ার সার্ভিস আগুনের মহড়া দেয়। কেউ একজন একটি দরজা জোরে টেনে দেখেন তালা টেকে কি না। প্রত্যেকে আলাদা জিনিস ধরেন। কেউ একা সব ধরেন না।

Software-এর নিরাপত্তা পরীক্ষাও ঠিক তাই। SAST নকশা মেলানোর মতো, কোড না চালিয়েই পড়ে। DAST চলন্ত সিস্টেমে বাইরে থেকে চাপ দেয়। Fuzzing অদ্ভুত ইনপুটে প্রোগ্রাম ভাঙে কি না দেখে। Code review-এ অভিজ্ঞ মানুষ দেখেন যুক্তি ঠিক আছে কি না। এই পর্বের স্থির উদাহরণ একটি কাল্পনিক অনলাইন বাজারের নিজের কোড। পরীক্ষার সব ডেটা ও finding মনগড়া, আর পাতার কোনো কোড বাইরে অনুরোধ পাঠায় না।

সীমা স্পষ্ট: এই পর্বের সবকিছু আপনার নিজের কোড, অথবা যে সিস্টেমের মালিকের লিখিত অনুমতি আপনার কাছে আছে, তার জন্য। অন্যের সাইটে scanner বা fuzzer চালানো শেখার চেষ্টা নয়, আইনি ঝুঁকি। বিস্তারিত আছে পর্ব ৬-এ। এটি আইনি পরামর্শ নয়।

Software-এর পথে কোন পরীক্ষা কোথায়

Where each kind of testing fits, from the editor to production

নিরাপত্তা পরীক্ষার মানচিত্র কোড লেখা Editor-এ SAST, commit-এর আগে secret Pull request SAST, SCA, secret, মানুষের review Build আর test Regression, misuse-case, ছোট fuzz Staging DAST, IAST, লম্বা fuzz, হাতে পরীক্ষা Production Monitoring, RASP, অনুমোদিত pentest সব finding এক জায়গায় triage
ছবি ৪৯.১ — ওপরের ধাপে ভুল ধরা সাধারণত সস্তা, কিন্তু কিছু ভুল শুধু চলন্ত সিস্টেমেই দেখা যায়। তাই একাধিক স্তর লাগে।
পরীক্ষাকী দেখেভালো ধরেপ্রায়ই মিস করে
SASTSource codeInjection-এর ধাঁচ, বিপজ্জনক ফাংশনBusiness logic, config, চলার সময়ের আচরণ
DASTচলন্ত applicationHeader, config, দৃশ্যমান input-এর ত্রুটিযে পথে crawler পৌঁছায় না, কোডের ঠিক লাইন
SCADependency-র তালিকাজানা CVE-যুক্ত libraryআপনার নিজের ভুল, অজানা ত্রুটি
Secret scanningকোড আর historyKey আর token-এর আকৃতিঅচেনা আকৃতির secret, কোডের বাইরের ফাঁস
Fuzzingচলন্ত ফাংশনParser-এর crash, memory ভুলযুক্তির ভুল, যদি নিয়ম লেখা না থাকে
মানুষের reviewকোড আর উদ্দেশ্যAuthorization, ব্যবসায়িক যুক্তিবড় কোডের একঘেয়ে পুনরাবৃত্তি

একটি প্রশ্ন আগেই পরিষ্কার করি। এটি penetration testing নয়। এখানে আপনি নিজের দলের ভেতর থেকে, নিয়মিত, স্বয়ংক্রিয়ভাবে কোড যাচাই করছেন। Scope আর চুক্তি নিয়ে বাইরের চোখে পরীক্ষা আসবে পরের পর্ব ৫০-এ। আর পরীক্ষার পরিকল্পনা শুরু হয় threat model থেকে: কোন সম্পদ, কোন পথ, কোন ক্ষতি। তা না হলে tool যা পারে তাই পরীক্ষা হয়, যা দরকার তা নয়। পুরো secure SDLC-এর ছবি আছে পর্ব ৪৫-এ।

২ · SAST — কোড না চালিয়ে কোড পড়া

পরীক্ষার খাতা দেখার সময় অভিজ্ঞ শিক্ষক উত্তর মিলিয়ে দেখেন, হাতেকলমে কিছু করে দেখেন না। SAST-ও প্রোগ্রাম চালায় না, পড়ে। কিন্তু পড়ার গভীরতার তিনটি স্তর আছে:

Pattern matching

কোডের নির্দিষ্ট গঠন খোঁজে, যেমন eval(...) ডাকা হয়েছে কি না। ভালো tool লেখার বদলে AST দেখে, তাই comment-এ শব্দটি থাকলে ঠকে না।

দ্রুত, কিন্তু প্রসঙ্গ বোঝে কম

Data-flow analysis

Data-flow analysis হিসাব করে, একটি মান কোন পথে কোন ভ্যারিয়েবল হয়ে কোথায় যেতে পারে। ফাংশনের ভেতরে করা সহজ, ফাংশন পেরিয়ে করা কঠিন।

ধীর, কিন্তু প্রসঙ্গ বোঝে বেশি

Taint analysis

Taint analysis data-flow-এর নিরাপত্তা-রূপ। অবিশ্বস্ত ডেটায় রং মাখিয়ে দেখে, সেই রং বিপজ্জনক জায়গায় পৌঁছায় কি না।

Injection ধরার মূল পদ্ধতি

এলাকার পানির লাইনে একটু রং মিশিয়ে দেওয়ার কথা ভাবুন। রং কোন পাইপ দিয়ে কোন কলে আসে, তা দেখা যাচ্ছে। পাইপের ঢোকার মুখ হলো source, যেমন URL parameter. খাওয়ার কল হলো sink, যেমন SQL চালানোর ফাংশন। মাঝের ফিল্টার হলো sanitizer. তবে সাবধান: বালি আটকানোর ফিল্টার জীবাণু আটকায় না। HTML-এর জন্য বানানো encoding SQL-কে নিরাপদ করে না।

নিচের ডায়াগ্রামে একটি ছোট খেলনা ফাংশনে taint ধাপে ধাপে ছড়াচ্ছে। raw এসেছে ব্যবহারকারীর কাছ থেকে। ডান পাশের ঘরে দেখুন, প্রতিটি ভ্যারিয়েবল কোন প্রসঙ্গে এখনো বিপজ্জনক।

1raw  = readParam("id")2q1   = "...id=" + raw3runQuery(q1)4id   = toInt(raw)5q2   = "...id=" + id6runQuery(q2)7name = escapeHtml(raw)8render(name)9q3   = "...name=" + name10runQuery(q3)
Taint-এর হিসাব
raw
q1
id
name
q3
Finding
  1. Source. readParam ব্যবহারকারীর ডেটা আনে। তাই raw দুই প্রসঙ্গেই বিপজ্জনক: SQL আর HTML.
  2. Taint ছড়ায়। লেখা জোড়া দিলে রং থেকে যায়। q1 এখন raw-এর সব taint পেল।
  3. Sink-এ পৌঁছাল। runQuery একটি SQL sink, আর q1-এ sql taint আছে। প্রথম finding.
  4. পূর্ণ পরিষ্কার। toInt লেখাকে সংখ্যায় বদলায়, বা ব্যর্থ হয়। সংখ্যা query-র গঠন বদলাতে পারে না, তাই id দুই প্রসঙ্গেই পরিষ্কার।
  5. পরিষ্কার ডেটা জোড়া। q2-তে কোনো taint যায়নি। Finding সংখ্যা ১-ই থাকল।
  6. Sink, কিন্তু নিরাপদ। একই runQuery, এবার ঘটনা ঘটল না। বাস্তবে এখানেও parameterized query-ই ভালো পথ, এখানে শুধু ধারণা দেখানো হচ্ছে।
  7. আংশিক পরিষ্কার। escapeHtml শুধু HTML প্রসঙ্গ পরিষ্কার করে। name-এ sql taint রয়ে গেল।
  8. HTML sink, ঠিক sanitizer. render HTML sink, আর name-এ html taint নেই। এখানে সমস্যা নেই।
  9. ভুল ভরসা। “এটা তো escape করা” ভেবে name query-তে জোড়া হলো। q3 পেল sql taint.
  10. দ্বিতীয় finding. ভুল sanitizer-এর ডেটা SQL sink-এ পৌঁছাল। সাধারণ চোখে কোডটি “পরিষ্কার করা” মনে হয়, কিন্তু প্রসঙ্গ মেলেনি। মোট ২টি finding.

এবার একই হিসাব সত্যিকারের কোডে চালিয়ে দেখুন। প্রোগ্রামটি এখানে শুধু object-এর একটি তালিকা। কিছুই চালানো হয় না, কোনো database নেই। এটি খেলনা tracker, আসল SAST অনেক জটিল।

// Toy taint tracker. The "program" is a list of objects; nothing is executed.
const SOURCES = ['readParam'];
const CLEANS = { toInt: ['sql', 'html'], escapeHtml: ['html'] }; // what each cleans
const SINKS = { runQuery: 'sql', render: 'html' };
const prog = [
  { line: 1, to: 'raw', fn: 'readParam', args: [] },
  { line: 2, to: 'q1', fn: 'concat', args: ['raw'] },
  { line: 3, fn: 'runQuery', args: ['q1'] },
  { line: 4, to: 'id', fn: 'toInt', args: ['raw'] },
  { line: 5, to: 'q2', fn: 'concat', args: ['id'] },
  { line: 6, fn: 'runQuery', args: ['q2'] },
  { line: 7, to: 'name', fn: 'escapeHtml', args: ['raw'] },
  { line: 8, fn: 'render', args: ['name'] },
  { line: 9, to: 'q3', fn: 'concat', args: ['name'] },
  { line: 10, fn: 'runQuery', args: ['q3'] },
];
const taint = {};            // variable -> contexts where it is still dangerous
let findings = 0;
for (const s of prog) {
  const incoming = [...new Set(s.args.flatMap(a => taint[a] || []))];
  if (SOURCES.includes(s.fn)) taint[s.to] = ['sql', 'html'];
  else if (CLEANS[s.fn]) taint[s.to] = incoming.filter(c => !CLEANS[s.fn].includes(c));
  else if (SINKS[s.fn]) {
    const bad = incoming.includes(SINKS[s.fn]);
    if (bad) findings++;
    console.log(`line ${s.line}: ${s.fn}(${s.args}) ${bad ? 'FINDING' : 'ok'}`);
    continue;
  } else taint[s.to] = incoming;  // unknown function: taint passes through
  console.log(`line ${s.line}: ${s.to} = ${s.fn}(${s.args}) taint=[${taint[s.to]}]`);
}
console.log(`findings: ${findings}`);

আউটপুট:

line 1: raw = readParam() taint=[sql,html]
line 2: q1 = concat(raw) taint=[sql,html]
line 3: runQuery(q1) FINDING
line 4: id = toInt(raw) taint=[]
line 5: q2 = concat(id) taint=[]
line 6: runQuery(q2) ok
line 7: name = escapeHtml(raw) taint=[sql]
line 8: render(name) ok
line 9: q3 = concat(name) taint=[sql]
line 10: runQuery(q3) FINDING
findings: 2

লাইন ৩ সরাসরি ভুল, আর লাইন ১০ ভুল sanitizer-এর ফল। লক্ষ করুন, CLEANS বলে দেয় কোন ফাংশন কোন প্রসঙ্গ পরিষ্কার করে। আসল tool-এও এই তালিকা rule-এর ভেতরে থাকে। আপনার দলের নিজের safeId() ফাংশন tool চেনে না, তাই সেটি শেখাতে হয়। না শেখালে false positive আসে। আর এই tracker ধরে নেয় অচেনা ফাংশন taint পার করে দেয়, যা শেষের else-এ আছে। সিদ্ধান্তটি উল্টো হলে false negative বাড়ত।

আরও গভীরে: Rice-এর উপপাদ্য (১৯৫৩) অনুযায়ী প্রোগ্রামের কোনো অর্থগত ধর্ম সব প্রোগ্রামের জন্য নির্ভুলভাবে নির্ধারণ করা যায় না। তাই static analyzer-কে আপস করতেই হয়। যে analyzer কোনো সত্যিকারের ত্রুটি বাদ দেয় না তাকে বলে sound, কিন্তু তার false positive বেশি। বাস্তবের বেশিরভাগ security tool ইচ্ছা করে কিছুটা unsound, যাতে ডেভেলপার ফলগুলো পড়েন। Java-র reflection, dynamic dispatch আর framework-এর জাদু এই ফাঁকের বড় উৎস।

৩ · SAST বাস্তবে — rule, tuning আর false positive

বাজারে নানা tool আছে। উদাহরণ হিসেবে Semgrep কোডের মতো দেখতে pattern দিয়ে rule লিখতে দেয়। CodeQL কোডকে একধরনের database বানিয়ে query চালায়, তাই গভীর data-flow লেখা যায়। দুটির কোনোটিকেই আমরা সুপারিশ করছি না। লাইসেন্সের শর্ত যাচাই করুন: যেমন CodeQL-এর query MIT লাইসেন্সে হলেও CLI আলাদা শর্তে আসে, আর বন্ধ-উৎস কোড বিশ্লেষণে আলাদা বাণিজ্যিক লাইসেন্স লাগার কথা তাদের repository-তে লেখা আছে। শর্ত বদলাতে পারে।

নিচের rule আপনার নিজের Python কোডে eval-এর ব্যবহার ধরে, কিন্তু স্থির লেখায় eval বাদ দেয়। এই শেষের অংশটিই tuning.

rules:
  - id: no-eval-on-dynamic-text
    languages: [python]
    severity: HIGH
    message: eval() runs text as code. Use int(), json.loads() or a small parser.
    patterns:
      - pattern: eval(...)
      - pattern-not: eval("...")
    metadata:
      cwe: "CWE-95"
Rule যা ধরবেসমাধান
total = eval(form["amount"])total = int(form["amount"])

Rule-টি উদাহরণ, Semgrep-এর docs-এর বর্ণিত গঠন মেনে লেখা। আমরা এখানে YAML-এর গঠন যাচাই করেছি, কিন্তু tool চালাইনি। severity-র মান docs অনুযায়ী এখন LOW থেকে CRITICAL, আর পুরোনো ERROR, WARNING, INFO এখনো চলে। নিজের version-এর docs দেখে নিন। Taint-এর rule-ও লেখা যায়, সেখানে source, sink আর sanitizer আলাদা করে দিতে হয়।

False positive-এর দাম — একটি হিসাব

False positive মানে tool বলল ত্রুটি, আসলে নয়। False negative মানে ত্রুটি আছে, tool চুপ। দুটোরই দাম আছে। ধরুন একটি কাল্পনিক দলের tool প্রথম চালানোয় ২০০টি finding দিল। যাচাই করে দেখা গেল ৩০টি সত্য। প্রতিটি দেখতে সময় লাগে ৬ মিনিট। এরপর দল rule tune করল: finding নামল ৪০-এ, যার ২৫টি সত্য।

মাপআগেপরে
Precision৩০ ÷ ২০০ = ১৫%২৫ ÷ ৪০ = ৬২.৫%
দেখার মোট সময়২০০ × ৬ = ১,২০০ মিনিট, ২০ ঘণ্টা৪০ × ৬ = ২৪০ মিনিট, ৪ ঘণ্টা
প্রতি সত্য ত্রুটির সময়১,২০০ ÷ ৩০ = ৪০ মিনিট২৪০ ÷ ২৫ = ৯.৬ মিনিট
জানা ৩০টির মধ্যে ধরা৩০ ÷ ৩০ = ১০০%২৫ ÷ ৩০ ≈ ৮৩.৩%

সময় পাঁচ ভাগের এক ভাগ হলো, কিন্তু ৫টি সত্য ত্রুটি হারাল। এটিই tuning-এর আসল সিদ্ধান্ত। Recall আর precision-এর টানাপোড়েন IDS-এর পর্বে দেখেছেন, এখানেও একই হিসাব। তাই কোন rule নামালেন, কেন নামালেন, কে অনুমোদন করলেন, লিখে রাখুন। Tuning-এর ভালো উপায়গুলো:

  • Sanitizer শেখানো: দলের নিজের নিরাপদ ফাংশন rule-এ যোগ করা, যা false positive কমায় কিন্তু সত্য ত্রুটি হারায় না।
  • Path বাদ দেওয়া: তৈরি করা কোড আর test fixture সীমিতভাবে বাদ দেওয়া। সতর্ক থাকুন, test ফোল্ডারে আসল secret-ও থাকতে পারে।
  • কারণসহ চুপ করানো: লাইনে suppression দিলে পাশে কারণ, তারিখ আর ticket লেখা। কারণ ছাড়া suppression পরে কেউ বোঝে না।
  • শুধু নতুন finding-এ আটকানো: পুরোনো ঋণ আলাদা তালিকায় রেখে শুধু নতুন কোডের ত্রুটিতে build থামানো।

৪ · DAST, IAST আর RASP — চলন্ত সিস্টেমে পরীক্ষা

কোড দেখে সব বোঝা যায় না। Reverse proxy হয়তো একটি header মুছে দিচ্ছে, config হয়তো debug চালু রেখেছে। এগুলো দেখা যায় শুধু সিস্টেম চললে। DAST বাইরে থেকে application-কে ব্যবহারকারীর মতো অনুরোধ পাঠায়, আর উত্তরে ত্রুটির চিহ্ন খোঁজে। এটি কোডের ভেতর দেখে না, তাই একে black-box পরীক্ষা বলে। ZAP-এর মতো tool এর পরিচিত উদাহরণ। DAST ধ্বংসাত্মক হতে পারে: form-এ হাজার হাজার ভুয়া order, মুছে ফেলার বোতাম চাপা, ভুল ঠিকানায় email পাঠানো। তাই এর নিয়ম কড়া।

DAST শুধু নিজের staging environment-এ, মালিকের অনুমতিতে: প্রতিষ্ঠানে কাজ করলেও সিস্টেমের মালিকের লিখিত সম্মতি নিন। Production ডেটা নয়, নকল ডেটা ব্যবহার করুন। Hosting বা cloud-এর নীতিও দেখুন। Target-এর তালিকা config-এ allowlist করে রাখুন, যাতে tool বাইরের কোনো ঠিকানায় না যায়।

নিরাপদ DAST চালানোর চেকলিস্ট:

IAST

IAST-এ পরীক্ষার সময় application-এর ভেতরে একটি agent বসে। আপনার test বা DAST চলার সময় agent ডেটার আসল পথ দেখে, আর কোডের লাইন বলে দিতে পারে। সীমা: শুধু যে পথ পরীক্ষায় চলল, সেটুকুই দেখে। ভাষা ও runtime-ও সমর্থিত হতে হয়।

Test চলাকালে, ভেতর থেকে

RASP

RASP production-এ application-এর ভেতরে বসে সন্দেহজনক কাজ জানায় বা আটকায়। এটি পরীক্ষা নয়, চলমান সুরক্ষা। সীমা: performance-এর খরচ, ভুল block-এর ঝুঁকি, আর কোড না সারিয়ে বসে থাকার প্রলোভন।

Production-এ, ভেতর থেকে

৫ · SCA আর secret scanning — দুটি সহজ জয়

আপনার application-এর বড় অংশই হয়তো অন্যের লেখা library. SCA সেই তালিকা ধরে জানা দুর্বলতার সাথে মেলায়। বিস্তারিত দেখেছেন পর্ব ৪৮-এ। এখানে শুধু তিনটি কথা:

  • Lockfile থেকে পড়ুন: Manifest-এ লেখা সরাসরি dependency নয়, আসল ইনস্টল হওয়া transitive dependency-সহ তালিকা দেখুন।
  • সব alert সমান নয়: পর্ব ৩২-এর CVSS, EPSS আর KEV দিয়ে সাজান। Reachability দেখায় ত্রুটিপূর্ণ ফাংশন আদৌ ডাকা হয় কি না, তবে “reachable নয়” বিচারটিও ভুল হতে পারে।
  • এটি অজানা ত্রুটি ধরে না: CVE না থাকলে SCA চুপ। আর ভুয়া নামের package বা supply chain কারসাজির জন্য আলাদা পাহারা লাগে।

Secret scanning কোড আর git history-তে key, token, password-এর আকৃতি খোঁজে। পদ্ধতি দুটি: পরিচিত prefix-এর regex, আর এলোমেলো দেখতে লেখার entropy মাপা। Entropy-ভিত্তিক খোঁজে false positive বেশি, কারণ hash আর ছবির base64-ও এলোমেলো দেখায়। Gitleaks বা TruffleHog-এর মতো tool এর উদাহরণ। তিন জায়গায় পাহারা রাখুন: commit-এর আগে, CI-তে, আর পুরো history-তে একবার।

মূল ধারণা

Commit-এ ওঠা secret-কে ফাঁস ধরে নিন, শুধু মুছে দেওয়া যথেষ্ট নয়। আগে secret বদলান, তারপর ব্যবহার হয়েছে কি না log দেখুন, শেষে history পরিষ্কার করুন। এই ক্রম গুরুত্বপূর্ণ। ধাপগুলো আছে Git নিরাপত্তার পর্বে আর secret ব্যবস্থাপনার পর্বে।

৬ · Fuzzing — অদ্ভুত ইনপুটে নিজের কোড ঝাঁকানো

বাচ্চার হাতে নতুন খেলনা দিলে কী হয়? সে নিয়ম পড়ে না। উল্টো ধরে, আছাড় দেয়, পানিতে ফেলে। ডিজাইনার যা ভাবেননি, তাই ঘটে। Fuzzing আপনার কোডের সাথে এই কাজটিই করে, লাখো বার। লক্ষ্য একটি: এমন ইনপুট খোঁজা যাতে প্রোগ্রাম crash করে, ঝুলে যায় বা কোনো নিয়ম ভাঙে। Parser, file format, protocol আর decoder এর সেরা লক্ষ্য।

পুরোপুরি এলোমেলো ইনপুট সাধারণত প্রথম যাচাইতেই বাদ পড়ে, ভেতরের কোডে পৌঁছায় না। Coverage-guided fuzzing এটি সমাধান করে। Fuzzer দেখে কোন ইনপুট কোডের নতুন পথ খুলল। সেই ইনপুট রেখে দেয়, আর তাকেই বদলে পরের ইনপুট বানায়। এক ধাপ এক ধাপ করে ভেতরে ঢোকে। মোট চারটি অংশ লাগে:

অংশকাজভুল করলে
HarnessFuzzer-এর ইনপুট নিয়ে আপনার ফাংশন ডাকেGlobal state থাকলে crash পুনরায় ঘটানো কঠিন হয়
Seed corpusশুরুর বৈধ নমুনা ইনপুটনমুনা না থাকলে গভীর পথে পৌঁছাতে বহু সময় লাগে
Sanitizerনীরব memory ভুলকে দৃশ্যমান crash বানায়না থাকলে সীমার বাইরে পড়া চুপচাপ পার হতে পারে
Oracleফল ঠিক না ভুল তা বলেশুধু crash দেখলে যুক্তির ভুল ধরা পড়ে না

এই sanitizer taint-এর sanitizer নয়, শব্দ এক কিন্তু কাজ আলাদা। ASan, UBSan, TSan আর MSan হলো compiler-এর দেওয়া পাহারা, যা যথাক্রমে memory, undefined behavior, thread আর অনির্ধারিত মানের ভুল ধরে। C-এর এই ভুলগুলোর ধারণা আছে C কোর্সের এই পর্বে, আর দেখেছেন পর্ব ৪৭-এ।

নিচের কোডটি যা দেখাবে, প্রথমে তা ধাপে ধাপে দেখুন। লক্ষ্য একটি খেলনা parser. একটি record-এ থাকে TV, তারপর kind, length, payload আর একটি flag. kind 2-এর শাখায় length যাচাই করতে ভুলে যাওয়া হয়েছে।

Fuzzer-এর অবস্থা (আসল চালানোর সংখ্যা)
Exec
ইনপুট
Coverage
Corpus
ফল
  1. Seed. একটি বৈধ kind 1 record দিয়ে শুরু। এতে kind1 আর flag1 পথ দেখা হলো। Corpus-এ ১টি ইনপুট।
  2. প্রথম বদল। তৃতীয় byte বদলে হলো 03. Parser বলল অচেনা kind, পরিষ্কারভাবে Reject. কিন্তু এটি নতুন পথ, তাই ইনপুটটি রাখা হলো। Corpus ২।
  3. বেশিরভাগ চেষ্টা বৃথা। কয়েকটি ইনপুট নতুন কোনো পথ খুলল না। Fuzzer সেগুলো ফেলে দিল। সাধারণ ঘটনা।
  4. দরজা খুলল। তৃতীয় byte হলো 02. এবার kind 2-এর শাখা আর flag2 দেখা হলো। Crash হয়নি, তবু এই ইনপুটটিই আসল সম্পদ। Corpus ৩।
  5. আবার বদল। Fuzzer corpus থেকে এলোমেলো ইনপুট নিয়ে এক byte বদলাতে থাকে। নতুন কিছু নেই।
  6. Crash. kind 2-এর ইনপুটের length byte হলো ff, মানে 255. Parser সেই সংখ্যাটি বিশ্বাস করল।
  7. কারণ। end = 4 + 255 = 259, কিন্তু record-এ আছে ৭ byte. d[259]-তে IndexError. Python-এ এটি পরিষ্কার error, কিন্তু C-তে এই একই ভুল নীরবে পাশের memory পড়তে পারত। ঠিক সেখানেই ASan লাগে।

এবার পুরো পরীক্ষাটি চালান। Fuzzer আর parser একই ব্লকে আছে। Fixed seed থাকায় প্রতিবার একই ফল আসবে। পরে মেরামত করে একই fuzzer আবার চলে।

# Mini coverage-guided fuzzer against a deliberately buggy TOY parser.
import random
class Reject(Exception): pass  # clean rejection; anything else is a crash

def parse(d, cov, fixed):            # record: T V kind length payload flag
    if d[:2] != b"TV" or len(d) < 4:
        raise Reject
    kind, length = d[2], d[3]
    cov.add(f"kind{kind}" if kind in (1, 2) else "bad-kind")
    end = 4 + length
    if kind not in (1, 2) or ((kind == 1 or fixed) and end >= len(d)):
        raise Reject                 # kind 2 forgot this length check
    flag = d[end]                    # BUG: kind 2 can read past the end
    cov.add(f"flag{kind}")
    return d[4:end], flag

def fuzz(fixed, execs=3000):
    rng = random.Random(49)          # fixed seed: same result every run
    corpus, seen = [b"TV\x01\x02ab!"], set()
    parse(corpus[0], seen, fixed)
    for i in range(1, execs + 1):
        m = rng.choice(corpus); pos = rng.randrange(len(m))
        m = m[:pos] + bytes([rng.choice([0, 1, 2, 3, 255])]) + m[pos + 1:]
        cov = set()
        try: parse(m, cov, fixed)
        except Reject: pass
        except IndexError:
            return print(f"exec {i}: CRASH input={m.hex(' ')}")
        if cov - seen:
            print(f"exec {i}: new {sorted(cov - seen)} input={m.hex(' ')}")
            seen |= cov; corpus.append(m)
    print(f"{execs} execs, no crash, corpus size {len(corpus)}")

fuzz(fixed=False); print("--- after the fix ---"); fuzz(fixed=True)

আউটপুট:

exec 1: new ['bad-kind'] input=54 56 03 02 61 62 21
exec 10: new ['flag2', 'kind2'] input=54 56 02 02 61 62 21
exec 13: CRASH input=54 56 02 ff 61 62 21
--- after the fix ---
exec 1: new ['bad-kind'] input=54 56 03 02 61 62 21
exec 10: new ['flag2', 'kind2'] input=54 56 02 02 61 62 21
3000 execs, no crash, corpus size 3

১৩ বার চালানোতেই crash, আর মেরামতের পরে ৩,০০০ বারে কিছু নেই। সংখ্যা এত ছোট, কারণ parser মাত্র কয়েক লাইনের আর আমরা 0, 1, 2, 3, 255-এর মতো “আকর্ষণীয়” মান বেছে দিয়েছি। আসল fuzzer-ও এমন সীমার মান পছন্দ করে। খেয়াল করুন fixed=True-তে শুধু length যাচাই যোগ হয়েছে। এটি বাকি পথগুলো বদলায়নি, তাই প্রথম দুটি লাইন হুবহু মিলেছে। ৩,০০০ বার নিরাপদ চলা মানে ত্রুটিমুক্ত প্রমাণ নয়, শুধু এই পরীক্ষায় কিছু মেলেনি। এখানে coverage হাতে লেখা লেবেল, আসল tool compiler দিয়ে কোডের প্রতিটি শাখায় গণনা বসায়।

বাস্তবে C কোডের জন্য harness এত ছোট হতে পারে। নিচের উদাহরণ libFuzzer-এর, Clang-এর সাথে আসে। AFL++ আর Honggfuzz-ও এমন engine. Python-এ Atheris, Java-তে Jazzer-এর মতো tool আছে। এটি শুধু আপনার নিজের parser-এর জন্য।

#include <stddef.h>
#include <stdint.h>

int parse_record(const uint8_t *data, size_t size);  /* your own code */

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    parse_record(data, size);
    return 0;
}

/* Build: clang -g -O1 -fsanitize=fuzzer,address fuzz_parser.c parser.c -o fuzz_parser
   Run on your own machine: ./fuzz_parser corpus/ */

Property-based testing — নিয়ম লিখুন, ইনপুট tool বানাবে

Fuzzing crash খোঁজে। Property-based testing খোঁজে নিয়মের ভঙ্গ। আপনি উদাহরণ লেখেন না, এমন একটি কথা লেখেন যা সব ইনপুটে সত্য হওয়ার কথা। যেমন: encode করে decode করলে আসল ডেটাই ফেরে। Tool নানা ইনপুট বানায়, আর ব্যর্থ ইনপুটকে ছোট করে আপনাকে দেখায়। Haskell-এর QuickCheck (২০০০) এই ধারার সূচনা, Python-এ Hypothesis পরিচিত।

from hypothesis import given, strategies as st
from myparser import encode, decode   # your own code

@given(st.binary(max_size=255))
def test_round_trip(payload):
    assert decode(encode(payload)) == payload

OSS-Fuzz: OSS-Fuzz Google-এর চালানো একটি বিনামূল্যের সেবা, যা open source project-এ অবিরাম fuzzing চালায় এবং maintainer-কে গোপনে ত্রুটি জানায়। প্রকল্পের নিজের পাতা অনুযায়ী, আগস্ট ২০২৩ পর্যন্ত এটি প্রায় ১,০০০ project-এ ১০,০০০-এর বেশি নিরাপত্তা ত্রুটি আর ৩৬,০০০-এর বেশি bug খুঁজে সারাতে সাহায্য করেছে। সংখ্যাগুলো তাদের প্রকাশিত, আর সময়ের সাথে বদলায়।

৭ · Bug থেকে স্থায়ী পাহারা — regression আর misuse-case test

সড়ক দুর্ঘটনার পরে শুধু গাড়ি মেরামত করলে একই মোড়ে আবার দুর্ঘটনা হবে। ভালো কর্তৃপক্ষ সেখানে স্পিড ব্রেকার বসায়, যা স্থায়ীভাবে থাকে। একটি সারানো bug-কে regression test বানানো ঠিক সেই স্পিড ব্রেকার। নিরাপত্তা bug প্রায়ই ফিরে আসে, যখন কেউ কোড নতুন করে লেখেন বা পুরোনো branch মেলান।

  1. Findingcrash input
  2. পুনরায় ঘটানোনিশ্চিত করা
  3. লাল testআগে ব্যর্থ
  4. Fixমূল কারণ
  5. সবুজ testCI-তে স্থায়ী
Ticket #142Fuzzer যা দিয়েছে
54 56 02 ff 61 62 21
IndexError in parse()
নিজের পরিবেশেএকই ইনপুট, একই ফল
parse(bytes.fromhex("54 56 02 ff 61 62 21"))
IndexError  (পুনরায় ঘটানো গেছে)
পুরোনো কোডে testএখন ব্যর্থ হওয়াই ঠিক
with self.assertRaises(Reject):
    parse(crash_input)
old code: problems 1
Fixপড়ার আগে যাচাই
if 4 + length >= len(d):
    raise Reject("length beyond record")
return d[4:4 + length], d[4 + length]
CIপ্রতিটি পরিবর্তনে চলে
fixed code: problems 0
crash input-টি fuzz corpus-এও যোগ হলো
  1. Finding. Fuzzer একটি ইনপুট দিল, ticket খোলা হলো। আসল fuzzer এই ইনপুটকে ছোট করতেও পারে (minimize), যাতে কারণ বোঝা সহজ হয়।
  2. পুনরায় ঘটানো। প্রথম কাজ নিজের পরিবেশে একই ফল দেখা। ঘটাতে না পারলে সারানো হয়েছে কি না, তাও জানা যাবে না।
  3. আগে লাল test. Fix-এর আগেই test লিখুন, আর দেখুন সেটি ব্যর্থ হচ্ছে। যে test পুরোনো কোডে পাস করে, সে ওই bug-কে পাহারা দেয় না।
  4. মূল কারণের fix. শুধু এই ইনপুট আটকানো নয়, length-এর যাচাই যোগ। ইনপুটের নির্দিষ্ট মান কালো তালিকায় রাখলে অন্য মানে আবার ভাঙবে।
  5. সবুজ ও স্থায়ী। Test পাস করল, CI-তে যুক্ত হলো। Crash ইনপুটটি seed corpus-এও গেল, যাতে fuzzer এখান থেকেই পরের পথ খোঁজে।

নিচের কোড একই test-কে পুরোনো আর মেরামত করা দুই কোডেই চালায়। এখানে unittest ব্যবহার হয়েছে, যা Python-এর সাথেই আসে (পুরো ধারণা আছে Python-এর testing পর্বে)।

# A past bug becomes a permanent test. Runs against old and fixed code.
import unittest

class Reject(Exception):
    pass

def parse_old(d):                    # kind-2 record, before the fix
    length = d[3]
    return d[4:4 + length], d[4 + length]

def parse_fixed(d):
    length = d[3]
    if 4 + length >= len(d):
        raise Reject("length beyond record")
    return d[4:4 + length], d[4 + length]

parse = None                         # chosen below

class Bug142Regression(unittest.TestCase):
    def test_length_past_end_is_rejected(self):
        with self.assertRaises(Reject):
            parse(bytes.fromhex("54 56 02 ff 61 62 21"))

    def test_valid_record_still_parses(self):
        self.assertEqual(parse(b"TV\x02\x02ab!"), (b"ab", 33))

    def test_last_valid_length(self):    # boundary: flag is the last byte
        self.assertEqual(parse(b"TV\x02\x03ab!?"), (b"ab!", 63))

for name, fn in [("old code", parse_old), ("fixed code", parse_fixed)]:
    parse = fn
    result = unittest.TestResult()
    unittest.defaultTestLoader.loadTestsFromTestCase(Bug142Regression).run(result)
    bad = [t.id().split(".")[-1] for t, _ in result.errors + result.failures]
    print(f"{name}: ran {result.testsRun}, problems {len(bad)} {bad}")

আউটপুট:

old code: ran 3, problems 1 ['test_length_past_end_is_rejected']
fixed code: ran 3, problems 0 []

পুরোনো কোডে ঠিক bug-এর test-টিই ব্যর্থ, বাকি দুটি পাস। অর্থাৎ test-টি সত্যিই এই ত্রুটি ধরে। মেরামতের পরে তিনটিই পাস। বাকি দুটি test-ও জরুরি: test_valid_record_still_parses নিশ্চিত করে যে fix বৈধ ডেটা ভাঙেনি, আর test_last_valid_length ঠিক সীমায় (>= বনাম >) পরীক্ষা করে। এখানে 33 আর 63 হলো ! আর ?-এর byte মান।

Misuse-case test — “কী করা উচিত নয়” পরীক্ষা করা

সাধারণ test বলে, ক্রেতা নিজের order দেখতে পান। Misuse-case test বলে, ক্রেতা অন্যের order দেখতে পান না। এই “না” গুলো সাধারণত কেউ লেখেন না, অথচ broken access control-এর ত্রুটি ঠিক এখানেই থাকে, আর SAST বা DAST এগুলো প্রায়ই ধরতে পারে না, কারণ tool জানে না কে কার order দেখার অধিকারী। Threat model-এর প্রতিটি ঝুঁকি থেকে একটি করে test বানান।

FeatureMisuse caseTest-এ যা প্রত্যাশিত
Order দেখাক্রেতা ক অন্য ক্রেতা খ-এর order চাইলেন403 বা 404, ডেটা নেই, ঘটনা log-এ
Refundএকই refund দুবার পাঠানো হলোদ্বিতীয়বার টাকা যায় না
কুপনঋণাত্মক পরিমাণ পাঠানো হলোমোট দাম কমে না, ইনপুট বাতিল
Password resetআগে ব্যবহৃত token আবার দেওয়া হলোToken অচল

৮ · হাতে secure code review আর পরীক্ষার গাইড

Tool “কোন ফাংশন বিপজ্জনক” বলতে পারে। “এই ক্রেতার কি এই ছাড় পাওয়ার কথা?” বলতে পারে না। সেই প্রশ্ন মানুষের। সাধারণ review-এর নিয়ম Code review পর্বে আছে। Secure review আলাদা, কারণ এতে আপনি কোডটি ওপর থেকে নিচে পড়েন না। আপনি ডেটার পিছু নেন।

মহল্লার পাহারাদার পুরো পাড়ার প্রতিটি ইট গোনেন না। তিনি দেখেন কোন কোন গেট দিয়ে লোক ঢোকে, আর দামি জিনিস কোথায় আছে। তারপর গেট থেকে সিন্দুক পর্যন্ত পথটি হেঁটে দেখেন। Code review-ও তাই: source থেকে sink পর্যন্ত হাঁটা।

একটি কার্যকর ক্রম:

  1. উদ্দেশ্য বুঝুন: এই পরিবর্তন কী করে, কোন ডেটা ছোঁয়, threat model কী বলে।
  2. প্রবেশপথের তালিকা করুন: Route, API, file upload, queue, webhook, cron job.
  3. Sink খুঁজুন: SQL, shell, HTML, file path, redirect, deserialize, URL fetch.
  4. দুটি জোড়া দিন: প্রতিটি sink থেকে পেছনে হেঁটে দেখুন, ডেটা কোথা থেকে আসে, পথে কী পরিষ্কার হয়।
  5. Trust boundary দেখুন: কোথায় identity আর অনুমতি যাচাই হয়, আর সেটি সব পথে আছে কি না।
  6. অনুপস্থিত জিনিস খুঁজুন: Diff-এ যা নেই, সেটিই প্রায়ই ত্রুটি: নতুন route-এ authorization না থাকা।
বিষয়যে প্রশ্ন করবেন
Authorizationপ্রতিটি পথে server-এ object-স্তরে অনুমতি যাচাই হয় কি?
ইনপুট আর আউটপুটInput validation server-এ? Output encoding প্রসঙ্গ মেনে? Query parameterized?
Authentication আর sessionSession rotate হয়? Token-এর মেয়াদ আছে?
Crypto আর secretনিজে বানানো crypto নেই তো? Secret কোডে নেই তো?
Error আর logError-এ বিবরণ ফাঁস হয় না? Log-এ password বা token যায় না?
ব্যবসায়িক যুক্তিধাপ ডিঙানো, দুবার পাঠানো, ঋণাত্মক সংখ্যা সামলানো হয়েছে?
ConcurrencyRace condition আছে কি? যাচাই আর ব্যবহারের মাঝে ডেটা বদলাতে পারে?

গাইড দুটি — WSTG আর ASVS

নিজে নিজে সব মনে রাখতে হবে না। OWASP-এর দুটি নথি কাজে লাগে, আর দুটির কাজ আলাদা:

WSTG — কীভাবে পরীক্ষা করবেন

Web Security Testing Guide পরীক্ষার পদ্ধতি বলে। প্রতিটি পরীক্ষার একটি পরিচয় আছে, যেমন WSTG-INPV-05 (SQL injection পরীক্ষা)। প্রকল্পের পাতা অনুযায়ী সর্বশেষ stable version 4.2, আর 5.0 তৈরি হচ্ছে।

পদ্ধতির বই

ASVS — কী যাচাই করবেন

ASVS যাচাইযোগ্য requirement-এর তালিকা, তিনটি level-এ। সর্বশেষ stable version 5.0.0 (মে ২০২৫)। পর্ব ৩৭-এ এর পরিচয় আছে। Requirement নম্বর version-ভেদে বদলায়, তাই উদ্ধৃতিতে version লিখুন।

মানদণ্ডের তালিকা

একসাথে ব্যবহার সহজ: ASVS থেকে আপনার application-এর level-এর requirement নিন, আর প্রতিটির জন্য WSTG থেকে পদ্ধতি ও নিজের misuse-case test বানান। আপনি যেদিন পড়ছেন সেদিনের version প্রকল্পের পাতায় দেখে নিন।

৯ · ফল সামলানো — triage, CI আর যা ধরা পড়ে না তা মাপা

তিনটি tool একই ত্রুটি তিনভাবে জানাতে পারে। লাইন নম্বর আলাদা, নাম আলাদা। হাসপাতালের জরুরি বিভাগে যেমন রোগী বাছাই হয়, finding triage-ও তাই: সত্য কি না, কতটা গুরুতর, কে সারাবেন, কবে। প্রথম কাজ deduplication. SARIF-এর মতো সাধারণ format নানা tool-এর ফল এক জায়গায় আনা সহজ করে।

একটি হিসাবে সতর্ক থাকুন। Tool-এর জগতে “false positive rate ৬০%” বলতে মানুষ প্রায়ই বোঝান, ফলের ৬০% ভুল। এটি আসলে FP ÷ (FP + TP). গবেষণা আর এই কোর্সের সংজ্ঞায় false positive rate হলো FP ÷ (FP + TN), অর্থাৎ নিরাপদ জিনিসের কত অংশে ভুল alert হলো। আলাদা দুটি সংখ্যা। Tool-এর দাবি পড়লে জিজ্ঞেস করুন, হরে কী আছে।

// Toy triage. Fake findings from three imaginary tools on your own code.
const raw = [
  ['sast-a', 'CWE-89', 'order.js', 'getOrder', 'real'],
  ['sast-b', 'CWE-89', 'order.js', 'getOrder', 'real'],
  ['dast-c', 'CWE-89', 'order.js', 'getOrder', 'real'],
  ['sast-a', 'CWE-79', 'view.js', 'showName', 'real'],
  ['dast-c', 'CWE-79', 'view.js', 'showName', 'real'],
  ['sast-a', 'CWE-22', 'file.js', 'download', 'real'],
  ['sast-a', 'CWE-89', 'report.js', 'daily', 'false'],
  ['sast-b', 'CWE-89', 'report.js', 'daily', 'false'],
  ['sast-a', 'CWE-79', 'admin.js', 'banner', 'false'],
  ['sast-b', 'CWE-79', 'admin.js', 'banner', 'false'],
  ['sast-b', 'CWE-327', 'hash.js', 'cacheKey', 'false'],
  ['sast-a', 'CWE-798', 'test/fixture.js', 'setup', 'false'],
];
const seen = new Map();
for (const [tool, cwe, file, fn, verdict] of raw) {
  const key = `${cwe}|${file}|${fn}`;     // line numbers left out: they move
  if (!seen.has(key)) seen.set(key, { tools: new Set(), verdict });
  seen.get(key).tools.add(tool);
}
const unique = [...seen.values()];
const tp = unique.filter(f => f.verdict === 'real').length;
const fp = unique.length - tp;
const safeCases = 40, seededBugs = 5;     // from a labelled test set of your own
const pct = x => (100 * x).toFixed(1) + '%';
console.log(`raw ${raw.length} -> unique ${unique.length}`);
for (const [k, f] of seen) console.log(`  ${k}  tools=${f.tools.size}  ${f.verdict}`);
console.log(`TP ${tp}, FP ${fp}, precision ${pct(tp / (tp + fp))}`);
console.log(`share of findings that were false: ${pct(fp / (tp + fp))}`);
console.log(`FP rate FP/(FP+TN): ${pct(fp / safeCases)} of ${safeCases} safe cases`);
console.log(`recall on seeded bugs: ${pct(tp / seededBugs)}`);
console.log(`Benchmark-style score TPR-FPR: ${pct(tp / seededBugs - fp / safeCases)}`);

আউটপুট:

raw 12 -> unique 7
  CWE-89|order.js|getOrder  tools=3  real
  CWE-79|view.js|showName  tools=2  real
  CWE-22|file.js|download  tools=1  real
  CWE-89|report.js|daily  tools=2  false
  CWE-79|admin.js|banner  tools=2  false
  CWE-327|hash.js|cacheKey  tools=1  false
  CWE-798|test/fixture.js|setup  tools=1  false
TP 3, FP 4, precision 42.9%
share of findings that were false: 57.1%
FP rate FP/(FP+TN): 10.0% of 40 safe cases
recall on seeded bugs: 60.0%
Benchmark-style score TPR-FPR: 50.0%

১২টি raw finding আসলে ৭টি আলাদা সমস্যা। Key-তে লাইন নম্বর রাখিনি, কারণ কোড সরলে লাইন বদলায়। একই ডেটা থেকে দুটি ভিন্ন “ভুলের হার”: ফলের ৫৭.১% ভুল, কিন্তু নিরাপদ ৪০টি জিনিসের মাত্র ১০% (৪ ÷ ৪০) ভুল alert পেয়েছে। ৫টি রেখে দেওয়া ত্রুটির ৩টি ধরা পড়ল, তাই recall ৬০%. একাধিক tool একমত হলেই finding সত্য হয় না: report.js-এর finding-টি দুই tool দিলেও ভুল। সবগুলো সংখ্যা খেলনা তালিকার, কোনো আসল tool-এর ফল নয়।

CI-র জন্য ছোট একটি tool set

দশটি tool বসিয়ে কেউ ফল না পড়লে লাভ নেই। প্রতিটি শ্রেণি থেকে একটি দিয়ে শুরু করুন, আর আগে ঠিক করুন কে দেখবেন। Pipeline-এর গঠন আর gate-এর নকশা আছে DevSecOps পর্বে।

কখনকী চলবেকী করবে
প্রতিটি pull requestSAST, SCA, secret scanning, unit আর regression testনতুন উচ্চ-আস্থার finding-এ আটকাবে
প্রতি রাতেStaging-এ DAST, লম্বা fuzz, পুরো SASTTicket খুলবে, build আটকাবে না
প্রতি ত্রৈমাসিকেহাতে review, rule-এর পর্যালোচনা, seeded bug পরীক্ষাফাঁক আর tuning যাচাই

Tool কী বাদ দিচ্ছে, তা মাপা

“শূন্য finding” দুই রকম হতে পারে: কোড ভালো, অথবা tool অন্ধ। পার্থক্য জানার উপায় আছে:

  • Seeded bug: Test branch-এ জানা ত্রুটি রেখে দেখুন tool ধরে কি না। শুধু নিজের test কোডে, production-এ কখনো নয়।
  • পুরোনো ঘটনা ফিরে দেখা: আগে যে bug production-এ গিয়েছিল, tool কি সেটি তখনকার কোডে ধরত?
  • জানা উত্তরের সেট: OWASP Benchmark-এর মতো সেটে TPR − FPR মাপা হয়। সব কিছুতে “ত্রুটি” বলা tool-এর স্কোর ০। তবে এই সেট ছোট ও কৃত্রিম কেস দিয়ে বানানো, তাই আপনার কোডে ফল আলাদা হতে পারে।
  • Coverage দেখা: DAST কত route ছুঁল? Fuzz কত শাখায় গেল? না ছোঁয়া অংশ নিয়ে tool কিছুই বলতে পারে না।
  • দুই tool-এর মিল: নিচের হিসাবটি দেখুন।
হিসাব · অদেখা ত্রুটির আন্দাজ

বনের বাঘ গোনার capture-recapture পদ্ধতি এখানেও আন্দাজ দেয়। ধরুন tool A ৮টি সত্য ত্রুটি পেল, tool B পেল ৬টি, আর দুটির মিল ৪টি। আনুমানিক মোট = ৮ × ৬ ÷ ৪ = ১২টি। ধরা পড়েছে ৮ + ৬ − ৪ = ১০টি। তাই প্রায় ২টি কেউ দেখেনি। শর্ত আছে: হিসাবটি ধরে নেয় দুটি পদ্ধতি স্বাধীন। দুটি একই ধরনের tool হলে তারা একই ভুল করে, মিল বাড়ে, আর আন্দাজ বাস্তবের চেয়ে কম আসে। তাই এটি মোটা আন্দাজ, প্রমাণ নয়।

পরীক্ষা ও ইন্টারভিউতে:

  • SAST আর DAST-এর পার্থক্য? SAST কোড না চালিয়ে পড়ে, DAST চলন্ত সিস্টেম বাইরে থেকে পরীক্ষা করে।
  • Source, sink, sanitizer কী? অবিশ্বস্ত ডেটার প্রবেশ, বিপজ্জনক ব্যবহার, আর মাঝের প্রসঙ্গ-নির্দিষ্ট পরিষ্কারকারী।
  • IAST আর RASP-এর পার্থক্য? IAST পরীক্ষার সময় দেখে জানায়, RASP production-এ চলে জানায় বা আটকায়।
  • Coverage-guided fuzzing কেন এলোমেলোর চেয়ে ভালো? নতুন পথ খোলা ইনপুট রেখে তাকেই বদলায়, তাই গভীরে পৌঁছায়।
  • Regression test পুরোনো কোডে পাস করলে? তাহলে সেটি ওই bug-কে ধরে না, test ভুল।

১০ · AI-এর যুগে নিরাপত্তা পরীক্ষা

AI কোড পড়তে পারে, test লিখতে পারে, finding বোঝাতে পারে। সে একজন দ্রুত সহকারী। কিন্তু নিরাপত্তায় তার ভুলের দাম বেশি, কারণ ভুল উত্তরও আত্মবিশ্বাসী শোনায়। সাধারণ ভুলগুলো:

AI-এর ভুলকেন বিপজ্জনকযাচাই
বানানো CVE বা standard-এর নম্বরভুল অগ্রাধিকার, ভুল রিপোর্টNVD আর মূল standard-এ মেলান
Triage-এ অতিরিক্ত আস্থাসত্য ত্রুটি “false positive” বলে বাদSource থেকে sink পর্যন্ত নিজে হাঁটুন
Test পাস করাতে test-ই বদলে দেওয়াপাহারা উঠে গেলDiff পড়ুন, পুরোনো কোডে test লাল কি না দেখুন
Error সারাতে নিরাপত্তা বন্ধ করা (verify=False, chmod 777, খোলা CORS)নতুন ত্রুটি ঢোকেমূল কারণ সারান, কোনো check বন্ধ নয়
পুরোনো crypto পরামর্শ (MD5, ECB, salt ছাড়া hash)দুর্বল নকশাপর্ব ১৪ মিলিয়ে দেখুন
Authorization ছাড়া বা injection-সহ কোডAI-এর লেখা কোডেরও একই পরীক্ষা লাগেএকই SAST, test আর review চালান

কোড পেস্ট করার আগে secret আর ব্যক্তিগত ডেটা সরান। আর AI-কে বলুন প্রমাণের পথ দেখাতে, শুধু রায় নয়। নিচের prompt রক্ষকের কাজের জন্য:

আমি নিজের প্রজেক্টের কোড review করছি। নিচের ফাংশনে কোনো secret বা ব্যক্তিগত ডেটা নেই। [এখানে নিজের কোড পেস্ট করুন, secret সরিয়ে] ১. এই ফাংশনের source আর sink-এর তালিকা করো। ২. প্রতিটি sink-এর জন্য দেখাও ডেটা কোন লাইন হয়ে আসে, আর পথে কোন sanitizer আছে। ৩. প্রতিটি সন্দেহের আস্থা বলো: নিশ্চিত, সম্ভবত, না জানি। অনুমান করলে অনুমান বলো। ৪. কোনো CVE বা standard নম্বর দিলে তা যাচাইয়ের সূত্র দাও, নিশ্চিত না হলে নম্বর দিও না। ৫. প্রতিটি ত্রুটির জন্য একটি regression test লেখো, যা পুরোনো কোডে ব্যর্থ হবে। এই test যেন শুধু আমার local কোডে চলে, বাইরের কোনো সিস্টেমে না। আমাকে হামলার ধাপ দিও না, প্রতিরোধ আর যাচাইয়ে থাকো।

AI-এর ফল হাতে পেলে — চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।

প্র ০১SAST tool-এর false positive কি শুধু দুর্বল প্রকৌশলের ফল, নাকি এটি গণিতের সীমা?

দুটোই, তবে মূলটি গণিত। ১৯৫৩ সালে Henry Gordon Rice দেখান, প্রোগ্রামের আচরণ সম্পর্কে যেকোনো অ-তুচ্ছ প্রশ্ন সব প্রোগ্রামের জন্য নির্ভুলভাবে উত্তর দেওয়ার কোনো সাধারণ পদ্ধতি নেই। “এই ডেটা কি কখনো ওই SQL-এ পৌঁছাবে?” তেমনই একটি প্রশ্ন। তাই static analyzer-কে তিনটির একটি ছাড়তে হয়: সত্য ত্রুটি না হারানো, ভুল alarm না দেওয়া, অথবা শেষ হওয়া।

গবেষণায় যে analyzer কোনো ত্রুটি হারায় না তাকে sound বলা হয়। এমন tool সন্দেহ হলেই জানায়, তাই false positive ভরে যায়। আর ডেভেলপার তখন ফল পড়া বন্ধ করে দেন। বাস্তবের অভিজ্ঞতা এখানে স্পষ্ট। Coverity-র দল ২০১০ সালে Communications of the ACM-এ লিখেছিল, বাণিজ্যিক জগতে কোনো tool-এর ফল বেশি ভুল হলে মানুষ পুরো tool-টিকেই অবিশ্বাস করেন। Google-এর গবেষকেরা ২০১৮ সালের একটি প্রবন্ধে effective false positive-এর কথা বলেন: যে ফল দেখে ডেভেলপার কিছু করেন না, তা কারিগরিভাবে ঠিক হলেও কার্যত ভুল। Facebook-এর Infer দলের ২০১৯ সালের অভিজ্ঞতায়, রাতে একসাথে তালিকা পাঠানোর চেয়ে code review-এর সময় ফল দেখালে সারানোর হার অনেক বেড়েছিল। অর্থাৎ শুধু ফলের মান নয়, ফল কখন পৌঁছায় সেটিও গুরুত্বপূর্ণ।

তাহলে দলের শিক্ষা কী? False positive শূন্য করা লক্ষ্য নয়, সেটি অসম্ভব। লক্ষ্য হলো ফল এমন মানে আনা, যাতে মানুষ পড়েন আর কাজ করেন। আর যা tune করলেন তার দাম জানা, কারণ প্রতিটি নীরবতা কিছু সত্য ত্রুটিকেও চুপ করাতে পারে।

মূল উপলব্ধি: নিখুঁত static analysis গাণিতিকভাবে অসম্ভব। ভালো tool মানে ভালো আপস, আর সেই আপস আপনার দলের মনোযোগের সীমা মেনে হতে হবে।

প্র ০২Heartbleed দুই বছরের বেশি OpenSSL-এ ছিল। পরে দেখা গেল fuzzer আর sanitizer দিয়ে এটি ধরা যেত। তাহলে আগে ধরা পড়েনি কেন?

২০১৪ সালে প্রকাশিত Heartbleed ছিল OpenSSL-এর heartbeat অংশের একটি ত্রুটি: কম ডেটা পাঠিয়ে বেশি দৈর্ঘ্য ঘোষণা করলে server যাচাই ছাড়াই পাশের memory ফেরত দিত। প্রকাশিত তথ্য অনুযায়ী Google-এর একজন গবেষক আর Codenomicon-এর প্রকৌশলীরা প্রায় একই সময়ে স্বাধীনভাবে এটি পান। ত্রুটিটি ঢুকেছিল ২০১১-১২ সময়ের কোডে।

২০১৫ সালে নিরাপত্তা গবেষক Hanno Böck একটি পরীক্ষা প্রকাশ করেন। OpenSSL-এর পুরোনো version-এ AFL আর AddressSanitizer চালিয়ে তিনি মাত্র প্রায় ছয় ঘণ্টায় এই ত্রুটিতে পৌঁছান। কৌশলটি ছিল একটি ছোট harness, যেখানে দুটি OpenSSL instance network ছাড়া buffer অদলবদল করে handshake করে। লক্ষ করুন, এই ফলের জন্য তিনটি জিনিস একসাথে লেগেছিল: coverage-guided fuzzer, sanitizer আর ভালো harness. এর যেকোনো একটি না থাকলে এত সহজে ধরা পড়ত না। ASan ছাড়া সীমার বাইরে পড়া crash করে না, তাই fuzzer চুপচাপ পার হয়ে যেত।

২০১৪ সালের আগে এই তিনটি একসাথে সাধারণ ছিল না। AFL এসেছে ২০১৩ সালে, ASan ২০১২ সালের কাছাকাছি। আর ইন্টারনেটের মূল ভিতের এই library চলত খুব অল্প লোকের শ্রমে। Heartbleed-এর পরে শিল্পজগৎ বুঝল, গুরুত্বপূর্ণ open source-এ অবিরাম পরীক্ষা দরকার। OSS-Fuzz ২০১৬ সালে শুরু হওয়ার পেছনে এই চিন্তাই ছিল। তবে সীমাও মনে রাখুন: ত্রুটি খুঁজে পরে “ধরা যেত” বলা, আগে না জেনে খোঁজার চেয়ে অনেক সহজ।

মূল উপলব্ধি: নীরব ভুলকে দৃশ্যমান করার পাহারা (sanitizer) আর অক্লান্ত ইনপুট (fuzzer) একসাথে অনেক শক্তিশালী। গুরুত্বপূর্ণ parser-এর জন্য এটি শৌখিন কাজ নয়, মৌলিক দায়িত্ব।

প্র ০৩একটি ঝড়ের রাতের modem-এর গোলমাল থেকে fuzzing-এর জন্ম। এত সরল ধারণা ত্রিশ বছরের বেশি পরেও কেন bug খুঁজে পায়?

গল্পটি Barton Miller-এর নিজের বর্ণনায় আছে। ১৯৮৮ সালের দিকে ঝড়ের রাতে তিনি dial-up লাইনে University of Wisconsin-এর কম্পিউটারে কাজ করছিলেন। লাইনের বৈদ্যুতিক গোলমালে অক্ষর বদলে যাচ্ছিল, আর সাধারণ UNIX প্রোগ্রাম সেই আবর্জনা পেয়ে crash করছিল। তিনি ভাবলেন, এত নির্ভরযোগ্য বলে পরিচিত প্রোগ্রাম কেন এভাবে ভাঙে? ক্লাসের একটি project-এ বিষয়টি পরীক্ষা হলো। ১৯৯০ সালে Miller, Fredriksen আর So-এর প্রবন্ধে দেখা যায়, এলোমেলো ইনপুটে UNIX-এর utility-গুলোর উল্লেখযোগ্য অংশ, প্রায় এক-চতুর্থাংশ থেকে এক-তৃতীয়াংশ, crash করে বা আটকে যায়।

১৯৯৫ সালের পুনর্মূল্যায়নে বাণিজ্যিক UNIX-এ ব্যর্থতার হার ছিল ১৫ থেকে ৪৩%, আর GNU আর Linux-এর utility-তে কম। সবচেয়ে বিস্ময়কর ফল আসে ২০২০ সালের দিকে: একই দলের নতুন পরীক্ষায় Linux-এর ৭৪টি utility-র ৯টি, অর্থাৎ ১২%, তখনও ব্যর্থ। প্রায় ত্রিশ বছর পরেও সেই পুরোনো ধরনের ভুল ছিল।

কেন? কারণ মানুষ test লেখেন নিজের ধারণা থেকে। ডেভেলপার যে ইনপুট কল্পনা করেননি, তার জন্য কোডও লেখেননি, test-ও লেখেননি। Fuzzer-এর কোনো ধারণা নেই, তাই সে ঠিক সেই জায়গায় যায়। এর পরের বড় লাফ ছিল feedback. ২০১৩ সালে Michał Zalewski-র AFL দেখাল, সরল coverage-এর হিসাব আর genetic-ধাঁচের বাছাই মিলে কোনো গভীর নকশা ছাড়াই জটিল format-এর ভেতরে ঢোকা যায়। তবে fuzzing-এরও সীমা আছে: বিচার করার oracle না থাকলে এটি শুধু crash চেনে, যুক্তির ভুল চেনে না।

মূল উপলব্ধি: মানুষের test মানুষের কল্পনায় সীমিত। Fuzzing কল্পনার বাইরের দিকে তাকায়। তাই বড় test suite থাকার পরেও এটি নতুন ত্রুটি পায়।

প্র ০৪Scanner-এর পরিষ্কার রিপোর্ট দেখে কি নিশ্চিন্ত হওয়া যায়? Equifax-এর ঘটনা এখানে কী শেখায়?

না, আর এর শিক্ষণীয় উদাহরণ Equifax (২০১৭)। প্রকাশিত তথ্য অনুযায়ী, Apache Struts-এর একটি গুরুতর ত্রুটির কথা জানার পরে প্রতিষ্ঠানটি নিজের সিস্টেমে scan চালিয়েছিল। তখনকার প্রধান নির্বাহীর মার্কিন কংগ্রেসে লিখিত সাক্ষ্যে স্বীকার করা হয়, scan ত্রুটিটি চিহ্নিত করতে পারেনি। পরে মার্কিন GAO-র ২০১৮ সালের প্রতিবেদনেও patch, scan আর পর্যবেক্ষণের নানা দুর্বলতা আলোচিত হয়। একটি scan-এর নীরবতাকে সবকিছু ঠিক থাকার প্রমাণ ধরা ছিল প্রতিরক্ষার একটি বড় ফাঁক। তদন্তে আরও নানা ব্যর্থতা এসেছে, তাই একটি কারণে পুরো ঘটনা ব্যাখ্যা করা ঠিক নয়।

এখান থেকে রক্ষকের সাধারণ শিক্ষা হলো: “tool কিছু পায়নি” আর “কিছু নেই” এক কথা নয়। Scanner চুপ থাকতে পারে কারণ সে ঠিক জায়গায় পৌঁছায়নি, login-এর পেছনে যেতে পারেনি, ভুল path দেখেছে, version চিনতে পারেনি, অথবা তার rule-এ ত্রুটিটি নেই। Tool নিজে থেকে বলে না সে কী দেখেনি। তাই প্রতিটি পরিষ্কার রিপোর্টের সাথে দেখতে হয়, কী কী পরীক্ষা হয়েছে।

ব্যবহারিক উত্তর তিনটি। এক, সম্পদের তালিকার সাথে scan-এর আওতা মেলান: কতটি সিস্টেমের কতটিতে scan সফল হয়েছে? দুই, seeded bug বা জানা ত্রুটি রেখে দেখুন tool ধরে কি না। তিন, tool-এর ওপর একটি মাত্র নিয়ন্ত্রণ দাঁড় করাবেন না। SBOM থেকে dependency-র তালিকা থাকলে নির্দিষ্ট library কোথায় আছে সরাসরি খোঁজা যায়। Patch-এর দায়িত্ব কার, শেষ হয়েছে কি না, সেটিও আলাদা করে যাচাই হওয়া দরকার।

মূল উপলব্ধি: একটি পরীক্ষার ফল শুধু তার আওতার সমান জোরালো। রিপোর্ট পড়ার আগে জিজ্ঞেস করুন: এটি কী দেখেছে, আর কী দেখেনি?

প্র ০৫“যথেষ্ট চোখ থাকলে সব bug ধরা পড়ে” — এই কথা কি নিরাপত্তায় সত্য? মানুষের review-এর আসল জোর কোথায়?

Eric Raymond তাঁর ১৯৯৯ সালের বই The Cathedral and the Bazaar-এ কথাটি জনপ্রিয় করেন, “Linus's Law” নামে। নিরাপত্তার ইতিহাস এতে কিছু সংশোধন এনেছে। Heartbleed সবার পড়ার মতো খোলা কোডে থাকলেও দুই বছরের বেশি টিকে ছিল। সমস্যাটি হলো, চোখ থাকা মানে সেই চোখ ওই লাইনে পড়া নয়, আর পড়লেও নিরাপত্তার মন দিয়ে পড়া নয়।

২০২৪ সালের xz ঘটনা আরও গভীর। প্রকাশিত বিশ্লেষণ অনুযায়ী, দীর্ঘ সময় ধরে বিশ্বাস অর্জন করা একজন অবদানকারীর পরিচয়ে backdoor ঢোকানো হয়েছিল। ক্ষতিকর অংশ লুকানো ছিল test ফাইল আর release tarball-এর build script-এ, যা repository-র সাধারণ কোড পড়ে ধরা কঠিন ছিল। একজন প্রকৌশলী এটি ধরেন SSH-এর অস্বাভাবিক ধীরগতি দেখে, code review-তে নয়। অর্থাৎ review-এর আওতার বাইরে থাকা জিনিস review ধরবে না।

তবু মানুষের review-এর জোর বাস্তব। ১৯৭৬ সালে IBM-এর Michael Fagan আনুষ্ঠানিক code inspection-এর পদ্ধতি প্রকাশ করেন, যেখানে নির্দিষ্ট চেকলিস্ট ধরে পড়া হতো। পরের গবেষণাও দেখিয়েছে, ছোট পরিবর্তন আর নির্দিষ্ট প্রশ্ন নিয়ে পড়লে ফল ভালো হয়। মানুষ এমন জিনিস ধরেন যা tool জানে না: “এই route-এ তো কোনো অনুমতি যাচাই নেই”, “এই ছাড় দুবার নেওয়া যাবে”। অর্থাৎ অনুপস্থিতি আর উদ্দেশ্য।

তাই সংখ্যা নয়, গঠন দরকার। ছোট diff, নিরাপত্তা-চেকলিস্ট, গুরুত্বপূর্ণ অংশের জন্য নির্দিষ্ট মালিক, আর build ও release-কেও review-এর আওতায় আনা।

মূল উপলব্ধি: চোখের সংখ্যা নয়, মনোযোগের গঠনই নিরাপত্তা দেয়। মানুষের review tool-এর বদলি নয়, এটি সেই ধরনের ত্রুটি ধরে যা tool-এর ভাষায় লেখাই যায় না।

অনুশীলন

খাতা-কলমে করুন। সবকিছু কাল্পনিক ডেটা আর নিজের কোডের জন্য।

  1. হিসাব করুন: Tool A দিল ৪০টি finding, যার ১২টি সত্য। Tool B দিল ২০টি, যার ৯টি সত্য। দুটির সত্য ত্রুটির মিল ৬টি। (ক) প্রতিটি tool-এর precision কত? (খ) মোট আলাদা সত্য ত্রুটি কয়টি ধরা পড়েছে? (গ) Capture-recapture-এ আনুমানিক মোট ত্রুটি কত, আর অদেখা কত?

    (ক) A: ১২ ÷ ৪০ = ৩০%. B: ৯ ÷ ২০ = ৪৫%.

    (খ) ১২ + ৯ − ৬ = ১৫টি।

    (গ) আনুমানিক মোট = ১২ × ৯ ÷ ৬ = ১৮টি। অদেখা ১৮ − ১৫ = ৩টি। দুটি tool একই পদ্ধতির হলে তারা একই ফাঁকে পড়ে, তাই আসল সংখ্যা এর চেয়ে বেশি হতে পারে।

  2. ফল আগে বলুন: Taint tracker-এর নিয়ম: readParam source, যা sql আর html দুটিই দেয়। escapeHtml শুধু html মোছে। toInt দুটিই মোছে। concat সব আর্গুমেন্টের taint মেলায়। render html sink, runQuery sql sink. এই প্রোগ্রামে কয়টি finding হবে, কোন লাইনে?
    1  raw = readParam("q")
    2  s   = escapeHtml(raw)
    3  render(s)
    4  n   = toInt(raw)
    5  q   = concat(s, n)
    6  runQuery(q)

    Tracker-এ চালিয়ে পাওয়া ফল:

    line 1: raw = readParam() taint=[sql,html]
    line 2: s = escapeHtml(raw) taint=[sql]
    line 3: render(s) ok
    line 4: n = toInt(raw) taint=[]
    line 5: q = concat(s,n) taint=[sql]
    line 6: runQuery(q) FINDING
    findings: 1

    ১টি finding, লাইন ৬-এ। n পরিষ্কার হলেও s-এর sql taint concat-এ চলে আসে। মিশ্রণে একটি অংশও অবিশ্বস্ত থাকলে পুরো ফল অবিশ্বস্ত। এর ঠিক সমাধান হলো parameterized query.

  3. নকশা করুন: ৫ জন ডেভেলপারের একটি কাল্পনিক অনলাইন বাজার। আছে Python API আর JavaScript frontend. Pull request-এর CI-তে নিরাপত্তার জন্য সর্বোচ্চ ৫ মিনিট দেওয়া যাবে, আর নিরাপত্তার আলাদা কোনো কর্মী নেই। কোন পরীক্ষা কোথায় রাখবেন, কী build আটকাবে, আর কে ফল দেখবেন?

    একটি যুক্তিসঙ্গত নকশা:

    • Pull request: শুধু পরিবর্তিত ফাইলে SAST, secret scanning, lockfile থেকে SCA, আর regression test. শুধু নতুন, উচ্চ-আস্থার finding আর যাচাইকৃত secret-এ build আটকাবে।
    • রাতে: নিজের staging-এ DAST, নকল ডেটায়, ticket খুলবে। Parser থাকলে ছোট fuzz.
    • মালিকানা: প্রতি সপ্তাহে একজন ডেভেলপার পালা করে triage করবেন। পুরোনো finding আলাদা তালিকায় থাকবে।
    • প্রতি ত্রৈমাসিকে: Authorization-এর misuse-case test আর seeded bug পরীক্ষা, কারণ tool এগুলো ধরে না।

    মূল বিচার: কম tool, কিন্তু প্রতিটির ফল কেউ পড়ছেন।

  4. সমালোচনা করুন: একজন সহকর্মী লিখলেন: “DAST ০টি high finding দিয়েছে। Coverage রিপোর্ট বলছে ৩৫% route ছোঁয়া হয়েছে। SAST-এর ৪০টি finding-এর ৩৯টি আমরা ‘false positive’ চিহ্নিত করেছি, কারণ AI তাই বলেছে। Regression test পুরোনো কোডেও পাস করছে। তাই release-এর জন্য আমরা নিরাপদ।” কোন চারটি ফাঁক দেখছেন?
    • Coverage ৩৫%: ৬৫% route ছোঁয়া হয়নি, তাই “০টি high” শুধু ৩৫%-এর কথা।
    • AI-এর রায়: ৩৯টি বাতিল মানুষে যাচাই করা হয়নি। অন্তত নমুনা নিয়ে source থেকে sink পর্যন্ত হাঁটা দরকার।
    • Regression test: পুরোনো কোডে পাস করা মানে test bug ধরছে না, এটি কোনো পাহারা নয়।
    • যুক্তিতে ভুল: “নিরাপদ” দাবিটি কোনো পরীক্ষা থেকে আসে না। Authorization আর ব্যবসায়িক যুক্তি পরীক্ষার উল্লেখই নেই। সঠিক বাক্য হবে: “এই আওতায় এই পরীক্ষায় এতটুকু পাওয়া গেছে।”

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি fuzzer-এর finding থেকে regression test-এর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Fuzzer-এর crash ইনপুট নিয়ে ticket খোলা
  2. নিজের পরিবেশে crash পুনরায় ঘটানো
  3. Regression test লিখে পুরোনো কোডে ব্যর্থ হতে দেখা
  4. মূল কারণ সারানো
  5. Test পাস দেখে CI আর seed corpus-এ যোগ করা

একটি SAST tool জানাল: ব্যবহারকারীর ইনপুট escapeHtml() হয়ে SQL query-তে জোড়া হচ্ছে। আপনার সিদ্ধান্ত কী হওয়া উচিত?

  • False positive, কারণ ইনপুট তো escape করা
  • ফাংশনের নাম বদলে দেওয়া, যাতে rule আর না মেলে
  • সত্য সমস্যা, কারণ sanitizer-টি SQL প্রসঙ্গের নয়; parameterized query ব্যবহার করা
  • escapeHtml দুবার চালানো

DAST সম্পর্কে কোন কথাটি সঠিক?

  • এটি source code পড়ে ত্রুটি খোঁজে
  • এটি চলন্ত application পরীক্ষা করে, এবং নিজের staging-এ মালিকের অনুমতিতে চালানো উচিত
  • যেকোনো public সাইটে চালানো যায়, কারণ সাইটটি সবার জন্য খোলা
  • এটি সব route আর সব business logic-এর ত্রুটি ধরে

C-তে লেখা একটি parser-এ fuzzing চালানোর সময় AddressSanitizer কেন যোগ করা হয়?

  • এটি নতুন ইনপুট তৈরি করে
  • এটি fuzzing-এর গতি বাড়ায়
  • এটি ব্যবহারকারীর ইনপুট পরিষ্কার করে
  • এটি নীরব memory ভুলকে দৃশ্যমান ত্রুটিতে বদলায়, যাতে ধরা পড়ে

একটি security bug-এর জন্য লেখা নতুন regression test পুরোনো, ত্রুটিপূর্ণ কোডেও পাস করল। এর অর্থ কী?

  • Test-টি এই bug ধরছে না, তাই এটিকে ঠিক করতে হবে
  • Bug আসলে ছিল না
  • Test ঠিক আছে, আর কিছু করার দরকার নেই
  • CI নষ্ট হয়ে গেছে

একটি tool ২৫০টি finding দিল, যার ৫০টি সত্য। লেবেল করা পরীক্ষায় নিরাপদ ৮০০টি জিনিসের ২০০টিতে ভুল alert এসেছে। Precision আর false positive rate (FP ÷ (FP + TN)) কত?

  • Precision ৮০%, FPR ২৫%
  • Precision ২০%, FPR ৮০%
  • Precision ২০%, FPR ২৫%
  • Precision ২৫%, FPR ২০%

“ক্রেতা অন্য ক্রেতার order দেখতে পারবেন না” — এটি কোন পদ্ধতিতে সবচেয়ে নির্ভরযোগ্যভাবে যাচাই করা যায়?

  • SCA চালানো
  • দুটি test account দিয়ে misuse-case test লেখা, সাথে code review
  • Secret scanning চালানো
  • শুধু fuzzing চালানো

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. একাধিক স্তর লাগে: SAST, DAST, SCA, secret scanning, fuzzing আর মানুষ আলাদা আলাদা ত্রুটি ধরে।
  2. Taint: Source থেকে sink, আর sanitizer প্রসঙ্গ-নির্দিষ্ট।
  3. Fuzzing: Coverage feedback, harness, seed corpus আর sanitizer একসাথে কাজ করে।
  4. Regression test আগে পুরোনো কোডে ব্যর্থ হতে হবে, আর misuse-case test “কী হওয়া উচিত নয়” পাহারা দেয়।
  5. ফল triage করুন, ফাঁক মাপুন। শূন্য finding মানে নিরাপদ নয়, আর পরীক্ষা শুধু নিজের সিস্টেমে বা লিখিত অনুমতিতে।

পরের পর্বে: নিজের দলের পরীক্ষার পরে বাইরের চোখ। পর্ব ৫০-এ দেখব অনুমোদিত penetration testing: scope, rules of engagement, রিপোর্ট আর bug bounty.

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান