sqli-labs通关笔记-第24关 SQL二次注入(单引号闭合)
目录
目录
一、二次注入
二、相关函数
1、limit函数
2、mysqli_real_escape_string
三、源码分析
1、注册源码
2、登录源码
3、修改密码源码
四、渗透实战
1、进入靶场
2、注入点分析
3、渗透实战
(1)注册新用户
(2)查看数据库
(3)用户登录
(4)修改密码
SQLI-LABS 是一个专门为学习和练习 SQL 注入技术而设计的开源靶场环境,本小节对第24关Less 24基于字符型的SQL二次注入关卡进行渗透实战。
一、二次注入
SQL 注入指攻击者通过在输入字段中插入恶意 SQL 代码来改变原 SQL 语句的逻辑。字符型注入通常发生在SQL 语句使用单引号或者双引号等包裹字符串参数的场景中。攻击者通过闭合单引号或者双引号等符号并注入额外的 SQL 代码,破坏原有语句结构。
SQL 二次注入是指攻击者在第一次输入数据时,将恶意 SQL 语句隐藏在看似正常的数据中,这些数据被存储在数据库中。当应用程序在后续的操作中再次使用这些存储的数据进行 SQL 查询时,恶意语句被执行,从而导致数据库被攻击。
SQL二次注入可以分为两个步骤,具体如下所示。
- 第一步:数据库中插入恶意数据(存储阶段):在向数据库插入数据时,对其中的特殊字符进行了转义处理(比如单引号'转义后变为\'),转义后的数据被存储到数据库。
- 第二步:执行恶意SQL代码(执行截断):开发者默在从数据库中数据时,未对其进行再次转义而直接拼接到新的SQL语句中(存储时转义了单引号,但取出时未转义),在执行SQL语句时形成二次注入。
二、相关函数
1、limit函数
Limit是 SQL 中用于限制查询结果数量的子句,不是真正的函数。Limit通常有两种常见形式,具体如下所示。
-
单参数形式:
LIMIT n
-
返回前 n 条记录
-
示例:
LIMIT 5
返回前5条结果
-
-
双参数形式:
LIMIT offset, count
-
offset
:跳过的记录数(从0开始) -
count
:要返回的记录数 -
示例:
LIMIT 10, 5
跳过前10条,返回接下来的5条
-
举例:SQL语句“SELECT * FROM users WHERE id='$id' LIMIT 0,1”中的LIMIT 0,1"表示获取第一条匹配的记录",LIMIT0,1的具体含义如下所示,
-
从第0条记录开始(即不跳过任何记录)
-
只返回1条记录
2、mysqli_real_escape_string
mysqli_real_escape_string()
是 PHP 中用于防止 SQL 注入攻击 的核心函数之一,功能是对用户输入的字符串进行转义处理,确保字符串中的特殊字符(如 SQL 语法中的引号、注释符等)被正确转义,从而避免恶意输入破坏 SQL 语句的结构。
mysqli_real_escape_string()
会转义 SQL 中具有特殊含义的字符,部分举例如下所示。
原始字符 | 转义后字符 |
---|---|
' (单引号) | \' |
" (双引号) | \" |
\ (反斜杠) | \\ |
NULL | \0 |
三、源码分析
本关卡Less24是基于字符型的SQL注入关卡,相关源码如下所示。
1、注册源码
Less24关卡new_user.php功能的源码功能是注册过程,详细注释后的源码如下所示。
<?php
session_start(); // 启动会话
?>
<div align="right"><!-- 右侧导航链接:返回主页 --><a style="font-size:.8em;color:#FFFF00" href='index.php'><img src="../images/Home.png" height='45' width='45'><br>HOME</a>
</div>
<?php
// 引入数据库连接配置
include("../sql-connections/sqli-connect.php");if (isset($_POST['submit'])) { // 检测表单提交// 获取用户输入并使用mysqli_real_escape_string转义$username = mysqli_real_escape_string($con1, $_POST['username']);$pass = mysqli_real_escape_string($con1, $_POST['password']);$re_pass = mysqli_real_escape_string($con1, $_POST['re_password']);echo "<font size='3' color='#FFFF00'>";// 检查用户名是否已存在(查询数据库)$sql = "SELECT COUNT(*) FROM users WHERE username='$username'";$res = mysqli_query($con1, $sql) or die('You tried to be smart, Try harder!!!! :( ');$row = mysqli_fetch_row($res);// 判断用户名是否存在if (!$row[0] == 0) { // 若存在(COUNT(*)不为0)?><script>alert("The username Already exists, Please choose a different username ")</script>;<?phpheader('refresh:1, url=new_user.php'); // 重定向回注册页} else { // 用户名不存在if ($pass == $re_pass) { // 验证两次密码是否一致// 插入新用户数据(使用双引号转义,且参数已转义)$sql = "INSERT INTO users (username, password) VALUES(\"$username\", \"$pass\")";mysqli_query($con1, $sql) or die('Error Creating your user account, : ' . mysqli_error($con1));echo "<center><img src=../images/Less-24-user-created.jpg><font size='3' color='#FFFF00'>";echo "<br>Redirecting you to login page in 5 sec................";echo "<br>If it does not redirect, click the home button on top right</center>";header('refresh:5, url=index.php'); // 注册成功后重定向到登录页} else { // 密码不一致?><script>alert('Please make sure that password field and retype password match correctly')</script><?phpheader('refresh:1, url=new_user.php'); // 重定向回注册页}}echo "</font>";
}
?>
该代码是一个用户注册功能模块,主要逻辑如下:
- 接收注册数据:获取用户提交的用户名、密码和重复密码。
- 输入转义:使用
mysqli_real_escape_string
对用户名和密码进行 SQL 转义。 - 用户名查重:查询数据库检查用户名是否已存在。
- 密码验证:对比两次输入的密码是否一致。
- 用户创建:若通过验证,将用户数据插入数据库,并提示注册成功。
本关卡的关键源码如下所示。
// 获取用户输入并使用mysqli_real_escape_string转义$username = mysqli_real_escape_string($con1, $_POST['username']);$pass = mysqli_real_escape_string($con1, $_POST['password']);$re_pass = mysqli_real_escape_string($con1, $_POST['re_password']);// 插入新用户数据(使用双引号转义,且参数已转义)$sql = "INSERT INTO users (username, password) VALUES(\"$username\", \"$pass\")";
2、登录源码
Less24关卡login.php功能的源码功能是登录过程,详细注释后的源码如下所示。
<?php
session_start(); // 启动会话管理
// 包含数据库连接配置文件
include("../sql-connections/sqli-connect.php");/*** 处理用户登录验证的函数* @param mysqli $con1 数据库连接对象* @return string|int 登录成功返回用户名,失败返回0*/
function sqllogin($con1) {// 获取并转义用户输入的用户名和密码$username = mysqli_real_escape_string($con1, $_POST["login_user"]);$password = mysqli_real_escape_string($con1, $_POST["login_password"]);// 构造SQL查询语句(字符串拼接方式)$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";// 执行SQL查询,失败时输出错误信息$res = mysqli_query($con1, $sql) or die('You tried to be real smart, Try harder!!!! :( ');$row = mysqli_fetch_row($res);// 判断查询结果:若存在则返回用户名,否则返回0if ($row[1]) {return $row[1];} else {return 0;}
}// 执行登录验证
$login = sqllogin($con1);// 根据验证结果处理后续逻辑
if (!$login == 0) { // 登录成功$_SESSION["username"] = $login; // 存储用户名到会话setcookie("Auth", 1, time() + 3600); // 设置认证Cookie(有效期1小时)header('Location: logged-in.php'); // 重定向到登录后页面
} else { // 登录失败// 显示失败提示图片?><tr><td colspan="2" style="text-align:center;"><br/><p style="color:#FF0000;"><center><img src="../images/slap1.jpg"></center></p></td></tr><?PHP
}
?>
这是一个基本的用户登录验证功能:
- 接收用户输入:从 POST 请求中获取用户名(
login_user
)和密码(login_password
)。 - 输入转义:使用
mysqli_real_escape_string
对输入进行转义,防止 SQL 注入。 - 数据库查询:查询
users
表中匹配用户名和密码的记录。 - 验证结果处理:
- 成功:将会话变量
username
设置为登录用户名,并创建有效期 1 小时的认证 Cookie,然后重定向到logged-in.php
。 - 失败:显示错误提示图片(
slap1.jpg
)。
- 成功:将会话变量
3、修改密码源码
Less23关卡pass_change.php功能的源码功能是修改密码,详细注释后的源码如下所示。
<html>
<head>
</head>
<body bgcolor="#000000">
<?PHP
// 开启Session
session_start();// 认证检查:必须同时存在Auth Cookie和username Session
if (!isset($_COOKIE["Auth"])) {if (!isset($_SESSION["username"])) {header('Location: index.php'); // 未登录重定向}header('Location: index.php'); // 冗余重定向
}
?><!-- 首页链接 -->
<div align="right"><a style="font-size:.8em;color:#FFFF00" href='index.php'><img src="../images/Home.png" height='45'; width='45'></br>HOME</a>
</div><?php
// 包含数据库连接配置
include("../sql-connections/sqli-connect.php");// 处理密码修改请求
if (isset($_POST['submit'])) {// 获取并过滤输入$username = $_SESSION["username"]; // 从Session获取用户名$curr_pass = mysqli_real_escape_string($con1, $_POST['current_password']); // 当前密码$pass = mysqli_real_escape_string($con1, $_POST['password']); // 新密码$re_pass = mysqli_real_escape_string($con1, $_POST['re_password']); // 确认密码// 验证两次新密码是否一致if($pass == $re_pass) { // 构建并执行密码更新SQL$sql = "UPDATE users SET PASSWORD='$pass' WHERE username='$username' AND password='$curr_pass'";$res = mysqli_query($con1, $sql) or die('You tried to be smart, Try harder!!!! :( ');$row = mysqli_affected_rows($con1);echo '<font size="3" color="#FFFF00">';echo '<center>';if($row == 1) {echo "Password successfully updated";} else {header('Location: failed.php'); // 更新失败重定向}} else {// 密码不一致处理echo '<font size="5" color="#FFFF00"><center>';echo "Make sure New Password and Retype Password fields have same value";header('refresh:2, url=index.php'); // 2秒后重定向}
}// 处理登出请求
if(isset($_POST['submit1'])) {session_destroy(); // 销毁Sessionsetcookie('Auth', 1 , time()-3600); // 使Cookie过期header('Location: index.php'); // 重定向到首页
}
?>
</center>
</body>
</html>
本代码的如下update更新的SQL注入语句有SQL注入风险,原因如下所示。
$username = $_SESSION["username"]; // 从Session获取用户名
$curr_pass = mysqli_real_escape_string($con1, $_POST['current_password']); // 当前密码
$pass = mysqli_real_escape_string($con1, $_POST['password']); // 新密码
$re_pass = mysqli_real_escape_string($con1, $_POST['re_password']); // 确认密码$sql = "UPDATE users SET PASSWORD='$pass' WHERE username='$username' AND password='$curr_pass'";
-
$pass
和$curr_pass
虽然经过mysqli_real_escape_string
过滤,但$username
直接从Session获取未过滤。 -
攻击者可篡改Session伪造管理员用户名实现垂直越权修改密码。
四、渗透实战
1、进入靶场
进入sqli-labs靶场首页,其中包含基础注入关卡、进阶挑战关卡、特殊技术关卡三部分有效关卡,如下所示。
http://127.0.0.1/sqli-labs/
点击进入Page2,如下图红框所示。
其中第24关在进阶挑战关卡“SQLi-LABS Page-2 (Adv Injections)”中, 点击进入如下页面。
http://127.0.0.1/sqli-labs/index-1.html#fm_imagemap
点击上图红框的Less24关卡,进入到靶场的第24关卡二次注入关卡,页面有3个基本功能,分别是登录、忘记密码和注册新用户,具体如下所示。
http://127.0.0.1/sqli-labs/Less-24/
2、注入点分析
根据源码分析可知,本关卡基于GET方法传入参数id,仅对id过滤#和-- 这两个注释符号,但未处理其他可能的注入符号。本关卡的注入点为id,SQL语句的含义是根据用户提供的ID值(字符型)从users表中查询并返回匹配的用户记录的所有字段信息,且仅返回第一条匹配结果,具体代码如下所示。
$id = $_GET['id']; // 获取id参数值// 过滤注释符号(#和--),防止使用注释绕过检测$reg = "/#/"; // 匹配#符号的正则表达式$reg1 = "/--/"; // 匹配--符号的正则表达式$replace = ""; // 替换为空字符串$id = preg_replace($reg, $replace, $id); // 替换#为空$id = preg_replace($reg1, $replace, $id); // 替换--为空// 构造SQL查询(存在SQL注入安全风险)$sql = "SELECT * FROM users WHERE id='$id' LIMIT 0,1";
根据 id='$id'可知闭合方式为单引号,虽然本关卡过滤了注释符号,但是由于本关卡只查询一个参数id,注入语句中不包含注释符号也不影响注入,id被单引号包裹故而SQL注入为字符型注入。
3、渗透实战
(1)注册新用户
bp开启拦截功能,进入到靶场的第24关卡,如下所示。
点击注册进入如下页面,此时新用户名设置为admin'#mooyuan,密码为123456,如下所示。
点击Register,此时页面先是说注册成功很快又跳到回了登录首页,如下所示。
bp抓包,发现一共产生两个报文,第一个是POST报文,服务器返回注册成功,如下所示。
第二个包则是注册成功后重定向到了登陆页面,具体如下所示。
(2)查看数据库
使用navicat查看数据库security中的user表,发现多了一条数据,用户名为admin'#mooyuan,密码为123456,正是我们第一步注册的新用户,很明显存储的内容并没有对单引号进行转义。特别注意admin账户的密码还是admin,具体如下所示。
(3)用户登录
回到登录页面,输入用户名admin'#mooyuan,密码123456登录,如下所示。
登录后效果如下所示,页面显示登录成功,“YOU ARE LOGGED IN AS admin'#mooyuan”,下面还有一个修改密码的输入框(包括当前密码,新密码,确认密码),具体如下所示。
(4)修改密码
将密码由123456修改为mooyuan123456,如下所示。
点击reset密码,如下所示提示修改成功。
原理如下所示,由于用户名此时为admin'#mooyuan,此时新密码为mooyuan123456,由于SQL的update语句如下所示。
$username = $_SESSION["username"]; // 从Session获取用户名
$curr_pass = mysqli_real_escape_string($con1, $_POST['current_password']); // 当前密码
$pass = mysqli_real_escape_string($con1, $_POST['password']); // 新密码$sql = "UPDATE users SET PASSWORD='$pass' WHERE username='$username' AND password='$curr_pass'";
-
$username
直接从Session获取未过滤,故而sql语句更新后变为如下内容。
$sql = "UPDATE users SET PASSWORD='mooyuan123456' WHERE username='admin'#' AND password='123456'";
这样SQL语句就变为如下内容,从而可以修改admin的密码,进而越权修改密码成功。
UPDATE users SET PASSWORD='mooyuan123456' WHERE username='admin'
(5)查看admin用户的密码
使用navicat查看数据库security中的user表,发现多了一条数据,用户名为admin'#mooyuan,密码为123456,并没有改变义。特别注意admin账户的密码却由admin变为了mooyuan123456,从而实现了越权修改amdin账户的密码,具体如下所示。