Authored by 徐炜

URL跳转漏洞。

@@ -19,7 +19,7 @@ const config = global.yoho.config; @@ -19,7 +19,7 @@ const config = global.yoho.config;
19 const cache = global.yoho.cache; 19 const cache = global.yoho.cache;
20 const LoginService = require('../models/login-service'); 20 const LoginService = require('../models/login-service');
21 const PassportHelper = require('../models/passport-helper'); 21 const PassportHelper = require('../models/passport-helper');
22 -const safeRedirectFilter = require('../../../doraemon/middleware/safe-redirect').safeRedirectFilter; 22 +const safeRedirect = require('../../../doraemon/middleware/safe-redirect').safeRedirect;
23 23
24 const loginPageURL = `${config.siteUrl}/passport/login`; 24 const loginPageURL = `${config.siteUrl}/passport/login`;
25 const BlockRedirectFilter = /sign|login|passport/; 25 const BlockRedirectFilter = /sign|login|passport/;
@@ -184,15 +184,12 @@ const local = { @@ -184,15 +184,12 @@ const local = {
184 } 184 }
185 185
186 refer = !BlockRedirectFilter.test(decodeURI(refer)) ? decodeURI(refer) : config.siteUrl; 186 refer = !BlockRedirectFilter.test(decodeURI(refer)) ? decodeURI(refer) : config.siteUrl;
187 - if (!/www\.yohoblk\.com/.test(refer)) {  
188 - refer = config.siteUrl;  
189 - }  
190 187
191 yield LoginService.syncUserSession(user.uid, req, res).then(() => { 188 yield LoginService.syncUserSession(user.uid, req, res).then(() => {
192 res.json({ 189 res.json({
193 code: 200, 190 code: 200,
194 data: { 191 data: {
195 - refer: safeRedirectFilter(refer) 192 + refer: safeRedirect(refer)
196 } 193 }
197 }); 194 });
198 }); 195 });
@@ -11,27 +11,52 @@ const url = require('url'); @@ -11,27 +11,52 @@ const url = require('url');
11 const domains = require('../../config/safe-domain').domains; 11 const domains = require('../../config/safe-domain').domains;
12 const _ = require('lodash'); 12 const _ = require('lodash');
13 13
  14 +
14 /** 15 /**
15 * 检查域名安全性 16 * 检查域名安全性
16 * 17 *
17 * @param uri 18 * @param uri
18 */ 19 */
19 const safeRedirect = (uri) => { 20 const safeRedirect = (uri) => {
20 - let result = url.parse(uri);  
21 - const ret = _.some(domains, (item)=> {  
22 - return item === result.host;  
23 - }); 21 + let formalUrl = url.parse(uri); // 匹配标准的URL
  22 + let informalUrl = uri.match(/^\/\/(.+)/); // 尝试匹配 '//' 开头的不规范的URL
  23 + let matchFunc;
  24 +
  25 + if (formalUrl.protocol) {
  26 + // 在白名单中尝试匹配
  27 + matchFunc = (item)=> {
  28 + return item === formalUrl.host;
  29 + };
  30 + } else if (informalUrl && informalUrl.length > 0) {
  31 + matchFunc = (item)=> {
  32 + return item === informalUrl[1];
  33 + };
  34 + }
  35 +
24 36
25 - return ret ? uri : '/'; 37 + return _.some(domains, matchFunc) ? uri : '/';
26 }; 38 };
27 39
  40 +/**
  41 + * 安全重定向中间件
  42 + *
  43 + * @returns {function(*, *=, *)}
  44 + */
28 const middleware = () => { 45 const middleware = () => {
29 return (req, res, next) => { 46 return (req, res, next) => {
30 const expressRedirect = res.redirect; 47 const expressRedirect = res.redirect;
31 48
32 res.redirect = function(uri) { 49 res.redirect = function(uri) {
33 - safeRedirect(uri);  
34 - return expressRedirect.apply(res, arguments); 50 + const safeUri = safeRedirect(uri);
  51 + let args = [];
  52 +
  53 + if (arguments.length === 1) {
  54 + args[0] = safeUri;
  55 + } else if (arguments.length === 2) {
  56 + args[1] = safeUri;
  57 + }
  58 +
  59 + return expressRedirect.apply(res, args);
35 }; 60 };
36 61
37 next(); 62 next();