SQL इंजेक्शन

OWASP कैटगरी: MASVS-CODE: कोड की क्वालिटी

खास जानकारी

एसक्यूएल इंजेक्शन, SQL स्टेटमेंट में कोड डालकर, जोखिम वाले ऐप्लिकेशन का फ़ायदा उठाता है. इससे, ऐप्लिकेशन के इंटरफ़ेस के अलावा, उसके डेटाबेस को भी ऐक्सेस किया जा सकता है. इस हमले से निजी डेटा का पता चल सकता है, डेटाबेस का कॉन्टेंट खराब हो सकता है, और यहां तक कि बैकएंड इन्फ़्रास्ट्रक्चर से भी समझौता किया जा सकता है.

क्वेरी को एक्ज़ीक्यूट करने से पहले, उपयोगकर्ता के इनपुट को डाइनैमिक तरीके से जोड़कर बनाई गई क्वेरी की मदद से, एसक्यूएल इंजेक्शन का जोखिम हो सकता है. एसक्यूएल इंजेक्शन, आम तौर पर वेब, मोबाइल, और किसी भी एसक्यूएल डेटाबेस ऐप्लिकेशन को टारगेट करता है. यह वेब से जुड़े जोखिमों की OWASP की टॉप 10 सूची में शामिल होता है. हैकर, कई बड़े डेटा उल्लंघनों में इस तकनीक का इस्तेमाल कर चुके हैं.

इस बुनियादी उदाहरण में, ऑर्डर नंबर वाले बॉक्स में उपयोगकर्ता के बिना एस्केप किए गए इनपुट को एसक्यूएल स्ट्रिंग में डाला जा सकता है. इसे इस क्वेरी के तौर पर समझा जा सकता है:

SELECT * FROM users WHERE email = 'example@example.com' AND order_number = '251542'' LIMIT 1

इस तरह के कोड से, वेब कंसोल में डेटाबेस सिंटैक्स से जुड़ी गड़बड़ी जनरेट होगी. इससे पता चलता है कि ऐप्लिकेशन में एसक्यूएल इंजेक्शन का जोखिम हो सकता है. ऑर्डर नंबर को 'OR 1=1– से बदलने का मतलब है कि पुष्टि की जा सकती है, क्योंकि डेटाबेस इस स्टेटमेंट को True के तौर पर देखता है. ऐसा इसलिए, क्योंकि एक हमेशा एक के बराबर होता है.

इसी तरह, यह क्वेरी किसी टेबल की सभी पंक्तियां दिखाती है:

SELECT * FROM purchases WHERE email='admin@app.com' OR 1=1;

कॉन्टेंट देने वाले ऐप्लिकेशन

कॉन्टेंट देने वाले ऐप्लिकेशन, स्ट्रक्चर्ड स्टोरेज का ऐसा तरीका उपलब्ध कराते हैं जिसे किसी ऐप्लिकेशन तक सीमित रखा जा सकता है या अन्य ऐप्लिकेशन के साथ शेयर करने के लिए एक्सपोर्ट किया जा सकता है. अनुमतियां, कम से कम विशेषाधिकार के सिद्धांत के आधार पर सेट की जानी चाहिए. एक्सपोर्ट किए गए ContentProvider के पास, पढ़ने और लिखने के लिए सिर्फ़ एक तय अनुमति हो सकती है.

ध्यान दें कि सभी एसक्यूएल इंजेक्शन से, डेटा का गलत इस्तेमाल नहीं होता. कुछ कॉन्टेंट देने वाले ऐप्लिकेशन, लोगों को SQLite डेटाबेस का पूरा ऐक्सेस पहले से ही देते हैं. ऐसे में, मनचाही क्वेरी को एक्ज़ीक्यूट करने से ज़्यादा फ़ायदा नहीं मिलता. सुरक्षा से जुड़ी समस्या को दिखाने वाले पैटर्न में ये शामिल हैं:

  • एक से ज़्यादा कॉन्टेंट देने वाले ऐप्लिकेशन, SQLite डेटाबेस की एक ही फ़ाइल शेयर कर रहे हैं.
    • इस मामले में, हर टेबल को किसी खास कॉन्टेंट देने वाले ऐप्लिकेशन के लिए बनाया जा सकता है. किसी एक कॉन्टेंट देने वाले ऐप्लिकेशन में एसक्यूएल इंजेक्शन होने पर, अन्य टेबल को भी ऐक्सेस किया जा सकेगा.
  • किसी कॉन्टेंट देने वाले ऐप्लिकेशन के पास, एक ही डेटाबेस में मौजूद कॉन्टेंट के लिए कई अनुमतियां हैं.
    • किसी एक कॉन्टेंट देने वाले ऐप्लिकेशन में एसक्यूएल इंजेक्शन होने पर, अलग-अलग अनुमति लेवल के साथ ऐक्सेस दिया जा सकता है. इससे, सुरक्षा या निजता सेटिंग को स्थानीय तौर पर बायपास किया जा सकता है.

असर

एसक्यूएल इंजेक्शन से, उपयोगकर्ता या ऐप्लिकेशन का संवेदनशील डेटा सामने आ सकता है. साथ ही, पुष्टि और अनुमति से जुड़ी पाबंदियों को भी तोड़ा जा सकता है. इससे, डेटाबेस के खराब होने या मिटने का जोखिम बढ़ जाता है. जिन लोगों का निजी डेटा सामने आया है उन पर इसका खतरनाक और स्थायी असर पड़ सकता है. ऐप्लिकेशन और सेवाएं देने वाली कंपनियों को, बौद्धिक संपदा या लोगों का भरोसा खोने का जोखिम हो सकता है.

जोखिम कम करने के तरीके

बदले जा सकने वाले पैरामीटर

सेलेक्शन क्लॉज़ में ? का इस्तेमाल, बदले जा सकने वाले पैरामीटर और चुने गए तर्कों के कलेक्शन के तौर पर किया जा सकता है. इससे, उपयोगकर्ता का इनपुट सीधे क्वेरी से जुड़ जाता है. इसे एसक्यूएल स्टेटमेंट के हिस्से के तौर पर नहीं समझा जाता.

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 ऑब्जेक्ट का इस्तेमाल करना

The PreparedStatement इंटरफ़ेस, एसक्यूएल स्टेटमेंट को ऑब्जेक्ट के तौर पर पहले से कंपाइल करता है. इसके बाद, इसे कई बार आसानी से एक्ज़ीक्यूट किया जा सकता है. 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)

क्वेरी के तरीकों का इस्तेमाल करना

इस लंबे उदाहरण में, selection और selectionArgs को मिलाकर, query() तरीके का 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 डेटाबेस के लिए, रूम परसिस्टेंस लाइब्रेरी को ऐब्स्ट्रैक्शन लेयर के तौर पर इस्तेमाल किया जाए. रूम की सुविधाओं में ये शामिल हैं:

  • डेटाबेस क्लास, जो ऐप्लिकेशन के बरकरार रखे गए डेटा से कनेक्ट करने के लिए, मुख्य ऐक्सेस पॉइंट के तौर पर काम करती है.
  • डेटा एंटिटी, जो डेटाबेस की टेबल को दिखाती हैं.
  • डेटा ऐक्सेस ऑब्जेक्ट (डीएओ), जो ऐसे तरीके उपलब्ध कराते हैं जिनका इस्तेमाल करके, ऐप्लिकेशन डेटा को क्वेरी, अपडेट, इंसर्ट, और मिटा सकता है.

रूम के ये फ़ायदे हैं:

  • SQL क्वेरी के कंपाइल होने में लगने वाले समय की पुष्टि.
  • गड़बड़ी होने की आशंका वाले, छोटे-मोटे बदलावों के साथ बार-बार इस्तेमाल किए जाने वाले लंबे टेक्स्ट कम करना.
  • डेटाबेस माइग्रेशन को आसान बनाना.

सबसे सही तरीके

एसक्यूएल इंजेक्शन, एक खतरनाक हमला है. इससे पूरी तरह से सुरक्षित रहना मुश्किल हो सकता है. खास तौर पर, बड़े और जटिल ऐप्लिकेशन के मामले में. डेटा इंटरफ़ेस में संभावित कमियों की गंभीरता को सीमित करने के लिए, सुरक्षा से जुड़े अतिरिक्त पहलुओं का ध्यान रखना चाहिए. इनमें ये शामिल हैं:

  • पासवर्ड को एन्क्रिप्ट करने के लिए, मज़बूत, एकतरफ़ा, और सॉल्टेड हैश:
    • कमर्शियल ऐप्लिकेशन के लिए, 256-बिट एईएस.
    • एलिप्टिक कर्व क्रिप्टोग्राफ़ी के लिए, 224 या 256-बिट की सार्वजनिक पासकोड साइज़.
  • अनुमतियों को सीमित करना.
  • डेटा फ़ॉर्मैट को सटीक तरीके से स्ट्रक्चर करना और यह पुष्टि करना कि डेटा, तय किए गए फ़ॉर्मैट के मुताबिक है.
  • जहां तक हो सके, निजी या संवेदनशील उपयोगकर्ता डेटा को सेव करने से बचना. उदाहरण के लिए, डेटा को ट्रांसमिट या सेव करने के बजाय, हैशिंग करके ऐप्लिकेशन लॉजिक लागू करना.
  • संवेदनशील डेटा को ऐक्सेस करने वाले एपीआई और तीसरे पक्ष के ऐप्लिकेशन को कम करना.

संसाधन