MySQL(十二)------ SQL中的安全問題

       日常開發過程中我們通常只關心SQL語句能否實現預期功能,往往忽略了SQL語句可能會帶來的系統漏洞,常遇到的就是SQL注入。

一、SQL注入簡介

       這裏不做抽象的解釋,可能說完也不會明白,直接用例子來演示SQL注入:

1. 首先我們創建一張表並插入一條數據來模擬實際情況下接觸不到的數據庫

CREATE TABLE users (
  id int(11) NOT NULL AUTO_INCREMENT,
  username varchar(64) NOT NULL,
  password varchar(64) NOT NULL,
  email varchar(64) NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY username (username)
  );

INSERT INTO users (username,password,email)
  VALUES('test',md5('admin'),'[email protected]');

2. 登陸界面,提交了用戶名和密碼後,表單會將數據提交給後臺進行驗證

<html>
<head>
<title>Sql注入演示</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body >
<form action="validate.php" method="post">
  <fieldset >
    <legend>Sql注入演示</legend>
    <table>
      <tr>
        <td>用戶名:</td>
        <td><input type="text" name="username"></td>
      </tr>
      <tr>
        <td>密&nbsp;&nbsp;碼:</td>
        <td><input type="text" name="password"></td>
      </tr>
      <tr>
        <td><input type="submit" value="提交"></td>
        <td><input type="reset" value="重置"></td>
      </tr>
    </table>
  </fieldset>
</form>
</body>
</html>

3.驗證模塊(PHP語言)

<?php
       $conn=@mysql_connect("localhost",'root','') or die("數據庫連接失敗!");;
       mysql_select_db("injection",$conn) or die("您要選擇的數據庫不存在");
       $name=$_POST['username'];
       $pwd=$_POST['password'];
       $sql="select * from users where username='$name' and password='$pwd'";
       $query=mysql_query($sql);
       $arr=mysql_fetch_array($query);
       if(is_array($arr)){
              header("Location:manager.php");
       }else{
              echo "您的用戶名或密碼輸入有誤,<a href=\"Login.php\">請重新登錄!</a>";
       }
?>

          代碼分析:如果,用戶名和密碼都匹配成功的話,將跳轉到管理員操作界面(manager.php),不成功,則給出友好提示信息。

         注意到了沒有,我們直接將用戶提交過來的數據(用戶名和密碼)直接拿去執行,並沒有實現進行特殊字符過濾,這就是SQL注入的基礎,待會就能看出這樣做的風險有多大。

4. 注入演示

       當我們在驗證界面填寫正確的用戶名 test 和密碼 admin 時,會成功登陸,它執行的SQL語句爲:select * from users where username='test' and password=md5('admin') ,顯然這條記錄在數據庫中存在,因此能成功登陸。

       當我們在用戶名輸入框那輸入 ’  or 1=1 # ,密碼隨便輸,此時在驗證過程中的SQL語句就是:select * from users where username='' or 1=1 # and password=md5('') ,由於#、/*在SQL語句中是註釋語句的開頭標識,因此,後面的都被註釋掉了,從而上面的SQL語句相當於 select * from users where username='' or 1=1,此時我們發現1=1恆成立,而且使用的是or連接,故而這條語句又等價於 select * from users ,顯然,不管什麼情況下該語句都能成功執行,也就是說我這樣輸入用戶名不用管密碼都可以成功登陸,這明顯是不對的。

       這就大概說明了SQL是怎樣進行注入的,你可能覺得不就是多一個登陸嘛,沒什麼危害,那你可能是還沒有看到它的厲害之處。既然我能假登陸,那我也可以通過這樣構造SQL來獲取你的數據庫信息,得到超級管理權權限,進而對你的數據隨意更改,這下你知道它的厲害了吧。更詳細說明參見https://www.2cto.com/article/201310/250877.html博客。

二、防範措施

2.1 PrepareStatement + Bind-Variable

       這種方法只能針對Java、JSP開發的應用。因爲PrepareStatement語句是由JDBC驅動來支持的,它僅僅是做了一些替換和轉義,MySQL本身只是綁定了變量。

       避免SQL注入的原理:sql注入只對sql語句的準備(編譯)過程有破壞作用,而PreparedStatement已經準備好了,執行階段只是把輸入串作爲數據處理,而不再對sql語句進行解析準備,因此也就避免了sql注入問題。

       比如上面用戶名傳的變量是 ” ’ or 1=1 # “,企圖利用構造SQL語句來矇混過關,但由於使用了PreparedStatement綁定變量,裏面的引號被轉義,#號被綁定到字符串上,從而它只能作爲參數傳遞給已經預編譯好的SQL語句,從而它無法和SQL語句組合後再編譯執行,這樣就避免了注入攻擊。

       使用好處:

       (1).代碼的可讀性和可維護性好;

       (2).PreparedStatement盡最大可能提高性能;

       (3).最重要的一點是極大地提高了安全性;

        所以一般能使用這種方法就推薦使用這種方式防範SQL注入。

2.2  使用應用程序提供的轉換函數

       很多應用程序接口都提供了對特殊字符進行轉換的函數,利用他們可以對用戶的輸入進行轉換,防止生成不被期望的語句。

  • MySQL C API:使用mysql_real_escape_string() API調用;
  • MySQL++:使用escape和quote修飾符;
  • PHP:4.3版本之前使用mysql_real_escape_string()函數,PHP5開始使用擴展的MySQLI;
  • Perl DBI:使用placeholders或者quote()方法;
  • Ruby DBI:使用placeholders或者quote()方法;

2.3 自定義函數進行校驗

       可以使用自定義的函數對輸入進行校驗,輸入驗證的途徑可分爲以下幾種:

  • 整理數據使之變得有效;
  • 拒絕已知的非法輸入;
  • 只接受已知的合法輸入;

      比如可以通過正則表達式來過濾掉非法字符,這樣攻擊者就無法通過字符的組合來注入SQL,從而防範攻擊。可以將非法字符列出,如果有新增的就加入到這裏面,還要注意字符的變形,比如它的16進製表示形式等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章