Published on

WEB安全基础

Authors
  • avatar
    Name
    游戏人生
    Twitter

同源策略

同源概念

同源策略是一个重要的安全策略,它用于限制一个origin的文档或它加载的脚本如何能与另一个源的资源进行交互。能够减少恶意文档,减少可能被攻击的媒介。

如果两个URL的协议、域名、端口号都相同,就称这两个URL同源。

默认情况下,浏览器支持跨域写和跨域资源嵌入,但不支持跨域读

script 、img、iframe、link、a 等标签不受同源策略限制,带src属性的标签每次加载的时候,实际上都是浏览器发起一次GET请求

跨域解决方案

document.domain + iframe (只有在主域相同的时候才可以使用)

在 a.com/a.html 中

document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://www.script.a.com/b.html';
ifr.display = none;
document.body.appendChild(ifr);
ifr.onload = function(){
  var doc = ifr.contentDocument || ifr.contentWindow.document;
  //在这里操作doc,也就是b.html
  ifr.onload = null;
};

在 script.a.com/b.html 中

document.domain = 'a.com';

动态创建script

function loadScript(url, func) {
  var head = document.head || document.getElementByTagName('head')[0];
  var script = document.createElement('script');
  script.src = url;

  script.onload = script.onreadystatechange = function(){
    if(!this.readyState || this.readyState=='loaded' || this.readyState=='complete'){
      func();
      script.onload = script.onreadystatechange = null;
    }
  };

  head.insertBefore(script, 0);
}
window.baidu = {
  sug: function(data){
    console.log(data);
  }
}
loadScript('http://suggestion.baidu.com/su?wd=w',function(){console.log('loaded')});
//我们请求的内容在哪里?
//我们可以在chorme调试面板的source中看到script引入的内容

location.hash + iframe

原理是利用location.hash来进行传值。

假设域名** a.com** 下的文件 a1.html 要和 blog.com 域名下的 blog2.html 传递信息。

  • a1.html首先创建一个隐藏的 iframe,iframe 的 src 指向 blog.com 域名下的 blog2.html 页面
  • blog2.html 响应请求后再将通过修改 a1.html 的 hash 值来传递数据
  • 同时在 a1.html 上加一个定时器,隔一段时间来判断 location.hash 的值有没有变化,一旦有变化则获取获取 hash 值

a1.html

function startRequest(){
  var ifr = document.createElement('iframe');
  ifr.style.display = 'none';
  ifr.src = 'http://www.blog.com/blog2.html#paramdo';
  document.body.appendChild(ifr);
}

function checkHash() {
  try {
    var data = location.hash ? location.hash.substring(1) : '';
    if (console.log) {
      console.log('Now the data is '+data);
    }
  } catch(e) {};
}
setInterval(checkHash, 2000);

blog2.html

//模拟一个简单的参数处理操作
switch(location.hash){
  case '#paramdo':
    callBack();
    break;
  case '#paramset':
    //do something……
    break;
}

function callBack(){
  try {
    parent.location.hash = 'somedata';
  } catch (e) {
    // ie、chrome的安全机制无法修改parent.location.hash,
    // 所以要利用一个中间的cnblogs域下的代理iframe
    var ifrproxy = document.createElement('iframe');
    ifrproxy.style.display = 'none';
    ifrproxy.src = 'http://www.a.com/a3.html#somedata';    // 注意该文件在"a.com"域下
    document.body.appendChild(ifrproxy);
  }
}

a.com 域名下的 a3.html

//因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);

window.name + iframe

window.name 的优点:

name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

  1. 创建a1.html
  2. 创建 proxy.html ,并加入如下代码
<head>
  <script>
    function proxy(url, func){
      var isFirst = true,
        ifr = document.createElement('iframe'),
        loadFunc = function(){
          if(isFirst){
            ifr.contentWindow.location = 'http://a.com/a1.html';
            isFirst = false;
          }else{
            func(ifr.contentWindow.name);
            ifr.contentWindow.close();
            document.body.removeChild(ifr);
            ifr.src = '';
            ifr = null;
          }
        };

      ifr.src = url;
      ifr.style.display = 'none';
      if(ifr.attachEvent) ifr.attachEvent('onload', loadFunc);
      else ifr.onload = loadFunc;

      document.body.appendChild(iframe);
    }
  </script>
</head>
<body>
  <script>
    proxy('http://www.baidu.com/', function(data){
      console.log(data);
    });
  </script>
</body>

在 b.com/b1.html 中包含:

<script>
  window.name = '要传送的内容';
</script>

postMessage

a.com/index.html

<iframe id="ifr" src="b.com/index.html"></iframe>
<script type="text/javascript">
  window.onload = function() {
    var ifr = document.getElementById('ifr');
    var targetOrigin = 'http://b.com';  
    // 若写成'http://b.com/c/proxy.html'效果一样
    // 若写成'http://c.com'就不会执行postMessage了
    ifr.contentWindow.postMessage('I was there!', targetOrigin);
  };
</script>

b.com/index.html

<script type="text/javascript">
  window.addEventListener('message', function(event){
    // 通过origin属性判断消息来源地址
    if (event.origin == 'http://a.com') {
      alert(event.data);    // 弹出"I was there!"
      alert(event.source);  
      // 对a.com、index.html中window对象的引用, 但由于同源策略,这里event.source不可以访问window对象
    }
  }, false);
</script>

CORS

CORS是一个 W3C标准 ,全称是”跨域资源共享”。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

实现CORS通信的关键是服务器,只要服务器实现了CORS接口,就可以跨源通信。

a.简单请求

对于简单请求,浏览器直接发出CORS请求,在头信息之中,增加一个Origin字段。服务器判断 origin 是否在许可范围内。

在许可范围内,响应头会添加如下信息返回:

  • Access-Control-Allow-Origin(必须):表明接受什么域名
  • Access-Control-Allow-Credentials:是否允许发送cookie
  • Access-Control-Expose-Headers:是否暴露其他headers值

Origin 不在许可范围内,返回跨域报错信息

b.非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是 application/json

对于非简单请求,浏览器会先发送一次预检请求,请求方式为 OPTIONS,头信息会携带 Origin 字段,及以下信息:

  • Access-Control-Request-Method 列出CORS会用到哪些方法
  • Access-Control-Request-Headers 额外的头信息字段

预检请求通过后,后续处理与简单请求一致。

JSONP

JSONP利用 script 标签的同源策略,突破浏览器的同源策略限制,实现跨域请求。 JSONP包含两部分:

  • 回调函数 当响应到来时要放在当前页面被调用的函数
  • 数据 传入回调函数中的json数据,也就是回调函数的参数
function handleResponse(response){
  console.log('The responsed data is: '+response.data);
}
var script = document.createElement('script');
script.src = 'http://www.baidu.com/json/?callback=handleResponse';
document.body.insertBefore(script, document.body.firstChild);
/*handleResonse({"data": "zhe"})*/

// 原理如下:
// 当通过 script 标签请求时,后台就会根据相应的参数(json, handleResponse)来生成相应的json 数据(handleResponse({"data": "zhe"}))
// 最后这个返回的json数据(代码)就会被放在当前js文件中被执行,至此跨域通信完成

缺点

  • 只能发送 GET 一种请求
  • 安全性 返回的数据是明文的,没有经过任何的加密或编码,容易被恶意攻击者获取并利用
  • 要确定 jsonp 请求是否失败并不容易

Web Sockets

Web Sockets 是一种浏览器的API,它的目标是在一个单独的持久连接上提供全双工、双向通信。(同源策略对web sockets不适用)。

原理

在JS创建了web socket之后,会有一个HTTP请求发送到浏览器以发起连接。取得服务器响应后,建立的连接会使用 HTTP 升级 从HTTP 协议转换为 web sockt 协议。

只有在支持web socket协议的服务器上才能正常工作

var socket = new WebSockt('ws://www.baidu.com');//http->ws; https->wss
socket.send('hello WebSockt');
socket.onmessage = function(event){
  var data = event.data;
}

XSS

XSS是一种常见的安全漏洞,它允许攻击者在受害者浏览器上执行恶意脚本。攻击者通过在网页中注入恶意代码,使得用户浏览该页面时,恶意代码会被执行。

XSS的分类

  • 存储型 XSS

    攻击者将恶意代码存储到目标网站的数据库中,当其他用户浏览相关页面时,恶意代码会从服务器上返回并在用户的浏览器中执行

  • 反射型 XSS

    攻击者通过构造带有恶意代码的URL,并诱导用户点击该链接,服务器接收到请求后,将恶意代码反射回用户的浏览器并执行

  • DOM 型 XSS

    攻击者利用网页的 DOM(文档对象模型)结构漏洞,修改了网页的内容,使得恶意代码被执行

常见的防御策略

  • 对用户输入做好过滤和转义处理,避免被直接执行作为脚本代码。开发者可以使用一些现成的库和框架来实现数据过滤和转义,例如OWASP ESAPI、JQuery等
  • 在请求中包含 HTTPOnly 标记的cookie,避免 JavaScript 脚本获取 Cookie 值
  • 配置 CSP 头部,限制页面资源的加载和执行,减少 XSS 攻击的可能性
  • 使用低特权账号权限分离策略,避免敏感操作的恶意执行和篡改页面内容
  • 对于发生XSS攻击的网站,及时清除和恢复受影响的数据,同时加强监控和日志审计,快速发现异常情况并进行处理

CSP 内容安全策略

CSP(内容安全策略)是一种额外的安全层,可以帮助检测和减轻某些类型的攻击,如跨站脚本攻击(XSS)和数据注入攻击。

CSP允许网站管理员定义哪些动态资源允许执行和加载,有助于防止恶意脚本执行以及非授权内容的加载。

通过HTTP头部设置 Content-Security-Policy 或者 设置 meta,网站可以控制用户代理如何执行页面的特定部分,这可以显著减少XSS攻击的风险。

常见指令

  • default-src

    设置默认加载资源的策略(比如 自身的源、任何地方的源、或不加载任何外部资源)

  • script-src

    定义哪些脚本可以执行, 例如script标签, a标签的JavaScript:location.href=”” 等.

  • style-src

    定义哪些样式表可以加载。

  • img-src

    定义哪些图片资源可以加载。

  • connect-src

    限制可以通过脚本接口进行连接的URL(例如,AJAX 请求、WebSocket)。

  • font-src

    定义哪些字体资源可以加载。

  • object-src

    限制可以加载哪些插件。

  • media-src

    定义哪些媒体资源(音频和视频)可以加载。

  • frame-src

    定义哪些iframe可以加载。

CSRF

CSRF(跨站请求伪造),是一种对网站的恶意利用,通过伪装来自受信任用户的请求来利用受信任的网站。

原理是攻击者构造网站后台某个功能接口的请求地址,诱导用户去点击或者用特殊方法让该请求地址自动加载。用户在登录状态下这个请求被服务端接收后会被误以为是用户合法的操作。对于 GET 形式的接口地址可轻易被攻击,对于 POST 形式的接口地址也不是百分百安全,攻击者可诱导用户进入带 Form 表单可用POST方式提交参数的页面。

攻击方式

主要包含:img标签等src属性、form标签的action、a标签的href

防范措施

  • 随机令牌

    每次向目标网站发送请求时,都要携带一个随机生成的令牌(Token),目标网站在处理请求时会校验该令牌的有效性,如果无效则拒绝请求

  • Referer 校验

    在处理请求时,校验请求的 Referer 头部信息,确保请求来源是合法的

  • SameSite Cookie

    在设置 Cookie 时,指定 Cookie 的 SameSite 属性为 Strict 或 Lax,以限制 Cookie 的跨站访问

SameSite Cookie 设置值含义

  • Strict

    仅允许同站点访问,禁止任何跨站访问

  • Lax

    允许一定程度的跨站访问,例如链接跳转等,但禁止一些敏感操作的跨站访问

SQL注入

SQL注入漏洞主要形成的原因是在数据交互中,前端的数据传入到后台处理时,没有做严格的判断,导致其传入的“数据”拼接到SQL语句中后,被当作SQL语句的一部分执行, 从而导致数据库受损(被脱裤、被删除、甚至整个服务器权限沦陷)

SQL注入的防范

  • 对传进SQL语句里面的变量进行过滤,不允许危险字符传入
  • 使用参数化(Parameterized Query 或 Parameterized Statement)

注意事项

  • 永远不要信任用户的输入。对用户的输入进行校验,可以通过正则表达式,或限制长度;对单引号和双”-“进行转换等。
  • 永远不要使用动态拼装sql,可以使用参数化的sql或者直接使用存储过程进行数据查询存取。
  • 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
  • 不要把机密信息直接存放,加密或者hash掉密码和敏感的信息。
  • 应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装

SSO单点登录

单点登录,简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统(例如淘宝、天猫之间的关系)。简而言之,多个系统,统一登陆。

CAS

CAS 是 Yale 大学发起的一个企业级的、开源的项目,旨在为 Web 应用系统提供一种可靠的单点登录解决方法(SSO的一种框架)。

CAS 包括两部分:CAS Server 和 CAS Client。

  • CAS Server 负责完成对用户的认证工作 , 需要独立部署
  • CAS Client 负责处理对客户端受保护资源的访问请求,需要对请求方进行身份认证时,重定向到 CAS Server 进行认证

实现方式

1、同域SSO 没有设置独立的 SSO 服务器,因为业务后台服务器本身就足以承担 SSO 的职能

2、同父域SSO 和同域SSO不同在于,服务器在返回 cookie 的时候,要把cookie 的 domain 设置为其父域

3、跨域SSO(CAS) 设置专门SSO服务器,当两个产品不同域时,cookie 无法共享,因此就需要搭建SSO服务器

CAS票据

1. TGT (Ticket Grangting Ticket) :

TGT 是 CAS 为用户签发的登录票据,拥有了 TGT,用户就可以证明自己在 CAS 成功登录过。TGT 封装了 Cookie 值以及此 Cookie 值对应的用户信息。

2.TGC(Ticket Granting Cookie) :

CAS Server 生成TGT放入自己的 Session 中,而 TGC 就是这个 Session 的唯一标识(SessionId),以 Cookie 形式放到浏览器端。

3.ST(Service Ticket) :

ST 是 CAS 为用户签发的访问某一 service 的票据。用户访问 service 时,service 发现用户没有 ST,则要求用户去 CAS 获取 ST。