基于约束的SQL攻击

会有一股神秘感。 提交于 2020-01-20 18:45:06

首先需要注意的是,本文所述的是基于约束的SQL攻击而非SQL注入攻击

碰到这个问题,是在做CTF题时碰到的(题目网址:http://123.206.31.85:49163/) 

这一道题是要求我们以管理员admin的身份登陆系统方可查看flag,方法就是在注册时注册一个admin (admin后面带一个或几个空格),然后登录系统,系统会误认为是管理员登录了系统,所以赋予管理员权限。

那么,就算我们采用PHP中的PDO以及预查寻的方式(详见简单的PDO技术以及预处理方法预防SQL注入)来处理是否能预防基于约束的SQL攻击呢?这里给出靶场的源代码,环境使用的是Windows上的phpstudy(php-5.6.27+apache)

前端代码loginPDO.php

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>登录界面</title>
	</head>
	<body>
		<form action = "actionPDO.php" method = "post">
			<div id = "main" class = "main">
				<center>
					<h2>
						登录
					</h2>
					<p><lable>用户名称:</lable><input type="text" name="userName" placeholder="*************"></p>
					<p><lable>用户密码:</lable><input type="text" name="userPassword" placeholder="*************"></p>
				
					<input type="submit" name="submit" value="登录">
					<input type="reset" value="清空">
				</center>	
		</form>	

		<form action = "registerPDO.php" method = "post">
			<center>
				<h2>
					注册
				</h2>
					<p><lable>用户名称:</lable><input type="text" name="userName" placeholder="*************"></p>
					<p><lable>用户密码:</lable><input type="text" name="userPassword" placeholder="*************"></p>
					<input type="submit" name="submit" value="注册">
					<input type="reset" value="清空">
			</center>
		</form>
	</body>
</html>

注册模块代码registerPDO.php

<?php
	$name = $_POST['userName'];
	$password = $_POST['userPassword'];
	if($name == null || $password == null){
		header("location:loginPDO.php");
		return;
	}
	try {
	    //创建PDO对象
	    $pdo = new PDO("mysql:host=localhost;dbname=php10", "root", "root");
	}catch(PDOException $e) {
	    echo "数据库连接失败:".$e->getMessage();
	    exit;
	}
	$sql = "INSERT INTO loginPDO(userName,userPassword) VALUES (?,?)";
	//$sql = "INSERT INTO loginPDO(userName,userPassword) VALUES (:userName,:userPassword)";
	$stmt = $pdo->prepare($sql);
	$stmt->bindParam(1,$name);
	$stmt->bindParam(2,$password);
	//$stmt->bindParam(":userName",$name);		//绑定 一个 PHP 变量到预处理语句中对应的命名占位符或问号占位符
	//$stmt->bindParam(":userPassword",$password);
	$pdo->quote($name);
	$pdo->quote($password);
	$result = $stmt->execute();
		if($result){
			echo "<script>alert('注册成功')</script>";
			echo "<h2>注册成功!,即将跳转至登录页面...</h2>";
			header("refresh:3; url = //localhost/loginPDO.php");
		}else{
			echo "<script>alert('注册值错误!')</script>";
			echo "<h2>注册失败,用户名已经注册或注册值为空...</h2>";
		}
?>

 登陆模块actionPDO.php

<?php
	$name = $_POST['userName'];
	$password = $_POST['userPassword'];
    if($name == null || $password == null){
		header("location:loginPDO.php");
		return;
	}
	try {
	    //创建对象
	    $con = new PDO("mysql:host=localhost;dbname=php10", "root", "root");
	}catch(PDOException $e) {
	    echo "数据库连接失败:".$e->getMessage();
	    exit;
	}
	$sql = "SELECT * FROM loginPDO WHERE userName = :userName AND userPassword = :userPassword";
	//$sql = "SELECT * FROM login WHERE userName = ? AND userPassword = ?";
	$stmt = $con->prepare($sql);
	$stmt->bindParam(':userName',$name);    //bindParam方法与bindValue方法的区别在于bindParam的第二个参数可以传值用变量,而bindValue第二个参数只能传值用常量或字符串
	$stmt->bindParam(':userPassword',$password);
	//$stmt->bindParam('1',$name);
    //$stmt->bindParam('2',$password);
	$con->quote($name);          //quote方法是为普通的字符串添加引号
	$con->quote($password);
	$re=$stmt->execute();
	if($stmt->rowCount()!=0){
		echo "<script>alert('登录成功!')</script>";
		echo "<h2>欢迎您{$name}</h2>";
	}else{
		echo "<script>alert('登录失败!')</script>";
		echo "<h2>登录失败,3秒后自动跳转...</h2>";
		header("refresh:3; url = //localhost/loginPDO.php");
	}
?>

靶场效果如下

根据数据库中的初始数据,账号admin,密码root可以成功登录,注册不允许有相同的用户名。

首先我尝试了在admin后面加一个空格,密码不变,提示登录成功

 

       然后尝试注册一个后面带空格的admin,结果提示注册失败,注册用户已存在,并不像刚开始所期望的那样。于是我猜测是否是因为bindParam()在绑定参数的时候去掉了末尾的空格,于是开始翻阅php手册,结果并没有发现手册中有提到bindParam()会去掉参数的首尾空格

       于是换一个方式,登录时在admin前面加一个空格,密码不变,点击登录提示登陆失败

 

再注册一个admin前有一个空格的用户,结果发现注册成功

 

 

       那么猜测应该是成立的,bindParam()会截断绑定参数值后面的空格,而不会截断前面的空格,也就是允许注册用户名的第一个字符为空格

 

       也就是说,在一个有严格权限区分的系统,如果得知了管理员的用户名,可以利用管理员用户名后加空格的方法注册,再登录,系统也会默认为管理员,类似CTF的这道题一样,产生基于约束的SQL攻击

 

防御方法

1、使用过滤空格的函数,$data = trim($data),去掉传入参数的首位空格,再进行数据库查询、插入等工作

2、对用户名的长度进行限制

3、验证成功后返回的必须是用户传递进来的用户名,而不是从数据库取出的用户名。因为当我们以用户admin和密码root登陆时,其实数据库返回的是我们自己的用户信息,而我们的用户名其实是[admin      ],如果此后的业务逻辑以该用户名为准,那么就不能达到越权的目的了。

4、将要求或者预期具有唯一性的那些列加上UNIQUE约束

5、使用id作为数据库表的主键并设置自动递增,并且数据应该通过程序中的id进行跟踪

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!