一、环境准备

1、在Linux准备一套Xampp或者Phpstudy:模拟攻防

2、在vscode安装Rremote Development插件,进行远程调试

新添加主机

ssh root@192.168.74.132

编辑ssh的配置文件的路径

在opt/lampp/security创建项目

三个页面
login.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <link rel="stylesheet" href="style.css">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" charset="utf-8"></script>
        <style>
            *{
    margin: 0;
    padding: 0;
    text-decoration: none;
    font-family: montserrat;
    box-sizing: border-box;
}

body{
    min-height: 100vh;
    background-image: linear-gradient(120deg, #3498db, #8e44ad);
}

.login-form{
    width: 360px;
    background: #f1f1f1;
    height: 580px;
    padding: 80px 40px;
    border-radius: 10px;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
}

.login-form h1{
    text-align: center;
    margin-bottom: 60px;
}

.txtb{
    border-bottom: 2px solid #adadad;
    position: relative;
    margin: 30px 0;
}

.txtb input{
    font-size: 15px;
    color: #333;
    border: none;
    width: 100%;
    outline: none;
    background: none;
    padding: 0 5px;
    height: 40px;
}

.txtb span::before{
    content: attr(data-placeholder);
    position: absolute;
    top: 50%;
    left: 5px;
    color: #adadad;
    transform: translateY(-50%);
    z-index: -1;
    transition: .5s;
}

.txtb span::after{
    content: '';
    position: absolute;
    left: 0%;
    top: 100%;
    width: 0%;
    height: 2px;
    background: linear-gradient(120deg, #3498db, #8e44ad);
    transition: .5s;
}

.focus + span::before{
    top: -5px;
}

.focus + span::after{
    width: 100%;
}

.logbtn{
    display: block;
    width: 100%;
    height: 50px;
    border: none;
    background: linear-gradient(120deg, #3498db, #8e44ad, #3498db);
    background-size: 200%;
    color: #fff;
    outline: none;
    cursor: pointer;
    transition: .5s;
}

.logbtn:hover{
    background-position: right;
}

.bottom-text{
    margin-top: 60px;
    text-align: center;
    font-size: 13px;
}

        </style>
    </head>
    <body>

        <form action="./login.php" class="login-form" method="post">
            <h1>欢迎登陆</h1>

            <div class="txtb">
                用户名:<input type="text" name="username">
                <span ></span>
            </div>

            <div class="txtb">
                密码:&nbsp;&nbsp;<input type="password" name="password">
                <span ></span>
            </div>

            <div class="txtb">
                验证码:<input type="text" name="vcode">
                <span ></span>
            </div>

            <input type="submit" class="logbtn" value="bt"  name="bt">

            
        </form>

        <script type="text/javascript">
            $(".txtb input").on("focus", function(){
                $(this).addClass("focus");
            });

            $(".txtb input").on("blur", function(){
                if($(this).val() == "")
                $(this).removeClass("focus");
            });
        </script>

    </body>
</html>
login.php
<?php
if(!empty($_POST['bt'])){
    //获取用户提交的登陆信息
    $username = $_POST["username"];
    $password = $_POST["password"];
    $vcode = $_POST["vcode"];
    //万能验证码,存在安全漏洞,owasp-认证和授权失败
    if ($vcode !== "0000"){
        die("vcode-error");
    }
    $conn = new mysqli("127.0.0.1","root","","learn") or die("数据库连接不成功");
    $conn -> query("set names utf8");
    $conn -> set_charset("utf8");
    $sql = "SELECT * FROM user where username ='$username' and password = '$password'";
    $result = $conn -> query($sql);
    //存在注入漏洞
    if($result->num_rows == 1){
        echo "login-pass";
        echo "<script>location.href='./welcome.php'</script>";
    }
    else{
        echo "login-fail";
        echo "<script>location.href='./login.html'</script>";
    }
    
    # 关闭数据库
    $conn -> close(); 
}
welcome.php
<?php
// 以下代码违背了owasp 失效访问控制
    echo "欢迎登陆到安全测试平台";

二、进行登陆的渗透测试

在登陆页面输入一个单引号[']作为用户名,密码任意,验证码0000,响应如下
---------------------------------------------------------------------------------------------------
<br />
<b>Notice</b>:  Trying to get property of non-object in <b>/opt/lampp/htdocs/security/login.php</b> on line <b>17</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 ='zhangsan' and password = 'zhangsan123'
攻击情况:SELECT * FROM user where username =''' and password = '123456'
username : x' or id =1#'
post正文:username=1' or 1=1 #'&password=111&vcode=0000&bt=bt

2、在报错语句中出现了敏感信息:
/opt/lampp/htdocs/security/login.php ,当前代码的绝对路径
注入类攻击的核心点:

(1)、拼接为有效的代码或语句

(2)、确保完成了闭合,并且可以改变原有执行逻辑

通常并非处理一下就可以完成拼接和闭合,需要不停的尝试,知道出现不一样的结果。这个尝试过程建议使用python+字典快速处理

上述代码一共存在6个漏洞

1、welcome.php页面谁都可以访问,没有进行登陆判断(中)
2、在登陆也买你输入 ' 作为用户名报错信息存在login.php的绝对路径,暴露了系统后台的敏感信息(低)
3、保存用户信息的数据表中,密码是明文保存,不够安全(中)
4、登陆页面可以进行SQL注入,进而轻易实现登陆(高)
5、login.php页面中使用了万能验证码(中)
6、登陆功能可以被爆破,没有进行爆破防护(中)

三、漏洞修复

一、使用python进行fuzz测试
# 利用python对php的登陆页面进行fuzz测试
import requests


def login_fuzz():
    url = "http://192.168.74.132/security/login.php"
    data = {"username": f"'", "password": f"{1}", "vcode": "0000", "bt": "bt"}
    resp = requests.post(url, data)
    if "Notice" or "error" or "Warning" in resp.text:
        print("本登陆功能可能存在sql注入漏洞,可以试一试")
        # 如果单引号存在嫌疑则继续利用
        with open("../dict/sql.txt", "r", encoding="utf8") as file:
            # 获取字典列表
            payload_list = file.readlines()
            file.close()
            for payload in payload_list:
                payload = payload.replace("\n","")
                resp = requests.post(url, {"username": f"{payload}", "password": f"12312", "vcode": "0000", "bt": "bt"})
                if "login-fail" not in resp.text:
                    print(f"测试payload成功:{payload}")
    else:
        print("通过测试,发现后台页面对单引号不敏感")


if __name__ == '__main__':
    login_fuzz()
二、任意访问授权
//1、在comm.php中启用session
    
session_start();

//2、welcome.php导入
    
if( empty($_SESSION['islogin']) or $_SESSION["islogin"] != 'true'){
    die("你还没有登陆,请先登陆"."<br>");
}
    echo "欢迎登陆到安全测试平台"."<br>";

//3、在login.php中设置session
    
    if($result->num_rows == 1){
        echo "login-pass";
        # 登陆成功后,记录session变量
        $_SESSION['username'] = $username;
        $_SESSION['islogin'] = 'true';
        echo "<script>location.href='./welcome.php'</script>";
    }
三、敏感信息暴露
//1、在login.php中如果sql语句执行失败

$sql = "SELECT * FROM user where username ='$username' and password = '$password'";
    $result = $conn -> query($sql) or die("SQL语句执行失败");
四、数据库用户密码是明文存储
//1、注册时应该设置为md5,使用php内置的md5函数
//2、将数据库密码字段长度设置为32位及以上
$result = md5("password");
echo $result;

SQL注入的防御

登陆漏洞:登陆页面可以进行SQL注入,进而轻易实现登陆(高)

1、从代码和SQL语句的逻辑层面进行考虑,不能让密码对比失效

2、基于将用户输入的引号(单引号和双引号)继续转义处理的前提,可以使用PHP内置函数addslashes进行强制转义

3、另外一种方法:使用mysqli预处理的功能,也可以进行防御

一、登陆SQL语句的逻辑问题
// sql存在逻辑问题,用户名和密码不应该放在同一条sql中
    //应该先通过查询user表,找到一条记录(用户名唯一),再进行密码的单独对比
    // $sql = "SELECT * FROM user where username ='$username' and password = '$password'";
    $sql = "SELECT * FROM user where username ='$username'";
    $result = $conn -> query($sql) or die("SQL语句执行失败");
// 如果用户名真实存在,刚好找到一条,则单独再继续密码的比,即使出现SQL注入漏洞,但是只要密码正确,也无法登录 
    if($result->num_rows == 1){
        $row = $result->fetch_assoc();
            if($password == $row['password']){
                echo "login-pass";
                # 登陆成功后,记录session变量
                $_SESSION['username'] = $username;
                $_SESSION['islogin'] = 'true';
                echo "<script>location.href='./welcome.php'</script>";
            }
            else{
                // echo "pass-error"; // 不建议直接明确告诉用户,是密码还是用户名错误
                echo "login-fail";
                echo "<script>location.href='./login.html'</script>";
            }
    }
    else{
        echo "login-fail";
        echo "<script>location.href='./login.html'</script>";
    }
二、使用addslashes函数

可以将字符串的单引号双引号,反斜杠,NULL值自动添加转义符,从而方式SQL注入中对单引号和双引号的预防

原始SQL语句如下:
$sql = "SELECT * FROM user where username ='$username'" and 'password' =$password;
如果用户输入 1' or 1=1 #',则SQL语句变成:
$sql = "SELECT * FROM user where username ='1' or 1=1 #''" and 'password' =$password;
如果使用addslashes函数,则变成
$sql = "SELECT * FROM user where username ='1\' or 1=1 #\''" and 'password' =$password;
逻辑上保持不变

$username = addslashes($_POST["username"]);
三、Mysqli面向对象的预处理

预处理的过程:先交给MYSQL数据库进行SQL语句的准备,准备好后再将SQL语句中的参数进行值的替换,引号会进行转义处理,将所有参数变成普通字符串,再进行第二次的正式的SQL语句执行。MYSQLi的预处理既支持面向对象,也支持面向过程

//mysqli预处理
function mysqli_prepare_oop(){
    $conn = new mysqli("127.0.0.1","root","","learn") or die("数据库连接不成功");
    $conn -> set_charset("utf8");
    // ?在预处理语句中表示参数
    $sql = "select * from user where id = ?";   
    // 实例化预处理对象
    $stmt = $conn ->prepare($sql);
    //实例化后需要将参数进行绑定,并在执行时进行替换,bind_param第一个参数位数据类型:i整数,s字符串,d小数,b二进制
    $stmt -> bind_param("i", $userid);
    $userid = 1;
    //正式执行sql语句,如果是更新类的操作,update,delete,insert,执行后不做其他操作没问题
    // $stmt->execute();   //execute方法返回不二类型,返回执行成功还是失败

    //如果针对 查询语句,单纯只是执行无法取得查询结果的,需要进行结果绑定
    $stmt->bind_result($id,$username,$password);
    $stmt->execute();
    // 调用结果并进行处理
    $stmt ->store_result();
    // 输出行数
    echo $stmt->num_rows()."<br>";
    //遍历结果
    while ($stmt->fetch()){
        echo $userid."&nbsp;&nbsp;&nbsp;".$username."&nbsp;&nbsp;&nbsp;".$password."<br>";
    }
}

2、使用预处理防止SQL注入

$sql = "SELECT * FROM user where username =?";
    // $result = $conn -> query($sql) or die("SQL语句执行失败");
    $stmt = $conn->prepare($sql);
    $stmt->bind_param('i',$username);  //绑定查询参数
    $stmt->bind_result($id,$username2,$password2);  //绑定结果参数
    $stmt->execute();
    $stmt->store_result();

    if($stmt->num_rows() == 1){
        $row = $stmt->fetch();
            if($password == $password2){
                echo "login-pass";
                # 登陆成功后,记录session变量
                $_SESSION['username'] = $username;
                $_SESSION['islogin'] = 'true';
                echo "<script>location.href='./welcome.php'</script>";
            }
            else{
                // echo "pass-error"; // 不建议直接明确告诉用户,是密码还是用户名错误
                echo "login-fail";
                echo "<script>location.href='./login.html'</script>";
            }
    }
    else{
        echo "login-fail";
        echo "<script>location.href='./login.html'</script>";
    }
Logo

更多推荐