多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Web登录逻辑漏洞实战剖析:从越权登录到会话劫持的攻防对抗

Web登录逻辑漏洞实战剖析:从越权登录到会话劫持的攻防对抗 1. 从一次真实的“越权登录”事件说起去年我参与了一个电商项目的安全审计。在测试登录模块时我发现了一个乍看之下非常“正常”的流程用户输入手机号和验证码点击登录。前端将手机号和验证码发送到/api/login接口后端验证通过后返回一个包含用户ID和Token的JSON。一切看起来都符合常规。然而当我用Burp Suite拦截这个登录成功的请求包并尝试修改其中的user_id字段然后重放请求时令人惊讶的事情发生了——我竟然以另一个用户的身份成功登录了系统并且获取到了该用户的完整会话和权限。这个漏洞的本质就是典型的登录状态绑定逻辑缺失。后端在验证验证码通过后没有将生成的会话Token与发起登录请求的原始用户身份即手机号进行强绑定而是信任了客户端传来的、可被篡改的user_id参数。这个案例让我深刻意识到登录逻辑的漏洞往往隐藏在那些看似“理所当然”的代码背后它们不是复杂的加密算法被攻破而是业务逻辑链条上某个环节的断裂或疏忽。今天我们就来系统性地梳理一下Web和移动端应用中那些高频出现、危害巨大的登录逻辑漏洞。无论你是开发、测试还是安全工程师理解这些漏洞的成因、利用方式及修复方案都至关重要。这不仅能帮助你在代码审查和系统设计阶段就规避风险也能让你在渗透测试中更有针对性地进行验证。我们将绕过那些教科书式的宽泛定义直接切入实战中遇到的各类场景从用户标识的混淆到验证过程的缺陷再到会话管理的失控逐一拆解。2. 用户标识与验证的“错位”漏洞登录的第一步是确认“你是谁”。这个环节一旦出现逻辑错位就等于为攻击者敞开了大门。以下是最常见的几类问题。2.1 用户标识可控导致的越权这是我开头案例的详细展开。其核心问题是后端用于最终确定登录身份的标识符如user_id, username其值来源于客户端可完全控制的输入。漏洞场景与利用基于ID的越权如上例登录请求包为POST /api/login {“phone”: “13800138000”, “code”: “123456”, “user_id”: 10001}。后端验证phone和code匹配后直接使用请求中的user_id10001来生成会话。攻击者只需将user_id改为10002即可登录用户10002的账户。基于用户名的越权在用户名密码登录中请求包为POST /login {“username”: “alice”, “password”: “pass123”}。后端验证密码正确后从数据库查询username“alice”的用户信息并创建会话。这里看似没问题。但考虑一种变形系统支持手机号或邮箱登录后端根据输入格式判断查询字段。如果代码逻辑是if (input.contains(“”)) { field “email”; } else { field “phone”; }然后执行SELECT * FROM users WHERE ${field} ‘${input}’。如果攻击者输入13800138000’ OR ‘1’’1作为用户名并且后端没有进行充分的过滤和参数化查询就可能造成SQL注入绕过认证。批量枚举与账户锁定绕过有些系统在登录失败多次后会锁定账户。但如果锁定是基于客户端传来的用户名未经验证攻击者可以不断变换用户名进行密码爆破而不会触发任何账户的锁定机制。根因分析后端在认证通过后没有从自己可信的数据源如验证码校验时存储的临时Session、数据库查询的唯一结果中获取最终的用户主键UID而是轻信了客户端传来的另一个标识参数。这违背了“认证结果应与认证凭据强关联”的基本原则。修复方案绑定会话与认证凭据在验证码校验通过后服务器端应在一个可信的存储如Redis中以手机号为Key存入一个临时的认证令牌或直接存入即将生成的Session ID。当客户端携带这个令牌请求最终登录时服务器根据令牌取出手机号再由此查询数据库获得真实的、不可篡改的UID。使用服务器端Session认证成功后在服务器端创建Session将用户ID存储在服务器Session对象中如PHP的$_SESSION[‘uid’]只将会话IDSession ID通过Cookie返回给客户端。用户身份完全由服务器状态决定。签名验证对于某些无状态Token如JWTToken本身必须包含用户标识如sub: “alice”并且整个Token需要经过服务器密钥签名。客户端无法篡改sub字段而不破坏签名。但需注意JWT的alg设置为none等漏洞。2.2 验证码与登录步骤的“时序”与“绑定”漏洞验证码短信、图形、邮件本是为了增强安全但逻辑错误会使其形同虚设。漏洞场景与利用验证码与账号未绑定系统先请求发送验证码到手机A然后在登录接口验证验证码。如果登录接口参数是{“phone”: “13800138000”, “code”: “123456”}但后端只校验code是否正确而没有校验这个code是否是属于phone这个手机号请求的。攻击者可以对自己的手机B请求一个验证码然后将B收到的验证码和受害者手机A的号码一起提交从而登录A的账户。验证码可被暴力破解验证码位数过短如4位数字且系统未对单个验证码的尝试次数做限制也未对单个账号/IP的验证请求做频率限制。攻击者可以编写脚本快速枚举所有可能0000-9999通常在几分钟内即可破解。验证码一步到位登录接口设计为/api/login?phonexxxcodexxx一步完成验证和登录。这通常意味着验证码使用后即失效delete from sms_codes where phonexxx。但如果后端逻辑是先校验校验通过后先返回登录成功信息再执行删除操作在高并发场景下攻击者可能通过并发请求在验证码被删除前多次使用同一个验证码登录条件竞争漏洞。验证码生命周期过长验证码有效期设置过长如30分钟增加了被爆破或截获的风险。根因分析验证码作为一个临时的、一次性的凭证其生成、存储、校验、销毁的整个生命周期管理存在缺陷。核心是缺乏“验证码-目标账户-请求来源”之间的强绑定关系以及对验证码本身强度的保护不足。修复方案强绑定在服务器端存储验证码时必须将其与目标账号手机号/邮箱、客户端标识如Session ID或一个随机生成的请求ID以及时间戳同时存储。校验时必须三者匹配才通过。增强强度与限制使用6位及以上数字字母混合验证码。实施严格的频率限制对单IP/单账号的发送请求和验证请求进行限流如1分钟1次1小时5次。对单验证码的尝试次数进行限制如最多错误5次即失效。原子操作校验验证码和使其失效删除或标记已用必须在一个数据库事务中完成确保原子性防止条件竞争。合理有效期根据业务场景设置合理的短有效期通常短信验证码为2-5分钟。3. 认证状态与会话管理的逻辑缺陷用户通过认证后系统如何维持和识别这个“已登录”状态是另一个漏洞高发区。3.1 会话固定攻击这是一种利用服务器对会话ID处理不当将受害者“固定”到攻击者已知的会话ID上的攻击。漏洞场景与利用攻击者先访问网站获得一个初始的会话ID例如SessionIDattacker_sid。此时该会话未登录。攻击者构造一个链接包含这个会话ID例如https://victim.com/login?sessionidattacker_sid并通过钓鱼邮件等方式诱使受害者点击。受害者点击链接浏览器使用attacker_sid这个会话ID与服务器通信。受害者输入用户名密码完成登录。此时服务器将attacker_sid这个会话的状态更新为“已登录”且关联了受害者的账户。攻击者由于知道attacker_sid的值他可以直接使用这个会话ID访问网站此时服务器会认为这是受害者的合法会话从而成功劫持受害者账户。根因分析根本原因在于应用程序在用户身份提升如从匿名到已登录时没有为其分配一个新的、随机的会话ID。它继续使用用户登录前可能已被攻击者知晓的旧会话ID。修复方案登录后重置会话在用户成功登录后服务器必须立即使其旧的会话ID失效并生成一个全新的、随机的会话ID返回给客户端。在PHP中通常使用session_regenerate_id(true)函数参数true表示删除旧会话文件。禁止URL传递会话ID避免通过GET参数传递会话标识符应始终使用Cookie并设置HttpOnly和Secure属性来传递。3.2 平行越权与垂直越权这类漏洞发生在登录之后系统对用户权限的校验不严。平行越权用户A和用户B属于同一权限等级。用户A可以访问自己的资源如/api/user/orders?user_id10001。如果后端在获取订单列表时只验证了用户是否登录而没有校验当前登录用户的ID是否与请求参数中的user_id一致那么用户A将user_id改为10002就能看到用户B的订单。垂直越权低权限用户能访问或操作高权限用户的功能。例如普通用户界面中隐藏了一个管理员功能的API链接如/api/admin/deleteUser。后端如果只通过前端菜单控制访问而没有在API接口层对每个请求进行角色/权限校验那么普通用户直接调用此API就可能执行管理员操作。根因分析权限校验的缺失或位置错误。校验仅在前端进行或是在后端Controller层进行了简单的登录验证但在具体的Service或数据访问层没有对“当前用户是否有权操作目标数据”进行二次校验。这违背了“最小权限原则”和“服务端不可信原则”。修复方案服务端强制校验任何涉及数据访问的操作必须在服务端进行“主体-客体”权限校验。例如在查询订单前先确认current_user_id requested_user_id。可以使用拦截器、AOP或装饰器模式在业务逻辑执行前统一进行权限校验。基于角色的访问控制对于功能权限定义清晰的角色和权限点。在每个API入口处校验当前会话用户是否拥有执行该API所需的权限。使用不可篡改的身份标识在生成访问令牌如JWT时可将用户角色role: “user”包含在内并签名。后端在处理请求时从已验证的令牌中解析角色进行校验而不是依赖客户端传来的参数。4. 密码重置流程中的致命逻辑漏洞密码重置是登录的“后门”这里的逻辑漏洞往往能直接导致账户沦陷。4.1 重置令牌与用户身份未绑定这是最经典的一类漏洞。流程通常是用户点击“忘记密码”输入邮箱或手机号系统发送一个包含重置令牌Token的链接到邮箱或短信。问题出在重置环节。漏洞场景与利用重置接口为POST /reset-password参数为{“token”: “abc123”, “new_password”: “Hack2024”}。后端验证token有效后即允许修改密码。漏洞利用攻击者先用自己的账户attackerevil.com申请重置密码获得一个重置令牌token_attacker。然后他构造请求将token_attacker和想要为受害者设置的新密码new_password一起提交。如果后端没有检查这个token是属于哪个用户的而是简单地执行了类似UPDATE users SET password‘[hash of new_password]’ WHERE reset_token‘abc123’的操作那么攻击者用自己的令牌修改的却是数据库中第一个匹配该令牌的用户密码。如果令牌是全局唯一且攻击者先于受害者申请他修改的很可能就是管理员或其他用户的密码。更糟糕的情况是如果后端根据令牌查询出用户ID后直接修改该ID对应的密码那么攻击者就成功地将他人的密码修改为自己设定的值。根因分析重置令牌在生成和消耗时没有与目标用户身份进行强关联。或者在消耗时关联逻辑存在缺陷如上述SQL语句可能更新多条记录。修复方案令牌与用户ID强绑定存储在数据库中重置令牌表应有user_id,token,expires_at字段。生成令牌时必须关联具体的user_id。重置时双重验证在重置密码的页面除了验证Token还应再次验证用户身份。一种常见做法是重置链接的页面会显示被重置账户的部分信息如邮箱/手机号的打码显示a***example.com要求用户确认。提交新密码时后端必须用Token查询出对应的user_id并且确保这个user_id与当前操作上下文如果存在中间步骤或最终修改语句中的WHERE user_id ?条件明确对应。一次性使用令牌在使用后立即失效防止重放。4.2 重置步骤可绕过多步骤的重置流程如果步骤间的状态依赖存在缺陷可能被绕过。漏洞场景与利用一个安全的密码重置流程可能是 步骤1: 输入邮箱 - 发送验证码到邮箱。 步骤2: 输入邮箱验证码 - 验证通过跳转到设置新密码页面此时服务器应标记该邮箱已验证。 步骤3: 设置新密码 - 提交新密码请求中应包含邮箱或一个服务器颁发的临时凭证。漏洞如果步骤3的接口设计为POST /final-reset {“email”: “victimmail.com”, “new_password”: “xxx”}并且后端没有检查该邮箱是否已经完成了步骤2的验证那么攻击者可以直接调用步骤3的接口从而绕过验证码校验。根因分析后端没有在步骤之间维持一个可信的、客户端无法篡改的状态机。每个步骤被视为独立的、无状态的API调用。修复方案使用服务器端Session管理状态在步骤1通过后在服务器Session中设置一个标志如$_SESSION[‘pwd_reset_verified_email’] “victimmail.com”。步骤3处理时只允许修改$_SESSION[‘pwd_reset_verified_email’]对应的账户密码。使用一次性临时令牌步骤2验证通过后生成一个高强度的临时令牌与重置令牌不同返回给客户端。步骤3必须提交此临时令牌。服务器验证此临时令牌有效且与邮箱绑定后才执行密码更新。临时令牌同样需一次性使用。5. 其他常见但危险的逻辑疏漏除了上述大类还有一些容易被忽视的“边角”漏洞。5.1 登录状态回退与多端登录管理登录状态回退用户登录后点击浏览器后退按钮可能会退回到登录页面。如果登录页面没有判断当前会话是否已登录可能会再次显示登录表单。如果这个表单被自动填充了密码或者用户不小心再次提交可能导致意外的会话创建或其他问题。更严重的是如果“退出登录”功能仅仅是客户端清除了Token而没有通知服务器端使会话失效那么服务器端的旧会话仍然有效可以被盗用。多端登录管理混乱系统是否允许同一个账号在多处同时登录如果允许是否有设备管理或会话列表功能如果不允许新登录是否会踢掉旧会话如果“踢下线”的逻辑有缺陷比如只删除了数据库中的某个令牌记录但没有使旧会话的JWT或Session立即失效那么旧会话在过期前依然可用。修复建议在登录页面和关键入口页面加入对已登录状态的检查如果已登录则直接跳转到首页。退出登录必须是服务端操作清除服务器端Session或将Token加入黑名单。对于JWT由于无状态需维护一个短期的黑名单或在服务端存储一个版本号退出时递增版本号校验时对比版本。实现清晰的会话管理策略。若需强制单点登录可在用户表中存储一个latest_session_id或login_token_version。每次成功登录就更新这个值。校验会话时对比当前会话的标识与数据库中存储的最新标识是否一致不一致则拒绝。5.2 前端认证逻辑可被绕过这是一个原则性问题所有最终的安全决策必须在服务器端执行。漏洞场景前端在登录后通过检查本地存储的localStorage.isLoggedIn true来控制页面元素的显示如“个人中心”按钮。攻击者可以直接在浏览器控制台修改这个值为true从而看到本应隐藏的界面元素。虽然点击后可能因无有效Token而无法获取数据但这暴露了接口信息。更危险的是某些应用的前端路由守卫仅通过检查本地是否有Token来判断能否访问某个页面。攻击者可以手动输入URL直接进入后台管理页面框架尽管可能无法加载数据但页面结构已暴露。修复方案前端的所有权限控制UI显示、路由跳转只是为了用户体验。每个需要权限的API接口必须在服务器端进行完整的会话和权限校验。对于敏感的管理界面服务器端甚至可以校验请求的Referer或直接渲染页面时注入数据避免纯前端加载。5.3 用户名枚举与信息泄露登录、注册、密码重置等接口的返回信息可能无意中泄露用户是否存在。统一化错误信息无论是用户名不存在还是密码错误都应该返回相同的模糊信息例如“用户名或密码错误”。避免“该用户不存在”和“密码错误”的不同提示。响应时间差异有时查询一个存在的用户需要核对密码哈希比查询一个不存在的用户直接返回耗时略长。攻击者可以通过精确测量响应时间来枚举用户。缓解措施包括对不存在的用户也执行一次模拟的密码哈希验证如使用一个固定的假哈希值进行比较使响应时间恒定。注册页面泄露在注册时输入已存在的用户名或邮箱提示“该用户已存在”这同样可用于枚举。可以考虑在用户注册成功后再通过邮件告知其用户名是否已被占用但体验不佳。更常见的做法是在用户提交注册信息后无论是否成功都返回“注册指令已提交请查收邮件确认”。6. 实战渗透测试中的登录逻辑漏洞挖掘思路了解了这些漏洞模式在实际测试中该如何系统性地挖掘呢以下是我个人的一套流程和心得。第一步信息收集与功能点梳理使用浏览器和代理工具如Burp Suite完整走查所有与认证相关的功能登录账号密码、手机验证码、第三方OAuth、注册、注销、密码重置/修改、邮箱/手机绑定/解绑、多因素认证MFA的启用/禁用。记录每一个请求的端点Endpoint、参数、触发条件。特别关注那些有“状态转换”的流程比如“获取验证码”-“提交验证码”-“登录成功”。第二步参数分析与篡改测试对每个请求中的每一个参数问三个问题这个参数是用于标识用户的吗(如user_id,username,email,phone) - 尝试修改为其他用户的标识看是否越权。这个参数是用于认证的吗(如password,code,token) - 尝试置空、删除、修改看校验是否严格尝试爆破需注意法律和授权范围。这个参数是用于控制流程状态的吗(如step2,typereset) - 尝试跳过步骤或改变流程类型。第三步流程跳跃与状态机测试尝试直接访问流程中后置的URL绕过前置条件。例如直接访问“设置新密码”的页面不经过“验证身份”的步骤。使用同一个客户端同一Session并行发起多个流程请求测试条件竞争。例如对同一个验证码并发多次登录请求。检查步骤间的依赖关系。完成步骤A后用另一个浏览器或工具不带步骤A产生的Token能否直接请求步骤B第四步会话与令牌深度测试登录成功后仔细检查返回的所有令牌、Cookie、响应头信息。尝试用其他用户的令牌替换当前令牌观察系统行为。测试会话固定记录未登录时的Cookie诱导在授权测试中模拟该会话登录检查登录后Cookie是否变化。测试退出登录退出后用之前的Token是否还能访问需要认证的API分析JWT令牌如果使用使用 jwt.io 解码检查alg字段是否为none检查签名密钥是否弱可爆破检查exp、nbf等时间字段是否可被绕过修改系统时间测试。第五步错误信息与响应差异分析故意输入错误观察不同错误类型用户名无效、密码错误、账户锁定的响应信息、HTTP状态码、响应时间、响应体长度是否有差异。细微的差异都可能成为枚举用户的突破口。在密码重置功能中输入已存在和不存在的邮箱/手机号观察响应差异。第六步工具辅助与自动化使用Burp Suite的Intruder模块对验证码、密码等参数进行爆破测试必须在授权范围内。使用Burp的Sequencer模块分析会话令牌Session ID的随机性。编写简单的Python脚本用于测试高并发下的条件竞争漏洞。在整个测试过程中保持“不信任客户端任何输入”和“状态由服务端掌控”这两个核心原则大多数逻辑漏洞都将无处遁形。登录逻辑的安全是系统安全的第一道闸门也是最容易被业务压力冲垮的一道防线。作为开发者在实现功能时多问一句“如果客户端恶意修改这个参数会怎样”作为测试者用攻击者的思维去尝试打断每一个逻辑链条。唯有如此才能构建起真正稳固的认证体系。
返回列表