続編(実装編)
Rust WebフレームワークのACTIXで使う安全なセッション管理機構を考えてみる#2
はじめに
こちらの記事で紹介したACTIXですが、セッション周りが貧弱なのでそれなりに堅牢なセッション管理機構を作りたいと思う。
ちょっとそのまま使うとヤバいかも
ACTIXのセッション管理機構をいろいろ使ってみた
#![allow(unused_variables)]
extern crate actix;
extern crate actix_web;
extern crate openssl;
use actix_web::*;
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
use actix_web::middleware::identity::RequestIdentity;
use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
fn index(req: HttpRequest) -> Result<String> {
// access request identity
if let Some(id) = req.identity() {
Ok(format!("Welcome! {}", id))
} else {
Ok("Welcome Anonymous!".to_owned())
}
}
fn login(mut req: HttpRequest) -> HttpResponse {
req.remember("NeoUser".to_owned()); // <- ログインIDを記憶する
HttpResponse::Found().header("location", "/").finish()
}
fn logout(mut req: HttpRequest) -> HttpResponse {
req.forget(); // <- ログインIDを削除(ただし、ログイン情報はCookieの値に署名と並べて保持されているので、Cookieの再利用でログイン状態を再現できる)
HttpResponse::Found().header("location", "/").finish()
}
fn main() {
let sys = actix::System::new("http2_api-test");
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
builder.set_certificate_chain_file("cert.pem").unwrap();
server::new(|| {
App::new()
.middleware(IdentityService::new(
// <- create identity middleware
CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend
.name("auth-cookie")
.secure(false),
))
.route("/", http::Method::GET, index)
.resource("/login", |r|{r.route().filter(pred::Any(pred::Get()).or(pred::Post())).f(login);})
.route("/logout",http::Method::GET,logout)
// }).bind_ssl("127.0.0.1:8443", builder).unwrap().start();
}).bind("127.0.0.1:8443").unwrap().start();
println!("Started http server: 127.0.0.1:8443");
let _ = sys.run();
}
多分同じソースならこれからやる手順で Welcome! User1って表示される。
NoeUserしかloginするときに定義してないのにね不思議だね
値は毎回固定ではないが、おそらく、JWTみたいに内部で署名していて、その署名と一致すればOKみたいなことをしているのだろう。
控えめに言って実装が微妙。なので使用者側が拡張する必要がある。
※内部をひも解くと、Cookieにそのまま値が入っているので不思議でも何でもないが、
一般的なWebサービスでは出来ないようになっている。理由は後述。
Cookie authの何がやばいのか
以下のユースケースでシステムの不正利用が起こる可能性がある。
1.ユーザ登録(User1で登録)
2.ログインセッション取得→サーバから正規に取得するので署名も正規のもの
→これを使ってみると↑のソースでUser1でログインしたことになる: Cookie: auth-cookie=b9zsU12yhqxwpWrGK9R8fb/BR2Qa3ihXg8AFKhR4gN16
3.ユーザ削除(本来は2で取ったCookieは使えなくなるはず)
4.2で取得したログインセッションIDが使えるのでユーザを削除してもログイン状態を維持できてしまう→上記ソースでは、NeoUserしかいないが、User1でログインされてしまう。
フレームワーク側で実装がちゃんとされずに、セッション管理機構!とかCookieAuth!
みたいに言っちゃってるので勘違いしてしまった人が間違った方法で使わないように
とりあえず回避策の概念を書く。
- Cookieに入れる認証情報は一時的な乱数のみ。可能であれば一時的に使用する乱数はCSPRNGを使用して生成する。
- 1で生成した乱数を内部で実装したセッション変数(DBまたはファイル)と紐づける。(古い乱数は使用できないように紐づけを解除(=破棄)する)
- 一定時間後1で生成した乱数を破棄または、再生成して2と紐づける
- 明示的なログアウト時には1で生成した乱数を破棄する。また2も同様に破棄する
多分こんな感じでやれば、CookieをARPキャッシュポイズニングとか、XSSで横取りされてもセッションの期限が一定で切れるから、まぁ、比較的安全には作れるかな。
Rustで作りたい思いが強いので、ここまで書いたけど
ぶっちゃけPHPとかRoRで書いたほうがセッション周りは堅牢かもね。
とはいえ、不正ログインについてはCookieに入ってる情報をそのまま使わなきゃいいだけなのでDBに、ちゃんとユーザはいるのか?とか確認するだけでもいいはず。
ただそれだと、セッションキーの期限が永続的なので、
一時的な乱数に紐づけた方がもっと安全になる。