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="javascript:alert('XSS')">