しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET Core で Open Redirect 攻撃を避けるために LocalRedirect / IsLocalUrl を使う

ちょっと前に ASP.NET Core で Open Redirect の脆弱性が見つかって修正したという話が出てました。

歴史的に ASP.NET は Open Redirect の脆弱性が多い気もしますが、デフォルトの挙動が ReturnUrl を受け取り、それを使うという形なので注意したい部分ではあります。

JVNDB-2011-003557 - JVN iPedia - 脆弱性対策情報データベース

今回の ASP.NET Core の脆弱性は IsLocalUrl のチェックに問題があったようなので、正しく使っていても影響を受けたという悲しい感じですが、そもそも正しく使えていなければ意味がないので書きます。

Open Redirect 攻撃を受ける例

よくあるログイン前にいたページに、ログイン後戻るコードを含むアクションです。ASP.NET では歴史的に ReturnUrl というクエリパラメータで、ログイン後に戻りたい URL が渡されることが多いです。

public class LoginController : Controller
{
    public IActionResult Index(string returnUrl)
    {
        ViewData["ReturnUrl"] = returnUrl;

        return View();
    }

    [HttpPost]
    public IActionResult Index(LoginModel model, string returnUrl = "/mypage")
    {
        if (!ModelState.IsValid)
        {
            return View(model);
        }

        return Redirect(returnUrl);
    }
}

この時に ReturnUrl の飛び先を検証していない場合、Open Redirect 攻撃を受けてしまいます。

なので、この例では ReturnUrl に全く別の URL を指定すると、ログイン成功後に別ドメインに容易にリダイレクトされてしまいます。実際に危険サイトに飛ばされていることが確認できますね。

f:id:shiba-yan:20171217184007p:plain

テンプレート通りの実装をしていれば大体問題はないですが、ログイン周りはカスタム実装になってしまうことが多いので、そうは言ってられません。なのでクエリパラメータなどでリダイレクト先を受け取る場合にはちゃんとチェックをしましょう。

LocalRedirect を使う

わかりやすい対処方法は Redirect を使っている部分を LocalRedirect に置き換えるだけです。

[HttpPost]
public IActionResult Index(LoginModel model, string returnUrl = "/mypage")
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    return LocalRedirect(returnUrl);
}

これで同じ URL からログインを試みると、以下のように例外が投げられてエラーページに飛びます。

f:id:shiba-yan:20171217183437p:plain

危険なページにリダイレクトすることは避けれますが、エラーページに飛ばしたくない場合のが多いと思うので、大体は IsLocalUrl でチェックをする方が良いと思いました。

IsLocalUrl を使う

こっちの方法は ASP.NET MVC 3 ぐらいから使えます。Url.IsLocalUrl を使ってリダイレクト先が正しいかチェックし、問題がある場合にはデフォルトのページに飛ばしてしまうコードです。

[HttpPost]
public IActionResult Index(LoginModel model, string returnUrl = "/mypage")
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    if (!Url.IsLocalUrl(returnUrl))
    {
        return LocalRedirect("/mypage");
    }

    return LocalRedirect(returnUrl);
}

試すと ReturnUrl で指定した URL とは関係なく、デフォルトのページに飛ばされていることが分かります。

f:id:shiba-yan:20171217183649p:plain

最後に軽くまとめると、ASP.NET Core でリダイレクトを書く場合には RedirectToAction か LocalRedirect を使っておけば 9 割は問題ありません。残り 1 割は OAuth のログインなどで、信頼済み URL にリダイレクトする場合などに限り Redirect を使えばよいでしょう。

とはいえ、ほとんどのケースでは RedirectToAction を使っているはずなので、対応は容易でしょう。