OWASP कैटगरी: MASVS-CODE: कोड क्वालिटी
खास जानकारी
एसक्यूएल इंजेक्शन, एसक्यूएल स्टेटमेंट में कोड डालकर, जोखिम वाले ऐप्लिकेशन का गलत इस्तेमाल करता है. ऐसा, जान-बूझकर एक्सपोज़ किए गए इंटरफ़ेस के अलावा, डेटाबेस को ऐक्सेस करने के लिए किया जाता है. इस हमले से निजी डेटा को सार्वजनिक किया जा सकता है, डेटाबेस के कॉन्टेंट को खराब किया जा सकता है, और बैकएंड इन्फ़्रास्ट्रक्चर को भी खतरा हो सकता है.
एसक्यूएल को क्वेरी के ज़रिए इंजेक्शन का खतरा हो सकता है. ये क्वेरी, उपयोगकर्ता के इनपुट को लागू करने से पहले, डाइनैमिक तरीके से बनाई जाती हैं. वेब, मोबाइल, और किसी भी एसक्यूएल डेटाबेस ऐप्लिकेशन को टारगेट करने वाले एसक्यूएल इंजेक्शन को आम तौर पर, वेब की OWASP की 10 सबसे ज़्यादा जोखिम वाली समस्याओं में शामिल किया जाता है. हैकर ने इस तकनीक का इस्तेमाल कई हाई-प्रोफ़ाइल ब्रीच में किया है.
इस बुनियादी उदाहरण में, उपयोगकर्ता के ऑर्डर नंबर बॉक्स में डाले गए बिना एस्केप किए गए इनपुट को 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 डेटाबेस फ़ाइल शेयर करते हैं.
- इस मामले में, हर टेबल किसी खास कॉन्टेंट प्रोवाइडर के लिए हो सकती है. किसी एक ContentProvider में एसक्यूएल इंजेक्शन की सफलता से, किसी भी अन्य टेबल का ऐक्सेस मिल जाएगा.
- कॉन्टेंट उपलब्ध कराने वाली कंपनी के पास, एक ही डेटाबेस में मौजूद कॉन्टेंट के लिए कई अनुमतियां होती हैं.
- अलग-अलग अनुमति लेवल के साथ ऐक्सेस देने वाले किसी एक कॉन्टेंट प्रोवाइडर में SQL इंजेक्शन की वजह से, सुरक्षा या निजता सेटिंग को स्थानीय तौर पर बायपास किया जा सकता है.
असर
एसक्यूएल इंजेक्शन से, उपयोगकर्ता या ऐप्लिकेशन का संवेदनशील डेटा ज़ाहिर हो सकता है. साथ ही, पुष्टि करने और अनुमति देने से जुड़ी पाबंदियों को भी हटाया जा सकता है. इससे डेटाबेस में गड़बड़ी हो सकती है या डेटा मिट सकता है. जिन उपयोगकर्ताओं का निजी डेटा ज़ाहिर हुआ है उन पर इसका असर खतरनाक और लंबे समय तक रह सकता है. ऐप्लिकेशन और सेवाएं देने वाली कंपनियों को, बौद्धिक संपत्ति या उपयोगकर्ता का भरोसा खोने का खतरा होता है.
जोखिम कम करने के तरीके
बदले जा सकने वाले पैरामीटर
सेलेक्शन क्लॉज़ में बदले जा सकने वाले पैरामीटर के तौर पर ?
का इस्तेमाल करने पर, उपयोगकर्ता के इनपुट को सीधे क्वेरी से जोड़ दिया जाता है. इसके बजाय, इनपुट को एसक्यूएल स्टेटमेंट के हिस्से के तौर पर नहीं समझा जाता.
Kotlin
// 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
Java
// 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;
उपयोगकर्ता के इनपुट को एसक्यूएल के तौर पर इस्तेमाल करने के बजाय, सीधे क्वेरी से जोड़ा जाता है. इससे कोड इंजेक्शन को रोका जा सकता है.
यहां एक और उदाहरण दिया गया है. इसमें, बदले जा सकने वाले पैरामीटर की मदद से खरीदारी की जानकारी पाने के लिए, शॉपिंग ऐप्लिकेशन की क्वेरी दिखाई गई है:
Kotlin
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
}
Java
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
स्टेटमेंट को आईडी की वैल्यू के तौर पर पढ़ा जाता है. ऐसा होने पर, शायद कोई नतीजा न मिले. वहीं, रॉ क्वेरी में OR 1=1
स्टेटमेंट को WHERE
क्लॉज़ के दूसरे हिस्से के तौर पर समझा जाएगा.
नीचे दिए गए उदाहरण में, पैरामीटर वाली क्वेरी दिखाई गई है:
Kotlin
val pstmt: PreparedStatement = con.prepareStatement(
"UPDATE EMPLOYEES SET ROLE = ? WHERE ID = ?").apply {
setString(1, "Barista")
setInt(2, 295094)
}
Java
PreparedStatement pstmt = con.prepareStatement(
"UPDATE EMPLOYEES SET ROLE = ? WHERE ID = ?");
pstmt.setString(1, "Barista")
pstmt.setInt(2, 295094)
क्वेरी के तरीके इस्तेमाल करना
इस लंबे उदाहरण में, query()
तरीके के selection
और selectionArgs
को WHERE
क्लॉज़ बनाने के लिए जोड़ा गया है. आर्ग्युमेंट अलग-अलग दिए जाते हैं, इसलिए उन्हें जोड़ने से पहले, उन्हें एसक्यूएल इंजेक्शन से बचाने के लिए, एस्केप किया जाता है.
Kotlin
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()
}
Java
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()
मोड. setStrictColumns()
, यह पुष्टि करने के लिए कि कॉलम, setProjectionMap में अनुमति वाली सूची में शामिल हैं.- सबक्वेरी को सीमित करने के लिए,
setStrictGrammar()
.
रूम लाइब्रेरी का इस्तेमाल करना
android.database.sqlite
पैकेज, Android पर डेटाबेस इस्तेमाल करने के लिए ज़रूरी एपीआई उपलब्ध कराता है. हालांकि, इस तरीके के लिए लो-लेवल कोड लिखना ज़रूरी है. साथ ही, इसमें रॉ SQL क्वेरी के कंपाइल होने में लगने वाले समय की पुष्टि नहीं होती. डेटा ग्राफ़ में बदलाव होने पर, जिन SQL क्वेरी पर असर पड़ा है उन्हें मैन्युअल तरीके से अपडेट करना पड़ता है. यह प्रोसेस समय लेने वाली और गड़बड़ी वाली होती है.
SQLite डेटाबेस के लिए, Room परसिस्टेंस लाइब्रेरी को ऐब्स्ट्रैक्शन लेयर के तौर पर इस्तेमाल करना, एक बेहतरीन तरीका है. Room की सुविधाओं में ये शामिल हैं:
- डेटाबेस क्लास, जो ऐप्लिकेशन के सेव किए गए डेटा से कनेक्ट करने के लिए मुख्य ऐक्सेस पॉइंट के तौर पर काम करती है.
- डेटाबेस की टेबल दिखाने वाली डेटा इकाइयां.
- डेटा ऐक्सेस ऑब्जेक्ट (डीएओ), जो ऐप्लिकेशन को डेटा क्वेरी करने, अपडेट करने, इंसर्ट करने, और मिटाने के लिए इस्तेमाल करने के तरीके उपलब्ध कराते हैं.
Room के ये फ़ायदे हैं:
- एसक्यूएल क्वेरी के कंपाइल होने में लगने वाले समय की पुष्टि.
- गड़बड़ी वाले बोइलरप्लेट कोड को कम करना.
- डेटाबेस माइग्रेशन को आसान बनाना.
सबसे सही तरीके
एसक्यूएल इंजेक्शन एक ऐसा खतरनाक हमला है जिससे पूरी तरह से सुरक्षित रहना मुश्किल हो सकता है. खास तौर पर, बड़े और जटिल ऐप्लिकेशन के लिए. डेटा इंटरफ़ेस में संभावित गड़बड़ियों को कम करने के लिए, सुरक्षा से जुड़ी अन्य बातों का ध्यान रखा जाना चाहिए. इनमें ये शामिल हैं:
- पासवर्ड एन्क्रिप्ट करने के लिए, बेहतर, वन-वे, और साल्टेड हैश:
- व्यावसायिक ऐप्लिकेशन के लिए 256-बिट एईएस.
- ईलिप्टिक कर्व क्रिप्टोग्राफ़ी के लिए, 224 या 256-बिट की सार्वजनिक कुंजी का साइज़.
- अनुमतियों को सीमित करना.
- डेटा फ़ॉर्मैट को सटीक तरीके से स्ट्रक्चर करना और यह पुष्टि करना कि डेटा, उम्मीद के मुताबिक फ़ॉर्मैट में है.
- जहां भी हो सके, उपयोगकर्ता का निजी या संवेदनशील डेटा सेव न करना. उदाहरण के लिए, डेटा को ट्रांसमिट या सेव करने के बजाय, हैश करके ऐप्लिकेशन लॉजिक लागू करना.
- संवेदनशील डेटा को ऐक्सेस करने वाले एपीआई और तीसरे पक्ष के ऐप्लिकेशन को कम से कम इस्तेमाल करना.