@eggjs/security 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +569 -0
  3. package/README.zh-CN.md +441 -0
  4. package/dist/commonjs/agent.d.ts +6 -0
  5. package/dist/commonjs/agent.js +14 -0
  6. package/dist/commonjs/app/extend/agent.d.ts +5 -0
  7. package/dist/commonjs/app/extend/agent.js +11 -0
  8. package/dist/commonjs/app/extend/application.d.ts +16 -0
  9. package/dist/commonjs/app/extend/application.js +35 -0
  10. package/dist/commonjs/app/extend/context.d.ts +68 -0
  11. package/dist/commonjs/app/extend/context.js +283 -0
  12. package/dist/commonjs/app/extend/helper.d.ts +12 -0
  13. package/dist/commonjs/app/extend/helper.js +10 -0
  14. package/dist/commonjs/app/extend/response.d.ts +41 -0
  15. package/dist/commonjs/app/extend/response.js +85 -0
  16. package/dist/commonjs/app/middleware/securities.d.ts +4 -0
  17. package/dist/commonjs/app/middleware/securities.js +55 -0
  18. package/dist/commonjs/app.d.ts +6 -0
  19. package/dist/commonjs/app.js +29 -0
  20. package/dist/commonjs/config/config.default.d.ts +871 -0
  21. package/dist/commonjs/config/config.default.js +357 -0
  22. package/dist/commonjs/config/config.local.d.ts +5 -0
  23. package/dist/commonjs/config/config.local.js +10 -0
  24. package/dist/commonjs/index.d.ts +1 -0
  25. package/dist/commonjs/index.js +14 -0
  26. package/dist/commonjs/lib/extend/safe_curl.d.ts +16 -0
  27. package/dist/commonjs/lib/extend/safe_curl.js +28 -0
  28. package/dist/commonjs/lib/helper/cliFilter.d.ts +4 -0
  29. package/dist/commonjs/lib/helper/cliFilter.js +20 -0
  30. package/dist/commonjs/lib/helper/escape.d.ts +2 -0
  31. package/dist/commonjs/lib/helper/escape.js +8 -0
  32. package/dist/commonjs/lib/helper/escapeShellArg.d.ts +1 -0
  33. package/dist/commonjs/lib/helper/escapeShellArg.js +8 -0
  34. package/dist/commonjs/lib/helper/escapeShellCmd.d.ts +1 -0
  35. package/dist/commonjs/lib/helper/escapeShellCmd.js +17 -0
  36. package/dist/commonjs/lib/helper/index.d.ts +21 -0
  37. package/dist/commonjs/lib/helper/index.js +26 -0
  38. package/dist/commonjs/lib/helper/shtml.d.ts +2 -0
  39. package/dist/commonjs/lib/helper/shtml.js +76 -0
  40. package/dist/commonjs/lib/helper/sjs.d.ts +4 -0
  41. package/dist/commonjs/lib/helper/sjs.js +52 -0
  42. package/dist/commonjs/lib/helper/sjson.d.ts +1 -0
  43. package/dist/commonjs/lib/helper/sjson.js +45 -0
  44. package/dist/commonjs/lib/helper/spath.d.ts +5 -0
  45. package/dist/commonjs/lib/helper/spath.js +28 -0
  46. package/dist/commonjs/lib/helper/surl.d.ts +2 -0
  47. package/dist/commonjs/lib/helper/surl.js +33 -0
  48. package/dist/commonjs/lib/middlewares/csp.d.ts +4 -0
  49. package/dist/commonjs/lib/middlewares/csp.js +68 -0
  50. package/dist/commonjs/lib/middlewares/csrf.d.ts +4 -0
  51. package/dist/commonjs/lib/middlewares/csrf.js +42 -0
  52. package/dist/commonjs/lib/middlewares/dta.d.ts +3 -0
  53. package/dist/commonjs/lib/middlewares/dta.js +14 -0
  54. package/dist/commonjs/lib/middlewares/hsts.d.ts +4 -0
  55. package/dist/commonjs/lib/middlewares/hsts.js +23 -0
  56. package/dist/commonjs/lib/middlewares/index.d.ts +13 -0
  57. package/dist/commonjs/lib/middlewares/index.js +28 -0
  58. package/dist/commonjs/lib/middlewares/methodnoallow.d.ts +3 -0
  59. package/dist/commonjs/lib/middlewares/methodnoallow.js +22 -0
  60. package/dist/commonjs/lib/middlewares/noopen.d.ts +4 -0
  61. package/dist/commonjs/lib/middlewares/noopen.js +17 -0
  62. package/dist/commonjs/lib/middlewares/nosniff.d.ts +4 -0
  63. package/dist/commonjs/lib/middlewares/nosniff.js +30 -0
  64. package/dist/commonjs/lib/middlewares/referrerPolicy.d.ts +4 -0
  65. package/dist/commonjs/lib/middlewares/referrerPolicy.js +36 -0
  66. package/dist/commonjs/lib/middlewares/xframe.d.ts +4 -0
  67. package/dist/commonjs/lib/middlewares/xframe.js +19 -0
  68. package/dist/commonjs/lib/middlewares/xssProtection.d.ts +4 -0
  69. package/dist/commonjs/lib/middlewares/xssProtection.js +16 -0
  70. package/dist/commonjs/lib/utils.d.ts +19 -0
  71. package/dist/commonjs/lib/utils.js +206 -0
  72. package/dist/commonjs/package.json +3 -0
  73. package/dist/commonjs/types.d.ts +10 -0
  74. package/dist/commonjs/types.js +5 -0
  75. package/dist/esm/agent.d.ts +6 -0
  76. package/dist/esm/agent.js +11 -0
  77. package/dist/esm/app/extend/agent.d.ts +5 -0
  78. package/dist/esm/app/extend/agent.js +8 -0
  79. package/dist/esm/app/extend/application.d.ts +16 -0
  80. package/dist/esm/app/extend/application.js +32 -0
  81. package/dist/esm/app/extend/context.d.ts +68 -0
  82. package/dist/esm/app/extend/context.js +244 -0
  83. package/dist/esm/app/extend/helper.d.ts +12 -0
  84. package/dist/esm/app/extend/helper.js +5 -0
  85. package/dist/esm/app/extend/response.d.ts +41 -0
  86. package/dist/esm/app/extend/response.js +82 -0
  87. package/dist/esm/app/middleware/securities.d.ts +4 -0
  88. package/dist/esm/app/middleware/securities.js +50 -0
  89. package/dist/esm/app.d.ts +6 -0
  90. package/dist/esm/app.js +26 -0
  91. package/dist/esm/config/config.default.d.ts +871 -0
  92. package/dist/esm/config/config.default.js +351 -0
  93. package/dist/esm/config/config.local.d.ts +5 -0
  94. package/dist/esm/config/config.local.js +8 -0
  95. package/dist/esm/index.d.ts +1 -0
  96. package/dist/esm/index.js +12 -0
  97. package/dist/esm/lib/extend/safe_curl.d.ts +16 -0
  98. package/dist/esm/lib/extend/safe_curl.js +25 -0
  99. package/dist/esm/lib/helper/cliFilter.d.ts +4 -0
  100. package/dist/esm/lib/helper/cliFilter.js +17 -0
  101. package/dist/esm/lib/helper/escape.d.ts +2 -0
  102. package/dist/esm/lib/helper/escape.js +3 -0
  103. package/dist/esm/lib/helper/escapeShellArg.d.ts +1 -0
  104. package/dist/esm/lib/helper/escapeShellArg.js +5 -0
  105. package/dist/esm/lib/helper/escapeShellCmd.d.ts +1 -0
  106. package/dist/esm/lib/helper/escapeShellCmd.js +14 -0
  107. package/dist/esm/lib/helper/index.d.ts +21 -0
  108. package/dist/esm/lib/helper/index.js +21 -0
  109. package/dist/esm/lib/helper/shtml.d.ts +2 -0
  110. package/dist/esm/lib/helper/shtml.js +70 -0
  111. package/dist/esm/lib/helper/sjs.d.ts +4 -0
  112. package/dist/esm/lib/helper/sjs.js +49 -0
  113. package/dist/esm/lib/helper/sjson.d.ts +1 -0
  114. package/dist/esm/lib/helper/sjson.js +39 -0
  115. package/dist/esm/lib/helper/spath.d.ts +5 -0
  116. package/dist/esm/lib/helper/spath.js +25 -0
  117. package/dist/esm/lib/helper/surl.d.ts +2 -0
  118. package/dist/esm/lib/helper/surl.js +30 -0
  119. package/dist/esm/lib/middlewares/csp.d.ts +4 -0
  120. package/dist/esm/lib/middlewares/csp.js +63 -0
  121. package/dist/esm/lib/middlewares/csrf.d.ts +4 -0
  122. package/dist/esm/lib/middlewares/csrf.js +37 -0
  123. package/dist/esm/lib/middlewares/dta.d.ts +3 -0
  124. package/dist/esm/lib/middlewares/dta.js +12 -0
  125. package/dist/esm/lib/middlewares/hsts.d.ts +4 -0
  126. package/dist/esm/lib/middlewares/hsts.js +21 -0
  127. package/dist/esm/lib/middlewares/index.d.ts +13 -0
  128. package/dist/esm/lib/middlewares/index.js +23 -0
  129. package/dist/esm/lib/middlewares/methodnoallow.d.ts +3 -0
  130. package/dist/esm/lib/middlewares/methodnoallow.js +20 -0
  131. package/dist/esm/lib/middlewares/noopen.d.ts +4 -0
  132. package/dist/esm/lib/middlewares/noopen.js +15 -0
  133. package/dist/esm/lib/middlewares/nosniff.d.ts +4 -0
  134. package/dist/esm/lib/middlewares/nosniff.js +28 -0
  135. package/dist/esm/lib/middlewares/referrerPolicy.d.ts +4 -0
  136. package/dist/esm/lib/middlewares/referrerPolicy.js +34 -0
  137. package/dist/esm/lib/middlewares/xframe.d.ts +4 -0
  138. package/dist/esm/lib/middlewares/xframe.js +17 -0
  139. package/dist/esm/lib/middlewares/xssProtection.d.ts +4 -0
  140. package/dist/esm/lib/middlewares/xssProtection.js +14 -0
  141. package/dist/esm/lib/utils.d.ts +19 -0
  142. package/dist/esm/lib/utils.js +194 -0
  143. package/dist/esm/package.json +3 -0
  144. package/dist/esm/types.d.ts +10 -0
  145. package/dist/esm/types.js +3 -0
  146. package/dist/package.json +4 -0
  147. package/package.json +116 -0
  148. package/src/agent.ts +14 -0
  149. package/src/app/extend/agent.ts +14 -0
  150. package/src/app/extend/application.ts +51 -0
  151. package/src/app/extend/context.ts +282 -0
  152. package/src/app/extend/helper.ts +5 -0
  153. package/src/app/extend/response.ts +95 -0
  154. package/src/app/middleware/securities.ts +63 -0
  155. package/src/app.ts +31 -0
  156. package/src/config/config.default.ts +379 -0
  157. package/src/config/config.local.ts +9 -0
  158. package/src/index.ts +12 -0
  159. package/src/lib/extend/safe_curl.ts +35 -0
  160. package/src/lib/helper/cliFilter.ts +20 -0
  161. package/src/lib/helper/escape.ts +3 -0
  162. package/src/lib/helper/escapeShellArg.ts +4 -0
  163. package/src/lib/helper/escapeShellCmd.ts +16 -0
  164. package/src/lib/helper/index.ts +21 -0
  165. package/src/lib/helper/shtml.ts +77 -0
  166. package/src/lib/helper/sjs.ts +57 -0
  167. package/src/lib/helper/sjson.ts +35 -0
  168. package/src/lib/helper/spath.ts +27 -0
  169. package/src/lib/helper/surl.ts +35 -0
  170. package/src/lib/middlewares/csp.ts +70 -0
  171. package/src/lib/middlewares/csrf.ts +44 -0
  172. package/src/lib/middlewares/dta.ts +13 -0
  173. package/src/lib/middlewares/hsts.ts +24 -0
  174. package/src/lib/middlewares/index.ts +23 -0
  175. package/src/lib/middlewares/methodnoallow.ts +23 -0
  176. package/src/lib/middlewares/noopen.ts +18 -0
  177. package/src/lib/middlewares/nosniff.ts +32 -0
  178. package/src/lib/middlewares/referrerPolicy.ts +39 -0
  179. package/src/lib/middlewares/xframe.ts +20 -0
  180. package/src/lib/middlewares/xssProtection.ts +17 -0
  181. package/src/lib/utils.ts +208 -0
  182. package/src/types.ts +16 -0
  183. package/src/typings/index.d.ts +4 -0
@@ -0,0 +1,441 @@
1
+ # @eggjs/security
2
+
3
+ [![NPM version][npm-image]][npm-url]
4
+ [![Node.js CI](https://github.com/eggjs/security/actions/workflows/nodejs.yml/badge.svg)](https://github.com/eggjs/security/actions/workflows/nodejs.yml)
5
+ [![Test coverage][codecov-image]][codecov-url]
6
+ [![Known Vulnerabilities][snyk-image]][snyk-url]
7
+ [![npm download][download-image]][download-url]
8
+ [![Node.js Version](https://img.shields.io/node/v/eggjs/security.svg?style=flat)](https://nodejs.org/en/download/)
9
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)
10
+ ![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/eggjs/security)
11
+
12
+ [npm-image]: https://img.shields.io/npm/v/@eggjs/security.svg?style=flat-square
13
+ [npm-url]: https://npmjs.org/package/@eggjs/security
14
+ [codecov-image]: https://codecov.io/gh/eggjs/security/branch/master/graph/badge.svg
15
+ [codecov-url]: https://codecov.io/gh/eggjs/security
16
+ [snyk-image]: https://snyk.io/test/npm/@eggjs/security/badge.svg?style=flat-square
17
+ [snyk-url]: https://snyk.io/test/npm/@eggjs/security
18
+ [download-image]: https://img.shields.io/npm/dm/@eggjs/security.svg?style=flat-square
19
+ [download-url]: https://npmjs.org/package/@eggjs/security
20
+
21
+ egg 内置的安全插件
22
+
23
+ ## 使用方式
24
+
25
+ egg 默认开启此插件,所以无需配置。
26
+
27
+ 修改 `config/config.js` 文件修改配置
28
+
29
+ ```js
30
+ exports.security = {
31
+ xframe: {
32
+ value: 'SAMEORIGIN',
33
+ },
34
+ };
35
+ ```
36
+
37
+ ### 关闭安全防范
38
+
39
+ 如果你想关闭其中一些安全防范,直接设置该项的 `enable` 属性为 false 即可,如关闭 xfame 防范:
40
+
41
+ ```js
42
+ exports.security = {
43
+ xframe: {
44
+ enable: false,
45
+ },
46
+ };
47
+ ```
48
+
49
+ ### match 和 ignore
50
+
51
+ 如果只想开启针对某一路径,则配置 match 选项,例如只针对 `/example` 开启 csp
52
+
53
+ ```js
54
+ exports.security = {
55
+ csp: {
56
+ match: '/example',
57
+ // match: /^\/api/, // support regexp
58
+ // match: ctx => ctx.path.startsWith('/api'), // support function
59
+ // match: [ ctx => ctx.path.startsWith('/api'), /^\/foo$/, '/bar'], // support Array
60
+ policy: {
61
+ //...
62
+ },
63
+ },
64
+ };
65
+
66
+ ```
67
+
68
+ 如果需要针对某一路径忽略某安全选项,则配置 ignore 选项,例如针对 `/example` 关闭 xframe,以便合作商户能够嵌入我们的页面:
69
+
70
+ ```js
71
+ exports.security = {
72
+ xframe: {
73
+ ignore: '/example',
74
+ // ignore: /^\/api/, // support regexp
75
+ // ignore: ctx => ctx.path.startsWith('/api'), // support function
76
+ // ignore: [ ctx => ctx.path.startsWith('/api'), /^\/foo$/, '/bar'], // support Array
77
+ // ...
78
+ },
79
+ };
80
+
81
+ ```
82
+
83
+ __注意:如果存在 match 则忽略 ignore。__
84
+
85
+ ## API
86
+
87
+ ### ctx.isSafeDomain(domain)
88
+
89
+ 是否为安全域名。安全域名在配置中配置,见 `ctx.redirect` 部分
90
+
91
+ ## 接口限制
92
+
93
+ ### csrf
94
+
95
+ __使用__
96
+
97
+ * `ctx.csrf` 获取 csrf token
98
+
99
+ 一般在 POST 表单时使用。
100
+
101
+ 页面渲染时,将 `ctx.csrf` 作为 form 隐藏域或 query string 渲染在页面上。(`_csrf` 作为 key)
102
+
103
+ 在提交表单时,带上 token 即可。
104
+
105
+ #### 通过 formData 上传时使用 csrf
106
+
107
+ 浏览器端 html 代码:
108
+
109
+ ```html
110
+ <form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
111
+ title: <input name="title" />
112
+ file: <input name="file" type="file" />
113
+ <button type="submit">上传</button>
114
+ </form>
115
+ ```
116
+
117
+ ### ctoken
118
+
119
+ ajax 防跨站攻击。
120
+
121
+ __使用__
122
+
123
+ 在 ajax 请求时,以 `ctoken` 为 name 带上 ctoken 即可。
124
+
125
+ ctoken 从 cookie 中获取
126
+
127
+ __安全开发者约定__
128
+
129
+ * `ctx.ctoken` 获取 ctoken 的逻辑。使用者不要调用,安全插件内部使用。
130
+ * `ctx.setCTOKEN()` 设置 ctoken 的逻辑。使用者不要调用,安全插件内部使用。
131
+ * `ctx.assertCTOKEN()` ctoken 校验逻辑。使用者不要调用,安全插件内部使用。
132
+ * `ctx.setCTOKEN()`会将cookie设置到主域名下,主要考虑主域名下其他子域名对应的应用之间的互相调用。例如 A.xx.com 域种了 ctoken,会设置cookie到xx.com域上,在 B.xx.com 域的时候可以利用 ctoken 去请求,在 A 域 jsonp 请求 B 域的时候,B 域也可以验证 ctoken。
133
+
134
+ 可拓展实现。例如 ctoken token 存在什么 cookie,存什么字段等,都可以通过以上两个接口拓展。
135
+
136
+ #### 配置项
137
+
138
+ ```js
139
+ exports.security = {
140
+ csrf: {
141
+ type: 'ctoken', // 可以是 ctoken / referer / all, 默认为 ctoken
142
+ useSession: false, // 如果设为 true,secret 将存储在 session 中
143
+ ignoreJSON: false, // 如果设为 true ,将忽略 json 请求
144
+ cookieName: 'csrfToken', // csrf 的 token 在 cookie 中存储的 key 名称
145
+ sessionName: 'csrfToken', // csrf 的 token 在 session 中存储的 key 名称
146
+ headerName: 'x-csrf-token', // csrf token 在 header 中的名称
147
+ bodyName: '_csrf', // csrf token 在 body 中的名称
148
+ queryName: '_csrf', // csrf token 在 query 中的名称
149
+ rotateWhenInvalid: false, // csrf invalid 时刷新 token,用于同域名下多个业务 token 可能互相影响的情况
150
+ refererWhiteList: [], // referer 白名单
151
+ supportedRequests: [ // 支持的 url path pattern 和方法,根据配置名单由上至下匹配 url path 正则,建议在自定义时配置 {path: /^\//, methods:['POST','PATCH','DELETE','PUT','CONNECT']} 为兜底规则
152
+ {path: /^\//, methods:['POST','PATCH','DELETE','PUT','CONNECT']},
153
+ ],
154
+ },
155
+ }
156
+ ```
157
+
158
+ 注意,methods 可以为空, 如果将 supportedRequests 设置为`supportedRequests: [{path: /^\//, methods:[]}]`, 那么等效于关闭 csrf 防御。
159
+
160
+ ### safe redirect
161
+
162
+ * `ctx.redirect(url)` 如果不在配置的白名单内,则禁止
163
+ * `ctx.unsafeRedirect(url)` 不建议使用
164
+
165
+ 安全方案覆盖了默认的`ctx.redirect`方法,所有的跳转均会经过安全域名的判断。
166
+
167
+ 用户如果使用`ctx.redirect`方法,需要在应用的配置文件中做如下配置:
168
+
169
+ ```js
170
+ exports.security = {
171
+ domainWhiteList:['.domain.com'], // 安全白名单,以.开头
172
+ };
173
+ ```
174
+
175
+ 若用户没有配置 `domainWhiteList` 或者 `domainWhiteList` 数组内为空,则默认会对所有跳转请求放行,即等同于`ctx.unsafeRedirect(url)`。同时域名和 url 检查时不区分大小写。
176
+
177
+ ### jsonp
178
+
179
+ 使用 [jsonp-body](https://github.com/node-modules/jsonp-body),在 egg context 中实现,并不再 egg-security 中。
180
+
181
+ 防御内容:
182
+
183
+ * callback函数名词最长50个字符限制
184
+ * callback函数名只允许"[","]","a-zA-Z0123456789_", "$" ".",防止一般的 xss,utf-7 xss等攻击
185
+
186
+ 可定义配置:
187
+
188
+ * callback 默认 `_callback`,可以改名
189
+ * limit - 函数名 length 限制,默认 50
190
+
191
+ ## helper
192
+
193
+ ### .escape()
194
+
195
+ 对字符串进行 xss 过滤,安全性最高的过滤方式。
196
+
197
+ ```js
198
+ const str = '><script>alert("abc") </script><';
199
+ console.log(ctx.helper.escape(str));
200
+ // => &gt;&lt;script&gt;alert(&quot;abc&quot;) &lt;/script&gt;&lt;
201
+ ```
202
+
203
+ 在 nunjucks 模板中,默认进行 escape,不需要显式调用。
204
+
205
+ ### .surl()
206
+
207
+ url 过滤。
208
+
209
+ 用于在html标签中中要解析 url 的地方(比如 `<a href=""/><img src=""/>`),其他地方不允许使用。
210
+
211
+ 对模板中要输出的变量,加 `helper.surl($value)`。
212
+
213
+ __特别需要注意的是在需要解析url的地方,surl 外面一定要加上双引号,否则就会导致XSS漏洞。__
214
+
215
+ 不使用 surl
216
+
217
+ ```html
218
+ <a href="$value" />
219
+ ```
220
+
221
+ output:
222
+
223
+ ```html
224
+ <a href="http://www.domain.com<script>" />
225
+ ```
226
+
227
+ 使用 surl
228
+
229
+ ```html
230
+ <a href="helper.surl($value)" />
231
+ ```
232
+
233
+ output:
234
+
235
+ ```html
236
+ <a href="http://www.domain.com&lt;script&gt;" />
237
+ ```
238
+
239
+ ### .sjs()
240
+
241
+ 用于在 js(包括 onload 等 event)中输出变量,会对变量中字符进行 JAVASCRIPT ENCODE,
242
+ 将所有非白名单字符转义为 `\x` 形式,防止xss攻击,也确保在 js 中输出的正确性。
243
+
244
+ ```js
245
+ const foo = '"hello"';
246
+
247
+ // 未使用 sjs
248
+ console.log(`var foo = "${foo}";`);
249
+ // => var foo = ""hello"";
250
+
251
+ // 使用 sjs
252
+ console.log(`var foo = "${this.helper.sjs(foo)}";`);
253
+ // => var foo = "\\x22hello\\x22";
254
+ ```
255
+
256
+ ### .shtml()
257
+
258
+ 将富文本(包含 html 代码的文本)当成变量直接在模版里面输出时,需要用到 shtml 来处理。
259
+ 使用 shtml 可以输出 html 的 tag,同时执行 xss 的过滤动作,过滤掉非法的脚本。
260
+
261
+ **由于是一个非常复杂的安全处理过程,对服务器处理性能一定影响,如果不是输出 HTML,请勿使用。**
262
+
263
+ 简单示例:
264
+
265
+ ```js
266
+ // js
267
+ const value = `<a href="http://www.domain.com">google</a><script>evilcode…</script>`;
268
+
269
+ // 模板
270
+ <html>
271
+ <body>
272
+ ${helper.shtml($value)}
273
+ </body>
274
+ </html>
275
+ // => <a href="http://www.domain.com">google</a>&lt;script&gt;evilcode…&lt;/script&gt;
276
+ ```
277
+
278
+ shtml 在 [xss](https://github.com/leizongmin/js-xss/) 模块基础上增加了针对域名的过滤。
279
+
280
+ * [默认规则](https://github.com/leizongmin/js-xss/blob/master/lib/default.js)
281
+ * 自定义过滤项 <http://jsxss.com/zh/options.html>
282
+
283
+ 例如只支持 a 标签,且除了 title 其他属性都过滤掉:
284
+
285
+ ```javascript
286
+ whiteList: {a: ['title']}
287
+ ```
288
+
289
+ options:
290
+
291
+ > `config.helper.shtml.domainWhiteList` 已过时,请使用 `config.security.domainWhiteList` 代替。
292
+
293
+ 注意,shtml 使用了严格的白名单机制,除了过滤掉 xss 风险的字符串外,
294
+ 在 [默认规则](https://github.com/leizongmin/js-xss/blob/master/lib/default.js) 外的 tag 和 attr 都会被过滤掉。
295
+
296
+ 例如 html 标签就不在白名单中,
297
+
298
+ ```js
299
+ const html = '<html></html>';
300
+
301
+ // html
302
+ ${helper.shtml($html)}
303
+
304
+ // 输出空
305
+ ```
306
+
307
+ 常见的 `data-xx` 属性由于不在白名单中,所以都会被过滤。
308
+
309
+ 所以,一定要注意 shtml 的适用场景,一般是针对来自用户的富文本输入,切忌滥用,功能既受到限制,又会影响服务端性能。
310
+ 此类场景一般是论坛、评论系统等,即便是论坛等如果不支持 html 内容输入,也不要使用此 helper,直接使用 escape 即可。
311
+
312
+ ### .spath()
313
+
314
+ 如果把输入字符串用作 path 路径,需要使用 spath 进行安全检验。若路径不合法,返回 null。
315
+
316
+ 不合法的路径包括:
317
+
318
+ * 使用 `..` 的相对路径
319
+ * 使用 `/` 开头的绝对路径
320
+ * 以及以上试图通过 url encode 试图绕过校验的结果字符串
321
+
322
+ ```js
323
+ const foo = '/usr/local/bin';
324
+ console.log(this.helper.spath(foo2));
325
+ // => null
326
+ ```
327
+
328
+ ### .sjson()
329
+
330
+ json转义
331
+
332
+ 在js中输出json,若未做转义,易被利用为xss漏洞。提供此宏做json encode,会遍历json中的key,将value的值中,所有非白名单字符转义为\x形式,防止xss攻击。同时保持json结构不变。
333
+ 若你有模板中输出一个json字符串给js应用的场景,请使用 `${this.helper.sjson(变量名)}`进行转义。
334
+
335
+ __处理过程较复杂,性能损耗较大,尽量避免使用__
336
+
337
+ 实例:
338
+
339
+ ```js
340
+ <script>
341
+ window.locals = ${this.helper.sjson(locals)};
342
+ </script>
343
+ ```
344
+
345
+ ### .cliFilter()
346
+
347
+ 远程命令执行漏洞,用户通过浏览器提交执行命令,由于服务器端没有针对执行函数做过滤,导致可以执行命令,通常可导致入侵服务器。
348
+
349
+ 如果用户可控变量为命令中的参数。则直接使用安全包函数过滤。
350
+
351
+ 修复前:
352
+
353
+ ```js
354
+
355
+ cp.exec("bash /home/admin/ali-knowledge-graph-backend/initrun.sh " + port);
356
+
357
+ ```
358
+
359
+ 修复后:
360
+
361
+ ```js
362
+
363
+ cp.exec("bash /home/admin/ali-knowledge-graph-backend/initrun.sh " + this.helper.cliFilter(port));
364
+
365
+ ```
366
+
367
+ 如果因为业务需要,需要在参数中添加白名单之外的字符。可以将用户输入按照该字符分割,并使用过滤函数过滤每一段数据。
368
+
369
+ 如果用户可控变量为命令中的命令,则和开发协商修改功能或功能下线。
370
+
371
+ ### .escapeShellArg()
372
+
373
+ 命令行参数转义。给字符串增加一对单引号并且能引用或者转码任何已经存在的单引号, 这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。
374
+
375
+ ```js
376
+ const ip = '127.0.0.1 && cat /etc/passwd'
377
+ const cmd = 'ping -c 1 ' + this.helper.escapeShellArg(ip);
378
+
379
+ console.log(cmd);
380
+ //ping -c 1 '127.0.0.1 && cat /etc/passwd'
381
+ ```
382
+
383
+ ### .escapeShellCmd()
384
+
385
+ 命令行转义,从输入的命令行中删除下列字符: ```#&;`|*?~<>^()[]{}$;'", 0x0A 和 0xFF```
386
+
387
+ ```js
388
+ const ip = '127.0.0.1 && cat /etc/passwd'
389
+ const cmd = 'ping -c 1 ' + this.helper.escapeShellCmd(ip);
390
+
391
+ console.log(cmd);
392
+ //ping -c 1 127.0.0.1 cat /etc/passwd
393
+ ```
394
+
395
+ ## Web 安全头
396
+
397
+ ### hsts Strict-Transport-Security
398
+
399
+ 默认开启,如果是 http 站点,需要关闭
400
+
401
+ * maxAge 默认一年 `365 * 24 * 3600`
402
+ * includeSubdomains 默认 false
403
+
404
+ ### csp
405
+
406
+ 默认关闭。需要开启的话,需要和安全工程师确定开启策略。
407
+
408
+ * policy 策略
409
+
410
+ ### X-Download-Options:noopen
411
+
412
+ 默认开启,禁用IE下下载框Open按钮,防止 ie 下下载文件默认被打开 xss
413
+
414
+ ### X-Content-Type-Options:nosniff
415
+
416
+ 禁用 IE8 自动嗅探 mime 功能。例如:`text/plain` 却当成 `text/html` 渲染,特别当本站点服务内容未必可信的时候。
417
+
418
+ ### X-Frame-Options
419
+
420
+ 默认 SAMEORIGIN,只允许同域把本页面当作 iframe 嵌入。
421
+
422
+ * value 默认值 `SAMEORIGIN`
423
+
424
+ ### X-XSS-Protection
425
+
426
+ * close 默认值false,即设置为 `1; mode=block`
427
+
428
+ ## 其他
429
+
430
+ * crossdomain.xml robots.txt 支持,默认都不加,系统可自行加,需要咨询项目安全工程师
431
+ * 禁止 trace track 两种类型请求
432
+
433
+ ## License
434
+
435
+ [MIT](LICENSE)
436
+
437
+ ## Contributors
438
+
439
+ [![Contributors](https://contrib.rocks/image?repo=eggjs/security)](https://github.com/eggjs/security/graphs/contributors)
440
+
441
+ Made with [contributors-img](https://contrib.rocks).
@@ -0,0 +1,6 @@
1
+ import type { ILifecycleBoot, EggCore } from '@eggjs/core';
2
+ export default class AgentBoot implements ILifecycleBoot {
3
+ private readonly agent;
4
+ constructor(agent: EggCore);
5
+ configWillLoad(): Promise<void>;
6
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_js_1 = require("./lib/utils.js");
4
+ class AgentBoot {
5
+ agent;
6
+ constructor(agent) {
7
+ this.agent = agent;
8
+ }
9
+ async configWillLoad() {
10
+ (0, utils_js_1.preprocessConfig)(this.agent.config.security);
11
+ }
12
+ }
13
+ exports.default = AgentBoot;
14
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWdlbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYWdlbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFDQSw2Q0FBa0Q7QUFFbEQsTUFBcUIsU0FBUztJQUNYLEtBQUssQ0FBQztJQUV2QixZQUFZLEtBQWM7UUFDeEIsSUFBSSxDQUFDLEtBQUssR0FBRyxLQUFLLENBQUM7SUFDckIsQ0FBQztJQUVELEtBQUssQ0FBQyxjQUFjO1FBQ2xCLElBQUEsMkJBQWdCLEVBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDL0MsQ0FBQztDQUNGO0FBVkQsNEJBVUMifQ==
@@ -0,0 +1,5 @@
1
+ import { EggCore } from '@eggjs/core';
2
+ import { type HttpClientRequestURL, type HttpClientOptions, type HttpClientResponse } from '../../lib/extend/safe_curl.js';
3
+ export default class SecurityAgent extends EggCore {
4
+ safeCurl<T = any>(url: HttpClientRequestURL, options?: HttpClientOptions): Promise<HttpClientResponse<T>>;
5
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@eggjs/core");
4
+ const safe_curl_js_1 = require("../../lib/extend/safe_curl.js");
5
+ class SecurityAgent extends core_1.EggCore {
6
+ async safeCurl(url, options) {
7
+ return await (0, safe_curl_js_1.safeCurlForApplication)(this, url, options);
8
+ }
9
+ }
10
+ exports.default = SecurityAgent;
11
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWdlbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvYXBwL2V4dGVuZC9hZ2VudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLHNDQUFzQztBQUN0QyxnRUFLdUM7QUFFdkMsTUFBcUIsYUFBYyxTQUFRLGNBQU87SUFDaEQsS0FBSyxDQUFDLFFBQVEsQ0FDWixHQUF5QixFQUFFLE9BQTJCO1FBQ3RELE9BQU8sTUFBTSxJQUFBLHFDQUFzQixFQUFJLElBQUksRUFBRSxHQUFHLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDN0QsQ0FBQztDQUNGO0FBTEQsZ0NBS0MifQ==
@@ -0,0 +1,16 @@
1
+ import { EggCore } from '@eggjs/core';
2
+ import { type HttpClientRequestURL, type HttpClientOptions, type HttpClientResponse } from '../../lib/extend/safe_curl.js';
3
+ export default class SecurityApplication extends EggCore {
4
+ injectCsrf(html: string): string;
5
+ injectNonce(html: string): string;
6
+ injectHijackingDefense(html: string): string;
7
+ safeCurl<T = any>(url: HttpClientRequestURL, options?: HttpClientOptions): Promise<HttpClientResponse<T>>;
8
+ }
9
+ declare module '@eggjs/core' {
10
+ interface EggCore {
11
+ injectCsrf(html: string): string;
12
+ injectNonce(html: string): string;
13
+ injectHijackingDefense(html: string): string;
14
+ safeCurl<T = any>(url: HttpClientRequestURL, options?: HttpClientOptions): Promise<HttpClientResponse<T>>;
15
+ }
16
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@eggjs/core");
4
+ const safe_curl_js_1 = require("../../lib/extend/safe_curl.js");
5
+ const INPUT_CSRF = '\r\n<input type="hidden" name="_csrf" value="{{ctx.csrf}}" /></form>';
6
+ const INJECTION_DEFENSE = '<!--for injection--><!--</html>--><!--for injection-->';
7
+ class SecurityApplication extends core_1.EggCore {
8
+ injectCsrf(html) {
9
+ html = html.replace(/(<form.*?>)([\s\S]*?)<\/form>/gi, (_, $1, $2) => {
10
+ const match = $2;
11
+ if (match.indexOf('name="_csrf"') !== -1 || match.indexOf('name=\'_csrf\'') !== -1) {
12
+ return $1 + match + '</form>';
13
+ }
14
+ return $1 + match + INPUT_CSRF;
15
+ });
16
+ return html;
17
+ }
18
+ injectNonce(html) {
19
+ html = html.replace(/<script(.*?)>([\s\S]*?)<\/script[^>]*?>/gi, (_, $1, $2) => {
20
+ if (!$1.includes('nonce=')) {
21
+ $1 += ' nonce="{{ctx.nonce}}"';
22
+ }
23
+ return '<script' + $1 + '>' + $2 + '</script>';
24
+ });
25
+ return html;
26
+ }
27
+ injectHijackingDefense(html) {
28
+ return INJECTION_DEFENSE + html + INJECTION_DEFENSE;
29
+ }
30
+ async safeCurl(url, options) {
31
+ return await (0, safe_curl_js_1.safeCurlForApplication)(this, url, options);
32
+ }
33
+ }
34
+ exports.default = SecurityApplication;
35
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwbGljYXRpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvYXBwL2V4dGVuZC9hcHBsaWNhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLHNDQUFzQztBQUN0QyxnRUFLdUM7QUFFdkMsTUFBTSxVQUFVLEdBQUcsc0VBQXNFLENBQUM7QUFDMUYsTUFBTSxpQkFBaUIsR0FBRyx3REFBd0QsQ0FBQztBQUVuRixNQUFxQixtQkFBb0IsU0FBUSxjQUFPO0lBQ3RELFVBQVUsQ0FBQyxJQUFZO1FBQ3JCLElBQUksR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLGlDQUFpQyxFQUFFLENBQUMsQ0FBQyxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRTtZQUNuRSxNQUFNLEtBQUssR0FBRyxFQUFFLENBQUM7WUFDakIsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxDQUFDO2dCQUNuRixPQUFPLEVBQUUsR0FBRyxLQUFLLEdBQUcsU0FBUyxDQUFDO1lBQ2hDLENBQUM7WUFDRCxPQUFPLEVBQUUsR0FBRyxLQUFLLEdBQUcsVUFBVSxDQUFDO1FBQ2pDLENBQUMsQ0FBQyxDQUFDO1FBQ0gsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQsV0FBVyxDQUFDLElBQVk7UUFDdEIsSUFBSSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsMkNBQTJDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFO1lBQzdFLElBQUksQ0FBQyxFQUFFLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUM7Z0JBQzNCLEVBQUUsSUFBSSx3QkFBd0IsQ0FBQztZQUNqQyxDQUFDO1lBQ0QsT0FBTyxTQUFTLEdBQUcsRUFBRSxHQUFHLEdBQUcsR0FBRyxFQUFFLEdBQUcsV0FBVyxDQUFDO1FBQ2pELENBQUMsQ0FBQyxDQUFDO1FBQ0gsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQsc0JBQXNCLENBQUMsSUFBWTtRQUNqQyxPQUFPLGlCQUFpQixHQUFHLElBQUksR0FBRyxpQkFBaUIsQ0FBQztJQUN0RCxDQUFDO0lBRUQsS0FBSyxDQUFDLFFBQVEsQ0FDWixHQUF5QixFQUFFLE9BQTJCO1FBQ3RELE9BQU8sTUFBTSxJQUFBLHFDQUFzQixFQUFJLElBQUksRUFBRSxHQUFHLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDN0QsQ0FBQztDQUNGO0FBOUJELHNDQThCQyJ9
@@ -0,0 +1,68 @@
1
+ import { Context } from '@eggjs/core';
2
+ import type { HttpClientRequestURL, HttpClientOptions, HttpClientResponse } from '../../lib/extend/safe_curl.js';
3
+ import { SecurityConfig, SecurityHelperConfig } from '../../types.js';
4
+ declare const CSRF_SECRET: unique symbol;
5
+ declare const LOG_CSRF_NOTICE: unique symbol;
6
+ declare const INPUT_TOKEN: unique symbol;
7
+ declare const CSRF_REFERER_CHECK: unique symbol;
8
+ declare const CSRF_CTOKEN_CHECK: unique symbol;
9
+ export default class SecurityContext extends Context {
10
+ get securityOptions(): Partial<SecurityConfig>;
11
+ /**
12
+ * Check whether the specific `domain` is in / matches the whiteList or not.
13
+ * @param {string} domain The assigned domain.
14
+ * @param {Array<string>} [customWhiteList] The custom white list for domain.
15
+ * @return {boolean} If the domain is in / matches the whiteList, return true;
16
+ * otherwise false.
17
+ */
18
+ isSafeDomain(domain: string, customWhiteList?: string[]): boolean;
19
+ get nonce(): string;
20
+ /**
21
+ * get csrf token, general use in template
22
+ * @return {String} csrf token
23
+ * @public
24
+ */
25
+ get csrf(): string;
26
+ /**
27
+ * get csrf secret from session or cookie
28
+ * @return {String} csrf secret
29
+ * @private
30
+ */
31
+ get [CSRF_SECRET](): string;
32
+ /**
33
+ * ensure csrf secret exists in session or cookie.
34
+ * @param {Boolean} [rotate] reset secret even if the secret exists
35
+ * @public
36
+ */
37
+ ensureCsrfSecret(rotate?: boolean): void;
38
+ get [INPUT_TOKEN](): string;
39
+ /**
40
+ * rotate csrf secret exists in session or cookie.
41
+ * must rotate the secret when user login
42
+ * @public
43
+ */
44
+ rotateCsrfSecret(): void;
45
+ /**
46
+ * assert csrf token/referer is present
47
+ * @public
48
+ */
49
+ assertCsrf(): void;
50
+ [CSRF_CTOKEN_CHECK](): "missing csrf token" | "invalid csrf token" | undefined;
51
+ [CSRF_REFERER_CHECK](): "missing csrf referer or origin" | "invalid csrf referer or origin" | undefined;
52
+ [LOG_CSRF_NOTICE](msg: string): void;
53
+ safeCurl<T = any>(url: HttpClientRequestURL, options?: HttpClientOptions): Promise<HttpClientResponse<T>>;
54
+ unsafeRedirect(url: string, alt?: string): void;
55
+ }
56
+ declare module '@eggjs/core' {
57
+ interface Context {
58
+ get securityOptions(): Partial<SecurityConfig & SecurityHelperConfig>;
59
+ isSafeDomain(domain: string, customWhiteList?: string[]): boolean;
60
+ get nonce(): string;
61
+ get csrf(): string;
62
+ ensureCsrfSecret(rotate?: boolean): void;
63
+ rotateCsrfSecret(): void;
64
+ assertCsrf(): void;
65
+ safeCurl<T = any>(url: HttpClientRequestURL, options?: HttpClientOptions): Promise<HttpClientResponse<T>>;
66
+ }
67
+ }
68
+ export {};