OWASP বিভাগ: MASVS-CODE: কোডের গুণমান
সংক্ষিপ্ত বিবরণ
এসকিউএল ইনজেকশন দুর্বল অ্যাপ্লিকেশনগুলোকে কাজে লাগায়। এটি এসকিউএল স্টেটমেন্টে কোড ঢুকিয়ে ইচ্ছাকৃতভাবে উন্মুক্ত করা ইন্টারফেসের বাইরে গিয়ে অন্তর্নিহিত ডেটাবেস অ্যাক্সেস করে। এই আক্রমণের ফলে ব্যক্তিগত তথ্য ফাঁস হতে পারে, ডেটাবেসের বিষয়বস্তু নষ্ট হতে পারে এবং এমনকি ব্যাকএন্ড অবকাঠামোও ঝুঁকির মুখে পড়তে পারে।
ব্যবহারকারীর ইনপুট যুক্ত করে এক্সিকিউশনের আগে ডাইনামিকভাবে তৈরি করা কোয়েরির মাধ্যমে SQL ইনজেকশনের ঝুঁকিতে পড়তে পারে। ওয়েব, মোবাইল এবং যেকোনো SQL ডাটাবেস অ্যাপ্লিকেশনকে লক্ষ্য করে করা SQL ইনজেকশন সাধারণত OWASP-এর শীর্ষ দশটি ওয়েব দুর্বলতার তালিকায় থাকে। আক্রমণকারীরা বেশ কয়েকটি বহুল আলোচিত ডেটা লঙ্ঘনের ঘটনায় এই কৌশলটি ব্যবহার করেছে।
এই সাধারণ উদাহরণে, একজন ব্যবহারকারীর অর্ডার নম্বর বক্সে দেওয়া অপরিবর্তিত ইনপুটকে SQL স্ট্রিং-এ প্রবেশ করানো যেতে পারে এবং নিম্নলিখিত কোয়েরি হিসাবে ব্যাখ্যা করা যেতে পারে:
SELECT * FROM users WHERE email = 'example@example.com' AND order_number = '251542'' LIMIT 1
এই ধরনের কোড একটি ওয়েব কনসোলে ডাটাবেস সিনট্যাক্স ত্রুটি তৈরি করবে, যা থেকে বোঝা যায় অ্যাপ্লিকেশনটি SQL ইনজেকশনের ঝুঁকিতে থাকতে পারে। অর্ডার নম্বরের পরিবর্তে 'OR 1=1– ব্যবহার করলে প্রমাণীকরণ সম্পন্ন হয়, কারণ ডাটাবেস স্টেটমেন্টটিকে True হিসেবে মূল্যায়ন করে, যেহেতু এক সর্বদা একের সমান।
একইভাবে, এই কোয়েরিটি একটি টেবিলের সমস্ত সারি ফেরত দেয়:
SELECT * FROM purchases WHERE email='admin@app.com' OR 1=1;
বিষয়বস্তু প্রদানকারী
কন্টেন্ট প্রোভাইডার একটি সুসংগঠিত স্টোরেজ ব্যবস্থা প্রদান করে, যা কোনো একটি অ্যাপ্লিকেশনের মধ্যে সীমাবদ্ধ রাখা যায় অথবা অন্যান্য অ্যাপের সাথে শেয়ার করার জন্য এক্সপোর্ট করা যায়। সর্বনিম্ন বিশেষাধিকারের নীতির উপর ভিত্তি করে অনুমতি নির্ধারণ করা উচিত; একটি এক্সপোর্ট করা ContentProvider পড়া এবং লেখার জন্য একটিমাত্র নির্দিষ্ট অনুমতি থাকতে পারে।
এটা উল্লেখ্য যে, সব SQL ইনজেকশনই এক্সপ্লয়টেশনের দিকে নিয়ে যায় না। কিছু কন্টেন্ট প্রোভাইডার ইতিমধ্যেই পাঠকদের SQLite ডেটাবেসে সম্পূর্ণ অ্যাক্সেস দিয়ে থাকে; যথেচ্ছ কোয়েরি চালানোর ক্ষমতা খুব একটা সুবিধা দেয় না। যে প্যাটার্নগুলো নিরাপত্তা ঝুঁকি তৈরি করতে পারে, সেগুলো হলো:
- একাধিক কন্টেন্ট প্রোভাইডার একটিমাত্র SQLite ডাটাবেস ফাইল শেয়ার করছে।
- এক্ষেত্রে, প্রতিটি টেবিল একটি স্বতন্ত্র কন্টেন্ট প্রোভাইডারের জন্য উদ্দিষ্ট হতে পারে। একটি কন্টেন্ট প্রোভাইডারে সফল SQL ইনজেকশন ঘটলে অন্য যেকোনো টেবিলে অ্যাক্সেস পাওয়া যাবে।
- একজন কন্টেন্ট প্রোভাইডারের একই ডেটাবেসের মধ্যে থাকা কন্টেন্টের জন্য একাধিক অনুমতি থাকে।
- একটিমাত্র কন্টেন্ট প্রোভাইডারে এসকিউএল ইনজেকশনের মাধ্যমে বিভিন্ন পারমিশন লেভেলে অ্যাক্সেস দেওয়া হলে, তা স্থানীয়ভাবে নিরাপত্তা বা গোপনীয়তা সেটিংস বাইপাস করার কারণ হতে পারে।
প্রভাব
এসকিউএল ইনজেকশন সংবেদনশীল ব্যবহারকারী বা অ্যাপ্লিকেশন ডেটা প্রকাশ করতে পারে, প্রমাণীকরণ এবং অনুমোদন সংক্রান্ত বিধিনিষেধ অতিক্রম করতে পারে এবং ডেটাবেসকে দুর্নীতি বা মুছে যাওয়ার ঝুঁকিতে ফেলতে পারে। এর ফলে যেসব ব্যবহারকারীর ব্যক্তিগত ডেটা ফাঁস হয়েছে, তাদের জন্য বিপজ্জনক এবং দীর্ঘস্থায়ী পরিণতি হতে পারে। অ্যাপ এবং পরিষেবা প্রদানকারীরা মেধা সম্পত্তি বা ব্যবহারকারীর আস্থা হারানোর ঝুঁকিতে থাকেন।
প্রশমন
প্রতিস্থাপনযোগ্য পরামিতি
সিলেকশন ক্লজে প্রতিস্থাপনযোগ্য প্যারামিটার হিসেবে ? এবং সিলেকশন আর্গুমেন্টের একটি পৃথক অ্যারে ব্যবহার করলে, ব্যবহারকারীর ইনপুটকে SQL স্টেটমেন্টের অংশ হিসেবে বিবেচনা না করে সরাসরি কোয়েরির সাথে যুক্ত করা হয়।
কোটলিন
// Constructs a selection clause with a replaceable parameter.
val selectionClause = "var = ?"
// Sets up an array of arguments.
val selectionArgs: Array<String> = arrayOf("")
// Adds values to the selection arguments array.
selectionArgs[0] = userInput
জাভা
// Constructs a selection clause with a replaceable parameter.
String selectionClause = "var = ?";
// Sets up an array of arguments.
String[] selectionArgs = {""};
// Adds values to the selection arguments array.
selectionArgs[0] = userInput;
ব্যবহারকারীর ইনপুটকে SQL হিসেবে বিবেচনা না করে সরাসরি কোয়েরির সাথে যুক্ত করা হয়, ফলে কোড ইনজেকশন প্রতিরোধ করা হয়।
এখানে একটি শপিং অ্যাপের কোয়েরির আরও বিশদ উদাহরণ দেওয়া হলো, যা পরিবর্তনযোগ্য প্যারামিটারসহ ক্রয়ের বিবরণ পুনরুদ্ধার করে:
কোটলিন
fun validateOrderDetails(email: String, orderNumber: String): Boolean {
val cursor = db.rawQuery(
"select * from purchases where EMAIL = ? and ORDER_NUMBER = ?",
arrayOf(email, orderNumber)
)
val bool = cursor?.moveToFirst() ?: false
cursor?.close()
return bool
}
জাভা
public boolean validateOrderDetails(String email, String orderNumber) {
boolean bool = false;
Cursor cursor = db.rawQuery(
"select * from purchases where EMAIL = ? and ORDER_NUMBER = ?",
new String[]{email, orderNumber});
if (cursor != null) {
if (cursor.moveToFirst()) {
bool = true;
}
cursor.close();
}
return bool;
}
PreparedStatement অবজেক্ট ব্যবহার করুন
PreparedStatement ইন্টারফেসটি SQL স্টেটমেন্টগুলোকে একটি অবজেক্ট হিসেবে প্রি-কম্পাইল করে, যা পরবর্তীতে একাধিকবার দক্ষতার সাথে এক্সিকিউট করা যায়। PreparedStatement প্যারামিটারের জন্য প্লেসহোল্ডার হিসেবে ? ব্যবহার করে, যা নিম্নলিখিত কম্পাইলড ইনজেকশনের প্রচেষ্টাকে অকার্যকর করে তুলবে:
WHERE id=295094 OR 1=1;
এই ক্ষেত্রে, 295094 OR 1=1 স্টেটমেন্টটিকে ID-এর ভ্যালু হিসেবে পড়া হয়, যার ফলে সম্ভবত কোনো ফলাফল পাওয়া যায় না, যেখানে একটি র' কোয়েরি OR 1=1 স্টেটমেন্টটিকে WHERE ক্লজের আরেকটি অংশ হিসেবে ব্যাখ্যা করবে। নিচের উদাহরণটি একটি প্যারামিটারাইজড কোয়েরি দেখাচ্ছে:
কোটলিন
val pstmt: PreparedStatement = con.prepareStatement(
"UPDATE EMPLOYEES SET ROLE = ? WHERE ID = ?").apply {
setString(1, "Barista")
setInt(2, 295094)
}
জাভা
PreparedStatement pstmt = con.prepareStatement(
"UPDATE EMPLOYEES SET ROLE = ? WHERE ID = ?");
pstmt.setString(1, "Barista")
pstmt.setInt(2, 295094)
কোয়েরি পদ্ধতি ব্যবহার করুন
এই দীর্ঘ উদাহরণটিতে, query() মেথডের selection এবং selectionArgs একত্রিত করে একটি WHERE ক্লজ তৈরি করা হয়েছে। যেহেতু আর্গুমেন্টগুলো আলাদাভাবে দেওয়া হয়েছে, তাই একত্রিত করার আগে সেগুলোকে এস্কেপ করা হয়, যা SQL ইনজেকশন প্রতিরোধ করে।
কোটলিন
val db: SQLiteDatabase = dbHelper.getReadableDatabase()
// Defines a projection that specifies which columns from the database
// should be selected.
val projection = arrayOf(
BaseColumns._ID,
FeedEntry.COLUMN_NAME_TITLE,
FeedEntry.COLUMN_NAME_SUBTITLE
)
// Filters results WHERE "title" = 'My Title'.
val selection: String = FeedEntry.COLUMN_NAME_TITLE.toString() + " = ?"
val selectionArgs = arrayOf("My Title")
// Specifies how to sort the results in the returned Cursor object.
val sortOrder: String = FeedEntry.COLUMN_NAME_SUBTITLE.toString() + " DESC"
val cursor = db.query(
FeedEntry.TABLE_NAME, // The table to query
projection, // The array of columns to return
// (pass null to get all)
selection, // The columns for the WHERE clause
selectionArgs, // The values for the WHERE clause
null, // Don't group the rows
null, // Don't filter by row groups
sortOrder // The sort order
).use {
// Perform operations on the query result here.
it.moveToFirst()
}
জাভা
SQLiteDatabase db = dbHelper.getReadableDatabase();
// Defines a projection that specifies which columns from the database
// should be selected.
String[] projection = {
BaseColumns._ID,
FeedEntry.COLUMN_NAME_TITLE,
FeedEntry.COLUMN_NAME_SUBTITLE
};
// Filters results WHERE "title" = 'My Title'.
String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
String[] selectionArgs = { "My Title" };
// Specifies how to sort the results in the returned Cursor object.
String sortOrder =
FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";
Cursor cursor = db.query(
FeedEntry.TABLE_NAME, // The table to query
projection, // The array of columns to return (pass null to get all)
selection, // The columns for the WHERE clause
selectionArgs, // The values for the WHERE clause
null, // don't group the rows
null, // don't filter by row groups
sortOrder // The sort order
);
সঠিকভাবে কনফিগার করা SQLiteQueryBuilder ব্যবহার করুন
ডেভেলপাররা SQLiteQueryBuilder ব্যবহার করে অ্যাপ্লিকেশনগুলোকে আরও সুরক্ষিত করতে পারেন, যা SQLiteDatabase অবজেক্টে পাঠানোর জন্য কোয়েরি তৈরি করতে সাহায্য করে। প্রস্তাবিত কনফিগারেশনগুলো হলো:
- কোয়েরি ভ্যালিডেশনের জন্য
setStrict()মোড। - setProjectionMap-এ কলামগুলো অনুমোদিত তালিকায় আছে কিনা তা যাচাই করতে
setStrictColumns()ব্যবহার করা হয়। - সাবকোয়েরি সীমিত করতে
setStrictGrammar()করুন।
রুম লাইব্রেরি ব্যবহার করুন
android.database.sqlite প্যাকেজটি অ্যান্ড্রয়েডে ডাটাবেস ব্যবহারের জন্য প্রয়োজনীয় এপিআই (API) সরবরাহ করে। তবে, এই পদ্ধতিতে লো-লেভেল কোড লিখতে হয় এবং এতে র SQL কোয়েরিগুলোর কম্পাইল-টাইম ভেরিফিকেশনের অভাব রয়েছে। ডেটা গ্রাফ পরিবর্তিত হলে, প্রভাবিত SQL কোয়েরিগুলো ম্যানুয়ালি আপডেট করতে হয় – যা একটি সময়সাপেক্ষ এবং ত্রুটিপ্রবণ প্রক্রিয়া।
একটি উচ্চ-স্তরের সমাধান হলো SQLite ডাটাবেসের জন্য একটি অ্যাবস্ট্রাকশন লেয়ার হিসেবে Room Persistence Library ব্যবহার করা। Room-এর বৈশিষ্ট্যগুলো হলো:
- একটি ডাটাবেস ক্লাস যা অ্যাপের সংরক্ষিত ডেটাতে সংযোগ করার প্রধান অ্যাক্সেস পয়েন্ট হিসেবে কাজ করে।
- ডেটা এনটিটি যা ডাটাবেসের টেবিলগুলোর প্রতিনিধিত্ব করে।
- ডেটা অ্যাক্সেস অবজেক্ট (DAO), যা অ্যাপকে ডেটা কোয়েরি, আপডেট, ইনসার্ট এবং ডিলিট করার জন্য বিভিন্ন মেথড প্রদান করে।
রুমের সুবিধাগুলোর মধ্যে রয়েছে:
- SQL কোয়েরিগুলোর কম্পাইল-টাইম যাচাইকরণ।
- ত্রুটিপ্রবণ গতানুগতিক কোডের হ্রাস।
- সুবিন্যস্ত ডাটাবেস মাইগ্রেশন।
সর্বোত্তম অনুশীলন
SQL ইনজেকশন একটি শক্তিশালী আক্রমণ, যার বিরুদ্ধে সম্পূর্ণরূপে সুরক্ষিত থাকা কঠিন হতে পারে, বিশেষ করে বড় এবং জটিল অ্যাপ্লিকেশনগুলোর ক্ষেত্রে। ডেটা ইন্টারফেসের সম্ভাব্য ত্রুটিগুলোর তীব্রতা সীমিত করার জন্য অতিরিক্ত নিরাপত্তা ব্যবস্থা থাকা উচিত, যার মধ্যে অন্তর্ভুক্ত রয়েছে:
- পাসওয়ার্ড এনক্রিপ্ট করার জন্য শক্তিশালী, একমুখী এবং সল্টেড হ্যাশ:
- বাণিজ্যিক অ্যাপ্লিকেশনের জন্য ২৫৬-বিট AES।
- এলিপটিক কার্ভ ক্রিপ্টোগ্রাফির জন্য ২২৪- অথবা ২৫৬-বিটের পাবলিক কী সাইজ।
- অনুমতি সীমিত করা।
- ডেটা ফরম্যাটের সুনির্দিষ্ট কাঠামো তৈরি করা এবং ডেটা প্রত্যাশিত ফরম্যাট মেনে চলছে কিনা তা যাচাই করা।
- যথাসম্ভব ব্যক্তিগত বা সংবেদনশীল ব্যবহারকারীর ডেটা সংরক্ষণ করা এড়িয়ে চলা (উদাহরণস্বরূপ, ডেটা প্রেরণ বা সংরক্ষণ না করে হ্যাশিংয়ের মাধ্যমে অ্যাপ্লিকেশন লজিক প্রয়োগ করা)।
- সংবেদনশীল ডেটা অ্যাক্সেস করে এমন এপিআই এবং থার্ড-পার্টি অ্যাপ্লিকেশন কমানো।