1. 程式人生 > >pymysql 解決 sql 注入問題

pymysql 解決 sql 注入問題

1. SQL 注入

SQL 注入是非常常見的一種網路攻擊方式,主要是通過引數來讓 mysql 執行 sql 語句時進行預期之外的操作。
例如,下面這段程式碼通過獲取使用者資訊來校驗使用者許可權:

import pymysql

sql = 'SELECT count(*) as count FROM user WHERE id = ' + str(input['id']) + ' AND password = "' + input['password'] + '"'
cursor = dbclient.cursor(
pymysql.cursors.DictCursor) cursor.execute(sql) count = cursor.fetchone() if count is not None and count['count'] > 0: print('登陸成功')

但是,如果傳入引數是:

input['id'] = '2 or 1=1'

你會發現,使用者能夠直接登入到系統中,因為原本 sql 語句的判斷條件被 or 短路成為了永遠正確的語句。
這裡僅僅是舉一個例子,事實上,sql 注入的方式還有很多種,這裡不深入介紹了。
總之,只要是通過使用者輸入資料來拼接 sql 語句,就必須在第一時間考慮如何避免 SQL 注入問題。
那麼,如何防止 SQL 注入呢?

2. 預防 SQL 注入 – pymysql 引數化語句

pymysql 的 execute 支援引數化 sql,通過佔位符 %s 配合引數就可以實現 sql 注入問題的避免。

import pymysql

sql = 'SELECT count(*) as count FROM user WHERE id = %s AND password = %s'
valus = [input['id'], input['password']]
cursor =
dbclient.cursor(pymysql.cursors.DictCursor) cursor.execute(sql, values) count = cursor.fetchone() if count is not None and count['count'] > 0: print('登陸成功')

這樣引數化的方式,讓 mysql 通過預處理的方式避免了 sql 注入的存在。
需要注意的是,不要因為引數是其他型別而換掉 %s,pymysql 的佔位符並不是 python 的通用佔位符。
同時,也不要因為引數是 string 就在 %s 兩邊加引號,mysql 會自動去處理。

3. 預防 SQL 注入 – mysql 儲存過程

資料庫儲存過程是 mysql 的一種高階用法,但是一般來說,並不建議使用資料庫的儲存過程。
主要原因是:

  1.  儲存過程的語法與普通 SQL 語句語法相差太大,增加維護成本
  2.  儲存過程在各資料庫間不通用且差別較大,給資料庫的移植和擴充套件帶來困難
  3.  編寫困難,資料庫指令碼語言使用起來還是很不方便的,包括很多資料結構的缺失,讓很多事情做起來很困難
  4.  除錯困難,雖然有一些功能強大的 IDE 提供了資料庫儲存過程的除錯功能,但是通常你需要同時在資料庫層面上和業務中同時進行除錯,兩處除錯極為不便
  5.  業務耦合,編寫儲存過程通常是需要在其中放入部分業務邏輯,這使得業務分散在資料層,業務層與資料層的耦合對於專案維護和擴充套件都會帶來極大地不便

但是,雖然不建議使用儲存過程,但是畢竟可以依賴他實現各種跨語言的 sql 注入預防,在複雜的場景下還是有其使用價值的,這裡僅做一些介紹。

3.1. 儲存過程編寫

delimiter \DROP PROCEDURE IF EXISTS proc_sql \CREATE PROCEDURE proc_sql (
    in nid1 INT,
    in nid2 INT,
    in callsql VARCHAR(255)
)
BEGIN
    set @nid1 = nid1;
    set @nid2 = nid2;
    set @callsql = callsql;
    PREPARE myprod FROM @callsql;
    --   PREPARE prod FROM 'select * from tb2 where nid>? and nid<?';  傳入的值為字串,?為佔位符
    --   用@p1,和@p2填充佔位符
    EXECUTE myprod USING @nid1,@nid2;
    DEALLOCATE prepare myprod;

END\delimiter ;

3.2. pymsql 中呼叫

import pymysql

cursor = conn.cursor()
mysql="SELECT * FROM user where nid > ? and nid < ?"
cursor.callproc('proc_sql', args=(11, 15, mysql))
rows = cursor.fetchall()
conn.commit()

4. 參考資料

https://stackoverflow.com/questions/5785154/python-mysqldb-issues-typeerror-d-format-a-number-is-required-not-str。
http://www.bubuko.com/infodetail-2050847.html。