egg框架安全

0x00 egg框架簡介與使用

一、簡介

Egg 選擇了 Koa 作爲其基礎框架,在它的模型基礎上,進一步對它進行了一些增強。

官方網站:https://eggjs.org/zh-cn/intro/egg-and-koa.html

二、簡單Demo

1、創建初始化一個egg項目

$ npm init egg --type=simple
$ npm i

2、目錄結構 

egg-project
├── package.json
├── app.js (可選)
├── agent.js (可選)
├── app
|   ├── router.js   //用於配置 URL 路由規則
│   ├── controller  //用於解析用戶的輸入,處理後返回相應的結果
│   |   └── home.js
│   ├── service (可選)
│   |   └── user.js
│   ├── middleware (可選)
│   |   └── response_time.js
│   ├── schedule (可選)
│   |   └── my_task.js
│   ├── public (可選)
│   |   └── reset.css
│   ├── view (可選)
│   |   └── home.tpl
│   └── extend (可選)
│       ├── helper.js (可選)
│       ├── request.js (可選)
│       ├── response.js (可選)
│       ├── context.js (可選)
│       ├── application.js (可選)
│       └── agent.js (可選)
├── config
|   ├── plugin.js   //用於配置需要加載的插件
|   ├── config.default.js  //用於編寫配置文件
│   ├── config.prod.js
|   ├── config.test.js (可選)
|   ├── config.local.js (可選)
|   └── config.unittest.js (可選)
└── test
    ├── middleware
    |   └── response_time.test.js
    └── controller
        └── home.test.js

3、在home.js中編寫請求的處理與響應操作

'use strict';

const Controller = require('egg').Controller;
const fs = require('fs');        //node.js 的文件模塊 fs

class HomeController extends Controller {
  async index() {
    const { ctx } = this;
    this.ctx.body = await fs.readFileSync(__dirname + '/../view/html/login.html').toString();
  }
}

module.exports = HomeController;

返回一個html頁面  

4、在router.js編寫路由 

'use strict';

/**
 * @param {Egg.Application} app - egg application
 */
module.exports = app => {
  const { router, controller } = app;
  router.get('/homePage', controller.home.index);
};

5、運行程序

$ npm run dev

6、瀏覽器訪問

 

0x01 點擊劫持

 點擊劫持是指在一個Web頁面下隱藏了一個透明的iframe(opacity:0),用外層假頁面誘導用戶點擊,實際上是在隱藏的frame上觸發了點擊事件進行一些用戶不知情的操作。

假設自己服務器有如下頁面login.html:

<!DOCTYPE html>
<html>
<head> 
<meta charset="utf-8"> 
<title>test</title> 
</head> 
<body>
<form action="demo_form.asp">
  First name: <input type="text" name="fname"><br>
  Last name: <input type="text" name="lname"><br>
  <input type="submit" value="提交">
</form>
</body>
</html>

 服務器同域下有個A頁面嵌入了login.html頁面:

<!DOCTYPE html>
<html>
<head> 
<meta charset="utf-8"> 
<title>test</title> 
</head> 
<body>

<iframe src="http://127.0.0.1:7001/homePage">
  <p>您的瀏覽器不支持  iframe 標籤。</p>
</iframe>

</body>
</html>

 訪問該頁面能夠把login頁面嵌入,因爲是同域下的,沒有風險,egg框架允許嵌入。

把上述A頁面拷貝到本機其他地方,瀏覽器打開該頁面,則無法獲取:

所以egg框架默認開啓了xframe的保護,默認爲SAMEORIGIN配置。

可以通過在config.default.js文件中配置xframe關閉保護(不建議),如下所示:

此時再次打開本地的A頁面,能夠把服務器中的頁面嵌入。

 

防護建議:不主動配置xframe,並enable屬性設置爲false,否則有點擊劫持的安全問題。保持默認配置即可。

0x02 URL釣魚

服務端未對傳入的跳轉 url 變量進行檢查和控制,可能導致可惡意構造任意一個惡意地址,誘導用戶跳轉到惡意網站。 由於是從可信的站點跳轉出去的,用戶會比較信任,所以跳轉漏洞一般用於釣魚攻擊,通過轉到惡意網站欺騙用戶輸入用戶名和密碼盜取用戶信息,或欺騙用戶進行金錢交易; 也可能引發的 XSS 漏洞(主要是跳轉常常使用 302 跳轉,即設置 HTTP 響應頭,Locatioin: url,如果 url 包含了 CRLF,則可能隔斷了 HTTP 響應頭,使得後面部分落到了 HTTP body,從而導致 XSS 漏洞)。

egg框架會產生網頁重定向的用法有:ctx.redirect(url)ctx.unsafeRedirect(url)

ctx.redirect(url)會經過配置的白名單的校驗後再進行跳轉,如果domainWhiteList 爲空則與ctx.unsafeRedirect(url)跳轉一樣不進行校驗。

下面是重定向網頁的代碼:

此時是可以跳轉到baidu的。沒有白名單配置的效果和ctx.unsafeRedirect(url)一樣

爲了防止URL釣魚攻擊,需要在使用ctx.redirect(url)的情況下配置可訪問域的白名單,如下所示:

此時重新訪問該URL,顯示該URL被禁止,達到URL重定向的安全

防護建議:在進行網頁重定向時需要調用ctx.redirect(url)函數並正確設置白名單domainWhiteList: ['domain']。

0x03 反射型XSS防護

反射型的 XSS 攻擊,主要是由於服務端接收到客戶端的不安全輸入,在客戶端觸發執行從而發起 Web 攻擊。比如:

在某購物網站搜索物品,搜索結果會顯示搜索的關鍵詞。搜索關鍵詞填入<script>alert('1')</script>, 點擊搜索。頁面沒有對關鍵詞進行過濾,這段代碼就會直接在頁面上執行,彈出 alert。

框架提供了 helper.escape() 方法對字符串進行 XSS 過濾。

const str = '><script>alert("abc") </script><';
console.log(ctx.helper.escape(str));
// 輸出結果 => &gt;&lt;script&gt;alert(&quot;abc&quot;) &lt;/script&gt;&lt;

 研究一下helper.escape() 的編碼機制,通過調試發現最終調用如下函數:

function escapeHtml(string) {
  var str = '' + string;
  var match = matchHtmlRegExp.exec(str);

  if (!match) {
    return str;
  }

  var escape;
  var html = '';
  var index = 0;
  var lastIndex = 0;

  for (index = match.index; index < str.length; index++) {
    switch (str.charCodeAt(index)) {
      case 34: // "
        escape = '&quot;';
        break;
      case 38: // &
        escape = '&amp;';
        break;
      case 39: // '
        escape = '&#39;';
        break;
      case 60: // <
        escape = '&lt;';
        break;
      case 62: // >
        escape = '&gt;';
        break;
      default:
        continue;
    }

    if (lastIndex !== index) {
      html += str.substring(lastIndex, index);
    }

    lastIndex = index + 1;
    html += escape;
  }

  return lastIndex !== index
    ? html + str.substring(lastIndex, index)
    : html;
}

從上述代碼可以看出,該函數對  "、&、 '、<、>五個字符進行了實體編碼,基本能夠防護大多數反射型XSS。

防護建議1:當網站需要直接輸出用戶輸入的結果時,請務必使用 helper.escape() 包裹起來。

另外一種情況,網站輸出的內容會提供給 JavaScript 來使用。這個時候需要使用 helper.sjs() 來進行過濾。

helper.sjs() 用於在 JavaScript(包括 onload 等 event)中輸出變量,會對變量中字符進行 JavaScript ENCODE, 將所有非白名單字符轉義爲 \x 形式,防止 XSS 攻擊,也確保在 js 中輸出的正確性。使用實例:

const foo = '"hello"';

// 未使用 sjs
console.log(`var foo = "${foo}";`);
// => var foo = ""hello"";

// 使用 sjs
console.log(`var foo = "${this.helper.sjs(foo)}";`);
// => var foo = "\\x22hello\\x22";

helper.sjs()最終實際調用如下函數:

function escapeJavaScript(string) {

  const str = '' + string;
  const match = MATCH_VULNERABLE_REGEXP.exec(str);

  if (!match) {
    return str;
  }

  let res = '';
  let index = 0;
  let lastIndex = 0;
  let ascii;

  for (index = match.index; index < str.length; index++) {
    ascii = str[index];
    if (BASIC_ALPHABETS.has(ascii)) {
      continue;
    } else {
      if (map[ascii] === undefined) {
        const code = ascii.charCodeAt(0);
        if (code > 127) {
          continue;
        } else {
          map[ascii] = '\\x' + code.toString(16);//若當前字符位於黑名單中,則使用\x轉義
        }
      }
    }

    if (lastIndex !== index) {
      res += str.substring(lastIndex, index);
    }

    lastIndex = index + 1;
    res += map[ascii];
  }

  return lastIndex !== index ? res + str.substring(lastIndex, index) : res;

}

防護建議2網站輸出的內容會提供給 JavaScript 來使用。這個時候需要使用 helper.sjs() 來進行過濾

還有一種情況,有時候我們需要在 JavaScript 中輸出 json ,若未做轉義,易被利用爲 XSS 漏洞。框架提供了 helper.sjson() 宏做 json encode,會遍歷 json 中的 key ,將 value 的值中,所有非白名單字符轉義爲 \x 形式,防止 XSS 攻擊。同時保持 json 結構不變。 若存在模板中輸出一個 JSON 字符串給 JavaScript 使用的場景,請使用 helper.sjson(變量名) 進行轉義。

輸出如下所示: 

白名單爲大小寫字母:

abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ

不在白名單內的字符按下述進行替換

若沒有替換的字符,則直接刪除。代碼如下所示:

防護建議3需要在 JavaScript 中輸出 json ,這個時候需要使用  helper.sjson() 來進行轉義

0x04 存儲型XSS

攻擊者事先將惡意代碼上傳或儲存到漏洞服務器中,只要受害者瀏覽包含此惡意代碼的頁面就會執行惡意代碼。這就意味着只要訪問了這個頁面的訪客,都有可能會執行這段惡意腳本,因此儲存型XSS的危害會更大。因爲存儲型XSS的代碼存在於網頁的代碼中,可以說是永久型的。

框架提供了 helper.shtml() 方法對字符串進行 XSS 過濾。將富文本(包含 HTML 代碼的文本)當成變量直接在模版裏面輸出時,需要用到 shtml 來處理。 使用 shtml 可以輸出 HTML 的 tag,同時執行 XSS 的過濾動作,過濾掉非法的腳本。默認情況下,使用以下白名單進行過濾,:

function getDefaultWhiteList() {
	return {
	a: ["target", "href", "title"],
	abbr: ["title"],
	address: [],
	area: ["shape", "coords", "href", "alt"],
	article: [],
	aside: [],
	audio: ["autoplay", "controls", "loop", "preload", "src"],
	b: [],
	bdi: ["dir"],
	bdo: ["dir"],
	big: [],
	blockquote: ["cite"],
	br: [],
	caption: [],
	center: [],
	cite: [],
	code: [],
	col: ["align", "valign", "span", "width"],
	colgroup: ["align", "valign", "span", "width"],
	dd: [],
	del: ["datetime"],
	details: ["open"],
	div: [],
	dl: [],
	dt: [],
	em: [],
	font: ["color", "size", "face"],
	footer: [],
	h1: [],
	h2: [],
	h3: [],
	h4: [],
	h5: [],
	h6: [],
	header: [],
	hr: [],
	i: [],
	img: ["src", "alt", "title", "width", "height"],
	ins: ["datetime"],
	li: [],
	mark: [],
	nav: [],
	ol: [],
	p: [],
	pre: [],
	s: [],
	section: [],
	small: [],
	span: [],
	sub: [],
	sup: [],
	strong: [],
	table: ["width", "border", "align", "valign"],
	tbody: ["align", "valign"],
	td: ["width", "rowspan", "colspan", "align", "valign"],
	tfoot: ["align", "valign"],
	th: ["width", "rowspan", "colspan", "align", "valign"],
	thead: ["align", "valign"],
	tr: ["rowspan", "align", "valign"],
	tt: [],
	u: [],
	ul: [],
	video: ["autoplay", "controls", "loop", "preload", "src", "height", "width"]
	};
	}

例如白名單 中a: ["target", "href", "title"]  表示可以使用html標籤<a>,並且標籤中可以使用屬性"target", "href", "title"。除了上述白名單之外的標籤和屬性egg框架將會刪除。特別的config.helper.shtml.domainWhiteList: [] 可拓展 href 和 src 中允許的域名白名單, 默認爲本域"http://localhost:7001"。所以如下代碼href將被刪除:

 ctx.app.config.security.domainWhiteList=["baidu.com"]/ 可拓展 href 和 src 中允許的域名白名單爲baidu的。代碼如下所示:

    async xss() {
    const { ctx } = this;
	const value = '<a href="http://www.baidu.com">google</a><script>alert("hello")</script>';
	ctx.app.config.security.domainWhiteList=["baidu.com"]//會加.號進行匹配
	var data =  ctx.helper.shtml(value);
	this.ctx.body = data;
  }
  

以下是內部白名單域校驗代碼:

 

訪問該URL,href屬性被保留,點擊google能夠跳轉到百度頁面:

 0x05 Web 安全頭防禦XSS

egg框架也提供了常見的用於指示瀏覽器防護XSS的方法-通過開啓 Web 安全頭。包括:

  1. CSP(Content Security Policy)。框架內支持 CSP 的配置,不過是默認關閉的,開啓後可以有效的防止 XSS 攻擊的發生,需要配置 CSP 。

  2. X-Download-Options:noopen。默認開啓,禁用 IE 下下載框Open按鈕,防止 IE 下下載文件默認被打開 XSS。

  3. X-Content-Type-Options:nosniff。禁用 IE8 自動嗅探 mime 功能例如 text/plain 卻當成 text/html 渲染,特別當本站點 serve 的內容未必可信的時候。

  4. X-XSS-Protection。IE 提供的一些 XSS 檢測與防範,默認開啓

        xssProtection: {
          enable: true,
          value: '1; mode=block',
        },

     

  5. Strict-Transport-Security。默認關閉,如果需要開啓https傳輸,需要開啓。

同樣在配置文件中配置,例如CSP:

exports.security = {
  csp: {
    match: '/example',
    policy: {
      //...
    },
  },
};

針對/example路由設置內容安全策略,csp策略按需進行配置 。其餘的見下文如何配置。

0x06 參數污染HPP

HPP是HTTP Parameter Pollution的縮寫,意爲HTTP參數污染。
原理:瀏覽器在跟服務器進行交互的過程中,瀏覽器往往會在GET/POST請求裏面帶上參數,這些參數會以 名稱-值 對的形勢出現,通常在一個請求中,同樣名稱的參數只會出現一次。但是在HTTP協議中是允許同樣名稱的參數出現多次的。比如下面這個鏈接:http://www.baidu.com?name=aa&name=bb ,針對同樣名稱的參數出現多次的情況,不同的服務器的處理方式會不一樣。有的服務器是取第一個參數,也就是name=aa。有的服務器是取第二個參數,也就是name=bb。有的服務器兩個參數都取,也就是name=aa,bb 。這種特性在繞過一些服務器端的邏輯判斷時,非常有用。

HPP漏洞,與Web服務器環境、服務端使用的腳本有關。如下是不同Web服務器對於出現多個參數時的選擇。


而在egg框架中,會強制使用第一個參數,用戶只要關心通過request獲取對應name的值即可,並做好校驗防護。

0x07 XST防護

"Cross-Site-Tracing"簡稱爲XST,如果開發者在設置cookie屬性時配置了httponly屬性,那麼通過XSS攻擊就無法讀取cookie數據,那麼如果服務器支持TRACE請求並且允許跨域的話,那麼還是可以讀取到cookie數據的。

XST介紹:https://www.owasp.org/index.php/XST

egg框架中已經禁止了 trace,track,options 三種危險類型請求。

0x08 SSRF防護

SSRF漏洞:(服務端請求僞造)是一種由攻擊者構造形成由服務端發起請求的一個安全漏洞。一般情況下,SSRF攻擊的目標是從外網無法訪問的內部系統。(正是因爲它是由服務端發起的,所以它能夠請求到與它相連而與外網隔離的內部系統)。SSRF 形成的原因大都是由於服務端提供了從其他服務器應用獲取數據的功能且沒有對目標地址做過濾與限制。比如從指定URL地址獲取網頁文本內容,加載指定地址的圖片,下載等等。利用的是服務端的請求僞造。ssrf是利用存在缺陷的web應用作爲代理攻擊遠程和本地的服務器。

框架在 ctxapp 和 agent 上都提供了 safeCurl 方法,在發起網絡請求的同時會對指定的內網 IP 地址過濾,除此之外,該方法和框架提供的 curl 方法一致。

ctx.safeCurl(url, options)
app.safeCurl(url, options)
agent.safeCurl(url, options)

直接調用 safeCurl 方法其實並沒有任何作用,還需要配合安全配置項。

  • ipBlackList(Array) - 配置內網 IP 名單,在這些網段內的 IP 地址無法被訪問。
  • checkAddress(Function) - 直接配置一個檢查 IP 地址的函數,根據函數的返回值來判斷是否允許在 safeCurl 中被訪問,當返回非 true 時,該 IP 無法被訪問。checkAddress 優先級高於 ipBlackList
// config/config.default.js
exports.security = {
  ssrf: {
    ipBlackList: [
      '10.0.0.0/8', // 支持 IP 網段
      '0.0.0.0/32',
      '127.0.0.1',  // 支持指定 IP 地址
    ],
    // 配置了 checkAddress 時,ipBlackList 不會生效
    checkAddress(ip) {
      return ip !== '127.0.0.1';
    },
  },
};

0x09 CSRF機制

使用egg提供的CSRF套件

0x10前文各項安全配置案例

一個完整的在config.default.js中對exports.security進行安全配置例子:

/**
   * security options
   * @member Config#security
   * @property {String} defaultMiddleware - default open security middleware
   * @property {Object} csrf - whether defend csrf attack
   * @property {Object} xframe - whether enable X-Frame-Options response header, default SAMEORIGIN
   * @property {Object} hsts - whether enable Strict-Transport-Security response header, default is one year
   * @property {Object} methodnoallow - whether enable Http Method filter
   * @property {Object} noopen - whether enable IE automaticlly download open
   * @property {Object} nosniff -  whether enable IE8 automaticlly dedect mime
   * @property {Object} xssProtection -  whether enable IE8 XSS Filter, default is open
   * @property {Object} csp - content security policy config
   * @property {Object} referrerPolicy - referrer policy config
   * @property {Object} dta - auto avoid directory traversal attack
   * @property {Array} domainWhiteList - domain white list
   * @property {Array} protocolWhiteList - protocal white list
   */
  exports.security = {
    domainWhiteList: [],
    protocolWhiteList: [],
    defaultMiddleware: 'csrf,hsts,methodnoallow,noopen,nosniff,csp,xssProtection,xframe,dta',

    csrf: {
      enable: true,

      // can be ctoken or referer or all
      type: 'ctoken',
      ignoreJSON: false,

      // These config works when using ctoken type
      useSession: false,
      // can be function(ctx) or String
      cookieDomain: undefined,
      cookieName: 'csrfToken',
      sessionName: 'csrfToken',
      headerName: 'x-csrf-token',
      bodyName: '_csrf',
      queryName: '_csrf',

      // These config works when using referer type
      refererWhiteList: [
        // 'eggjs.org'
      ],
    },

    xframe: {
      enable: true,
      // 'SAMEORIGIN', 'DENY' or 'ALLOW-FROM http://example.jp'
      value: 'SAMEORIGIN',
    },

    hsts: {
      enable: false,
      maxAge: 365 * 24 * 3600,
      includeSubdomains: false,
    },

    dta: {
      enable: true,
    },

    methodnoallow: {
      enable: true,
    },

    noopen: {
      enable: true,
    },

    nosniff: {
      enable: true,
    },

    referrerPolicy: {
      enable: false,
      value: 'no-referrer-when-downgrade',
    },

    xssProtection: {
      enable: true,
      value: '1; mode=block',
    },

    csp: {
      enable: false,
      policy: {},
    },

    ssrf: {
      ipBlackList: null,
      checkAddress: null,
    },
  };

詳情可見:https://github.com/eggjs/egg-security/blob/master/config/config.default.js

 

0x11 參考資料

https://eggjs.org/zh-cn/intro/quickstart.html

https://blog.csdn.net/starcrius/article/details/83750022

https://www.jianshu.com/p/81dc757b25f1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章