这里用到的环境是phpstudy的集成环境,版本为php-5.6.27-nts+apache,编辑代码的工具为Sublime Text 3,数据库的管理是Navicat for MYSQL。
首先把前端代码完成,使用HTML5来实现(因为我的前端能力实在有限,所以做出来的页面很难看,但是不影响接下来的测试),几个关键点如下:
1、我使用的是post的提交方式,这样无法在url栏中直接看到提交的数据
2、最好在开始前先用Navicat把数据库的列确定好再开始写HTML,这里我在之前创建好的php10的数据库中新建了一个名为login的表,共3列数据:id为主键,int类型并勾选自动递增;userName存储用户名,varchar类型,不是NULL;userPassword存储用户密码,varchar类型,不是NULL。
3、在前端还增设了一个注册的通道,方便模拟用户的注册。
以下为前端login.html的代码
登录界面
网页上的显示效果如下:
用户登录的表单提交后会跳转到action.php,用户注册的表单会提交到register.php。现在开始编辑php的代码。
首先要定义变量传值的方式为post
$name = $_POST['userName'];
$password = $_POST['userPassword'];
并且不允许用户提交的用户名和密码为空,如果为空则重新跳转至login.php
if($name == null || $password == null){
header("location:login.php");
}
然后开始进行数据库的连接操作,用到的连接方式为new mysqli("127.0.0.1","root","root","php10") ,其中,127.0.0.1是以本机作为服务器,两个root分别为数据库的登录名以及密码,php10为存储login表的数据库。
$con=new mysqli("127.0.0.1","root","root","php10");
if ($con->connect_error)
die('Connect error: ' . $con->connect_error); //若连接失败页面报错
$con->select_db('login'); //若连接成功返回true,连接失败返回false
$sql="SELECT * FROM login WHERE userName='{$name}' AND userPassword='{$password}'";
$result=$con->query($sql); //用result来存储con的query方法来执行数据库查询的命令
$num_users = $result->num_rows; //在数据库中搜索到符合的用户
上述代码需要注意的是$sql的语句一定不能出错,否则页面会报对象引用不正确,需要将用户输入的数据作为SQL查询语句的一部分需要将变量$name,$password用单引号和大括号包住!然后可以直接验证$num_users是否为0,若不为0则表示查询成功,允许用户登录;若为0,则表示查询失败,用户名和密码不存在。
整体action.php的代码如下:
connect_error)
die('Connect error: ' . $con->connect_error);
$con->select_db('login');
$sql="SELECT * FROM login WHERE userName='{$name}' AND userPassword='{$password}'";
$result=$con->query($sql);
$num_users = $result->num_rows;//在数据库中搜索到符合的用户
if($num_users!=0){//搜索到该用户
echo "";
echo "欢迎您{$name}
";
}
else{
echo "";
echo "用户名或密码错误";
}
?>
用同样类似的方法,完成register.php的页面,代码如下:
connect_error<>0){
echo "CONNECT ERROR";
echo $db->connect_error;
exit;
}
$db->query("SET NAMES UTF8");
//if(!empty($name=base64_encode($_POST['userName'])) && !empty($password=base64_encode($_POST['userPassword'])))
if(!empty($name = $_POST['userName']) && !empty($password = $_POST['userPassword']))
{
$sql="INSERT INTO login(userName,userPassword) values('{$name}','{$password}')";
$is=$db->query($sql);
echo "注册成功!,即将跳转至登录页面...
";
header("refresh:3; url = //localhost/login.php");
}
else{
echo "";
echo "注册失败,3秒后自动跳转...
";
header("refresh:3; url = //localhost/login.php");
}
?>
在注册栏提交一组数据
在Navicat中刷新得到一组数据
(因为之前测试了很多组数据然后清空了,所以这一个数据的id不是从1开始的)
在用户登录栏输入刚刚提交的数据,点击登录,显示登录成功,说明靶场搭建成功
在登录页面用户名输入以下字符串,密码随意输入,就能能达到绕过登录的目的
' or 1 = 1 #
登录情况如下:
从php代码的角度来分析出现这种绕过现象的原因,重点关注$sql存储的SQL语句,输入的用户名为' or 1 = 1 #,则$sql则变成:
$sql = "SELECT * FROM login WHERE userName = '{' or 1 = 1 # }' AND userPassword = '{$password}'";
输入的第一个单引号与userName后的第一个引号闭合,输入的 # 将之后的语句全部注释不进行编译,同时,or连接之前的
SELECT * FROM login WHERE userName = 和 1 = 1。而1 = 1显然是恒成立的,所以连起来语句为真,密码部分已经被注释,所以只要密码输入那一栏不为空就可以登录成功。
防御办法很简单,就是永远不要信任用户输入的任何数据,对用户输入的数据进行过滤就可以有效避免这一漏洞,部分过滤函数如下:
addslashes( string ) 用反斜线引用字符串
trim( string ) 去除字符串首位的空白字符或其他字符
strip_tags( string ) 从字符串中去除 HTML 和 PHP 标记
stripslashes( string ) 删除由 addslashes() 函数添加的反斜杠
htmlspecialchars( string ) 转换特殊字符为HTML字符编码
str_repalce(" ' "," string ", $str) 替换字符串中的特殊字符
......
可以自定义一个函数,对输入的数据进行处理
function test_input($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data);
$data = addslashes($data);
$data = strip_tags($data);
return $data;
}
整体代码变为:
connect_error)
die('Connect error: ' . $con->connect_error);
$con->select_db('login');
$sql = "SELECT * FROM login WHERE userName = '{$name}' AND userPassword = '{$password}'";
$result=$con->query($sql);
$num_users = $result->num_rows;//在数据库中搜索到符合的用户
if($num_users!=0){//搜索到该用户
//$name = base64_decode($name);
echo "";
echo "欢迎您{$name}
";
}
else{
echo "";
echo "用户名或密码错误";
}
function test_input($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data);
$data = addslashes($data);
$data = strip_tags($data);
return $data;
}
?>
再一次尝试绕过登录,结果登录失败:
另外一种防止绕过登录的方法就是对输入的数据进行加密后再存入数据库,可以用到的加密函数有
1、不可逆加密函数
md5( )
crypt( )
2、可逆加密函数
base64_encode( ) base64_decode( )
urlencode( ) urldecode( )
......
这里采用base64_encode的方式加密后存入数据库,代码如下:
connect_error<>0){
echo "CONNECT ERROR";
echo $db->connect_error;
exit;
}
$db->query("SET NAMES UTF8");
if(!empty($name=base64_encode($_POST['userName'])) && !empty($password=base64_encode($_POST['userPassword']))) //base64的加密方法
//if(!empty($name = $_POST['userName']) && !empty($password = $_POST['userPassword']))
{
$sql="INSERT INTO login(userName,userPassword) values('{$name}','{$password}')";
$is=$db->query($sql);
echo "注册成功!,即将跳转至登录页面...
";
header("refresh:3; url = //localhost/login.php");
}
else{
echo "";
echo "注册失败,3秒后自动跳转...
";
header("refresh:3; url = //localhost/login.php");
}
?>
再一次注册一个用户名为admin,密码为root的用户
查看数据库存入的数据
发现存入的数据是经过加密处理的
需要注意的是,如果存入数据库的是经过加密的数据,那么在接受用户输入的登录的数据时也需要对其先进行加密再进行匹配查询,否则会出现登录不了的情况。
预防SQL注入的另一种更为有效的方法:PDO以及预处理方法
这里附上笔者另外一篇博客:PDO技术以及预处理方法预防SQL注入
这篇博客中所运用的是最优的预防SQL注入的方法。