xss属于top10漏洞之一,之前或多或少接触了解过,不过这次准备全面学习一手
xss介绍
XSS(Cross-Site Scripting,跨站脚本攻击)(与css进行区别) 是一种常见的Web安全漏洞,攻击者通过在目标网站上注入恶意脚本(通常是JavaScript),使得这些脚本在其他用户的浏览器中执行。XSS攻击可以窃取用户数据(如Cookie、Session Token)、篡改网页内容、重定向用户到恶意网站,甚至进行更高级的攻击(如CSRF、键盘记录等)。
xss存在的原因
Web应用程序对用户输入数据的不充分过滤和验证,导致恶意脚本被注入到网页中并在其他用户的浏览器中执行。
xss的分类
分为:存储型、反射型、DOM型
存储型XSS(Persistent XSS)
是一种恶意脚本被永久存储在目标服务器(如数据库、文件系统、评论区等)的XSS攻击方式。当其他用户访问受影响页面时,存储的恶意脚本会自动加载并执行,无需用户点击特殊链接。
示例场景
(1)评论区XSS
1 | <!-- 攻击者提交的评论 --> |
该评论被存入数据库。其他用户访问该页面时,脚本自动执行,发送他们的Cookie到攻击者服务器
(2)用户名XSS
1 | <!-- 攻击者注册的用户名 --> |
- 用户名显示在所有页面(如论坛、用户列表)。
- 其他用户浏览时触发
onerror
,执行恶意代码。
反射型XSS(Reflected Cross-Site Scripting)
是一种恶意脚本不会存储在服务器上,而是通过URL参数、表单提交等方式反射回浏览器并执行的XSS攻击方式。
示例场景
(1)搜索框反射XSS
1 | https://example.com/search?query=<script>alert('XSS')</script> |
- 如果网站直接返回
query
参数到HTML页面:
1 | <div>您搜索的内容:<script>alert('XSS')</script></div> |
访问该URL时,脚本被执行。
DOM型XSS(Document Object Model-based XSS)
是一种完全在客户端浏览器中发生的XSS攻击,恶意代码通过修改DOM环境执行,不经过服务器。与反射型/存储型XSS不同,DOM型XSS的漏洞存在于前端JavaScript代码中。
以下 JavaScript 操作可能触发 DOM XSS:
危险操作 | 示例 | 风险 |
---|---|---|
直接修改 innerHTML |
element.innerHTML = userInput |
插入的 HTML 会被解析 |
动态执行脚本 | eval(userInput) |
直接执行字符串代码 |
未转义的 URL 处理 | location.href = userInput |
可能触发 javascript: 伪协议 |
不安全的 DOM API | document.write(userInput) |
直接写入原始 HTML |
示例场景
(1)不安全的 innerHTML
操作
1 | // 从URL获取参数并直接插入DOM |
攻击者构造URL:
1 | http://example.com/#<img src=x onerror=alert('XSS')> |
访问后,onerror
触发XSS。
三种xss区别:
类型 | 数据来源 | 触发位置 | 是否依赖服务器 |
---|---|---|---|
DOM 型 | URL/前端存储 | 纯前端处理 | ❌ 不依赖 |
反射型 | URL 参数 | 服务器返回恶意脚本 | ✅ 依赖 |
存储型 | 数据库/存储 | 服务器返回恶意脚本 | ✅ 依赖 |
XSS的攻击载荷
以下所有标签的 > 都可以用 // 代替,例如 < script> alert(1) </script//
1 | <script> |
XSS漏洞的挖掘
一般情况下,反射型XSS在搜索框啊,或者是页面跳转啊这些地方,而存储型XSS一般是留言,或者用户存储的地方,而DOM呢?是在DOM位置上,不取决于输入环境上。
XSS的简单绕过滤和绕过
1 | 1.区分大小写过滤标签: |
dvwa
Reflected型xss
low:
1 | payload:<script>alert("x")</script> |
源码:
1 |
|
未对输入内容过滤,导致反射xss
medium:
1 | payload:<Script>alert("xss")</Script> |
这里可以用大小写,也可以用双写绕过;
源码:
1 |
|
high:
1 | payload: <img src=0 onerror=alert("xss")> |
源码:
1 |
|
很明显这里无法双写和大小写绕过
impossible :
因为绕不了来看源码:
1 |
|
用到了htmlspecialchars
函数校验,默认会转义<
, >
, &
, "
,'
等特殊字符
Store型XSS
low:
1 | payload:<script>alert("xss")</script> |
可见被写入了服务器中:
源码:
1 |
|
medium:
这里在message中加入xss内容一直不行,于是想到在name里面加入,但是有输入长度限制,可以使用抓包发送:
源码:
1 |
|
我们可以看到只对message参数使用了addslashes
过滤,而name并没有,所以name存在xss
addslashes()
函数主要用于在特定字符前添加反斜杠进行转义,这些字符包括:单引号 ('
),双引号 ("
),反斜杠 (\
),NULL 字符
防止sql注入;还用到了mysqli_real_escape_string()
防止sql注入
而htmlspecialchars()
是 PHP 中专门用于防御 XSS 攻击的核心函数,该函数将以下特殊字符转换为 HTML 实体:
1 | & → & |
可见这里将我们xss所用到的字符都给转义了,这种是绕不了的,看到了就放弃吧
high:
1 | payload:txtName=<img src=0 onerror=alert(document.cookie)> |
和上面的中级一样,只能用txtname参数,不过这次过滤了<script>
,改用其他标签即可
源码:
1 |
|
impossible:
源码:
1 |
|
有mysqli_real_escape_string
和htmlspecialchars
这两个函数基本能防御绝大多数sql注入和xss
dom型xss
low:
这里只有可选框,没有一个输入框,但我们可以尝试控制url的参数,而不一定非要通过可选框控制:
1 | payload:?default=<script>alert(document.cookie)</script> |
源码:
1 |
|
medium:
前置知识:
1 | 浏览器在渲染<select>和<option>标签时,option的内容(无论是标签体还是value属性)都只会被当作纯文本显示,而不会解析为HTML或执行其中的JavaScript代码。因此,即便在value或标签体中插入了<img>标签或onerror事件,浏览器也不会把它当作HTML标签处理,而是直接显示出来 |
测试:?default=<img src=0 onerror=alert(document.cookie)>
并没有弹窗,f12查看源码:
写入的语句被写在<select>
标签,所以不会被当作js内容解析,解决的办法就是我们注入的语句最前面加上</select>
,主动去闭合前面的<select>
1 | payload:?default=</select><img src=0 onerror=alert(document.cookie)> |
源码:
1 |
|
high:
1 | payload:?default=English#<script>alert("xss")</script> |
这里的服务器和前端处理方式不一样:服务器看到的 default
参数:English
(#
后面的内容被忽略),但前端 JavaScript 读取的是完整 URL,前端就会生成:
1 | <option value='English#<script>alert("xss")</script>'>English#<script>alert("xss")</script></option> |
我们看下区别:
提示:medium也可以用#绕过后端
1 |
|
impossible:
1 |
|
我们输入的内容只会经过url编码处理,所以无法形成xss
pikachu
经过dvwa
反射型xss(get)
1 | payload:<script>alert("xss")</script> |
因为输入框长度受限,或者在url中输入也可以
反射型xss(post)
根据提示,账号密码为admin:123456,也可以爆破出来
1 | paylaod:<script>alert(document.cookie)</script> |
存储型xss
1 | paylaod:<script>alert(document.cookie)</script> |
每当我们一刷新页面就会弹窗出cookie,因为代码写入了服务器里,这就是与反射型的区别
dom型xss
注入:<img src="x" onerror="alert('XSS')">
无反应,但提示我们'>
1 | payload:'><img src="x" onerror="alert('XSS')"> |
来看下前后的区别:
可以看出后者是用'>
将href的链接给闭合了,所以注入成功,看下源码就明白了
dom型xss-x
先看下源码如何处理的:
和上面一样都是将输入的内容写入href链接中,我们需要主动闭合,我们需要先点击有些费尽心机想要忘记的事情,后来真的就忘掉了
这个链接,然后才会触发domxss
函数里的xss内容
1 | paylaod:'><img src="x" onerror="alert('XSS')"> |
按照提示给的payload也可以,不过触发的条件不一样,第一个是移动鼠标到那个位置,另一个是需要点击
xss之盲打
给出的页面类似一个留言板,两个输入框都输入xss语句:<script>alert(document.cookie)</script>
,刷新并没有任何反应,查看源码:
不知道写入的数据如何处理的,但给了提示,登录后台,账号密码:admin/123456,然后就直接弹窗了:
我们看到这里是以admin登录的,这种存储型xss,如果cookie被发送到恶意服务器,就会产生安全威胁
xss之过滤
不知道过滤了什么,只能一个一个试,
1 | <script> |
发现<Script>
就可以弹窗,但是弹不出来cookie不知道为啥,看前端源码不知道怎么处理的,只能看后端的处理
这种双写绕不了,只能大小写,或者更换标签(这种属于反射型,由服务器处理传送回前端)
xss之htmlspecialchars
htmlspecialchars函数用于防范xss,一般会过滤:',",<,>,&
这几个字符,我们在输入框输入这几个字符,看看有没有没被过滤的字符
查看前端源码:
发现服务器返回的数据又被拼接到href中,上面的圈二和四是我们输入的单引号,并没有被转移,所以后端并未对单引号做处理,于是我们可以用单引号闭合href原先的左单引号
1 | paylaod:#' onclick='alert(document.cookie) |
alert左边单引号用于闭合原先href的右单引号,上面payload的#可写可不写,主要是为了闭合
htmlspecialchars默认不对单引号处理
xss之href输出
先看看有没有过滤,使用' " < > &
测试,查看前端源码:
发现都被转义了,虽然<a>
标签的 href 属性用于指定超链接目标的 URL,但href 属性的值可以是任何有效文档的相对或绝对 URL,包括片段标识符和 JavaScript 代码段
。如果用户选择了<a>
标签中的内容,那么浏览器会尝试检索并显示 href 属性指定的 URL 所表示的文档,或者执行 JavaScript 表达式、方法和函数的列表。
利用javascript表达式生成payload:
1 | payload:javascript:alert(document.cookie) |
查看后端处理:
ENT_QUOTES:用于开启编码双引号和单引号
xss之js输出
测试:</script><script>alert(document.cookie)</script>
,没反应,查看源码:
发现我们输入的语句经过服务器传递给了$ms,被包含在了<script>
标签中,那如果我们主动闭合一个</script>
标签,那我们的命令是不是就能执行了???,
1 | paylaod:</script><script>alert(document.cookie)</script> |
其实还有一种方法,也是主动去闭合,我们看到我们写入的语句被单引号包括,如果我们主动闭合左单引号,紧接一个分号,那么当前js语句就终止了
1 | payload:';alert(1);// |
<img>
是 HTML 标签,需插入到 DOM 后才会被解析。直接写在 <script>
中会被视为无效语法。
xss的防御
- 设置关键词黑名单:包括
” ‘ ”,” “ ”,”<”,”>” “on*”
等非法字符。 - 设置http-only:只要 cookie 中含有 Http-only 字段,那么任何 JavaScript 脚本都没有权限读取这条 cookie 的内容
- 使用html实体编码等
- SOP同源策略:SOP把拥有相同主机名 、协议和端口的页面视为同一来源。不同来源的资源之间交互是受到限制的。
xss常用Payload
1 | 1. '"><script>alert(1)</script> |
实际场景中:一般情况下,反射型XSS在搜索框啊,或者是页面跳转啊这些地方,而存储型XSS一般是留言,或者用户存储的地方,而DOM呢?是在DOM位置上,不取决于输入环境上。
大多反射型都需要去闭合前面的标签(我们可以用上面的payload),存储和dom型可能不需要
js学习
JavaScript(JS)是一种多用途的脚本语言,主要用于 网页交互 和 应用开发
<script>
元素用于嵌入可执行代码或数据,这通常用作嵌入或者引用 JavaScript 代码,所以在<script>
标签中输入<image><svg><body>
等常规html标签都是无效的
js在html中两种使用方式:
DOM学习
DOM(文档对象模型) 是浏览器将 HTML/XML 文档解析成的 树形结构对象,允许 JavaScript 动态访问和操作网页内容、结构和样式
1. DOM 的本质
- 浏览器解析 HTML 后生成的树状结构,每个标签、属性、文本都是树的节点(Node)。
- 编程接口:提供 JavaScript 操作网页的 API(如修改内容、添加事件等)。
2. DOM 节点类型
节点类型 | 说明 | 示例 |
---|---|---|
文档节点 (Document) | 整个文档的根节点 | document |
元素节点 (Element) | HTML 标签(如 <div> ) |
document.getElementById() |
属性节点 (Attr) | 标签的属性(如 class="box" ) |
element.getAttribute() |
文本节点 (Text) | 标签内的文本内容 | node.textContent |
注释节点 (Comment) | HTML 注释 | <!-- 注释 --> |
同源策略
同源策略是浏览器实施的一种安全机制,它限制了一个源的文档或脚本如何与另一个源的资源进行交互。
同源的定义:两个URL在以下三个方面完全相同:
- 协议相同(http/https)
- 域名相同(包括子域名)
- 端口号相同
以 http://www.example.com/dir/page.html
为例:
对比URL | 结果 | 结果 |
---|---|---|
http://www.example.com/dir/page2.html |
同源 | 相同的协议,主机,端口 |
http://www.example.com/dir2/other.html |
同源 | 相同的协议,主机,端口 |
http://username:password@www.example.com/dir2/other.html |
同源 | 相同的协议,主机,端口 |
http://www.example.com:81/dir/other.html |
不同源 | 相同的协议,主机,端口不同 |
https://www.example.com/dir/other.html |
不同源 | 协议不同 |
http://en.example.com/dir/other.html |
不同源 | 不同主机 |
http://example.com/dir/other.html |
不同源 | 不同主机(需要精确匹配) |
http://v2.www.example.com/dir/other.html |
不同源 | 不同主机(需要精确匹配) |
http://www.example.com:80/dir/other.html |
看情况 | 端口明确,依赖浏览器实现 |
- 有同源策略:就像每家商店有自己的门禁系统,小偷需要分别突破
- 没有同源策略:所有商店共用同一把钥匙,突破一家等于突破所有
攻击方式 | 是否需要银行有XSS? | 是否依赖同源策略? |
---|---|---|
XSS攻击 | ✅ 需要 | ❌ 不依赖(XSS是同源攻击) |
无同源策略的跨域AJAX | ❌ 不需要 | ✅ 依赖(无SOP才能跨域) |
CSRF(跨站请求伪造) | ❌ 不需要 | ✅ 依赖(SOP限制部分CSRF) |
直接读取DOM/Cookie | ❌ 不需要 | ✅ 依赖(SOP阻止跨域访问) |
跨域请求
当请求的目标URL与当前页面的源不同时,就发生了跨域请求。浏览器会阻止这类请求的响应,除非服务器明确允许。
跨域请求示例:假设当前页面URL是 http://www.example.com/index.html
,以下请求会被视为跨域:
- 向
https://www.example.com/api
发送AJAX请求- 原因:协议不同(http vs https)
- 向
http://api.example.com/data
获取数据- 原因:域名不同(www vs api子域名)
- 向
http://www.example.com:8080/service
请求服务- 原因:端口不同(80 vs 8080)
- 向
http://anotherdomain.com/resource
获取资源- 原因:完全不同的域名
解决跨域问题的方法
CORS (跨域资源共享)
案例背景:小明在开发了一个A网站(用来记录本公司的设备信息),访问地址是https://www.a.com,需要使用ajax请求**B服务器**上的数据,但是又苦于同源策略的限制…… 后来找到了一种解决方法CORS
基本流程
- 浏览器检测到跨域请求:当A网站的JavaScript尝试向B服务器发送AJAX请求时
- 自动添加Origin头:浏览器自动在请求头中添加
Origin: https://www.a.com
- 服务器响应CORS头:B服务器需要在响应中包含特定的CORS头
- 浏览器检查响应头:浏览器验证响应头是否允许当前源访问
- 允许/阻止请求:根据验证结果决定是否让前端代码访问响应数据
最推荐的标准解决方案,需要后端配合设置响应头:
1 | Access-Control-Allow-Origin: https://yourdomain.com // 或 * (不推荐) |
JSONP (仅限GET请求)
JSONP(JSON with Padding)是一种早期解决跨域问题的技术,它利用<script>
标签不受同源策略限制的特性来实现跨域数据获取。
核心特点
- 仅支持GET请求:这是其最大限制
- 无需服务器特殊配置:不像CORS需要服务器设置响应头
- 逐渐被淘汰:现代开发推荐使用CORS
JSON、JSONP的区别:
1、JSON返回的是一串数据、JSONP返回的是脚本代码(包含一个函数调用)
2、JSONP 只支持get请求、不支持post请求
其余方法 | 原理 | 实现方式 |
---|---|---|
代理服务器 | 通过同源服务器中转请求 | Nginx反向代理 ,Node.js中间层代理 ,开发服务器代理 |
document.domain | 修改document.domain实现子域间通信 | 设置相同父域 :如document.domain = "example.com" |
图像ping | 利用<img> 标签发送简单请求 |
动态创建<img> 标签,设置src为目标URL |
WebSocket | 基于TCP的全双工通信协议 | 建立WebSocket连接,通过该连接进行双向通信 |
postMessage API | HTML5提供的跨文档通信API | 发送方调用targetWindow.postMessage() 接收方监听message 事件 |
window.name | 利用window.name在不同页面间传递数据 | 设置iframe的window.name, 跳转到同源页面后读取 |
因为初次学习,所以参考了许多博主的blog:
参考文章:
https://blog.csdn.net/weixin_40950781/article/details/100007103
https://www.cnblogs.com/heiwa-0924/p/12520547.html
https://www.freebuf.com/articles/web/253523.html