
前一阵帮客户做等保复测安全扫描报告里又出现一条醒目告警会话cookie中缺少HttpOnly属性。这类问题在IIS环境中出现频率很高——你打开浏览器明明能正常登录业务也没报错可扫描器就是盯着响应头里的Set-Cookie不放。今天我就把这个问题的来龙去脉、修复方案和踩坑记录完整梳理一遍希望你在IIS上遇到同样告警时能少走一点弯路。这不是什么高深漏洞但如果不搞清楚原理很容易在修复时越搞越乱。很多人第一次看到扫描报告第一反应是去IIS管理器里找“设置Cookie”的按钮结果翻遍整个界面也找不到。原因很简单IIS本身只负责把应用生成的响应头发送出去它并不知道你的会话Cookie应该带什么属性。真正决定Cookie是否带HttpOnly的是应用代码和配置文件。理解了这一点后面所有修复方案就都顺理成章了。1. 这个告警到底在说什么先搞懂HttpOnly属性1.1 会话Cookie是网站的一张“临时通行证”几乎所有需要登录的网站都会在用户登录成功后下发一个会话标识比如ASP.NET环境里的ASP.NET_SessionIdPHP环境里的PHPSESSID。这个标识就是你的“临时通行证”浏览器每次请求都会把它带在Cookie里服务器看到这张通行证就知道“哦这是那个已经登录的用户”不需要每次重新输密码。通行证一旦被别人拿走对方就能冒充你登录。Cookie泄露的常见途径是跨站脚本攻击XSS——攻击者往页面里注入一段JavaScript脚本执行后通过document.cookie读取当前页面的所有Cookie然后悄悄发到攻击者的服务器上。如果你的会话Cookie没有设置HttpOnly属性这段脚本就能直接读到通行证内容用户的会话就被人偷走了。1.2 HttpOnly属性的防御原理HttpOnly是Cookie的一个属性设置之后浏览器会允许服务器读取这个Cookie但禁止JavaScript通过document.cookie访问它。换句话说即使页面被注入了XSS脚本脚本也无法直接拿到这个Cookie的内容会话标识被偷的风险就下降了一大截。这个属性由微软在2002年的IE6 SP1中首次引入现在所有现代浏览器都已支持。它不需要额外安装组件也不需要改服务器端的运行时逻辑纯粹是服务器在Set-Cookie响应头里加上一个标记浏览器看到后自动执行限制——就这么简单。但正因为实现门槛低很多老项目、快速开发上线的系统常常忽略在会话Cookie上加上它。1.3 为什么IIS上老是被扫描器报这个问题扫描器判断“会话Cookie缺少HttpOnly属性”的方式并不复杂它模拟一个普通用户访问站点观察服务器返回的Set-Cookie响应头检查其中的会话Cookie是否带HttpOnly标记没有就报漏洞。IIS在这里容易“躺枪”是因为IIS本身只是一个承载应用的平台它不会替PHP、ASP.NET或静态页面生成统一的会话Cookie策略。很多开发者在使用IIS部署旧版ASP.NET项目时默认配置并不会给自定义Cookie加上HttpOnlyPHP站点如果没改php.ini和setcookie配置生成的会话Cookie同样裸奔。甚至有些SPA前端项目直接用JavaScript在浏览器端创建了会话相关的Cookie——这里有个关键知识点通过JS设置的Cookie永远无法带上HttpOnly属性因为HttpOnly本身就是设计来禁止JS读写Cookie的它必须由服务器在响应头中下发。遇到这种项目你要么把会话改到后端生成要么接受扫描器持续报这个告警。1.4 明确影响范围哪些Cookie会被盯上安全扫描报告说的“会话cookie”在不同站点里指的东西不同可能是ASP.NET_SessionId可能是PHPSESSID也可能是应用自定义的Token、Auth、UserId之类。修复前一定要先搞清楚报告里说的Cookie名称不要盲目把所有Cookie都加上HttpOnly。有些Cookie是前端用来存用户偏好的比如记住列表视图模式、记住主题色这种即使加了HttpOnly也不影响功能但因为JS读不到部分前端逻辑反而会报错。会话Cookie和功能性Cookie要区别对待这是很多初学修复的人最容易忽略的细节。2. 修复前的环境定位先给站点做一个“体检”2.1 用开发者工具确认Cookie名称和来源先说怎么拿到当前站点实际下发的Cookie。你在浏览器里按F12切到Network面板刷新页面找到主文档请求doc类型看Response Headers里的Set-Cookie。如果你有登录操作登录后的那个请求里通常就会下发会话Cookie。我习惯再用curl确认一遍因为浏览器插件有时会干扰。比如curl -I -k https://yourdomain.com如果是POST登录接口则用curl -k -i -X POST https://yourdomain.com/login -d usernametestpasswordtest在返回的响应头里看清这些信息项目需要确认的内容Cookie名称是ASP.NET_SessionId、PHPSESSID还是自定义名称现有属性是否已有HttpOnly、Secure、SameSite生成方由后端下发还是前端JS写入了同名CookieCookie作用域是整个根域名还是某个路径专用这一步做完你才知道该改配置文件、改代码、改php.ini还是用URL Rewrite兜底。我见过有人拿着扫描报告直接往Web.config里加了一个remove nameSet-Cookie /结果把登录态直接弄丢了纯粹因为没搞清楚Cookie是谁下发的。2.2 确认站点技术栈和IIS版本不同技术栈修复入口完全不一样。IIS上常见的有这么几类ASP.NET WebForms / ASP.NET MVC.NET Framework走web.config的httpCookies节点或代码里的HttpCookie.HttpOnly属性。ASP.NET Core走Startup.cs里的CookiePolicyOptions或services.ConfigureCookiePolicyOptions配置。PHP通过FastCGI运行改php.ini里的会话配置或修改setcookie调用参数。纯静态站点本身不生成会话Cookie扫描器报的多半是某一个静态资源上的Cookie实际上静态站一般没有会话如果报告仍然报了再看是不是CDN或WAF注入的Cookie。IIS版本影响的主要是管理界面和模块支持。Windows Server 2012 R2上的IIS 8.5、Server 2016/2019的IIS 10功能上差别不是很大。如果你要在IIS管理器里操作“身份验证”、“应用程序池”不同版本菜单略有差异但Web.config的节点结构是一致的。2.3 备份是第一优先级顺便把IIS配置备份做成习惯修改任何配置前先在IIS环境里做一个快照级备份。这个方法很多老运维都在用但新手经常跳过真出了问题只能干瞪眼。IIS自带了AppCmd工具可以做备份%windir%\system32\inetsrv\appcmd.exe add backup BeforeHttpOnlyFix恢复的时候执行%windir%\system32\inetsrv\appcmd.exe restore backup BeforeHttpOnlyFix这样备份的是IIS整体配置包括站点、应用池、绑定等。另外你准备修改的web.config文件也单独复制一份到网站目录之外防止改错之后无法快速回滚。很多老手还会把C:\Windows\System32\inetsrv\config\applicationHost.config一并备份。等你以后维护多了就会明白配置备份这件事看着不起眼关键时刻能救命。3. 四种主流修复方案总有一种适合你的环境3.1 方案一ASP.NET项目修改Web.config首选对于传统的ASP.NET WebForms、MVC项目最简单的办法是在web.config的system.web节点下添加或修改httpCookies配置configuration system.web httpCookies httpOnlyCookiestrue requireSSLtrue sameSiteLax / /system.web /configuration加完保存ASP.NET会自动回收应用程序池并应用新配置。这个节点的作用是把应用通过Response.Cookies下发的所有Cookie默认都设置为带HttpOnly属性requireSSLtrue会同时给Cookie加上Secure标记sameSiteLax则能缓解一部分CSRF攻击。但要注意两个细节第一如果你在代码里显式设置了某个Cookie的HttpOnly为false配置文件的默认值会被覆盖。老项目里经常出现这种代码HttpCookie cookie new HttpCookie(UserId, userId); cookie.HttpOnly false; // 这个显式赋值会覆盖web.config的默认设置 Response.Cookies.Add(cookie);这样即使你改了Web.config这个Cookie依然不带HttpOnly。扫描器如果盯的是这个自定义Cookie你改完配置也不会通过复测。第二httpCookies节点主要影响ASP.NET写入的Cookie对于第三方组件或者非ASP.NET写入的Cookie可能管不到。所以方案一改完一定要按第4节的验证方法再抓一次响应头。3.2 方案二代码层显式设置最稳妥如果你的项目还在维护并且能找到下发会话Cookie的代码那直接在代码里设置是最清晰、最指向明确的方案。C#里面这么写HttpCookie authCookie new HttpCookie(UserToken, token); authCookie.HttpOnly true; authCookie.Secure true; authCookie.SameSite SameSiteMode.Lax; authCookie.Path /; Response.Cookies.Add(authCookie);如果是通过FormsAuthentication下发的CookieFormsAuthenticationTicket ticket new FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.AddMinutes(30), false, userData); string encryptedData FormsAuthentication.Encrypt(ticket); HttpCookie cookie new HttpCookie(FormsAuthentication.FormsCookieName, encryptedData); cookie.HttpOnly true; cookie.Secure Request.IsSecureConnection; Response.Cookies.Add(cookie);PHP在IIS上同样可以在代码里设置setcookie(session_id, $sessionId, [ expires time() 3600, path /, domain example.com, secure true, httponly true, samesite Lax, ]);代码方案的好处是“指哪打哪”不会误伤其他Cookie坏处是需要改代码、重新发布。如果只是临时应对扫描你不太愿意动代码或找不到源码了那就看方案四。3.3 方案三PHP站点的php.ini配置如果你的IIS里跑的是PHP应用最省事的修复是直接改php.ini。在[Session]相关区域调整session.cookie_httponly 1 session.cookie_secure 1 session.cookie_samesite Lax这三项改完后重启PHP进程或回收对应应用程序池。它只对PHP会话机制生成的Cookie生效。如果应用里有很多自定义Cookie那还是得靠setcookie函数层面的httponly参数来补。另外补充一句session.cookie_secure 1只应该在纯HTTPS环境下开启否则HTTP访问时Cookie无法下发用户会陷入无限登录循环。如果你还没做全站HTTPS先不要急着加Secure。3.4 方案四IIS URL Rewrite出站规则兜底不给代码也能修这是一个“无代码兜底”方案适合两种情况一是找不到源码、没法改代码的遗留系统二是多个应用统一收敛想在IIS层面做一层全局修复。首先需要确保IIS安装了URL Rewrite 2.0模块。没装的话Web.config里写rewrite节点会直接报“无法识别的配置节”。然后在站点根目录的web.config中添加出站规则configuration system.webServer rewrite outboundRules rule nameAdd HttpOnly to Session Cookie enabledtrue match serverVariableRESPONSE_Set_Cookie pattern^(.*SessionId.*)$ / conditions add input{R:1} patternHttpOnly negatetrue / /conditions action typeRewrite value{R:1}; HttpOnly / /rule /outboundRules /rewrite /system.webServer /configuration注意这里有坑。Set-Cookie响应头可能在一次请求里包含多个Cookie每个Cookie之间用逗号分隔使用URL Rewrite的serverVariable操作必须小心不要把整个响应头里的所有Cookie全拼到同一行。实际设Rewirte规则时建议根据你的Cookie名做更精确的匹配比如^(.*ASP.NET_SessionId[^;]*)(.*)$。如果站点里同时有多个Cookie需要加HttpOnly尽量写成多条规则每条规则只处理一个指定名称的Cookie避免误伤。我用这个方案救过不少“只剩部署包、源代码丢了”的情况确实有效。但它的短板也很明显规则只是“改头”如果应用代码显式下发了一个带cookie.HttpOnly false的Cookie出站规则是可以覆盖的因为它改的是IIS响应头级别比代码晚一步执行。所以它能兜住绝大多数场景。4. 实操记录从备份到验证的完整流程4.1 在Windows功能中确认IIS模块齐全如果你是在Windows 10/11或Server上用本地环境做复现先确认IIS已经装上。控制面板 - “启用或关闭Windows功能”里展开“Internet Information Services”至少要勾上“Web管理工具”下的“IIS管理控制台”以及“万维网服务”下的“常见HTTP功能”、“ASP.NET”或“CGI”取决于你的应用类型。这里顺便提一个很多新人会卡住的点IIS面板打开后首页没有“URL重写”图标那就是没装URL Rewrite模块。去官网下载对应系统版本的rewrite_amd64.msi安装包装完重启IIS管理器就能看到了。server2019 iis添加进度不动这种问题多半是打开IIS管理器时在初始化配置或者权限不足用管理员身份重新打开一般能解决。4.2 区分站点类型并应用对应配置我给客户处理时一般先按2.1节判断站点类型再决定用什么方案。举两个真实案例案例一是台Windows Server 2012 R2上的老ASP.NET MVC站点扫描报告点名ASP.NET_SessionId缺少HttpOnly。我在站点的web.config里加上system.web httpCookies httpOnlyCookiestrue requireSSLfalse sameSiteLax / /system.web因为这个站点当时只做了HTTP内网访问requireSSL没开。保存配置文件后应用池自动回收用户重新登录一次我再抓响应头ASP.NET_SessionId后面已经带上了HttpOnly标记。案例二是台Win2019上的PHP站点报告点名PHPSESSID。我改了php.ini启用session.cookie_httponly 1然后回收应用程序池。这里要特别注意修改php.ini后很多新手只刷新浏览器不回收池子PHP进程还保持着旧的配置怎么刷新都看不到效果。IIS里PHP是通过FastCGI进程运行的配置变化后需要回收进程才能生效。我在IIS管理器中找到站点对应的应用程序池点击“回收”再看响应头问题解决。4.3 验证修改是否生效修改完成后验证这一步绝不能省。我习惯分两层验证第一层浏览器开发者工具。F12打开Network重新请求页面找到Set-Cookie响应头确认目标Cookie后面有HttpOnly。如果之前是通过HTTPS访问同时看下Cookie有没有Secure标记。第二层命令行抓头排除浏览器插件干扰curl -I -k https://yourdomain.com如果要看到完整登录流程里的Set-Cookie用curl -k -i -X POST https://yourdomain.com/login -d usernameadminpassword123456看到类似这行就是成功了Set-Cookie: ASP.NET_SessionId2f3a8b9c...; path/; HttpOnly; SameSiteLax之后再用安全扫描器复测报告中的“会话cookie中缺少HttpOnly属性”就会消失。这里有个绕不开的坑如果你的站点前端脚本也在往Cookie里写东西而你把所有Cookie都设成HttpOnly前端某些读取逻辑就坏了。比如用户偏好类CookieJS在读取时直接返回空字符串页面功能看起来“时好时坏”。所以修复前必须分清楚哪些Cookie是会话必需的、哪些只是前端功能用的。我一般不会全局把sitecore_analytics、theme这类自定义Cookie都改成HttpOnly除非业务确认不再通过JS读取它。4.4 别忘了配合Secure和SameSite一起看安全扫描报告通常不止查HttpOnly经常连带着要求Cookie带Secure、SameSite。既然这次改了配置不如一次性把三个属性都理顺。SecureCookie只能通过HTTPS传输。如果站点已经全站HTTPS加上没毛病如果还有HTTP的URL能访问网站业务加了需要确认业务链路支持。SameSite主要用来限制第三方请求携带Cookie缓解CSRF。可选值为Strict、Lax、None。现代浏览器默认已把没有SameSite的Cookie按Lax处理但扫描报告还是会提示最好显式声明。Domain和Path最好也显式设置别让Cookie作用域扩散到不必要的目录。这三个属性并不冲突都能在同一行Set-Cookie里共存Set-Cookie: SessionIdabc123; path/; HttpOnly; Secure; SameSiteLax我在实际项目中习惯按这个标准统一配置既满足了扫描器要求也对生产环境更安全。5. 常见问题与排查技巧实录5.1 为什么改完Web.configCookie还是没带HttpOnly这是留言区提问率最高的问题。我排查的顺序一般是这样的第一看有没有代码显式覆盖。前面提过HttpCookie.HttpOnly false这种代码会把配置文件的默认值盖掉。用文本搜索工具全项目搜一下HttpOnly\s*\s*false能搜出一堆历史遗留代码。第二看改的是不是站点实际使用的web.config。IIS里站点可以嵌套目录子目录下的web.config会覆盖根目录的配置。如果你有多个web.config改错层级也会无效。在IIS管理器中选中对应站点右键“浏览”确认站点根目录的真实路径。第三看是不是有出站规则和自定义Header模块冲突。有些人之前用customHeaders把Set-Cookie加进所有响应这实际上是错误的做法——因为Set-Cookie是特殊响应头不能通过customHeaders追加HttpOnly属性反而会造成响应头里出现两个Set-Cookie这会让浏览器糊涂甚至忽略其中一行的属性。正确做法是走URL Rewrite出站规则或应用配置。第四回收应用程序池。修改web.config会自动触发回收但如果你改的是machine.config这一步不能少。5.2 IIS报错“执行此操作时出错文件名: administration.config”这类问题这个热词搜寻度很高也常和本次修改动作同时发生。改动Web.config后如果文件内容格式不对、层级错误、权限不足IIS管理器在读取配置时就会弹“执行此操作时出错”并指向C:\Windows\System32\inetsrv\config\administration.config或applicationHost.config这种路径。我的处理经验先看事件查看器的“Windows日志-应用程序”里有没有ASP.NET或IIS报错详情再用命令行工具检查配置语法。跑一下%windir%\system32\inetsrv\appcmd.exe list config能正常输出就说明基础配置可读。如果还报错用备份恢复是最快的。这也就是我在3.3节反复强调备份非常重要的原因。改配置之前你永远觉得不会出事出问题时你一定会后悔没备份。5.3 一些关联问题.NET8部署、外网打不开、模块被改既然本文讲的是IIS这几个关联热词我也顺带提一下因为排查HttpOnly时你很可能同时遇到IIS里没有.NET 8的选项这是很多人在Server 2019上部署新版应用遇到的。IIS管理器里看不到.NET 8不是你安装问题而是你需要安装ASP.NET Core Module v2并且把应用程序池的“.NET CLR版本”设置为“无托管代码”。这和给Cookie加HttpOnly不冲突都存在同一个环境里。服务器IIS网站外网打不开如果修完配置从外网还是无法访问站点优先检查Windows防火墙是否放行了80/443端口以及IIS站点绑定是否监听在*、具体IP还是localhost上。IP变化后老绑定会悄悄失效。IIS模块被篡改写入黑链这是老生常谈的安全问题了。如果你在排查配置时发现IIS莫名多了很多未知模块或处理程序要立即检查applicationHost.config里的modules节和handlers节。配置安全的底线是不用的模块移除未知模块坚决删除。5.4 常见问题速查表现象最可能原因处理办法改了web.config不生效代码显式覆盖了HttpOnlyfalse搜索代码删除或改成true浏览器里看不到HttpOnly看过期缓存或浏览器插件换无痕窗口用curl重新验证CustomHeaders设置了Set-Cookie无效Set-Cookie不能用customHeaders追加删除customHeaders用URL Rewrite或代码方案PHP改了php.ini没变化FastCGI进程未回收回收应用程序池或重启进程配置后前端功能异常Cookie设置了HttpOnly导致JS读不到识别功能性Cookie单独排除IIS管理器打不开/报错配置文件权限或语法问题用appcmd检查恢复备份扫描报告仍显示漏洞其他自定义Cookie未处理定位报告里的具体Cookie名补规则5.5 我个人的一点实操心得做了这么多IIS安全加固我个人最大的体会是配置修复本身不复杂真正考验人的是“不要扩大修复面”。加HttpOnly是一件防御性操作但它会影响浏览器端JavaScript对Cookie的可见性。如果你不确认哪些Cookie被前端读取、哪些是后端专用就一股脑全加上最终很可能把正常业务改出问题。另外很多人改完配置只检查首页忽略登录接口。实际上会话Cookie往往是在登录成功那一刻才下发的你不走一遍完整的登录流程根本看不清修复效果。所以每次验证我都会先清空浏览器Cookie再重新走一次登录再去看Set-Cookie响应头。最后再分享一个小技巧如果公司有多个IIS站点最好把HttpOnly、Secure、SameSite这套配置的校验做成一个固定检查项不用每次都靠人工抓包。写一个简单的PowerShell循环模拟请求每个站点的登录页或首页检查Set-Cookie里是否含HttpOnly把结果输出成一个表格。定时跑一遍发现问题早处理就不用每次等扫描报告送到脸上才手忙脚乱了。