IRCAR201310189
تاريخ: 28/07/92
اولين موضوعي كه به طور كلي در برنامه نويسي امن (رجوع شود به مقاله اصول برنامه نويسي امن) و همچنين در برنامه نويسي امن با زبان جاوا مورد توجه قرار مي گيرد مربوط به اعتبار سنجي ورودي و پاكسازي داده ها است. در اين موضوع چهارده قانون معرفي مي گردد كه سطوح امنيتي مختلفي دارند (رجوع شود به مقاله برنامهنويسي امن با زبان جاوا – قوانين اعتبارسنجيورودي و پاكسازي دادهها - آشنايي). اولين قانون از اين موضوع داراي داراي سطح امنيتي يك (L1) بوده و از بالاترين اولويت (P12) برخوردار مي باشد.
قانون IDS00-J - داده هاي نامطمئن را كه از مرزهاي معتمد عبور مي كنند پاكسازي نماييد.
بسياري از برنامه ها داده هاي نامعتمد را كه منشأ آنها كاربران تأييد نشده، اتصالات شبكه و ديگر منابع نامطمئن است، پذيرفته و سپس اين داده را (تغيير يافته يا نيافته) از طريق مرزهاي معتمد به يك دامنه معتمد ديگر مي فرستند. معمولاً داده ها در قالب يك رشته با ساختار معنايي داخلي هستند كه زير سيستم بايد آن را تجزيه نمايد. اين داده بايد به دو دليل پاكسازي شود، اولاً ممكن است زير سيستم آمادگي مواجهه با ورودي هاي غير متعارف را نداشته باشد و ثانياً احتمال حمله تزريق در ورودي پاكسازي نشده وجود دارد.
برنامه ها لازم است همه داده هاي رشته اي را كه به مفسرهاي دستور يا تجزيه گرها ارسال مي شوند، پاكسازي نمايند به طوري كه رشته نهايي در محيطي كه قرار است تفسير يا تجزيه شود، بي خطر باشد.
بسياري از مفسرهاي دستور و تجزيه گرها توابع اعتبارسنجي و پاكسازي مخصوص به خود را دارا مي باشند. زماني كه اين توابع وجود دارند، استفاده از آنها بر استفاده از توابع و تكنيك هايي كه توسط برنامه نويس تعريف مي شوند، ارجحيت دارد زيرا اينگونه تكنيك ها معمولاً حالت هاي خاص يا پيچيدگي هاي پنهان در تجزيه گر را ناديده مي گيرند. مشكل ديگري كه با توابع تعريف شده توسط برنامه نويس وجود دارد اينست كه اين توابع معمولاً به خوبي نگهداري نمي شوند مخصوصاً در مواقعي كه قابليت هاي جديد به مفسر دستورات يا تجزيه گر اضافه مي شود.
تزريق SQL
يك آسيب پذيري تزريق SQL زماني به وجود مي آيد كه پرس و جوي اوليه SQL مي تواند به يك پرس و جوي كاملاً متفاوت تغيير شكل پيدا كند. اجراي چنين پرس و جويي منجر به نشت اطلاعات يا تغيير داده ها مي شود. اصلي ترين راه ممانعت از تزريق SQL پاكسازي و اعتبارسنجي ورودي هاي نامعتبر و پارامتري كردن پرس و جوها است.
فرض كنيد كه يك پايگاه داده حاوي نام كاربري و كلمه عبور كاربران است كه براي تأييد هويت كاربران سيستم مورد استفاده قرار مي گيرد. نام هاي كاربري داراي محدوديت اندازه رشته اي هشت كاراكتر هستند و كلمات عبور محدوديت 20 را دارا مي باشند.
يك دستور SQL كه تأييد هويت كاربر را انجام مي دهد ممكن است داراي شكل زير باشد:
SELECT * FROM db_user WHERE username='<USERNAME>' AND password='<PASSWORD>'
در صورتي كه اين پرس و جو ركوردي را برگرداند، نشان دهنده معتبر بودن نام كاربري و كلمه عبور است.
با اين وجود، اگر مهاجم بتواند يك رشته دلخواه را جايگزين <USERNAME> و <PASSWORD> كند مي تواند يك تزريق SQL را از طريق رشته زير به جاي <USERNAME> انجام دهد:
validuser' OR '1'='1
دستور SQL بعد از تزريق كد مذكور در آن به صورت زير در مي آيد:
SELECT * FROM db_user WHERE user name='validuser' OR '1'='1' AND password=<PASSWORD>
در صورتي كه validuser يك نام كاربري معتبر باشد، عبارت SELECT ركورد validuser را از جدول انتخاب مي كند. كلمه عبور هرگز مورد آزمون قرار نخواهد گرفت زيرا عبارت username='validuser' صحيح مي باشد. در نتيجه آيتم هايي كه بعد از OR است مورد آزمون قرار نمي گيرند. تا زماني كه آيتم هاي بعد از OR از لحاظ نحوي يك عبارت صحيح SQL باشند، مهاجم دسترسي كاربر validuser را دارا است.
به علاوه مهاجم مي تواند يك رشته مانند رشته زير را جايگزين <PASSWORD> نمايد:
' OR '1'='1
اين رشته منجر به دستور زير خواهد شد:
SELECT * FROM db_user WHERE username='' AND password='' OR '1'='1'
اين بار، عبارت هميشه صحيح '1'='1' اعتبارسنجي هم نام كاربري و هم كلمه عبور را غير فعال مي سازد و مهاجم مي تواند به صورت غلط بدون داشتن نام كاربري و يا كلمه عبور صحيح به سيستم ورود نمايد.
يك نمونه ناسازگار با قانون
نمونه كد ناسازگار با قانون يك كد JDBC را براي تأييد هويت كاربر در سيستم نشان مي دهد. كلمه عبور به صورت يك آرايه از كاراكترها ارسال شده، ارتباط پايگاه داده ساخته مي شود و سپس كلمه عبور درهم سازي مي گردد.
متأسفانه، اين مثال اجازه حمله تزريق SQL را مي دهد زيرا عبارت sqlString آرگومان هاي ورودي پاكسازي نشده را مي پذيرد. سناريوي حمله اي كه پيش از اين شرح داده شد در اينجا عمل مي كند.
class Login { public Connection getConnection() throws SQLException { DriverManager.registerDriver(new com.microsoft.sqlserver.jdbc.SQLServerDriver()); String dbConnection = PropertyManager.getProperty("db.connection"); // Can hold some value like // "jdbc:microsoft:sqlserver://<HOST>:1433,<UID>,<PWD>" return DriverManager.getConnection(dbConnection); } String hashPassword(char[] password) { // Create hash of password } public void doPrivilegedAction(String username, char[] password) throws SQLException { Connection connection = getConnection(); if (connection == null) { // Handle error } try { String pwd = hashPassword(password); String sqlString = "SELECT * FROM db_user WHERE username = '" + username + "' AND password = '" + pwd + "'"; Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(sqlString); if (!rs.next()) { throw new SecurityException( "User name or password incorrect" ); } // Authenticated; proceed } finally { try { connection.close(); } catch (SQLException x) { // Forward to handler } } } }
راه حل سازگار با قانون (عبارت از پيش تعريف شده)
خوشبختانه كتابخانه JDBC يك API را براي ساخت دستورات SQL فراهم مي كند كه داده هاي نامعتبر را پاكسازي مي نمايد. كلاس java.sql.PreparedStatementزماني كه به درستي مورد استفاده قرار گيرد، از حملات تزريق SQL ممانعت به عمل مي آورد. اين يك نمونه از پاكسازي بر اساس كامپوننت است.
اين راه حل سازگار تابع doPrivilegedAction()را به گونه اي دستكاري مي نمايد كه از يك عبارت از پيش تعيين شده ياPreparedStatementبه جايjava.sql.Statementاستفاده نمايد. اين كد نيز طول آرگومان username را مورد بررسي قرار مي دهد و از وارد كردن يك نام كاربري طولاني توسط مهاجم جلوگيري به عمل مي آورد.
public void doPrivilegedAction( String username, char[] password ) throws SQLException { Connection connection = getConnection(); if (connection == null) { // Handle error } try { String pwd = hashPassword(password); // Ensure that the length of user name is legitimate if (username.length() > 8) { // Handle error } String sqlString = "select * from db_user where username=? and password=?"; PreparedStatement stmt = connection.prepareStatement(sqlString); stmt.setString(1, username); stmt.setString(2, pwd); ResultSet rs = stmt.executeQuery(); if (!rs.next()) { throw new SecurityException("User name or password incorrect"); } // Authenticated; proceed } finally { try { connection.close(); } catch (SQLException x) { // Forward to handler } } }
از توابع set*() در كلاس PreparedStatement براي آزمون هاي قدرتمند استفاده كنيد. اين تكنيك آسيب پذيري تزريق SQL را كاهش مي دهد زيرا ورودي را براي قرار داشتن در بين دو گيومه مورد بررسي قرار مي دهد. توجه شود كه عبارت هاي از پيش تعيين شده حتي در پرس و جوهايي كه داده ها را وارد پايگاه داده مي كنند نيز بايد مورد استفاده قرار گيرند.
درمقاله بعدي به مبحث تزريق XML و همچنين ارزيابي خطر و تشخيص اتوماتيك اين آسيب پذيري مي پردازيم.
- 8