আমি ভাবতাম নিজের অথেন্টিকেশন লেয়ার তৈরি করাটা একটা সম্মানের বিষয়। আপনি যদি JWT এবং পাসওয়ার্ড হ্যাশ করা বোঝেন, তবে এটি করা কতটা কঠিন হতে পারে? আমি একটি Node ব্যাকএন্ড তৈরি করলাম, টোকেন ইস্যু করলাম এবং কাজ শেষ বলে ভাবলাম। কোডটি কাজ করছিল। টেস্টগুলো পাস করছিল। তারপর আমি পড়তে শুরু করলাম কীভাবে বাস্তবে আক্রমণগুলো ঘটে, এবং আমার সব ধারণা ভেঙে চুরমার হয়ে গেল। ইনজেকশন, এনামারেশন এবং সাইড-চ্যানেল লিক নিয়ে প্রতিটি অধ্যায় আমাকে হতাশ করে তুলল। আমার অথেন্টিকেশন সিস্টেমে শুধু ফাঁক ছিল না; এতে চারটি বড় দরজা ছিল যা আমি নিজেই লাগিয়েছিলাম। আমি যা খুঁজে পেয়েছি এবং ঠিক যা পরিবর্তন করেছি তা নিচে দেওয়া হলো।
স্ট্রিং কনক্যাটেনেশনের মাধ্যমে SQL ইনজেকশন
প্রথম বাগটি ছিল সবচেয়ে পুরনো কৌশল। আমি ইউজারের ইনপুট সরাসরি SQL স্ট্রিংয়ের মধ্যে বসিয়ে দিচ্ছিলাম। আমার লগইন রুটে, আমি রিকোয়েস্ট বডি থেকে ইমেলটি সংগ্রহ করতাম এবং সেটি একটি কুয়েরির সাথে এভাবে যুক্ত করতাম: SELECT * FROM users WHERE email = '${email}'। যেহেতু ফ্রন্টএন্ড আমার নিয়ন্ত্রণে ছিল, তাই এটি নিরীহ মনে হয়েছিল। এটি একটি বিপজ্জনক ধারণা। একজন আক্রমণকারীর আপনার ফ্রন্টএন্ডের প্রয়োজন নেই। একটি মাত্র সুনিপুণভাবে তৈরি করা POST বডি সেই লগইন চেকটিকে ডেটা ব্রিচ বা ডেটাবেস মুছে ফেলার ঘটনায় পরিণত করতে পারে। ' OR '1'='1 এর মতো একটি পেলোড অথবা আরও খারাপ, টেবিল ড্রপ করার মতো একটি স্ট্যাকড কুয়েরি পাঠিয়ে যদি স্ট্রিংটি সরাসরি এক্সিকিউট হয়, তবে আপনার ডেটা হারিয়ে যাবে। আমি ডেটাবেসকে আমার কোড এবং আক্রমণকারীর ডেটার মধ্যে পার্থক্য করার কোনো উপায় দিইনি।
সমাধানটি কেবল ইনপুট ভ্যালিডেশন বা হাতে স্ট্রিং এস্কেপ করা ছিল না। আসল সমাধান ছিল প্যারামিটারাইজড কুয়েরি (parameterized queries)। আমি node-postgres-এ সুইচ করলাম এবং $1-এর মতো প্লেসহোল্ডার ব্যবহার করা শুরু করলাম। কুয়েরিটি এখন একটি টেমপ্লেট হয়ে গেল: SELECT * FROM users WHERE email = $1। ড্রাইভার SQL এবং ভ্যালুগুলোকে আলাদা চ্যানেলের মাধ্যমে পাঠায়। ডেটাবেস ইনপুটটিকে কঠোরভাবে ডেটা হিসেবেই বিবেচনা করে, তাতে কী ধরনের ক্যারেক্টার থাকুক না কেন। এই একটি পরিবর্তন ইনজেকশন অ্যাটাকের পুরো শ্রেণীটিকেই বন্ধ করে দেয়। এটি পড়তে সহজ, রক্ষণাবেক্ষণ করা সহজ এবং প্রতিবার WHERE ক্লজ লেখার সময় রেজেক্স (regex) জাদুকর হওয়ার বোঝা থেকে মুক্তি দেয়।
এরর মেসেজের মাধ্যমে ইমেল এনামারেশন
আমার দ্বিতীয় ভুলটি দেখতে ভালো UX-এর মতো মনে হচ্ছিল। যখন একজন ব্যবহারকারী ভুল ইমেল টাইপ করতেন, আমি User not found রিটার্ন করতাম। যখন তারা সঠিক ইমেল কিন্তু ভুল পাসওয়ার্ড দিতেন, আমি Incorrect password রিটার্ন করতাম। এটি সাহায্যকারী মনে হয়েছিল। কিন্তু এটি আক্রমণকারীদের জন্য একটি রিকনাইসেন্স (reconnaissance) টুল হিসেবে কাজ করছিল। এনামারেশন স্ক্রিপ্ট হাজার হাজার ইমেল অ্যাড্রেস দিয়ে আপনার লগইন এন্ডপয়েন্টে হামলা চালাতে পারে। যদি অ্যাকাউন্টটি আছে কি নেই তার ওপর ভিত্তি করে রেসপন্স বডি বা স্ট্যাটাস কোড পরিবর্তিত হয়, তবে স্ক্রিপ্টটি আপনার ব্যবহারকারীদের একটি যাচাইকৃত তালিকা তৈরি করতে পারে। সেই তালিকাটি ক্রেডেনশিয়াল স্টাফিং, টার্গেটেড ফিশিং এবং আরও ব্রুট-ফোর্স প্রচেষ্টার ভিত্তি হয়ে দাঁড়ায়।
আমাকে মেনে নিতে হয়েছে যে ইউজার-ফ্রেন্ডলিনেসের চেয়ে নিরাপত্তাকে মাঝে মাঝে বেশি গুরুত্ব দিতে হয়। আমি প্রতিটি ব্যর্থ লগইন পাথকে ঠিক একই স্ট্রিং রিটার্ন করার জন্য পরিবর্তন করেছি: Invalid credentials। কোনো ইঙ্গিত নেই। এরর রেসপন্সে কোনো ব্রাঞ্চিং লজিক নেই। ইমেলটি নেই, পাসওয়ার্ড ভুল, নাকি অ্যাকাউন্টটি লক করা—টেক্সটটি সব ক্ষেত্রে একই থাকে। এটি রেজিস্ট্রেশন এবং পাসওয়ার্ড-রিসেট ফ্লোর ক্ষেত্রেও প্রযোজ্য; কোনো ইমেল আপনার সিস্টেমে আগে থেকেই আছে কি না তা প্রকাশ করবেন না। একটি সাধারণ মেসেজ সেই তথ্য ফাঁসের (information leak) পথ বন্ধ করে দেয় যা আক্রমণকারীরা ব্যবহার করে।
পাসওয়ার্ড তুলনার ক্ষেত্রে টাইমিং অ্যাটাক
তৃতীয় বাগটি ছিল অদৃশ্য। আমি...
