You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

移动端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防护机制,攻击者可以轻松构造攻击,步骤如下:

  1. 攻击者创建一个恶意网页,包含自动提交的表单:
<!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>
  1. 攻击者诱导受害者点击该网页链接(比如伪装成「领取优惠券」)。
  2. 受害者的浏览器会自动向payment.php发送POST请求,带上预设的username和amount——因为你的系统没用到Session/Cookie,甚至不需要受害者登录,只要攻击者知道某个存在的用户名就能发起攻击。

因为payment.php没有验证请求来源,也不校验用户是否真的登录,所以这个请求会被正常处理,造成业务损失。

4. 移动端认证的最佳方案:Token机制(或Session+Cookie)

Token机制的运作流程(以JWT为例):

  1. 登录阶段:用户提交用户名和密码到服务端,服务端验证密码正确后,生成一个签名的Token(比如JWT,JSON Web Token)。JWT包含用户ID、过期时间等信息,并用服务端专属密钥签名,确保无法被篡改。
  2. 客户端存储:客户端收到Token后,将其存储在安全位置——Web端可用localStorage/sessionStorage(需注意XSS风险),原生移动端用系统安全存储(如Android Keystore、iOS Keychain)。
  3. 后续请求:客户端每次发起敏感请求(如支付、查询用户信息)时,在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');
  }
});
  1. 服务端校验:服务端收到请求后,先验证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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 10:05:13