移动端PHP认证安全咨询:LocalStorage存用户信息的风险与优化
首先直接说结论:你的当前实现存在严重的安全漏洞,完全依赖客户端存储和前端校验,服务端几乎没有任何有效的身份验证逻辑。下面逐个分析你的问题,并给出修复方案:
1. LocalStorage篡改风险:完全可以绕过
答案是肯定的,攻击者能轻松篡改LocalStorage里的用户名。LocalStorage是浏览器提供的客户端存储,任何人都可以通过开发者工具(F12 → Application → LocalStorage)直接修改其中的值。
你的welcome.html仅做了前端判断:如果LocalStorage没有用户名就跳转,但攻击者只要手动添加或修改username字段,就能直接绕过这个检查进入页面。更危险的是,payment.php直接使用POST过来的username参数,完全不验证这个用户是否真的登录过——攻击者甚至可以随便填一个存在的用户名,就能发起支付请求,根本不需要知道密码。
2. Session固定/劫持:当前实现不涉及Session,但问题更严重
你的代码里完全没有启用服务端Session(连session_start()都没调用),所以Session固定和劫持攻击在这里不适用,但这绝非好事——因为你根本没有服务端层面的身份验证凭证。
LocalStorage和Session是完全无关的两个概念:Session是服务端存储的会话信息,客户端靠Cookie里的Session ID关联;而LocalStorage只是客户端本地的存储,服务端无法验证它的真实性。换句话说,你的实现连最基础的服务端身份校验都没有,比存在Session劫持的系统更不安全。
3. CSRF攻击的实施方式
由于你的payment.php没有任何CSRF防护机制,攻击者可以轻松构造攻击,步骤如下:
- 攻击者创建一个恶意网页,包含自动提交的表单:
<!DOCTYPE html> <html> <body> <form id="csrf-form" action="https://你的域名/payment.php" method="POST"> <input type="hidden" name="username" value="victim_user"> <!-- 攻击者可猜测或通过其他方式获取受害者用户名 --> <input type="hidden" name="amount" value="9999"> </form> <script> // 页面加载后自动提交表单 document.getElementById('csrf-form').submit(); </script> </body> </html>
- 攻击者诱导受害者点击该网页链接(比如伪装成「领取优惠券」)。
- 受害者的浏览器会自动向
payment.php发送POST请求,带上预设的username和amount——因为你的系统没用到Session/Cookie,甚至不需要受害者登录,只要攻击者知道某个存在的用户名就能发起攻击。
因为payment.php没有验证请求来源,也不校验用户是否真的登录,所以这个请求会被正常处理,造成业务损失。
4. 移动端认证的最佳方案:Token机制(或Session+Cookie)
Token机制的运作流程(以JWT为例):
- 登录阶段:用户提交用户名和密码到服务端,服务端验证密码正确后,生成一个签名的Token(比如JWT,JSON Web Token)。JWT包含用户ID、过期时间等信息,并用服务端专属密钥签名,确保无法被篡改。
- 客户端存储:客户端收到Token后,将其存储在安全位置——Web端可用
localStorage/sessionStorage(需注意XSS风险),原生移动端用系统安全存储(如Android Keystore、iOS Keychain)。 - 后续请求:客户端每次发起敏感请求(如支付、查询用户信息)时,在HTTP请求头中带上Token:
$.ajax({ type: 'POST', url: 'payment.php', headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }, data: {amount: amount}, crossDomain: true, cache: false, success:function(msg){ alert('payment successful'); } });
- 服务端校验:服务端收到请求后,先验证Token的签名有效性和过期时间,通过后从Token中取出用户ID,再进行业务逻辑处理。
为什么Token适合移动端?
- 无状态:服务端无需存储Session信息,便于系统水平扩展。
- 跨平台:Web端、原生App、小程序都能使用相同的Token机制。
- 可控性:可设置Token过期时间,还能支持刷新Token机制,提升安全性。
漏洞修复方案(代码修改示例)
1. 修复login.php:使用密码哈希验证+Session/Token
首先确保数据库中用户密码是用password_hash()存储的,然后修改登录逻辑:
<?php error_reporting(E_ALL); ob_start(); session_start(); // 开启Session require('db.php'); $username = strip_tags($_POST['username']); $password = strip_tags($_POST['password']); // 仅查询用户信息(不直接查密码) $result = $db->prepare('SELECT id, username, password FROM users where username = :username'); $result->execute(array(':username' => $username)); $count = $result->rowCount(); $row = $result->fetch(); if ($count == 1 && password_verify($password, $row['password'])) { // 密码验证成功,初始化Session $_SESSION['user_id'] = $row['id']; $_SESSION['username'] = $row['username']; // 若使用JWT,需引入firebase/php-jwt库,示例如下: // require 'vendor/autoload.php'; // use Firebase\JWT\JWT; // $token = JWT::encode( // ['user_id' => $row['id'], 'exp' => time() + 3600], // 'your_secure_secret_key' // ); // echo json_encode(['status' => 'success', 'token' => $token]); echo "<div class='alert alert-success'>login successful</div>"; echo "<script>window.location='welcome.html'</script>"; } else { echo "<div class='alert alert-danger' id='alerts_img'><font color=red>Either Username or Password is Wrong</font></div>"; } ob_end_flush(); ?>
2. 修复welcome.html:前端提示+依赖后端校验
前端判断仅作为友好提示,核心校验必须放在服务端:
<script> if (localStorage.getItem("username") === null) { alert('Please login first.'); window.location='index.html' } </script> <script> $(document).ready(function(){ $('#payment').click(function(){ var amount = $('#amount').val(); if(amount==""){ alert('please Enter amount'); } else{ $.ajax({ type:'POST', url:'payment.php', // 使用Session则无需额外配置(浏览器自动带Session Cookie);使用JWT需在headers中携带Token // headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }, data: {amount: amount}, // 不再传递username,后端从Session/Token获取 crossDomain: true, cache:false, success:function(msg){ alert('payment successful'); } }); } }) }); </script> <input type="text" id="amount"> <button id="payment">Pay Now</button>
3. 修复payment.php:添加身份校验+CSRF防护
<?php session_start(); // 若使用JWT,替换为Token校验逻辑 // $headers = getallheaders(); // if (!isset($headers['Authorization'])) die('Unauthorized'); // $token = str_replace('Bearer ', '', $headers['Authorization']); // try { // $decoded = JWT::decode($token, 'your_secure_secret_key', ['HS256']); // $user_id = $decoded->user_id; // } catch (Exception $e) { // die('Unauthorized'); // } // 校验Session有效性 if (!isset($_SESSION['user_id'])) { die('Unauthorized access'); } // CSRF防护:需在welcome.html中生成CSRF Token并存入Session和页面 // if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) { // die('CSRF validation failed'); // } $user_id = $_SESSION['user_id']; $amount = strip_tags($_POST['amount']); // 后续业务逻辑:根据user_id处理支付,而非前端传递的username // ... echo "Payment successful for user ID: $user_id"; ?>
额外安全建议:
- 启用HTTPS:所有请求通过HTTPS传输,防止数据被窃听或篡改。
- 限制会话有效期:Session设置1小时过期,Token设置短有效期+刷新Token机制。
- 防范XSS攻击:严格过滤用户输入,避免XSS漏洞窃取LocalStorage中的Token。
- 敏感信息存储:优先用HttpOnly Cookie存储Session ID(比LocalStorage更安全),避免存储敏感数据在客户端。
内容的提问来源于stack exchange,提问作者Markjames11

