从Cookie伪造到权限绕过:CTF“需要管理员”解题思路与Web安全启示

从Cookie伪造到权限绕过:CTF“需要管理员”解题思路与Web安全启示 1. 题目整体拆解这题到底在考什么1.1 题目描述与核心考点Bugku平台上的Web题“需要管理员”光看名字就透着一股“权限”的味道。在CTF的Web方向里凡是跟“管理员”沾边的题目十有八九是在考身份认证绕过和权限校验逻辑。这道题在Bugku那里属于新手进阶的典型题目它不涉及复杂的注入或者反序列化而是把重点放在了一个特别容易被新手忽略的地方——Cookie。我第一次做这道题的时候以为是要爆破后台密码或者找一个隐藏的admin页面。结果打开题目页面一看整个页面上只有一个孤零零的按钮点击之后页面提示你不是管理员无法执行操作。这时候再扭头看URL和响应头问题就浮出水面了。这道题考察的核心就是你能不能意识到“服务端校验身份”这件事在很多入门级Web应用里其实只依赖于一个客户端可以随意修改的Cookie字段。我把这类题归为“会话伪造入门题”。它的意义不在于题目本身有多难而在于它把你从“只会盯着页面看”的思路拉到了“要去看请求、看响应、看存储”的层面。如果你能独立把这道题做出来那你在Web安全这条路上就算是真正迈过了第一道门槛。1.2 预备知识HTTP无状态与身份认证的底子在动手解题之前有几个基础概念必须理清楚不然你就算跟着教程把flag拿到了换一道题照样抓瞎。HTTP协议是无状态的。这句话你肯定听过无数遍但它的真正含义是服务器默认不记得“你是谁”。每一次HTTP请求都是一次全新的握手服务器不会因为你上一次请求时输入过密码就默认你这一次也是合法用户。那网站是怎么记住用户登录状态的呢靠的就是会话机制。常见的有Cookie、Session、Token这几大类。在CTF入门题里Cookie和Session是出镜率最高的两个东西。Cookie是服务器下发并存储在客户端浏览器里的一小段数据每次请求时浏览器会自动把它带在请求头里发给服务器。Session则是服务器端保存的一份会话记录通常通过一个SessionID与客户端的Cookie进行关联。这里就出现了一个非常经典的逻辑漏洞如果服务器只是通过读取Cookie里的某个字段比如admin0来判断你是不是管理员而你又能随意修改这个字段的值那“成为管理员”这件事就只是改一个数字的问题。我见过很多零基础的朋友一上来就直接开Burp Suite去改包结果连“为什么要改这个参数”都没搞明白。所以说做这类题之前先把Cookie的机制吃透比背十道题的flag都管用。2. 信息收集阶段三分做题七分看源码2.1 第一步打开页面先看结构进入题目环境之后我习惯先不着急点任何按钮而是先按F12打开开发者工具依次看三个地方Elements网页源码、Network网络请求、Application存储区。网页源码里可能藏着注释掉的提示、隐藏的表单字段、或者外部引入的JS文件。有时候出题人会故意留点“废渣”在源码里看起来像是忘记了清理实际上是在给解题指路。比如某个输入框的value属性里写死了一个用户名或者某段JS代码里硬编码了一个跳转链接。这些细节丢在源码里不显眼但对解题方向有很强的提示作用。Network面板记录的是页面加载过程中所有的HTTP请求。你要关注的是有没有额外的接口请求响应状态码是200还是302有没有重定向如果点击按钮之后页面没有发生变化那多半是前端JS在拦截或者请求发出去了但返回的数据被隐藏了。这时候就要去Network里看具体发出去的请求包和返回包。Application面板是Cookie和LocalStorage的住所。我在这里最常干的事情就是直接去看有没有已经存在Cookie。有的题目比较“善良”在第一次访问时就已经给你下发了一个Cookie字段值写的是user0或者adminfalse之类的。看到这种东西基本就等于直接告诉你改这里就能过关。2.2 第二步Cookie里的秘密Cookie这玩意儿在安全圈子里有个很经典的比喻它就像你进停车场时领到的一张卡片上面写着你是“普通车主”还是“VIP车主”。停车场保安服务器不核对你的车牌也不看你的脸只认你递过去的卡片上写了什么。那你想想如果卡片上的字可以用笔自己改会发生什么在实际解题过程中我遇到过的Cookie字段五花八门。有的叫admin值是0或者1有的叫user值是普通用户的名字有的长得像一串加密字符或者一个数字ID。遇到加密字符先别急着放弃——很多题目所谓的“加密”只是Base64编码复制出来用工具解码一下里面可能就写着admin或者某个用户名。把解码结果改一改再编码回去放回Cookie里发给服务器搞不好就直接变成管理员了。另外还要注意一个点Cookie的取值不一定只有字符串和数字。有些题目会在Cookie里放一个JSON结构比如{username:guest,role:user}。这种就更明显了把role字段改成admin甚至把username改成admin都是常见的解法。总之看到Cookie结构越复杂越说明出题人想让你改的就是它。2.3 第三步结合后端逻辑做推断很多新手做题做到一半就卡住了原因不是想不到要改Cookie而是改了之后不知道有没有生效。这时候就需要顺着请求流程去推断后端代码大概长什么样。我们先假设题目的后端代码大概是这样的用伪代码描述一下# 伪代码非题目真实代码 def get_user_role(request): cookie request.cookies.get(user) if cookie admin: return admin else: return guest def index(request): role get_user_role(request) if role admin: return flag{you_are_admin} else: return 你不是管理员无法查看flag你看这段逻辑里根本没有Session校验没有数据库查询完全信任Cookie里user字段的值。凡是值不等于admin的统一按guest处理而值等于admin的直接放行。这种代码在真实生产环境里是灾难但在CTF题里却是教学利器——它告诉你服务端在做权限判断时必须依赖不可被客户端篡改的数据源比如Session而不是Cookie里的明文字段。当然真实题目不一定只有一层判断。有些题会先校验Cookie里是否存在某个字段再校验这个字段的值是否符合预期甚至还会校验几个字段之间的匹配关系。所以你在做题时不能只盯着一个点要系统地看整个请求链路里有哪些可控的参数哪些参数被服务端读取之后会影响返回结果。把这些点画成一张“输入影响表”你心里就有底了。3. 完整解题实操改Cookie骗过服务器3.1 方法一浏览器开发者工具直接改这是最直观、也最适合新手练手的方式。打开题目页面后按F12进入开发者工具切到Application面板在左侧找到Cookies点击题目域名的那个节点右侧就会显示出当前站点存储的所有Cookie。如果当前没有Cookie那也没关系。你可以先在Console里执行一句JS手动种下一个Cookiedocument.cookie useradmin; path/;这句代码的意思很直白在当前域名下设置一个名为user、值为admin的Cookie并且让它在整个路径下都生效。设置完之后刷新页面再点那个按钮看看页面内容有没有变化。如果页面上原本就有Cookie比如userguest那你直接双击Value那一栏把guest改成admin回车保存然后刷新页面即可。这里有一个容易踩的坑有些题目的Cookie字段名不叫user也不叫admin而是叫admin_name、user_role、is_admin、login_name之类的。你要是只改值不改字段名改半天也没用。所以先仔细看一眼现有Cookie的字段名再决定怎么改。提示修改Cookie之后如果发现没生效先按F12到Network面板里看看请求头。确认一下请求里Cookie这一行的值是不是真的变成你设置的内容了。有时候浏览器缓存会让页面看起来“没变化”但实际上请求已经带上新Cookie了。遇到这种情况勾选Network面板的“Disable cache”再强制刷新CtrlShiftR一次。3.2 方法二用Burp Suite抓包改请求用浏览器开发者工具改Cookie虽然快但如果你想真正理解HTTP请求的结构还是建议用Burp Suite走一遍抓包改包的流程。这也是后续所有Web方向题目的基本功。步骤很简单打开Burp Suite确认Proxy模块的Intercept开关是打开的默认是On。在浏览器里配置代理指向127.0.0.1:8080Burp默认监听端口。访问题目页面点击那个触发请求的按钮。回到Burp会看到一个被拦截的HTTP请求。找到请求头里的Cookie那一行。如果请求头里没有Cookie就直接在请求头的位置加一行Cookie: useradmin如果有现有Cookie就修改对应字段的值。点击Forward放行请求让请求到达服务器。改完之后在Burp的HTTP History里能看到返回包。如果返回的响应体里出现了flag那就说明数据库里的用户验证已经通过其实就是骗过了服务端。使用Burp的好处是你可以不依赖浏览器的Cookie管理机制直接手动构造任意Cookie。这在遇到一些“必须同时修改两个Cookie字段”的题目时特别有用。比如服务端同时读取user和role两个Cookie你必须把这一对字段的值都改对了才能拿到flag。在Burp里你可以直接对请求包做整体编辑比在浏览器里一个个改要高效得多。3.3 方法三用Python脚本模拟请求如果你平时写代码比较多或者想把这个过程自动化用Python的requests库也是完全可以的。下面是一段非常简短的示例脚本直接模拟登录和修改Cookie的过程import requests url http://xxx.xxx.xxx.xxx/ # 替换为题目地址 # 第一次请求不带Cookie resp requests.get(url) print(第一次访问状态码:, resp.status_code) print(页面内容片段:, resp.text[:200]) # 第二次请求携带伪造的Cookie cookies {user: admin} resp2 requests.get(url, cookiescookies) print(伪造Cookie后的页面内容:, resp2.text)注意这里的关键在于cookies参数的格式它是一个字典键是你想设置的Cookie字段名值是对应的内容。requests库会自动把字典转换成Cookie字符串放到请求头里发送出去。如果你发现修改一个Cookie还不够可以再试试同时设置多个cookies { user: admin, role: admin, admin: true }有些题目服务端会同时读取多个Cookie字段然后做一个逻辑判断比如if username admin and role admin and token secret: flag()这时候你就得多试几种组合。我在做题时常用的做法是先猜字段名admin、user、role、username、isadmin再猜字段值admin、true、1、guest改admin最后用脚本批量跑一遍所有组合几秒钟就能出结果。3.4 实操记录从被拒到拿flag的完整路径我拿这道题举个例子还原一下完整的实操现场。我第一次访问题目页面时看到的是一个简单到不能再简单的页面居中位置有一个按钮按钮旁边写着“普通用户”。点击之后弹出来的内容是“只有管理员才能执行此操作”我先看了一下Network面板发现点击按钮触发的请求是这样的GET /flag HTTP/1.1 Host: xxx.xxx.xxx.xxx Cookie: userguest响应是这样HTTP/1.1 200 OK Content-Type: text/html 你不是管理员无法查看flag看到没有请求头里带着一个明文的Cookie值就是guest而且整个请求没有SessionID也没有Token。也就是说服务器判断身份的线索完全依赖这个Cookie字段。于是我在开发者工具里直接把Cookie改成useradmin刷新页面再点按钮。这一次响应直接变成了HTTP/1.1 200 OK Content-Type: text/html flag{xxxxxxxxxxxxx}整个过程前后不超过三分钟。你别觉得这个过程太简单它的意义在于你第一次通过“修改请求数据”的方式完成了一次对服务端权限校验逻辑的绕过。这个思路在后面的SQL注入、逻辑漏洞、越权漏洞里都会反复用到。4. 常见问题与排查技巧实录4.1 改完Cookie没反应先排查这几个点“我明明把Cookie改成admin了为什么页面还是说不是管理员”——这个问题我在各个CTF交流群里见到过无数次。根据我的经验九成以上都是下面这几个原因之一。第一Cookie字段名猜错了。你光顾着把值从guest改成admin却没注意到服务端可能不认user这个字段它实际读取的字段是username。把请求头里的Cookie改一下多加一个字段进去试试比如Cookie: usernameadmin; useradmin; roleadmin这么做是通过增加覆盖范围来试探服务端到底读哪个字段。第二服务端可能做了额外的校验。有些题目会校验Cookie里的某个值与URL参数中的某个值是否一致或者校验Cookie里是否有一个时间戳字段与当前时间是否匹配。你只改了用户名没改其他字段自然会被拦截。排查方法是多看几个请求包的交互变化尤其是页面里有没有传一些看起来很随机的字符串那可能就是校验用的token。第三浏览器缓存了旧页面。修改Cookie之后页面看上去没变可能是本地缓存导致你看到的还是旧版本。处理办法是强制刷新一次或者直接无痕模式下重开一遍。4.2 这道题的兄弟题权限越界、JWT、弱口令“需要管理员”这道题做熟之后你会发现在Bugku平台以及各大CTF比赛里还有很多跟“管理员”有关的变体题。Bugku的“网站被黑”就是典型的后台爆破题考的是弱口令和字典扫描暴力猜解管理员后台路径以及弱密码。“头等舱”那道题则是典型的HTTP头分析题服务端要求请求必须带有特定的请求头字段比如X-Forwarded-For或者User-Agent才能拿到flag。这些题和“需要管理员”都有一个共同点——服务端在判断“你是谁”这件事上过于信任来自客户端的信息只不过信任的对象从Cookie变成了URL参数、请求头、甚至是Token字符串。再往外延伸还有一类更高级的题目考的是JWT伪造。JWTJSON Web Token是一种结构更复杂的Token它由Header、Payload、Signature三部分构成。出题人有时候会故意把签名算法设置为none或者把密钥设置为空让你可以自己构造任意身份。如果你已经把Cookie伪造玩明白了再去学JWT伪造上手速度会快得多因为你已经理解了同一件事客户端可控的数据永远不能作为安全判断的唯一依据。4.3 从CTF到实战开发时如何避免这类漏洞说到这里必须泼一盆冷水。CTF里这种“改Cookie变管理员”的漏洞在真实世界里是绝对的高危漏洞。如果你将来写代码时也这么干那你的站基本等于裸奔。避免这类问题的核心原则只有一条权限判断必须依赖服务端持有的数据不能依赖客户端提交的数据。也就是说判断用户是不是管理员应该去查服务端的Session里的角色信息或者去数据库里查这个用户ID对应的角色而不是读Cookie里的一个字段。如果你用的是框架开发比如Django、Flask、Spring、Express这些框架自带的Session机制通常已经帮你规避了这类问题。SessionID会存成HttpOnly、Secure的Cookie用户无法通过修改Cookie内容来直接篡改会话数据因为真正的角色信息存在服务端。但要注意框架不是万能的。有些开发者为了方便会把用户角色直接塞进Cookie里或者把用户ID放在URL参数里然后在后端拿这个ID去查权限这就会引发越权漏洞。正确做法是用户ID最好从Session中获取角色信息每次请求时都从数据库查询一次或者用缓存但要做好缓存刷新机制至少不要直接信任Cookie里的明文角色值。我在带新人做项目的时候经常出一到两道这类安全测试题。做完之后我会要求他们去阅读自己项目里的用户认证代码找出所有把权限判断建立在客户端数据上的位置。这个练习做上两三遍他们就会养成一种“不信任输入”的思维习惯这才是做Web安全最重要的一课。4.4 解题避坑技巧速查表下面这张表是我在刷Bugku以及其他CTF平台时总结出来的按“现象-原因-对策”的方式列出来希望对你有帮助。现象可能原因处理对策改了Cookie刷新后无变化字段名错误或服务端读取其他字段同时添加user、username、role、admin等字段页面提示不是管理员但内容有变化服务端要求多个条件同时成立检查是否还有URL参数或请求头需要修改请求里没有Cookie服务器尚未下发或题目需要自行构造直接在请求头手动添加Cookie字段Cookie值看起来像乱码可能是Base64或其他编码解码后修改再编码放回改完Cookie直接跳转404管理员权限下访问了不存在的路径检查题目页面的请求路径确认是否是多步操作返回的内容是在JS里渲染的服务端返回数据但被前端逻辑判断拦截看JS代码或直接看Network里响应体的原始内容4.5 延展思考权限控制还有哪些坑做完“需要管理员”这道题建议你一定再想一个问题如果是真正的后台系统管理员权限的含义其实是很宽的。有些管理员只能管内容不能管用户有些管理员只有某个模块的权限。一旦权限控制只做一层“是不是管理员”的判断就很容易出问题。CTF里经常考的水平越权和垂直越权就是从这里来的。水平越权是你以普通用户身份去操作另一个普通用户的资源比如修改订单、查看隐私信息垂直越权是普通用户去执行管理员才能执行的操作。这两种漏洞在真实业务系统里非常常见因为它们往往不是因为“没有登录校验”而是因为“登录校验之后没有对每次操作做二次权限校验”。所以这道题虽然简单但它其实是一个引子引出一条关于权限控制的完整学习路径。做题的时候可以顺手把OWASP API Security Top 10里关于对象级授权失效BOLA和功能级授权失效BFLA的条目读一读你会发现自己对这道题的理解会突然“升值”。做个小小的查漏补缺当你在浏览器里手动添加Cookie后建议先清一次所有Cookie再测试因为有些题目环境会在Session里缓存你的第一次访问状态导致你改了Cookie之后服务端拿到的还是旧状态。无痕模式是做题神器这是我在一次比赛现场用真实代价换来的经验——当时整个界面都卡在“不是管理员”的提示上我怀疑人生怀疑了十分钟最后发现是浏览器之前残留了一个guest的Cookie一直覆盖我手动设置的admin值。从那以后我只要做Web题一律无痕窗口起步。