utils.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. 'use strict';
  2. const normalize = require('path').normalize;
  3. const IP = require('ip');
  4. const matcher = require('matcher');
  5. const URL = require('url').URL;
  6. /**
  7. * Check whether a domain is in the safe domain white list or not.
  8. * @param {String} domain The inputted domain.
  9. * @param {Array<string>} whiteList The white list for domain.
  10. * @return {Boolean} If the `domain` is in the white list, return true; otherwise false.
  11. */
  12. exports.isSafeDomain = function isSafeDomain(domain, whiteList) {
  13. // domain must be string, otherwise return false
  14. if (typeof domain !== 'string') return false;
  15. // Ignore case sensitive first
  16. domain = domain.toLowerCase();
  17. // add prefix `.`, because all domains in white list start with `.`
  18. const hostname = '.' + domain;
  19. return whiteList.some(rule => {
  20. // Check whether we've got '*' as a wild character symbol
  21. if (rule.includes('*')) {
  22. return matcher.isMatch(domain, rule);
  23. }
  24. // If domain is an absolute path such as `http://...`
  25. // We can directly check whether it directly equals to `domain`
  26. // And we don't need to cope with `endWith`.
  27. if (domain === rule) return true;
  28. // ensure wwweggjs.com not match eggjs.com
  29. if (!/^\./.test(rule)) rule = `.${rule}`;
  30. return hostname.endsWith(rule);
  31. });
  32. };
  33. exports.isSafePath = function isSafePath(path, ctx) {
  34. path = '.' + path;
  35. if (path.indexOf('%') !== -1) {
  36. try {
  37. path = decodeURIComponent(path);
  38. } catch (e) {
  39. if (ctx.app.config.env === 'local' || ctx.app.config.env === 'unittest') {
  40. // not under production environment, output log
  41. ctx.coreLogger.warn('[egg-security: dta global block] : decode file path %s failed.', path);
  42. }
  43. }
  44. }
  45. const normalizePath = normalize(path);
  46. return !(normalizePath.startsWith('../') || normalizePath.startsWith('..\\'));
  47. };
  48. exports.checkIfIgnore = function checkIfIgnore(opts, ctx) {
  49. // check opts.enable first
  50. if (!opts.enable) return true;
  51. return !opts.matching(ctx);
  52. };
  53. const IP_RE = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
  54. const topDomains = {};
  55. [ '.net.cn', '.gov.cn', '.org.cn', '.com.cn' ].forEach(function(item) {
  56. topDomains[item] = 2 - item.split('.').length;
  57. });
  58. exports.getCookieDomain = function getCookieDomain(hostname) {
  59. if (IP_RE.test(hostname)) {
  60. return hostname;
  61. }
  62. // app.test.domain.com => .test.domain.com
  63. // app.stable.domain.com => .domain.com
  64. // app.domain.com => .domain.com
  65. // domain=.domain.com;
  66. const splits = hostname.split('.');
  67. let index = -2;
  68. // only when `*.test.*.com` set `.test.*.com`
  69. if (splits.length >= 4 && splits[splits.length - 3] === 'test') {
  70. index = -3;
  71. }
  72. let domain = getDomain(splits, index);
  73. if (topDomains[domain]) {
  74. domain = getDomain(splits, index + topDomains[domain]);
  75. }
  76. return domain;
  77. };
  78. function getDomain(arr, index) {
  79. return '.' + arr.slice(index).join('.');
  80. }
  81. exports.merge = function merge(origin, opts) {
  82. if (!opts) return origin;
  83. const res = {};
  84. const originKeys = Object.keys(origin);
  85. for (let i = 0; i < originKeys.length; i++) {
  86. const key = originKeys[i];
  87. res[key] = origin[key];
  88. }
  89. const keys = Object.keys(opts);
  90. for (let i = 0; i < keys.length; i++) {
  91. const key = keys[i];
  92. res[key] = opts[key];
  93. }
  94. return res;
  95. };
  96. exports.preprocessConfig = function(config) {
  97. // transfor ssrf.ipBlackList to ssrf.checkAddress
  98. // ssrf.ipExceptionList can easily pick out unwanted ips from ipBlackList
  99. // checkAddress has higher priority than ipBlackList
  100. const ssrf = config.ssrf;
  101. if (ssrf && ssrf.ipBlackList && !ssrf.checkAddress) {
  102. const containsList = ssrf.ipBlackList.map(getContains);
  103. const exceptionList = (ssrf.ipExceptionList || []).map(getContains);
  104. ssrf.checkAddress = ip => {
  105. for (const exception of exceptionList) {
  106. if (exception(ip)) {
  107. return true;
  108. }
  109. }
  110. for (const contains of containsList) {
  111. if (contains(ip)) {
  112. return false;
  113. }
  114. }
  115. return true;
  116. };
  117. }
  118. // Make sure that `whiteList` or `protocolWhiteList` is case insensitive
  119. config.domainWhiteList = config.domainWhiteList || [];
  120. config.domainWhiteList = config.domainWhiteList.map(domain => domain.toLowerCase());
  121. config.protocolWhiteList = config.protocolWhiteList || [];
  122. config.protocolWhiteList = config.protocolWhiteList.map(protocol => protocol.toLowerCase());
  123. // Make sure refererWhiteList is case insensitive
  124. if (config.csrf && config.csrf.refererWhiteList) {
  125. config.csrf.refererWhiteList = config.csrf.refererWhiteList.map(ref => ref.toLowerCase());
  126. }
  127. // Directly converted to Set collection by a private property (not documented),
  128. // And we NO LONGER need to do conversion in `foreach` again and again in `surl.js`.
  129. config._protocolWhiteListSet = new Set(config.protocolWhiteList);
  130. config._protocolWhiteListSet.add('http');
  131. config._protocolWhiteListSet.add('https');
  132. config._protocolWhiteListSet.add('file');
  133. config._protocolWhiteListSet.add('data');
  134. };
  135. exports.getFromUrl = function(url, prop) {
  136. try {
  137. const parsed = new URL(url);
  138. return prop ? parsed[prop] : parsed;
  139. } catch (err) {
  140. return null;
  141. }
  142. };
  143. function getContains(ip) {
  144. if (IP.isV4Format(ip) || IP.isV6Format(ip)) {
  145. return _ip => ip === _ip;
  146. }
  147. return IP.cidrSubnet(ip).contains;
  148. }