結論
発生します。
理由
CSRFは、セッション管理の不備を悪用した攻撃だからです。 認証方式として、クライアント証明書を使っていても、その不備を補うことはできません。
CSRF一般の話になりますが、攻撃者は対象のウェブサイトにアクセス権限(クライアント証明書)を持っている必要は ない(正当な権限をもつユーザーにリクエストを強要しているだけ)ためです。1
実際にやってみる
準備1: クライアント証明書とサーバー証明書を発行する
サーバー証明書とクライアント証明書がそれぞれ必要になりますので、 プライベート認証局を作り、そこから発行します。
まず、認証局(ルート)を作成します。
ここで作成した、DevelopmentPurposePrivateRootCA1.crt
をブラウザのトラストストアにインストールしておきます。
$ openssl genrsa -des3 -out DevelopmentPurposePrivateRootCA1.key 2048 $ openssl req -x509 -new -nodes \ -key DevelopmentPurposePrivateRootCA1.key \ -sha256 -days 90 -out DevelopmentPurposePrivateRootCA1.crt \ -subj "/C=JP/ST=Tokyo/L=Chiyoda/O=Dummy Company/OU=Dummy Division/CN=DevelopmentPurposePrivateRootCA1"
つづいて、サーバー証明書を先ほどの認証局から発行します。
ここで発行したサーバー証明書(example.jp.crt
)と秘密鍵(private-server-rsa-key
)は、次に説明するhttpsサーバー(Nginx)の設定の時に使います。
発行先のドメイン(common name)には、example.jpを指定することとします。
# CSRを作成する $ openssl genrsa 2048 > ./private-server-rsa-key $ openssl req -new -key ./private-server-rsa-key -sha256 -out ./server-csr
# CSRをもとに、サーバー証明書を発行する $ cat << __EOF__ > ./signing_config authorityKeyIdentifier=keyid,issuer basicConstraints=CA:FALSE keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment extendedKeyUsage = serverAuth subjectAltName = @alt_names [alt_names] DNS.1 = example.jp __EOF__ $ openssl x509 -req -in ./server-csr -CA DevelopmentPurposePrivateRootCA1.crt \ -CAkey DevelopmentPurposePrivateRootCA1.key \ -CAcreateserial \ -out example.jp.crt -days 90 -sha256 -extfile ./signing_config
同じように、クライアント証明書を発行します。
発行したクライアント証明書(client.crt.p12
)をブラウザにimportしておきます。
# クライアント証明書用のCSRをつくる $ openssl genrsa 2048 > ./private-client-rsa-key $ openssl req -new -key ./private-client-rsa-key -sha256 -out ./client-csr
# クライアント証明書を発行する $ cat << __EOF__ > ./signing_config authorityKeyIdentifier=keyid,issuer basicConstraints=CA:FALSE keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment extendedKeyUsage = clientAuth __EOF__ $ openssl x509 -req -in ./client-csr \ -CA DevelopmentPurposePrivateRootCA1.crt \ -CAkey DevelopmentPurposePrivateRootCA1.key \ -CAcreateserial \ -out client.crt -days 90 -sha256 -extfile ./signing_config $ openssl pkcs12 -export -out client.crt.p12 -inkey ./private-client-rsa-key -in client.crt
証明書の準備、めんどくさいですね。
準備2: Nginxを立ち上げる
以下のような設定にてNginxでhttpsのサーバーを立ち上げます。 裏側のアプリ(CSRFあり)をPythonのFlaskで作ろうとしているので、uWSGIの設定を入れています。 example.jpとして立ち上げたので、アクセス元のブラウザがexample.jpを名前解決できるように、hostsファイルなどに設定を追加しておく必要がありますが、 割愛します。
server { server_name example.jp; listen 443; ssl on; ssl_protocols TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; ssl_certificate /path/to/example.jp.crt; ssl_certificate_key /path/to/private-server-rsa-key; ssl_verify_client on; ssl_client_certificate /path/to/DevelopmentPurposeRootCA1.crt; # Add index.php to the list if you are using PHP index index.html index.htm index.nginx-debian.html; location / { include uwsgi_params; uwsgi_pass unix:/tmp/csrfapp.sock; } }
準備3: CSRFの脆弱性があるアプリを立ち上げる
なんでもよいですが、CSRFの脆弱性があるアプリを立ち上げます。
このアプリは、buy_confirm
からbuy_complete
に遷移するときに、画面遷移の正当性を確認していないので、
CSRFの脆弱性があります。
CSRFを起こす
まず、対象のウェブサイトにログインしておきます。先ほど発行したクライアント証明書を提示します。
つづいて、ログイン状態を保持したまま、悪意のあるウェブサイトのボタンをクリックしてみます。 悪意のあるウェブサイトは例えば以下のようになっています。
<html> 当選おめでとうございます! 以下をクリックして、賞金を獲得! <form action="https://example.jp/buy_complete" method="POST"> <input type="hidden" name="amount" value=100> <input type="submit" value="アクセス"> </form> </html>
これにアクセスすると、CSRFが発生し、ユーザーの意図しない操作が実行されてしまいます。
防ぎ方
防ぎ方は、クライアント証明書があるか無いかなどは関係なく、様々なウェブサイトで説明されている通りです。 操作を完了する直前の画面で、攻撃者に知られることのない情報を埋め込み、画面遷移した際にその値が保持されているかチェックすることで、防ぐことができます。