模块1 网站搭建

  • 在docker环境下,使用现有php-apache和mysql的镜像构建网站环境

  • 网站主界面如下:

  • 点击留言可以查看详细的留言信息,界面如下:

模块2 查找网站漏洞

  • 利用漏洞扫描工具(Bursuite和AWVS)对网站进行风险评估和分析漏洞
    1. 扫描结果

      • 使用 Bursuite 工具分析

        发现 14 处安全问题,其中包括常见的跨站脚本攻击、数据库注入等

      • 使用 AWVS 工具分析

        发现 13 处安全问题,与 Bursuite 扫描到的问题一致(因标准不同少1处错误为正常现象)

    2. 分析漏洞

      • 跨站脚本攻击:没有对用户输入进行过滤,例如 HTML 标签、JavaScript 脚本,这样会导致网页中被插入恶意代码,从而使用用户的 Cookie、身份信息等敏感内容被盗取,或者在用户不知情的情况下,对用户的浏览器进行恶意操作。
      • SQL 注入:没有对数据库查询进行过滤,这样会导致数据库被恶意修改,从而导致数据泄露、篡改等。
      • 明文传输:在网站请求中所有内容均为明文传输,并未使用 HTTPS 协议进行基本的加密,这样有很大可能因中间人攻击导致用户的敏感信息被窃取。
      • CORS 头未正确配置:CORS 头直接设置为 * 是非常不安全的,应该执行白名单机制,只允许指定的域名访问。

模块3 防范漏洞

  • 防范跨站脚本攻击与 HTML 转义

    1. 触发过程
      • 在留言板 index_low.php 中提交新留言,名字为 <script>alert(1)</script>,内容为 <script>alert(1)</script>,提交后,点击留言
      • 在留言查看页面 show_low.php 中,可以看到弹出框,说明存在跨站脚本攻击。
    2. 漏洞修复
      • low.php 的第 21 行与第 24 行中换行,使用 htmlspecialchars 函数转义用户名与留言内容中包含的 HTML 标签与 JavaScript 脚本,防止新增留言出现跨站脚本攻击。

      • show_log.php 的第 32 行被注解的正则替换虽然能防止 JavaScript 脚本攻击,但还是会正常渲染其他 HTML 标签(例如 img 标签,也能用于 xss 攻击,img 标签中的 onerror 可以包含 JavaScript 脚本),因此还是需要使用 htmlspecialchars 函数转义数据库中曾经包含的 HTML 标签的内容。

  • 防范 SQL 注入

    1. 触发过程

      • 留言查看页面 show_low.php 的请求中包含 ?id=1 之类的请求,由于没对传入参数进行过滤,可尝试注入。
        • 尝试在 id 中加入 1' AND (SELECT+IF(LENGTH(DATABASE())>2,1,0));-- 来尝试判断数据库名字长度,在 LENGTH(DATABASE()) > 3 时没再返回任何内容,则能得出数据库名字长度为 3。
    2. 漏洞修复

      此处漏洞存在于 low.phpshow_low.php

      • 留言提交接口 low.php 中,向数据库插入信息的 SQL 语句 INSERT INTO guestbooklow ( comment_id,comment, name ) VALUES ( '$comment_id','$message','$name' ); 中的 $comment_id $message $name 均用 ? 代替,然后使用 prepare 函数预处理 SQL 语句,将 SQL 语句中的 ? 替换为实际的参数,详细请看下图与修改的主要代码

        • 代码部分
          // 原本代码
          // 在第 17 行至第 34 行
          $message = trim( $_POST[ 'mtxMessage' ] );
          $name    = trim( $_POST[ 'txtName' ] );
          

        // Sanitize message input
        $message = stripslashes( $message ); //stripslashes()删除反斜杠
        $message = mysqli_real_escape_string($conn,$message);
        // Sanitize name input
        $name =mysqli_real_escape_string($conn,$name);
        //get count
        $get_count=“SELECT COUNT(*) FROM guestbooklow;“;
        //$result = mysqli_query($conn, $query ) or die( '

        ' . ((is_object($conn)) ? mysqli_error($conn) : (($mysqli_res = mysqli_connect_error()) ? $mysqli_res : false)) . '
        ' );
        $r_count=mysqli_query($conn, $get_count );
        $row = mysqli_fetch_row( $r_count );
        $comment_id=$row[0]+1;
        // Update database
        $query = “INSERT INTO guestbooklow ( comment_id,comment, name ) VALUES ( '$comment_id','$message', '$name' );“;
        $result = mysqli_query($conn, $query ) or die( '
        ' . ((is_object($conn)) ? mysqli_error($conn) : (($mysqli_res = mysqli_connect_error()) ? $mysqli_res : false)) . '
        ' );

        // ====================修改后的代码====================
        // 从 17 行开始替换
        // Sanitize message input
        $message = stripslashes( $message ); //stripslashes()删除反斜杠
        // 转义 HTML 标签
        $message = htmlspecialchars($message);
        $message = mysqli_real_escape_string($conn,$message);

        // Sanitize name input
        // 转义 HTML 标签
        $name = htmlspecialchars($name);
        $name =mysqli_real_escape_string($conn,$name);
        //get count
        $get_count=“SELECT COUNT(*) FROM guestbooklow;“;
        //$result = mysqli_query($conn, $query ) or die( '

        ' . ((is_object($conn)) ? mysqli_error($conn) : (($mysqli_res = mysqli_connect_error()) ? $mysqli_res : false)) . '
        ' );
        $r_count=mysqli_query($conn, $get_count );
        $row = mysqli_fetch_row( $r_count );
        $comment_id=$row[0]+1;
        // Update database
        // 将 $comment_id $message $name 变量都替换为 ? 进行占位
        $query = “INSERT INTO guestbooklow ( comment_id,comment, name ) VALUES ( ?,?,? );“;
        // 对 SQL 语句进行预处理
        if ($pre = $conn->prepare($query)) {

        // 占位符与变量绑定
        // bind_param() 第一个传入参数为数据类型
        // $comment_id(评论 ID)为数字则为 int,$message(留言内容)与 $name(评论者昵称)是字符串则为 string,所以传入的第一个参数为三个类型的首字母缩写 iss
        $pre->bind_param("iss", $comment_id, $message, $name);
        // 执行数据库查询
        $pre->execute();
        // 关闭数据库链接
        $pre->close();
        

        } else {

        die( '<pre>' . ((is_object($conn)) ? mysqli_error($conn) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
        

        }

      • 留言展示页面 show_low.php 中,使用 prepare 函数预处理 SQL 语句,将 SQL 语句 SELECT comment FROM guestbooklow WHERE comment_id='$id' 中的 '$id' 替换为 ? 来进行占位,并使用 bind_param 来绑定参数。详细请看下图

        • 代码部分
          // 原本代码
          // 在第 24 到 32 行
          $query  = "SELECT comment FROM guestbooklow WHERE comment_id='$id';";
          $result = mysqli_query($conn,  $query );
          if(!$result)
          {
          echo "yuju fail connet";
          }
          $row = mysqli_fetch_row( $result );
          $result_id=$row[0];
          //$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] ); 
          

        // ====================修改后的代码====================
        // 从 24 行开始替换
        // 将 '$id' 替换为 ? 进行占位
        $query = “SELECT comment FROM guestbooklow WHERE comment_id=?;“;

        // 使用 prepare 对 SQL 语句进行预处理,过滤不安全字符
        if ($pre = $conn->prepare($query)) {

        // 使用 bind_param 与 SQL 语句中的占位符进行绑定
        // bind_param 的第一个传入参数为占位符相绑定的数据类型
        // 例如:$id 的类型为 int 则应该为 bind_param("i", $id)
        $pre->bind_param("i", $id);
        // 执行语句查询
        $pre->execute();
        // 将查询结果绑定到变量 $message 中
        $pre->bind_result($message);
        // fetch() 将会返回数据总条数,在此需要使用 while 循环来为 $result_id 赋值
        // 此处因为是查询评论 id 所以结果只会有 1 条,循环并不可能会出现变量被覆盖的现象
        while($pre->fetch()) {
            $result_id = $message;
        }
        // 关闭数据库链接
        $pre->close();
        

        } else {

        echo "yuju fail connet";
        exit;
        

        }
        //$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t /i', '', $_GET[ 'name' ] );
        // 转义 HTML 标签
        $result_id = htmlspecialchars($result_id);