準(zhǔn)備
SQL注入是一個(gè)過(guò)氣的話題(現(xiàn)在的項(xiàng)目大部分都已經(jīng)使用ORM來(lái)對(duì)數(shù)據(jù)庫(kù)進(jìn)行查詢了,所以SQL注入在大部分情況下都已經(jīng)被框架給規(guī)避掉了);然而不管你在不在乎,它都在那里,它是WEB領(lǐng)域攻擊的一種最常見(jiàn)的手段,它通過(guò)利用可視化界面的表單錄入(例如: 登錄界面的用戶名輸入框),輸入一些亂七八糟的字符來(lái)影響你的SQL原聲語(yǔ)句的拼接,從而影響整個(gè)查詢結(jié)果來(lái)達(dá)到Hacker的目的。
先聲明一下我的操作系統(tǒng)是Windows7,數(shù)據(jù)庫(kù)采用的是MySQL,數(shù)據(jù)庫(kù)連接工具用的是Navicat,編程語(yǔ)言采用的是Python 2.7,下面是記錄一個(gè)完整的練習(xí)過(guò)程。
?
建表
創(chuàng)建一張用戶表
CREATE TABLE `user` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`realname` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
插入數(shù)據(jù)
insert into user (`username`, `realname`) values ('zhangsan1', '張三1');
insert into user (`username`, `realname`) values ('zhangsan2', '張三2');
insert into user (`username`, `realname`) values ('zhangsan3', '張三3');
insert into user (`username`, `realname`) values ('zhangsan4', '張三4');
insert into user (`username`, `realname`) values ('zhangsan5', '張三5');
insert into user (`username`, `realname`) values ('zhangsan6', '張三6');
insert into user (`username`, `realname`) values ('zhangsan7', '張三7');
insert into user (`username`, `realname`) values ('zhangsan8', '張三8');
insert into user (`username`, `realname`) values ('zhangsan9', '張三9');
insert into user (`username`, `realname`) values ('zhangsan10', '張三10');
Navicat查看數(shù)據(jù)
# 查詢語(yǔ)句
select * from user;
# 顯示結(jié)果
1 zhangsan1 張三1
2 zhangsan2 張三2
3 zhangsan3 張三3
4 zhangsan4 張三4
5 zhangsan5 張三5
6 zhangsan6 張三6
7 zhangsan7 張三7
8 zhangsan8 張三8
9 zhangsan9 張三9
10 zhangsan10 張三10
Python查看數(shù)據(jù)
# 安裝依賴庫(kù)
pip install torndb mysqlclient==1.3.7
# 創(chuàng)建代碼文件prepare.py
import torndb
conn = torndb.Connection('127.0.0.1', 'sql_injection', 'root', '123456')
result = conn.query('select * from user')
for i in result:
print(i)
# 運(yùn)行代碼
python prepare.py
# 顯示結(jié)果
{'username': u'zhangsan1', 'id': 1L, 'realname': u'\u5f20\u4e091'}
{'username': u'zhangsan2', 'id': 2L, 'realname': u'\u5f20\u4e092'}
{'username': u'zhangsan3', 'id': 3L, 'realname': u'\u5f20\u4e093'}
{'username': u'zhangsan4', 'id': 4L, 'realname': u'\u5f20\u4e094'}
{'username': u'zhangsan5', 'id': 5L, 'realname': u'\u5f20\u4e095'}
{'username': u'zhangsan6', 'id': 6L, 'realname': u'\u5f20\u4e096'}
{'username': u'zhangsan7', 'id': 7L, 'realname': u'\u5f20\u4e097'}
{'username': u'zhangsan8', 'id': 8L, 'realname': u'\u5f20\u4e098'}
{'username': u'zhangsan9', 'id': 9L, 'realname': u'\u5f20\u4e099'}
{'username': u'zhangsan10', 'id': 10L, 'realname': u'\u5f20\u4e0910'}
?
注入
常規(guī)查詢
# -.- coding:utf-8 -.-
import torndb
# 模擬框架中的獲取字段參數(shù)的方法.
def get_argument(argument):
return str(argument)
# 連接數(shù)據(jù)庫(kù)
conn = torndb.Connection('127.0.0.1', 'sql_injection', 'root', '123456')
# 獲取參數(shù)
user_id = get_argument('10')
# sql語(yǔ)句拼接
# _sql相當(dāng)于 'select * from user where id=10'
_sql = 'select * from user where id=' + user_id
# 執(zhí)行查詢并取得結(jié)果集
result = conn.query(_sql)
# 打印結(jié)果
for i in result:
print(i)
# 顯示結(jié)果
{'username': u'zhangsan10', 'id': 10L, 'realname': u'\u5f20\u4e0910'}
?
關(guān)鍵字(or)注入
# -.- coding:utf-8 -.-
import torndb
# 模擬框架中的獲取字段參數(shù)的方法.
def get_argument(argument):
return str(argument)
# 連接數(shù)據(jù)庫(kù)
conn = torndb.Connection('127.0.0.1', 'sql_injection', 'root', '123456')
# 獲取參數(shù)
user_id = get_argument('10 or True')
# sql語(yǔ)句拼接
# _sql相當(dāng)于 'select * from user where id=10 or True'
_sql = 'select * from user where id=' + user_id
# 執(zhí)行查詢并取得結(jié)果集
result = conn.query(_sql)
# 打印結(jié)果
for i in result:
print(i)
# 顯示結(jié)果
{'username': u'zhangsan1', 'id': 1L, 'realname': u'\u5f20\u4e091'}
{'username': u'zhangsan2', 'id': 2L, 'realname': u'\u5f20\u4e092'}
{'username': u'zhangsan3', 'id': 3L, 'realname': u'\u5f20\u4e093'}
{'username': u'zhangsan4', 'id': 4L, 'realname': u'\u5f20\u4e094'}
{'username': u'zhangsan5', 'id': 5L, 'realname': u'\u5f20\u4e095'}
{'username': u'zhangsan6', 'id': 6L, 'realname': u'\u5f20\u4e096'}
{'username': u'zhangsan7', 'id': 7L, 'realname': u'\u5f20\u4e097'}
{'username': u'zhangsan8', 'id': 8L, 'realname': u'\u5f20\u4e098'}
{'username': u'zhangsan9', 'id': 9L, 'realname': u'\u5f20\u4e099'}
{'username': u'zhangsan10', 'id': 10L, 'realname': u'\u5f20\u4e0910'}
SQL語(yǔ)句的where條件只要你的語(yǔ)法沒(méi)問(wèn)題,那么它背后的機(jī)制是支持三種東西:True、False、表達(dá)式。 or關(guān)鍵字跟編程語(yǔ)言中的機(jī)制是一樣的,不管其他表達(dá)式是否成立,只要or關(guān)鍵字后面的表達(dá)式是True那么整個(gè)where條件都是成立的。
布爾(True)
select * from user where True; # 返回所有結(jié)果布爾(False)
select * from user where False; # 返回空結(jié)果表達(dá)式(1=1)
select * from user where 1=1; # 返回所有結(jié)果表達(dá)式(id=8)
select * from user where id=8; # 返回一條結(jié)果表達(dá)式(id=8 or True) / (id=8 or 1=1)
select * from user where id=8 or 1=1; # 返回所有結(jié)果
?
特殊字符(=)注入
正常查詢
# -.- coding:utf-8 -.-
import torndb
# 模擬框架中的獲取字段參數(shù)的方法.
def get_argument(argument):
return str(argument)
# 連接數(shù)據(jù)庫(kù)
conn = torndb.Connection('127.0.0.1', 'sql_injection', 'root', '123456')
# 獲取參數(shù)
username = get_argument('zhangsan4')
realname = get_argument('張三4')
# sql語(yǔ)句拼接
# _sql相當(dāng)于 'select * from user where username="zhangsan4" and realname="張三4"'
_sql = 'select * from user where username="' + username + '" and realname="' + realname + '"'
# 執(zhí)行查詢并取得結(jié)果集
result = conn.query(_sql)
# 打印結(jié)果
for i in result:
print(i)
# 顯示結(jié)果
{'username': u'zhangsan4', 'id': 4L, 'realname': u'\u5f20\u4e094'}
注入查詢
# -.- coding:utf-8 -.-
import torndb
# 模擬框架中的獲取字段參數(shù)的方法.
def get_argument(argument):
return str(argument)
# 連接數(shù)據(jù)庫(kù)
conn = torndb.Connection('127.0.0.1', 'sql_injection', 'root', '123456')
# 獲取參數(shù)
username = get_argument('"or ""="')
realname = get_argument('"or ""="')
# sql語(yǔ)句拼接
# _sql相當(dāng)于 'select * from user where username=""or ""="" and realname=""or ""=""'
_sql = 'select * from user where username="' + username + '" and realname="' + realname + '"'
# 執(zhí)行查詢并取得結(jié)果集
result = conn.query(_sql)
# 打印結(jié)果
for i in result:
print(i)
# 顯示結(jié)果
{'username': u'zhangsan1', 'id': 1L, 'realname': u'\u5f20\u4e091'}
{'username': u'zhangsan2', 'id': 2L, 'realname': u'\u5f20\u4e092'}
{'username': u'zhangsan3', 'id': 3L, 'realname': u'\u5f20\u4e093'}
{'username': u'zhangsan4', 'id': 4L, 'realname': u'\u5f20\u4e094'}
{'username': u'zhangsan5', 'id': 5L, 'realname': u'\u5f20\u4e095'}
{'username': u'zhangsan6', 'id': 6L, 'realname': u'\u5f20\u4e096'}
{'username': u'zhangsan7', 'id': 7L, 'realname': u'\u5f20\u4e097'}
{'username': u'zhangsan8', 'id': 8L, 'realname': u'\u5f20\u4e098'}
{'username': u'zhangsan9', 'id': 9L, 'realname': u'\u5f20\u4e099'}
{'username': u'zhangsan10', 'id': 10L, 'realname': u'\u5f20\u4e0910'}
特殊字符(;)注入
在sql語(yǔ)句執(zhí)行環(huán)境中,每條sql都用分號(hào)來(lái)區(qū)分,大部分?jǐn)?shù)據(jù)庫(kù)都支持多條語(yǔ)句一起執(zhí)行,只要用分號(hào)隔開(kāi)即可;那么通過(guò)利用分號(hào)也可以做其他危險(xiǎn)動(dòng)作或執(zhí)行其他查詢。
查詢兩條數(shù)據(jù)
select * from user where id=8; select * from user where id=4;執(zhí)行危險(xiǎn)語(yǔ)句(刪除掉用戶表)
select * from user where id=8; drop table user;
# -.- coding:utf-8 -.-
import torndb
# 模擬框架中的獲取字段參數(shù)的方法.
def get_argument(argument):
return str(argument)
# 連接數(shù)據(jù)庫(kù)
conn = torndb.Connection('127.0.0.1', 'sql_injection', 'root', '123456')
# 獲取參數(shù)
user_id = get_argument('8; drop table user;')
# sql語(yǔ)句拼接
# _sql相當(dāng)于 'select * from user where id=8; drop table user;'
_sql = 'select * from user where id=' + user_id
# 執(zhí)行查詢并取得結(jié)果集
result = conn.query(_sql)
# 打印結(jié)果
for i in result:
print(i)
# 顯示結(jié)果
{'username': u'zhangsan8', 'id': 8L, 'realname': u'\u5f20\u4e098'}
執(zhí)行完這塊代碼之后,雖然數(shù)據(jù)時(shí)正常顯示了,但是表也被刪除了。。。。。。(只能再次建表插入數(shù)據(jù),才能往下走了)。
?
使用SQL參數(shù)(SQL Parameters)來(lái)規(guī)避注入
規(guī)避or關(guān)鍵字
# -.- coding:utf-8 -.-
import torndb
# 模擬框架中的獲取字段參數(shù)的方法.
def get_argument(argument):
return str(argument)
# 連接數(shù)據(jù)庫(kù)
conn = torndb.Connection('127.0.0.1', 'sql_injection', 'root', '123456')
# 獲取參數(shù)
user_id = get_argument('8 or 1=1')
# sql語(yǔ)句拼接
_sql = 'select * from user where id=%s'
# 執(zhí)行查詢并取得結(jié)果集
result = conn.query(_sql, *[user_id, ])
# 打印結(jié)果
for i in result:
print(i)
# 顯示結(jié)果
C:\Python27\lib\site-packages\torndb.py:234: Warning: Truncated incorrect DOUBLE value: '8 or 1=1'
return cursor.execute(query, kwparameters or parameters)
{'username': u'zhangsan8', 'id': 8L, 'realname': u'\u5f20\u4e098'}
?
規(guī)避分號(hào)(;)關(guān)鍵字
# -.- coding:utf-8 -.-
import torndb
# 模擬框架中的獲取字段參數(shù)的方法.
def get_argument(argument):
return str(argument)
# 連接數(shù)據(jù)庫(kù)
conn = torndb.Connection('127.0.0.1', 'sql_injection', 'root', '123456')
# 獲取參數(shù)
user_id = get_argument('8;drop table user;')
# sql語(yǔ)句拼接
_sql = 'select * from user where id=%s'
# 執(zhí)行查詢并取得結(jié)果集
result = conn.query(_sql, *[user_id, ])
# 打印結(jié)果
for i in result:
print(i)
# 顯示結(jié)果
C:\Python27\lib\site-packages\torndb.py:234: Warning: Truncated incorrect DOUBLE value: '8;drop table user;'
return cursor.execute(query, kwparameters or parameters)
{'username': u'zhangsan8', 'id': 8L, 'realname': u'\u5f20\u4e098'}
?
規(guī)避等號(hào)(=)特殊字符
# -.- coding:utf-8 -.-
import torndb
# 模擬框架中的獲取字段參數(shù)的方法.
def get_argument(argument):
return str(argument)
# 連接數(shù)據(jù)庫(kù)
conn = torndb.Connection('127.0.0.1', 'sql_injection', 'root', '123456')
# 獲取參數(shù)
username = get_argument('" or "1"="1')
realname = get_argument('" or "1"="1')
# sql語(yǔ)句拼接
_sql = 'select * from user where username="%s" and realname="%s"'
# 執(zhí)行查詢并取得結(jié)果集
result = conn.query(_sql, *[username, realname])
# 打印結(jié)果
for i in result:
print(i)
# 顯示結(jié)果
沒(méi)有任何結(jié)果
?
追溯源碼
# torndb.py
Class Connection:
def query(self, query, *parameters, **kwparameters):
self._execute(cursor, query, parameters, kwparameters)
def _execute(self, cursor, query, parameters, kwparameters):
cursor.execute(query, kwparameters or parameters)
# MySQLdb/cursors.py
class BaseCursor(object):
def execute(self, query, args=None):
# 將sql語(yǔ)句進(jìn)行encode轉(zhuǎn)碼
# 執(zhí)行sql語(yǔ)句(虛執(zhí)行)
res = self._query(query)
def _query(self, q):
# 執(zhí)行sql語(yǔ)句(虛執(zhí)行)
return self._do_query(q)
def _do_query(self, q):
# 執(zhí)行sql語(yǔ)句(交給C語(yǔ)言接口去查詢)
db.query(q)
# 異步返回結(jié)果
self._do_get_result()
參考
- [x] SQL Injection