ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

IIS下会话Cookie缺少HttpOnly属性修复指南:原理、配置与全局加固

IIS下会话Cookie缺少HttpOnly属性修复指南:原理、配置与全局加固 1. 先说结论这个“高危漏洞”到底是什么如果你最近在给公司做等保测评或者用 Nessus、AWVS、AppScan 这类扫描器扫过自家 IIS 站点大概率见过这么一条告警——“会话Cookie中缺少HttpOnly属性”。我第一次看到这玩意儿是在给某个客户的 OA 系统做上线前检测当时还被客户拉着问“是不是被攻击了”其实真不是这属于安全配置缺陷离被实际利用还有一段距离但确实该修。这个问题的本质很简单你的 Web 应用在用户登录后会产生一个会话标识Session ID一般存在 Cookie 里。如果这个 Cookie 没有设置 HttpOnly 标志那么浏览器端的恶意 JavaScript 脚本就可以通过document.cookie把会话标识读走。攻击者一旦拿到这个值就可以伪造会话直接以受害者的身份登录系统这就是常说的Session 劫持。HttpOnly 这个属性干的事更简单它告诉浏览器这个 Cookie 只能通过 HTTP 请求头自动携带禁止任何脚本访问它。也就是说即使站点存在 XSS 漏洞攻击者注入的脚本也拿不到这个 Cookie等于给会话上了把锁。在 IIS 环境下这个问题的覆盖面相当广。不管是古老的 ASP.NET Web Forms、现在主流的 ASP.NET Core还是纯静态站点、PHP 站点如果你用 FastCGI 挂在 IIS 下只要会话 Cookie 没打上 HttpOnly 标志扫描器都会给你报一条中危或高危出来。所以别觉得“我用的不是 .NET问题就不存在”该查照样得查。这篇我直接把 IIS 上处理 HttpOnly 的完整思路写清楚从原理到底层机制从单站点配置到服务器级全局加固再到排查过程中的常见坑全部覆盖。保证你看完能直接上手操作不用再东搜西找。2. 搞懂原理再动手Cookie、Session 与 HttpOnly 之间的关系2.1 会话机制里 Cookie 扮演的角色HTTP 协议本身是无状态的服务器响应完请求就“忘了”你是谁。为了让用户在多个请求之间保持登录状态服务器会在这条无状态的协议上叠一层会话状态机制用户登录成功后服务器生成一个唯一的 Session ID然后让浏览器把它存下来。之后浏览器每次请求都自动带上这个 ID服务器看到 ID 就知道“哦是这个人”整个过程对用户无感。Cookie 就是存储 Session ID 的主要载体。但这里存在一个关键问题Cookie 既会被浏览器自动放进请求头也能被页面里的 JavaScript 用document.cookie读取前提是没做额外限制。这个特性原本是为了方便开发者做一些客户端逻辑比如记住用户偏好、统计行为数据但也正是这个“方便”给了攻击者可乘之机。2.2 HttpOnly 属性的作用边界HttpOnly 是微软在 IE6 SP1 时期引入的一个 Cookie 属性后来被纳入 RFC 6265 规范现在所有主流浏览器都支持。它只做一件事禁止document.cookie等脚本接口访问带有该属性的 Cookie。但这并不是说 Cookie 就加密了、别人拿不到了。它照样明文存储、照样随请求头发送只是“脚本层”读不到而已。换句话说HttpOnly 防的不是网络嗅探而是XSS 攻击链条中窃取凭证的那一步。打个比方你就明白了HttpOnly 相当于把保险柜的钥匙从门口挪到了主人身上。小偷要是翻窗进来XSS 注入成功在房间里翻一圈也找不到钥匙自然打不开保险柜。但如果小偷直接在大街上把你包抢了网络层嗅探保险柜照样保不住——那是 HTTPS 该解决的问题。2.3 为什么偏偏 IIS 环境下这个问题被频繁报出来我排查过不少客户环境发现 IIS 下 HttpOnly 缺失的情况之所以常见核心原因有两个。一是 ASP.NET 的默认配置并不强制全局开启 HttpOnly。在 .NET Framework 4.x 及更早版本中如果你用原生 Session 管理默认情况下列表头所示ASP.NET 默认会话 Cookie 设置配置项默认值是否开启 HttpOnlyFormsAuthentication Cookie是较新版本默认开启视版本而定Session State CookieASP.NET_SessionId未显式设置旧版本默认否自定义 Cookie需要手动设置否另一个原因是很多开发者在项目中自定义了 Cookie或者第三方组件比如某些支付回调、单点登录模块自己往响应里塞了 Cookie压根没走 ASP.NET 的 Session 管理自然也不会带上 HttpOnly。扫描器检测的是整个响应头部里的 Set-Cookie 字段只要发现任何一条 Cookie 没带 HttpOnly就会报出来。所以你在网上搜“IIS 会话cookie缺少HttpOnly”会发现解决方案五花八门有人说改配置文件有人说装模块有人说改代码。都对但要看你那个 Cookie 到底是哪一类、从哪一层生成的。下面我把方案按层级拆开讲。3. 解决方案全景图从最低成本到最强兜底3.1 先搞清楚你要修的是哪类 Cookie动手之前先抓包或看响应头确认报错的 Cookie 长什么样。在浏览器按 F12 切到 Network 面板刷新页面随便点开一个文档请求看 Response Headers 里的 Set-Cookie 字段。常见的几种形态ASP.NET_SessionIdxxxx; path/; HttpOnly—— 这是 ASP.NET 原生会话自带 HttpOnly基本不用管.ASPXAUTHxxxx; path/; HttpOnly—— Forms 认证票据新版默认带老版本可能需要配置CookieNamevalue; path/——没有任何属性这就是问题所在tokenxxxx; expires...; path/—— 自定义业务 Cookie同样缺 HttpOnly搞清楚是哪一类后再去选方案。修错了位置配置半天不生效最后发现是自定义 Cookie 的问题白白浪费时间。3.2 方案一改应用代码治本但有时轮不到你改如果你手上有源码或者能推动开发配合优先在代码层解决。以 ASP.NET 为例两种常见场景自定义 Cookie 的场景代码里创建时直接带上 HttpOnlyHttpCookie userCookie new HttpCookie(UserName, admin); userCookie.HttpOnly true; userCookie.Secure true; // 如果有 HTTPS 环境建议一起开 userCookie.Path /; HttpContext.Current.Response.Cookies.Add(userCookie);如果是 .NET Core 场景通过 CookiePolicy 中间件统一控制services.ConfigureCookiePolicyOptions(options { options.CheckConsentNeeded context false; options.MinimumSameSitePolicy SameSiteMode.Lax; options.HttpOnly HttpOnlyPolicy.Always; // 关键所有 Cookie 一律强制 HttpOnly options.Secure CookieSecurePolicy.Always; // 生产环境强烈建议 });代码方案的优势是精确、可控哪些 Cookie 允许被前端读取比如必须暴露给 JS 的一些埋点标识可以灵活设置。但实际工作中很多站点的维护者根本拿不到源码或者站点是买来的 CMS改代码不现实。那就往下看下面几种方案。3.3 方案二通过 web.config 全局配置适合 .NET 站点如果应用是 ASP.NET包括 .NET Framework 和 .NET Core 部署在 IIS 上的情况最省事的方式是直接改网站的web.config。这个文件在站点根目录下修改后 IIS 会自动重新加载一般 10 秒内生效不需要重启站点。针对 Forms 认证票据和 Session 的配置configuration system.web !-- 设置 Forms 认证 Cookie 的 HttpOnly -- httpCookies httpOnlyCookiestrue requireSSLfalse sameSiteModeLax / !-- 兼容旧的 ASP.NET 4.0 以下版本 -- machineKey compatibilityModeFramework20SP2 / /system.web /configuration这段配置的作用是把 ASP.NET 运行时发出的所有httpCookies强制打上 HttpOnly 标签同时会对除 Session Cookie 之外的所有应用 Cookie 生效。注意requireSSLfalse是给纯 HTTP 站点留的后路如果你的站点是全 HTTPS建议改成true一起把 Secure 属性也开了。如果是 .NET Core 部署在 IIS 上web.config 方式不管用因为 .NET Core 不走system.web这套配置。这种情况建议要么改代码用 CookiePolicy要么用下面讲的 IIS 全局方案。3.4 方案三安装 URL Rewrite 模块做响应头重写万能兜底这里要敲黑板了——如果你想把所有站点、所有 Cookie 一次性全部强制加上 HttpOnly不考虑代码、不区分语言最靠谱的思路就是用 IIS 的 URL Rewrite 模块做 outbound rewrite出站重写。有人可能要问为什么不用 IIS 自带的“HTTP 响应标头”功能因为那个功能只能固定添加头部不能修改或追加 Set-Cookie 属性。你要加的 HttpOnly 是追加在 Cookie 属性列表里的不是新增一个独立的 Header所以必须靠 Rewrite 模块做规则替换。先确认你装了 URL Rewrite 模块。没装的话去官网下载安装搜索“IIS URL Rewrite Module 2.1”微软官方页面就有安装后重启 IIS。然后在站点根目录的web.config的system.webServer节点下加这段rewrite outboundRules rule nameAdd HttpOnly to Session Cookie enabledtrue patternSyntaxECMAScript !-- 匹配所有输出响应中的 Set-Cookie 头 -- match serverVariableRESPONSE_Set_Cookie pattern(.*)(ASP.NET_SessionId|.ASPXAUTH|PHPSESSID|JSESSIONID|sessionid)(.*) / !-- 执行重写操作在 Cookie 属性里补 HttpOnly -- action typeRewrite value{R:1}{R:2}{R:3}; HttpOnly / /rule /outboundRules /rewrite先别急着复制我解释一下这段规则的工作原理不然踩坑了都不知道怎么排。这条规则匹配的是服务器响应头里的Set-Cookie内容。括号拆了三组第一组是 Cookie 名前半部分第二组是 Cookie 名称这里是白名单匹配了几种常见的会话 Cookie第三组是剩余部分。重写时把三段拼回去再在后面追加; HttpOnly。这样响应到浏览器时Cookie 就带上了 HttpOnly 属性。但这里有两个问题你得注意第一上述写法只处理了命中了白名单的 Cookie 名。如果扫描器报的是自定义 Cookie比如loginToken、traceId这条规则不生效。要覆盖所有 Cookie直接改成pattern(.*)但这会把某些前端需要读的 Cookie 也一起禁止得自己权衡。第二这种规则在 Cookie 已经带了HttpOnly的情况下会追加成HttpOnly; HttpOnly。好在浏览器对此是包容的能正常解析不会报错但强迫症看着难受。下面给一版更严谨的写法用反向匹配排除已有 HttpOnly 的情况rewrite outboundRules rule nameAdd HttpOnly if missing enabledtrue patternSyntaxECMAScript match serverVariableRESPONSE_Set_Cookie pattern(.*)(ASP.NET_SessionId|.ASPXAUTH|PHPSESSID|JSESSIONID|sessionid)([^;]*)(?![^;]*HttpOnly) / action typeRewrite value{R:1}{R:2}{R:3}; HttpOnly / /rule /outboundRules /rewrite(?![^;]*HttpOnly)这部分是负向前瞻意思是“如果这个 Cookie 属性列表里已经存在 HttpOnly就不要匹配”从而避免重复添加。3.5 方案四服务器级全局把关多站点环境的最优解如果你运维的服务器上有几十个站点一个站点一个站点去改 web.config 效率太低而且漏配概率大。这时候直接在服务器层面把好安全关口才是正道。思路是这样的把上述的 rewrite 规则写到一个独立的 XML 文件中然后在 IIS 的应用宿主配置文件applicationHost.config里全局引用。这样所有站点都会继承这条出站重写规则。具体操作步骤在任意位置创建一个文件比如C:\inetpub\custerr\GlobalHttpOnly.xml位置随意但别放站点目录里避免被清理掉内容就是上面的 rewrite 规则。打开 IIS 管理器选中服务器根节点不是具体站点双击“配置编辑器”。在“节”下拉框里选择system.webServer/rewrite/globalRules在右侧的Collection处直接编辑把规则粘贴进去。点击右上角“应用”。没有“配置编辑器”入口的可以直接用记事本改C:\Windows\System32\inetsrv\config\applicationHost.config找到rewrite区域在globalRules节点下插入规则。修改前记得先备份这个文件改错会导致 IIS 起不来。全局规则的好处是“一劳永逸”但风险也在这里——如果某个应用的前端逻辑依赖读取 Cookie被全局 HttpOnly 一卡功能会异常。所以生产环境改全局之前强烈建议先在测试站点上验证一遍。3.6 方案对比我把几种方案放在一起对比一下方便你根据自己环境选方案对比表方案适用范围实施成本生效级别优点缺点修改代码所有语言中代码级最精确可控需开发配合时间不可控web.config httpCookiesASP.NET低站点级配置简单只对 .NET 运行时 Cookie 生效URL Rewrite 出站规则所有站点中站点/全局支持自定义与全局批量规则语法有学习成本应用宿主全局规则所有站点中机器级一次配置全部生效影响面大需要充分测试就我个人的建议开发能改代码优先改代码改不动就用 URL Rewrite站点少用站点级站点多直接用全局规则。但不管选哪种改完之后的验证这一步都不能省。4. 实操演示以最典型的 IIS ASP.NET 站点为例完整走一遍光讲原理和方案不行这里我按最常见的场景——一台 Windows Server 2019 IIS 10上面跑着一个 ASP.NET 4.x 站点扫描报告提示会话 Cookie 缺 HttpOnly——完整操作一遍。整个流程从验证开始到收尾走下来大概 20 分钟。4.1 第一步确认漏洞现状定位问题 Cookie在服务器上打开浏览器访问站点按 F12 打开开发者工具选择 Network 标签页刷新页面然后点击任意一个请求查看 Response Headers。或者更直接一点用 PowerShell 模拟一个请求看响应头$response Invoke-WebRequest -Uri http://your-site.com/login -SessionVariable sess $response.Headers[Set-Cookie]执行后你会看到类似输出ASP.NET_SessionIdfg3j2k...; path/; HttpOnly .ASPXAUTHxyz...; path/; HttpOnly CustomTokenabc123; path/这里前两条是安全的问题就出在第三条CustomToken上它没有任何属性。从名字能猜出这是业务自定义的身份令牌但不确定是哪个模块设置的。为了验证可以继续对受保护的页面发请求逐个排除。不过大多数时候定位到名字就能确认来源了。4.2 第二步按场景选择修复方式并实施既然这个CustomToken是自定义 Cookie此时web.config里的httpCookies httpOnlyCookiestrue配置会有用但有个前提该属性只影响 ASP.NET 通过Response.CookiesAPI 写入的 Cookie如果 Cookie 是第三方组件直接用HttpResponse.AddHeader方式写入的则不受此配置控制。这种情况下最稳妥的就是 URL Rewrite 出站规则方案。我直接在站点 web.config 里加上规则system.webServer rewrite outboundRules rule nameForce HttpOnly for CustomToken enabledtrue match serverVariableRESPONSE_Set_Cookie pattern(.*)(CustomToken|ASP.NET_SessionId|.ASPXAUTH)([^;]*)(?![^;]*HttpOnly) / action typeRewrite value{R:1}{R:2}{R:3}; HttpOnly / /rule /outboundRules /rewrite /system.webServer这里我把CustomToken加进了白名单同时保留几个常见会话 Cookie避免第三方模块的某些 Cookie 漏掉。把这段配置保存到web.config后IIS 会在几秒内自动生效不需要重启。如果希望验证一下 Rewrite 规则本身是否生效可以在 IIS 管理器的“URL 重写”功能里找到对应规则进行测试也可以直接看下一步的抓包结果。4.3 第三步验证修改结果确认扫描告警消除再次执行 PowerShell 请求看 Set-Cookie 输出$response2 Invoke-WebRequest -Uri http://your-site.com/login -SessionVariable sess2 $response2.Headers[Set-Cookie]这次能看到ASP.NET_SessionIdfg3j2k...; path/; HttpOnly .ASPXAUTHxyz...; path/; HttpOnly CustomTokenabc123; path/; HttpOnly三条 Cookie 全部带上了 HttpOnly。此时再用扫描器复扫这条告警就会从报告里消失。有条件的可以在浏览器里打开一个带登录态的页面在 Console 里执行document.cookie会发现刚才那几条会话 Cookie 都看不到了只剩一些不带 HttpOnly 的非敏感 Cookie如果有的话。4.4 第四步做回归测试确保应用功能不受影响这一步很多人会忽略但我每次都会提醒——加 HttpOnly 后面临的最大风险不是技术配置错误而是应用业务受损。比如有些前端框架例如旧版 AngularJS、部分第三方图表组件会把用户信息存储在 Cookie 里然后通过 JS 读取来渲染页面。一旦强制加上 HttpOnlyJS 全部读不到页面功能直接挂掉。所以改完之后务必把站点的核心流程走一遍登录、登出、下单、支付回调、文件上传下载等。重点观察浏览器控制台有没有报 JS 错误页面有没有出现“未登录”的异常跳转。如果发现某个需要前端读取的 Cookie 被误伤处理方式是在 Rewrite 规则里把该 Cookie 名排除掉。具体做法是把匹配模式改成“不匹配指定 Cookie 名的所有 Cookie”用负向匹配实现rule nameAdd HttpOnly except allowlist enabledtrue match serverVariableRESPONSE_Set_Cookie pattern^(?!.*(FrontendFlag)) / action typeRewrite value{R:0}; HttpOnly / /rule注意上面这条规则的作用是“只要不是 FrontendFlag 同名 Cookie 都追加 HttpOnly”。用^(?!.*name)的负向前瞻排除无需 HttpOnly 的 Cookie。但这种盲追加方式容易造成重复尽量只在确实找不到其他方案时使用。5. 高频踩坑记录这些坑我替你踩过了5.1 web.config 改了没生效检查是否拼错节点最典型的错误是配置写没问题但节点位置搞错了。httpCookies必须放在system.web节点下而不是system.webServer。后者是 IIS 托管模块的配置区不认识这个元素。如果你在日志或事件查看器里看到“无法识别的配置节 httpCookies”那基本就是放错位置了。5.2 URL Rewrite 规则不触发注意 outboundRules 的匹配时机出站重写规则的工作时机是“服务器端响应即将输出时”在 IIS 管线中属于比较靠后的阶段。如果你发现规则加了但响应头毫无变化优先排查三件事模块是否真的安装了进入 IIS 管理器看站点是否有“URL 重写”图标没有说明模块缺失。规则是否在正确的规则集里站点级规则在web.config的system.webServer/rewrite/outboundRules中。全局规则在applicationHost.config的rewrite/globalRules中。别把出站规则写到rules里那是入站规则。服务器变量是否声明RESPONSE_Set_Cookie属于服务器变量需要在rewrite节点下声明allowedServerVariablesadd nameRESPONSE_Set_Cookie //allowedServerVariables。不然规则即使写了也不会执行。第三点是我踩过最隐蔽的坑——规则看起来完全正确但 Response 头就是不变最后查了半天发现是没声明服务器变量Rewrite 引擎直接忽略了这条规则。加了声明后立刻生效。5.3 加了 HttpOnly 后Cookie 变没了看是不是被全局规则扫到如果你在服务器级别配置了全局规则影响范围是这台机器上所有站点。这时候出现某个老系统登录状态异常写个页面在服务器本地输出document.cookie检查发现全是空字符串那就是全局规则把业务需要读取的 Cookie 也强制 HttpOnly 了。解决办法全局规则优先但不是一刀切。把规则里的匹配改成白名单模式只对包含Session、Auth、Token等关键字的 Cookie 生效其余不碰。这样既能覆盖绝大多数安全需求又能把业务影响降到最低。5.4 扫描器还是报可能是缓存导致误报有时候配置完全正确浏览器抓包也能看到 HttpOnly但扫描器依然报缺失。原因多半是扫描器抓取的是未登录状态的 Cookie或者命中的是静态页面的缓存响应。让扫描器带登录态跑一次或者清掉 CDN/缓存层的缓存后再扫一般就能消除误报。5.5 HTTPS 场景顺便把 Secure 一起加如果你的站点是全 HTTPS建议在追加 HttpOnly 的同时把 Secure 属性也加上。Secure 标志的作用是确保这条 Cookie 只在 HTTPS 连接中传输防止被降级到 HTTP 后被中间人截获。在自建场景中如果确实无法全站 HTTPS那还是别加 Secure否则用户从 HTTPS 页面跳转到 HTTP 页面时Cookie 就不带了会导致莫名其妙的掉登录。5.6 从 IIS 6 升级到 IIS 8 后配置丢失每次 IIS 版本大升级applicationHost.config的 schema 也可能变化老的web.config里有些配置会失效或报错。升级前把配置文件全部备份升级后用 IIS 管理器的“配置编辑器”导出当前所有设置再和旧版对比缺什么补什么。别想着“IIS 版本兼容配置应该没问题”我在实际工作中遇到过不止一次老站点迁移后 Cookie 属性丢失的情况。5.7 一台机器上有多个应用池规则冲突怎么办全局规则对这台机器上的所有应用池生效不同应用池里的应用如果是不同技术栈一个 .NET、一个 PHP规则对两者都会起作用。这会带来一个潜在问题PHP 应用如果自己的session.cookie_httponly已经设置为 On那么通过 PHP 发出的 Set-Cookie 本来就带 HttpOnly此时全局规则还可能再给它追加一个形成HttpOnly; HttpOnly。虽然浏览器能接受但在某些严格 HTTP 解析器中会报警告。这种情况下有两类处理方式一是在全局规则里增加负向前瞻排除已有 HttpOnly 的 Cookie二是在 PHP 等应用中关闭自带的 HttpOnly 设置统一交给 IIS 全局规则管理。我一般选择后者让安全策略集中在服务器层面避免各应用各搞一套。6. 加固之后的延伸思考HttpOnly 只是安全之旅的起点修完 HttpOnly 缺口别急着高兴这仅仅是通过安全扫描的第一道门槛。按照等保 2.0 和 OWASP 的常规要求和 Cookie 安全相关的属性还要配套检查Secure 标志确保 Cookie 只在 HTTPS 下传输。配置方式是让站点全站 HTTPS然后在 IIS 的 URL Rewrite 中再加一条规则或者直接在应用代码里设置。对 .NET 站点配置requireSSLtrue即可。SameSite 属性控制 Cookie 在跨站请求中是否携带对 CSRF 防护有明显作用。Lax 模式默认同站携带、跨站 GET 链接不携带是当前比较安全的配置。在 URL Rewrite 中追加方法和 HttpOnly 同思路。Cookie 过期时间不要设置永久会话建议 20-30 分钟无操作失效。可在应用层通过设置Session.Timeout或者在 Cookie 上限制Max-Age实现。Token 随机性即便 HttpOnly 阻止了脚本窃取如果 Session ID 的随机性不足攻击者依然可能通过暴力预测拿到有效会话。ASP.NET 默认的 Session ID 生成器强度较高但自定义会话方案需要重点关注。还想再强调一点——HttpOnly 不是对抗 Session 劫持的万能药。如果攻击者已经拿到了受害者的网络链路控制权比如在同一个局域网内做 ARP 欺骗或者通过响应头注入等方式直接读取明文 CookieHttpOnly 一样挡不住。所以安全建设从来不是单一防线而是纵深防御HttpOnly 防 XSS 链条HTTPS 防网络嗅探短超时 固定 Session 管理策略防会话固定这四样要一起上。说回 IIS 运维本身我也给你几句掏心窝的建议配置安全策略前先备份机器上所有配置文件每次改完都做一次完整回归测试规则尽量从简不要搞一长串正则去“面面俱到”那只会给自己徒增排障难度。这些事看似琐碎其实最能体现一个运维人员的功底。我自己带过不少新人看他们上来就“一顿操作猛如虎”把服务器规则配得密密麻麻出问题后又一头雾水往往就是因为没有把逻辑理清楚也没有在方案的简单性和覆盖范围之间找到合适的平衡点。这次关于 IIS 会话 Cookie 缺 HttpOnly 的修复思路就分享到这里。如果你在实际操作中遇到了其他奇怪的 Cookie 配置问题或是扫描器“顽固不化”一直报漏洞欢迎对照文中的排查清单一步步排除。安全加固这件事耐心和思路比技术本身更重要。
返回列表