オープンリダイレクトとは?

[/用語集/オープンリダイレクト]

Express に於けるリダイレクト

Express 4.x API#res.redirect external_link

  • URL パラメータ等、ユーザーが指定可能な文字列をそのままリダイレクトさせるコード書くとセキュリティホールとなる

過去実際にあった脆弱性

対策

方針

  1. URL をパースし、不正な形式(//evil.example.com 等)であればリダイレクトしない
  2. URL が、リクエストのホストと不一致ならリダイレクトしない
  3. URL が、ホワイトリストに入っていなければリダイレクトしない

コード

最小の対策(方針1,2まで)

middleware/safe-redirect.js
/** * Redirect with prevention from Open Redirect * * Usage: app.use(require('middleware/safe-redirect')()) */ const logger = request('path/to/logger'); module.exports = () => { return function(req, res, next) { // extend res object res.safeRedirect = function(redirectTo) { if (redirectTo == null) { return res.redirect('/'); } try { // check inner redirect const redirectUrl = new URL(redirectTo, `${req.protocol}://${req.get('host')}`); if (redirectUrl.hostname === req.hostname) { logger.debug(`Requested redirect URL (${redirectTo}) is local.`); return res.redirect(redirectUrl.href); } logger.debug(`Requested redirect URL (${redirectTo}) is NOT local.`); } catch (err) { logger.warn(`Requested redirect URL (${redirectTo}) is invalid.`, err); } logger.warn(`Requested redirect URL (${redirectTo}) is UNSAFE, redirecting to root page.`); return res.redirect('/'); }; next(); }; };

利用方法

app.use(require('middleware/safe-redirect')());

ホワイトリスト指定機能付き(方針3まで)

middleware/safe-redirect.js
/** * Redirect with prevention from Open Redirect * * Usage: app.use(require('middleware/safe-redirect')(['example.com', 'some.example.com:8080'])) */ const logger = request('path/to/logger')('middleware:safe-redirect'); /** * Check whether the redirect url host is in specified whitelist * @param {Array<string>} whitelistOfHosts * @param {string} redirectToFqdn */ function isInWhitelist(whitelistOfHosts, redirectToFqdn) { if (whitelistOfHosts == null || whitelistOfHosts.length === 0) { return false; } const redirectUrl = new URL(redirectToFqdn); return whitelistOfHosts.includes(redirectUrl.hostname) || whitelistOfHosts.includes(redirectUrl.host); } module.exports = (whitelistOfHosts) => { return function(req, res, next) { // extend res object res.safeRedirect = function(redirectTo) { if (redirectTo == null) { return res.redirect('/'); } try { // check inner redirect const redirectUrl = new URL(redirectTo, `${req.protocol}://${req.get('host')}`); if (redirectUrl.hostname === req.hostname) { logger.debug(`Requested redirect URL (${redirectTo}) is local.`); return res.redirect(redirectUrl.href); } logger.debug(`Requested redirect URL (${redirectTo}) is NOT local.`); // check whitelisted redirect const isWhitelisted = isInWhitelist(whitelistOfHosts, redirectTo); if (isWhitelisted) { logger.debug(`Requested redirect URL (${redirectTo}) is in whitelist.`, `whitelist=${whitelistOfHosts}`); return res.redirect(redirectTo); } logger.debug(`Requested redirect URL (${redirectTo}) is NOT in whitelist.`, `whitelist=${whitelistOfHosts}`); } catch (err) { logger.warn(`Requested redirect URL (${redirectTo}) is invalid.`, err); } logger.warn(`Requested redirect URL (${redirectTo}) is UNSAFE, redirecting to root page.`); return res.redirect('/'); }; next(); }; };