php 单页面登陆注册实例,附 XSS 攻击试验实例

 php 单页面登陆、注册试验程序

  包括前后端验证,后端防 XSS 攻击,不包括 SQL 注入内容;密码散列值(hash)的创建与哈希验证。

/* 这是 php 官方推荐的密码处理函数 */
password_hash() //创建密码的散列(hash)
password_verify() //验证密码是否和指定的散列值匹配。

  程序如下,分三部分:php 部分、html部分和javascript部分:

<?php
/**
 * php 后端程序部分 #############################
 * 
 */
 
class manage{
    
    /**
     * 对 POST 请求进行过滤
     * 
     */
    public static function post($input){
        
        //既转换双引号也转换单引号。
        $str = htmlspecialchars($_POST[$input], ENT_QUOTES);
        return substr($str, 0, 30);
    }
    
    /**
     * 对 GET 请求进行过滤
     * 
     */
    public static function get($input){
        $str = htmlspecialchars($_GET[$input], ENT_QUOTES);
        return substr($str, 0, 30);
    }
    
    /**
     * 如果用上面的 POST、GET 过滤觉得万事大吉了,
     * 那也不一定够用,因为可以把非法代码进行各种
     * 形式的编码,使漏洞防不胜防。
     * 
     * 请参考下面的链接到 XSS 速查表,替换一些特征
     * 字符,把漏洞降到最低水平。
     * https://www.freebuf.com/news/153055.html
     */
    public static function customFilter($str){
        
        /**
         * 字符串替换函数替换敏感字符串
         */
         
        //搜索字符串示例
        $findStr = array(
            '%' //防十六进制编码
            //...省略
            );
            
        //替换字符串
        $replaceStr = array(
            ''
            //...省略
            );
            
        $str1 = str_replace($findStr, $replaceStr, $str);
        
        /**
         * 正则表达式替换敏感字符
         * 
         */
         
        //正则搜索字符串示例
        $patterns = array(
            '/javascript:/i',
            '/<script.*/script>/i'
            //...省略
            );
            
        //替换字符串
        $replacements = array(
            '[forbid]',
            '[forbid]',
            //...省略
            );
            
        return preg_replace($patterns, $replacements, $str1);
        
    }
}

//就当数据库来用
$file = 'data.txt';
 
//【1】登陆处理
if(filter_has_var(INPUT_POST, "isVerify")){
    $isVer = manage::post('isVerify');
    if($isVer === 'display'){
        $email = manage::post('email');
        $pass  = manage::post('pwd');
        
        // filter 过滤器进行邮箱合规性检查
        if(!filter_var($email, FILTER_VALIDATE_EMAIL)){
            exit('输入不是一个合法邮箱,服务器已拒绝执行!');
        }else if(empty($pass)){
            exit('密码不能为空!');
        }else{
            $email = manage::customFilter($email);
            $content  = file_get_contents($file);
            $data = json_decode($content);
            
            //验证邮箱是否匹配
            $isUser = $email === $data->email;
            
            /**
             * 验证密码是否和指定的散列值匹配。
             * 
             * 注:时序攻击(timing attacks)对此函数不起作用。
             * 
             */
            $isPass = password_verify($pass, $data->password);
            
            if($isUser && $isPass){
                echo '<script>alert("登陆成功!")</script>';
            }else{
                echo '<script>alert("登陆失败!")</script>';
            }
        }
    }
}

//【2】注册处理
if(filter_has_var(INPUT_POST, 'isRegister')){
    $isReg = manage::post('isRegister');
    if($isReg === 'display'){
        $data['email'] = manage::post('email');
        $data['password']  = manage::post('pwd');
        
        // filter 过滤器进行邮箱合规性检查
        if(!filter_var($data['email'], FILTER_VALIDATE_EMAIL)){
            exit('不是一个合法邮箱,服务器已拒绝!');
        }else if(empty($data['password'])){
            exit('密码不能为空!');
        }else if(strlen($data['password']) < 6){
            exit('密码至少6位!');
        }else{
            $data['email'] = manage::customFilter($data['email']);
            /**
             * 创建密码的散列(hash)
             * 
             * 1.使用默认算法散列密码,当前是 BCRYPT,并会产生 60 个字符的结果。
             *   需要储存的空间能够超过 60 字符(255字符不错)。
             * 2.注意 password_hash() 返回的散列包含了算法、 cost 和盐值。
             *   因此,所有需要的信息都包含内。使得验证函数不需要储存额外盐值等信息即可验证哈希。
             */
            $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
            file_put_contents($file, json_encode($data));
            echo '<script>alert("注册成功!")</script>';
        }
    }
}

$tag = '';

//接收参数,用于判断是否显示登陆页、注册页
if(filter_has_var(INPUT_GET, "tag")){
    $tag   = manage::get('tag');
}

switch($tag){
    case 'register';
        $title  = '注册实例';
        $decide = false;
        $hidd   = '<input type="hidden" name="isRegister" value="display">';
        $butt   = '注册';
        break;
    case 'login':
    default:
        $title  = '登陆实例';
        $decide = true;
        $hidd   = '<input type="hidden" name="isVerify" value="display">';
        $butt   = '登陆';
}

?>
<?php
/**
 * HTML 部分 #############################
 * 
 */
?>
<!DOCTYPE html>
<html>
    <head>
        <title><?=$title?></title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/css/bootstrap.min.css">
        <script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
        <script src="https://cdn.staticfile.org/popper.js/1.12.5/umd/popper.min.js"></script>
        <script src="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/js/bootstrap.min.js"></script>
    </head>
<body>
    <div class="container">
        <h3 style="text-align:center;color:#789"><?=$title?></h3>
        <form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">
            <div class="form-group">
                <label for="email">Email地址:</label>
                <input type="text" class="form-control" id="email" name="email" maxlength="30" value="<?=$_POST['email']?>" />
            </div>
            <div class="form-group">
                <label for="pwd">密码:</label>
                <input type="password" class="form-control" id="pwd" name="pwd" maxlength="30" />
            </div>
            
            <?php if($decide): ?>
            
                <div class="form-check">
                    <label class="form-check-label">
                        <input class="form-check-input" type="checkbox"> 记住我 
                    </label>
                </div>
            
            <?php endif; ?> <?=$hidd?>
            
            <div style="text-align:right;padding-top:10px">
                <button type="submit" class="btn btn-primary" onclick="check(event)"><?=$butt?></button>
            </div>
        </form>
        
        <?php if($decide): ?>
            <p style="padding-top:10px;text-align:center">
                <a href="?tag=register">这里注册</a>
            </p>
        <?php endif; ?>
        
    <div>
<?php
/**
 * javascript 部分 #############################
 * 
 */
?>
    <script>
        function check(event){
            
            //邮箱字段验证
            var em = email.value;
            var reg = /^([a-zA-Z]|[0-9])+(w|-)+@[a-zA-Z0-9]+.([a-zA-Z]{2,4})$/;
            var ret = em.match(reg);
            if(ret === null){
                event.preventDefault();
                alert('请输入正确的邮箱格式!');
                return false;
            }
            
            //密码字段验证
            var pass = pwd.value;
            if(isEmpty(pass)){
                event.preventDefault();
                alert('密码字段不能为空!');
                return false;
            }
        }
        
        //判断字符串变量是否为空
        function isEmpty(obj){
            if(typeof obj == "undefined" || obj == null || obj == ""){
                return true;
            }else{
                return false;
            }
        }
    </script>
</body>
</html>

  XSS 攻击试验代码

  是用文件代替的数据库,刷新页面后只需点击“提交”,就会看见浏览器作出反应,提示:

net::ERR_BLOCKED_BY_XSS_AUDITOR

但为时已晚,已经被写入了“数据库”,只要刷新页面,就会看见提示框,如果能写提示框,当然也能写些别的代码,如非法获取 cookie,造成 cookie 劫持和 session 劫持。可以看出,浏览器的拦截简直是马后炮。此文只是希望读者有安全防范意识,不是教大家一步一步地做黑客,怎么干坏事,当然是该写的才写,不该写的不写。今天使用的代码如下:

<img src=x onerror=alert("OK")>

这个应该不难理解,img 标签读不出图片,会触发 onerror 事件,引号内的 js 代码会执行,所以要记得 js 代码并不一定要在js 标签内执行。

  XSS 攻击实验代码如下:

<?php

//模拟写入数据库
if(!empty($_REQUEST['user'])){
    file_put_contents('test.txt', $_REQUEST['user']);
}

//模拟从数据库取出数据
@$str = file_get_contents('test.txt');
?>

<!DOCTYPE html>
<html>
    <head>
        <title>XSS 攻击试验</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/css/bootstrap.min.css">
        <script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
        <script src="https://cdn.staticfile.org/popper.js/1.12.5/umd/popper.min.js"></script>
        <script src="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/js/bootstrap.min.js"></script>
    </head>
<body>
    <div class="container">
        <?=$str?>
        <form action="" method="post">
            <input name="user" id="user">
            <input type="submit" class="btn btn-primary" value="提交" />
        </form>
    <div>
<script>
    user.value = '<img src=x onerror=alert("ok") style=display:none>';
</script>
</body>
</html>

  下面一条是编码后的 XSS 代码,经测试后会弹出警告框,能有效攻击,虽然此代码是无害的,万一别有用心的人写的不是弹框呢。

<img src=x onerror="&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041">
原文地址:https://www.cnblogs.com/qingsong/p/13782601.html