Egg 选择了 Koa 作为其基础框架,在它的模型基础上,进一步对它进行了一些增强。
官方网站:https://eggjs.org/zh-cn/intro/egg-and-koa.html
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、浏览器访问
点击劫持是指在一个Web页面下隐藏了一个透明的iframe(opacity:0),用外层假页面诱导用户点击,实际上是在隐藏的frame上触发了点击事件进行一些用户不知情的操作。
假设自己服务器有如下页面login.html:
test
服务器同域下有个A页面嵌入了login.html页面:
test
访问该页面能够把login页面嵌入,因为是同域下的,没有风险,egg框架允许嵌入。
把上述A页面拷贝到本机其他地方,浏览器打开该页面,则无法获取:
所以egg框架默认开启了xframe的保护,默认为SAMEORIGIN配置。
可以通过在config.default.js文件中配置xframe关闭保护(不建议),如下所示:
此时再次打开本地的A页面,能够把服务器中的页面嵌入。
防护建议:不主动配置xframe,并enable属性设置为false,否则有点击劫持的安全问题。保持默认配置即可。
服务端未对传入的跳转 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']。
反射型的 XSS 攻击,主要是由于服务端接收到客户端的不安全输入,在客户端触发执行从而发起 Web 攻击。比如:
在某购物网站搜索物品,搜索结果会显示搜索的关键词。搜索关键词填入, 点击搜索。页面没有对关键词进行过滤,这段代码就会直接在页面上执行,弹出 alert。
框架提供了 helper.escape() 方法对字符串进行 XSS 过滤。
const str = '><';
console.log(ctx.helper.escape(str));
// 输出结果 => ><script>alert("abc") </script><
研究一下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 = '"';
break;
case 38: // &
escape = '&';
break;
case 39: // '
escape = ''';
break;
case 60: // <
escape = '<';
break;
case 62: // >
escape = '>';
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()
来进行转义
攻击者事先将恶意代码上传或储存到漏洞服务器中,只要受害者浏览包含此恶意代码的页面就会执行恶意代码。这就意味着只要访问了这个页面的访客,都有可能会执行这段恶意脚本,因此储存型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标签,并且标签中可以使用属性"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 = 'google';
ctx.app.config.security.domainWhiteList=["baidu.com"]//会加.号进行匹配
var data = ctx.helper.shtml(value);
this.ctx.body = data;
}
以下是内部白名单域校验代码:
访问该URL,href属性被保留,点击google能够跳转到百度页面:
egg框架也提供了常见的用于指示浏览器防护XSS的方法-通过开启 Web 安全头。包括:
CSP(Content Security Policy)。框架内支持 CSP 的配置,不过是默认关闭的,开启后可以有效的防止 XSS 攻击的发生,需要配置 CSP 。
X-Download-Options:noopen。默认开启,禁用 IE 下下载框Open按钮,防止 IE 下下载文件默认被打开 XSS。
X-Content-Type-Options:nosniff。禁用 IE8 自动嗅探 mime 功能例如 text/plain
却当成 text/html
渲染,特别当本站点 serve 的内容未必可信的时候。
X-XSS-Protection。IE 提供的一些 XSS 检测与防范,默认开启
xssProtection: {
enable: true,
value: '1; mode=block',
},
Strict-Transport-Security。默认关闭,如果需要开启https传输,需要开启。
同样在配置文件中配置,例如CSP:
exports.security = {
csp: {
match: '/example',
policy: {
//...
},
},
};
针对/example路由设置内容安全策略,csp策略按需进行配置 。其余的见下文如何配置。
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的值即可,并做好校验防护。
"Cross-Site-Tracing"简称为XST,如果开发者在设置cookie属性时配置了httponly属性,那么通过XSS攻击就无法读取cookie数据,那么如果服务器支持TRACE请求并且允许跨域的话,那么还是可以读取到cookie数据的。
XST介绍:https://www.owasp.org/index.php/XST
egg框架中已经禁止了 trace,track,options 三种危险类型请求。
SSRF漏洞:(服务端请求伪造)是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)。SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等。利用的是服务端的请求伪造。ssrf是利用存在缺陷的web应用作为代理攻击远程和本地的服务器。
框架在 ctx
, app
和 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';
},
},
};
使用egg提供的CSRF套件
一个完整的在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
https://eggjs.org/zh-cn/intro/quickstart.html
https://blog.csdn.net/starcrius/article/details/83750022
https://www.jianshu.com/p/81dc757b25f1