你不能錯過的XSS指引

引子

前段時間,部門一直在招聘前端開發,本着爲了讓咱們的網站javascript

  • 更快html

  • 更穩定前端

  • 更安全java

    的三大原則,我也簡要的問了一下候選人前端安全方面的問題,其中就有涉及到 XSS 攻擊相關的知識。如下就是咱們的面試過程。node

    我:xxx 你瞭解過 XSS 攻擊嗎?react

    候選人:XSS 知道啊,就是……,可是我感受如今咱們使用 react 等現代框架,已經不須要去作 XSS 相關的防禦了,框架已經幫咱們作了這方面的工做。git

    我:難道使用了 react 等框架就能夠確保不會發生 XSS 攻擊嗎?github

問到這裏候選人就開始支支吾吾了。各位讀者,也默默地問一下本身,使用了 react 難道就真的不會發生 XSS 攻擊了嗎? 筆者爲了讓你們更好地掌握 XSS 攻擊相關的知識,主要會圍繞如下幾點向你們闡述。web

  • 什麼是 XSS 攻擊
  • XSS 攻擊舉例
  • react 框架中的 XSS 攻擊
  • XSS 攻擊防禦
  • 結語

什麼是 XSS 攻擊

XSS 攻擊全稱跨站腳本攻擊,是爲不和層疊樣式表(Cascading Style Sheets, CSS)的縮寫混淆,故將跨站腳本攻擊縮寫爲 XSS,XSS 是一種在 web 應用中的計算機安全漏洞,它容許惡意 web 用戶將代碼植入到提供給其它用戶使用的頁面中。面試

XSS 攻擊分類

  • 反射型

    反射型 XSS 只是簡單地把用戶輸入的數據 「反射」 給瀏覽器。也就是說,黑客每每須要誘使用戶「點擊」一個惡意連接,才能攻擊成功。反射型 XSS 也叫「非持久型 XSS」(Non-persistent XSS)。

  • 存儲型

    存儲型 XSS 會把用戶輸入的數據「存儲」在服務器端(數據庫)。存儲型 XSS 一般也叫作「持久型 XSS」(Persistent XSS)

  • Dom Based XSS

    Dom Based XSS 並不是按照「數據是否存在服務器端」來劃分,DOM Based XSS 從效果上來講也是反射型 XSS。單獨劃分開來,是由於 DOM Based XSS 造成緣由比較特別——經過修改頁面的 DOM 節點造成的 XSS。

XSS 攻擊舉例

既然咱們已經熟悉了 XSS 攻擊是什麼已經 XSS 攻擊的分類了,那就讓咱們當一回黑客吧!接下來咱們將會分別模擬三種類型的攻擊,建議你們先 clone 一下個人示例代碼。

文章使用的代碼,見倉庫,目錄爲 security/xss

運行實例

git clone https://github.com/chenshengshui/Web-Frontend-Study-Map.git
複製代碼
cd security
複製代碼
npm install
複製代碼
npm run xss
複製代碼

反射型 XSS 舉例

示例演示

打開瀏覽器,進入威脅網站:http://localhost:3000/non-persistent-xss.html

源碼解析

目標網站把用戶輸入的參數直接輸出到頁面上:

/* * 目標網站 */
const express = require('express');
const app = express();

app.use(express.static('./xss'));
app.get('/', function(req, res) {
  res.setHeader('X-XSS-Protection', 0); // 這個處理是爲了關閉現代瀏覽器的xss防禦
  res.send('早上好' + req.query['name']);
});

app.listen(3000);
複製代碼

威脅網站:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>

  <body>
    <a href="http://localhost:3000/?name='<script>alert(/xss/)</script>'" >點我進入目標網站</a >
  </body>
</html>
複製代碼

威脅網站誘導用戶點擊連接,可是連接包含攻擊的腳本,若是咱們從威脅網站進入到咱們的目標網站,將會發現 alert('xss')在目標網站執行了。查看源碼發現參數中的 script 標籤,已經被寫入頁面中,而這顯然不是開發者但願看到的。

存儲型 XSS 舉例

存儲型 XSS 攻擊的一個經典場景就是,黑客寫了一篇包含有惡意 Javascript 代碼的博客文章,文章發表後,全部訪問博客文章的用戶,都會在他們的瀏覽器中執行這段惡意的 Javascript 代碼。惡意的腳本將保存到服務端,因此這種 XSS 攻擊 就叫作「存儲型 XSS」。

示例演示

打開瀏覽器,進入網站:http://localhost:3000/persistent-xss.html, 在文本框輸入如下內容:

<div style="color: red">
  歡迎你們閱讀個人博客,我是WaterMan,喜歡就關注一下唄!
</div>
<script> alert('哈哈,我竊取了你的token了,下次注意噢' + document.cookie); </script>
複製代碼

點擊保存後,咱們的 cookie 將被竊取。

源碼解析 首先看咱們的前端代碼,So easy,就只有一個 textarea 和一個提交按鈕,點擊提交按鈕將會把用戶輸入的內容提交到後臺,沒毛病,咱們日常就是這麼開發的。

document.getElementById('submit').onclick = function() {
  var content = document.getElementById('textarea').value;
  var url = 'http://localhost:3000/postArticle';
  easyRequest()
    .post(url, {
      content: content
    })
    .then(res => {
      alert(res.message);
      window.open('http://localhost:3000/persistent-xss');
    });
};

function easyRequest() {
  return {
    post: function(url, data) {
      return fetch(url, {
        body: JSON.stringify(data),
        cache: 'no-cache',
        credentials: 'same-origin',
        headers: {
          'content-type': 'application/json'
        },
        method: 'POST',
        mode: 'cors',
        redirect: 'follow',
        referrer: 'no-referrer'
      }).then(response => response.json());
    }
  };
}
複製代碼

再看咱們的後端代碼,後端代碼也及其的簡單,會將咱們的提交保存到文件,這裏用文件來模擬通常的數據庫。

const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const fs = require('fs');

app.use(express.static('./xss'));
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));
// parse application/json
app.use(bodyParser.json());

app.use(function(req, res, next) {
  // 模擬設置用戶tookie
  res.setHeader('Content-Type', 'text/html;charset=utf-8');
  res.cookie('token', 'zkskskdngqkkkgn245tkdkgj');
  next();
});
app.get('/', function(req, res) {
  res.setHeader('X-XSS-Protection', 0);
  res.send('早上好' + req.query['name']);
});

app.get('/persistent-xss', function(req, res) {
  fs.readFile('./xss/data.txt', function(err, data) {
    if (err) {
      return res.send(err);
    }
    res.send(data);
  });
});

app.post('/postArticle', function(req, res) {
  const content = req.body.content + '\n';
  fs.writeFile('./xss/data.txt', content, { flag: 'w' }, function(err) {
    if (err) {
      return res.send({ status: 500, message: err.message });
    }
    res.send({ status: 200, message: '保存成功' });
  });
});

app.listen(3000);
複製代碼

這樣一來,只要任意一個用戶訪問文章詳情網站http://localhost:3000/persistent-xss,他的tooken都會被獲取。

DOM Based XSS

經過修改頁面的 DOM 節點造成的 XSS,稱之爲 DOM Based XSS。 直接看如下代碼

<body>
  <div id="content"></div>
  <input type="text" id="text" value="" style="width: 500px" />
  <input type="button" id="submit" value="write" />
</body>
<script> document.getElementById('submit').onclick = function() { var str = document.getElementById('text').value; document.getElementById('content').innerHTML = "<a href='" + str + "'>testLink</a>"; }; </script>
複製代碼

點擊「write」按鈕後,會在當前頁面插入一個超連接,其地址爲文本框的內容。在這裏,「write」按鈕的 onclick 事件觸發後,會修改頁面的 DOM 節點,經過 innerHTML 把一段用戶數據看成 HTML 寫入到頁面中,這就形成了 DOM Based XSS。 構造以下數據

' onclick=alert(/xss/) // 輸入後,頁面代碼就變成了:

<a href='' onclick=alert(/xss/)//' > testLink</a>
複製代碼

首先用一個單引號閉合掉 href 的第一個單引號,而後插入一個 onclik 事件,最後再用註釋符註釋掉第二個單引號。點擊這個新生成的連接,腳本將被執行。這樣一來咱們就發起了一次 DOM Based XSS 攻擊。 你們也能夠啓動項目後,打開http://localhost:3000/dom-based-xss.html網站體驗一下。

react 框架中的 XSS 攻擊

react 框架確實相比使用 Jquery 編寫代碼安全很多,由於 React 已經幫咱們作了很多工做。 這裏咱們簡要介紹一下 React 到底作了什麼。

React 官網介紹,有這樣一段描述:

It is safe to embed user input in JSX:

const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;
複製代碼

By default, React DOM escapes any values embedded in JSX before rendering them. Thus it ensures that you can never inject anything that’s not explicitly written in your application. Everything is converted to a string before being rendered. This helps prevent XSS (cross-site-scripting) attacks.

翻譯一下就是,在 React 中直接嵌入用戶輸入是安全的,由於 React Dom 會在渲染以前對 JSX 中的值進行實體編碼,這樣就能夠確保你不會注入任何威脅應用的代碼。在渲染前,全部東西都會被轉換爲字符串,這樣有助於防護 XSS 攻擊。

那這個 HTML 實體編碼是怎麼作的呢?

& becomes &amp;
< becomes &lt;
> becomes &gt;
" becomes &quot; ' becomes &#39 複製代碼

固然 React 也不是一股腦的把全部都轉換了,不然咱們就沒法正常渲染等標籤了,這裏後文後詳細講述 xss-fitler 的防護機制。

這裏先回到 React 中的 XSS 攻擊。React 中有一個 API dangerouslySetInnerHTML,專門用來渲染 HTML,咱們看官網的介紹:

dangerouslySetInnerHTML is React’s replacement for using innerHTML in the browser DOM. In general, setting HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS) attack. So, you can set HTML directly from React, but you have to type out dangerouslySetInnerHTML and pass an object with a __html key, to remind yourself that it’s dangerous. For example:

function createMarkup() {
  return { __html: 'First &middot; Second' };
}

function MyComponent() {
  return <div dangerouslySetInnerHTML={createMarkup()} />; } 複製代碼

也就是說,dangerouslySetInnerHTML 是 innerHtml 的替代品,可是它仍是存在安全性問題。

運行示例

仍是以前的倉庫,咱們進入到目錄/security/react-xss

cd security/react-xss
複製代碼
npm install
複製代碼
npm start
複製代碼

源碼分析

class Chat extends Component {
  state = {
    input: '',
    result: ''
  };

  onChange = e => {
    this.setState({
      input: e.target.value
    });
  };

  onSubmit = () => {
    this.setState({
      result: this.state.input
    });
  };

  render() {
    return (
      <div styleName="container">
        <div styleName="msg">
          <textarea onChange={this.onChange} placeholder="在此輸入內容" />
          <button onClick={this.onSubmit}>提交</button>
          <div dangerouslySetInnerHTML={{ __html: this.state.result }} />
        </div>
      </div>
    );
  }
}
複製代碼

也就是當 react 中使用了 dangerouslySetInnerHTML 這個 api 時,是須要注意很容易引起 XSS 攻擊的。

前面咱們已經充分了解了 XSS 攻擊的定義及分類,也認識到 React 並非萬無一失的,那咱們怎麼來提高咱們網站的安全性呢?接下來就到了本篇文章的重中之重了,我將講述 XSS 攻擊防禦的若干手段,讓你的網站更安全。

XSS 攻擊防禦

XSS 的防護是複雜的。下面我會講述我瞭解到的幾條 XSS 防護的手段,若是還有其餘我未說起的,歡迎讀者在評論區補充,咱們的目的就是讓咱們的網站更安全,也提高你們的能力。

接下來主要圍繞如下幾點來說述 XSS 攻擊防禦:

  • 四兩撥千金——HttpOnly
  • 輸入檢查
  • 輸出檢查
  • 正確處理富文本

四兩撥千金——HttpOnly

什麼是 HttpOnly?

HttpOnly 是一個設置 cookie 是否能夠被 javasript 腳本讀取的屬性,瀏覽器將禁止頁面的 Javascript 訪問帶有 HttpOnly 屬性的 Cookie。

咱們回到示例的代碼,請看下面設置 cookie 的語法:

const express = require('express');
const app = express();
app.use(function(req, res, next) {
  res.setHeader('Content-Type', 'text/html;charset=utf-8');
  // 模擬設置用戶tookie
  res.cookie('token', 'zkskskdngqkkkgn245tkdkgj');
  next();
});
複製代碼

這裏設置的 cookie,未設置 HttpOnly 爲 true,這樣一來咱們就直接用 document.cookie 獲取 token 值了。以下圖

能夠在控制檯的 Application 標籤欄查看 cookie 的詳細信息

爲了防止黑客經過 Javascript 腳本獲取 cookie 信息,咱們能夠將 HttpOnly 開啓。

const express = require('express');
const app = express();
app.use(function(req, res, next) {
  res.setHeader('Content-Type', 'text/html;charset=utf-8');
  // 模擬設置用戶tookie
  res.cookie('token', 'zkskskdngqkkkgn245tkdkgj', { httpOnly: true });
  next();
});
複製代碼

不使用 express 等框架時,原生 nodejs 能夠這樣設置

response.setHeader('Set-Cookie', 'token=zkskskdngqkkkgn245tkdkgj;HttpOnly');
複製代碼

嚴格來講,HttpOnly並不是爲了對抗XSS,HttpOnly解決的是XSS後的Cookie劫持攻擊。

Cookie 設置知識延展 Cookie 設置 HttpOnly,Secure 屬性能夠有效防止 XSS 攻擊,設置 X-Frame-Options 響應 頭可避免點擊劫持。

屬性介紹:

  • Secure 屬性 當設置爲 true 時,表示建立的 Cookie 會被以安全的形式向服務器傳輸(ssl),即只能在 HTTPS 鏈接中被瀏覽器傳遞到服務端進行會話驗證,若是是 HTTP 鏈接則不會傳遞該信息,因此不會被竊取到 Cookie 的具體內容。

  • HttpOnly 屬性 若是在 Cookie 中設置了「HttpOnly」屬性,那麼經過程序(JS 腳本,Applet 等)將沒法讀取到 Cookie 信息,這樣能有效的防止 XSS 攻擊。

  • X-Frame-Options 響應頭 X-Frame-Options Http 響應頭表示是否容許一個頁面在<frame>、<iframe>、<object>中展示的標記。經過設置 X-Frame-Options 阻止站點內的頁面被其餘頁面嵌入從而防止點擊劫持。X-Frame-Options 有三個值: OENY:該頁面不容許在 frame 中展現,即使是在相同域名的頁面中嵌套也不容許。 SAMEORIGIN:該頁面可在相同域名頁面的 frame 中展現。 ALLOW-FROM uri:該頁面可在指定來源的 frame 中展現。

//設置cookie
response.setHeader(
  'Set-Cookie',
  'JSESSIONID=' + sessionid + ';Secure;HttpOnly'
); //設置Secure;HttpOnly
response.setHeader('x-frame-options', 'SAMEORIGIN'); //設置x-frame-options
複製代碼

輸入檢查

常見的Web漏洞如XSS,SQL Injection等,都要求攻擊者構造一些特殊字符,這些特殊字符多是正經常使用戶不會用到的,因此輸入檢查就有存在的必要了。

輸入檢查,不少時候用於格式檢查,簡單的說就是對用戶輸入的信息添加一個白名單。好比在網站註冊時要求填寫的用戶名,會被要求爲字母、數字的組合。好比「waterMan1」就是一個合法的用戶名,而「waterMan$^」就不被容許了。輸入檢查必須放在服務器端代碼中實現,由於在客戶端使用javascript進行輸入檢查,很容易被攻擊者繞開。那做爲一個前端的咱們,是否是就能夠兩手插袋了呢?其實在當前nodejs大行其道的時機,咱們這樣認爲每每顯得過於單純。咱們能夠在node端進行輸入檢查,確保咱們的網站安全行。退一萬步說,咱們在客戶端代碼加入輸入檢查,也能夠阻擋大部分誤操做的正經常使用戶,從而節約服務器資源,也能夠提升黑客的攻擊門檻。

那咱們該怎麼進行輸入檢查呢?

粗暴的作法

對輸入檢查最有效的方式,就是對一些特殊字符進行過濾或者編碼,如

& becomes &amp;
< becomes &lt;
> becomes &gt;
" becomes &quot;
' becomes &#39
複製代碼

可是因爲缺少語境,這樣暴力的處理,確實是安全的,可是也會讓一些正常的輸入變得難於理解,而讓用戶抓狂。

好比:

1 + 2 < 4 becomes 1 + 2 &lt 4
複製代碼

這樣的結果顯然不是用戶但願看到的。

更加智能的xss-filter

XSS Filter在用戶提交數據時獲取變量,進行XSS檢查,進行比較智能的「輸入檢查」,匹配XSS的特徵。要設計這樣一個智能的XSS Filter庫是很是困難的,咱們在接下來的一篇文章中,會詳細講述一個XSS-Filter庫的實現。歡迎你們和我一塊兒見證一個XSS-Filter庫從無到有的過程。

輸出檢查

其實輸出檢查和輸入檢查作的事都差很少,可是輸出檢查會更加簡單,由於輸出檢查能夠結合具體語境,可是輸出檢查工做量會比較大,其實React框架自己已經給咱們作了大量輸出檢查。其實輸出檢查也能夠在接口處統一攔截,這樣就和輸入檢查一致了。

處理富文本

處理富文本和輸入檢查是一致的,咱們會對用戶輸入的信息添加一個白名單,好比在富文本里面容許用戶輸入<img >等標籤,不容許輸入<script>等標籤,這個也涉及到xss-filter這個庫,會在接下來的文章詳細講述。

結語

又到告終尾了,很開心你們能堅持看到最後。XSS防護仍是很值得你們去深刻研究的,總之一句話,但願咱們你們能從個人文章學到知識。最後,祝你們週末愉快!

@Author: WaterMan

相關文章
相關標籤/搜索