Japan Computer Emergency Response Team Coordination Center 電子署名者 Japan Computer Emergency Response Team Coordination Center DN c=jp, st=tokyo, l=chiyoda-ku, email=office@jpcert.or.jp, o=japan Computer Emergency Response Team Coordination Center, cn=japan Computer Emergency Response Team Coordination Center 日付 2013.09.30 161856 +09'00' Javaアプリケーション脆弱性事例調査資料 について この資料は Javaプログラマである皆様に 脆弱性を身 近な問題として感じてもらい セキュアコーディングの 重要性を認識していただくことを目指して作成していま す Javaセキュアコーディングスタンダード CERT/Oracle版 と合わせて セキュアコーディングに 関する理解を深めるためにご利用ください JPCERTコーディネーションセンター セキュアコーディングプロジェクト secure-coding@jpcert.or.jp 1
Apache Tomcat における クロスサイトリクエストフォージェリ (CSRF) 保 護 メカニズム 回 避 の 脆 弱 性 CVE-2012-4431 JVNDB-2012-005750 2
Apache Tomcatとは Java Servlet や JavaServer Pages (JSP) を 実 行 するため のサーブレットコンテナ(サーブレットエンジン) CSRF 対 策 のために トークンを 使 ったリクエスト フォームの 検 証 機 能 が 実 装 されている 3
脆 弱 性 の 概 要 Apache Tomcatには クロスサイトリクエストフォー ジェリ 対 策 をバイパスできる 脆 弱 性 が 存 在 する 脆 弱 性 を 悪 用 されることで 被 害 者 が 意 図 しない 操 作 を 実 行 させられる 可 能 性 がある Apache Tomcat 上 で 展 開 されるWebアプリケーションの 機 能 を 不 正 に 実 行 させることが 可 能 となる 被 害 者 バイパス!! 攻 撃 成 功!! 4
通 常 の 処 理 フロー Tomcat Webアプリケーションマネージャのサイト 停 止 機 能 における 処 理 フ ローを 解 説 する サイト 停 止 機 能 を 実 行 する 際 のApache Tomcatの 処 理 フロー 1 Tomcat Managerにクライアント(サイト 管 理 者 )がBasic 認 証 でログイン する 2 管 理 者 画 面 にアクセス 時 に アプリケーションはトークンを 発 行 しForm 要 素 に 埋 め 込 む さらにセッション 変 数 にトークンを 格 納 する 3 クライアントがサイト 停 止 機 能 を 実 行 しリクエストが 送 信 される 4 アプリケーションは 送 信 されてきたトークンとセッション 変 数 に 格 納 さ れているトークンが 同 一 かを 検 証 する 5 アプリケーションがリクエストを 受 信 し 処 理 を 実 行 する 6 結 果 を 含 むレスポンスがクライアント(サイト 管 理 者 )へ 送 信 される 5
サイト 停 止 機 能 実 行 時 アプリケーションはリクエストを 受 信 後 CSRFトークンを 検 証 を 実 施 し 正 規 のトークンが 送 信 されてきたときのみに 機 能 を 実 行 する セッション オブジェク ト トークン 検 証!! サイトの 停 止 HTTPリクエスト トークン GET /manager/html/stop?path=/&org.apache.catalina.filters.csrf_nonce=1a740989db7347fb6fe1ff02ec6a2c59 HTTP/1.1 Host www.example.com Cookie JSESSIONID=F11C4A2BDEE2759214A79D46F69B283E Authorization Basic Og== CSRFトークンは 付 与 され 検 証 もされている 6
dofilterメソッドによるトークンの 検 証 トークンの 検 証 はCsrfPreventionFilterクラスの dofilter メソッドで 実 行 される HTTPリクエスト トークンの 検 証 処 理 CsrfPreventionFilter.java public class CsrfPreventionFilter public void dofilter( 7
dofilterメソッドによるトークンの 検 証 セッションからトークンの 取 得 CsrfPreventionFilter.java public class CsrfPreventionFilter extends FilterBase { public void dofilter(servletrequest request, ServletResponse response,filterchain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; @SuppressWarnings("unchecked") LruCache<String> noncecache = (LruCache<String>) req.getsession(true).getattribute( Constants.CSRF_NONCE_SESSION_ATTR_NAME); if (!skipnoncecheck) { 文 字 列 "org.apache.catalina.filters.csrf_nonce" String previousnonce = req.getparameter(constants.csrf_nonce_request_param);...(b) リクエストのセッションからセッ ション 変 数 を 取 得 し 変 数 noncecacheに 格 納 する セッション オブジェクト トークン noncecache トークン if (noncecache!= null &&!noncecache.contains(previousnonce)) {...(C) res.senderror(httpservletresponse.sc_forbidden); //エラー 処 理 return; 8
dofilterメソッドによるトークンの 検 証 リクエストからトークンの 取 得 CsrfPreventionFilter.java public class CsrfPreventionFilter extends FilterBase { public void dofilter(servletrequest request, ServletResponse response,filterchain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; @SuppressWarnings("unchecked") LruCache<String> noncecache = (LruCache<String>) req.getsession(true).getattribute( Constants.CSRF_NONCE_SESSION_ATTR_NAME); リクエストのパラメータから 変 数 を 取 得 し 変 数 previousnonceに 格 納 する HTTPリクエスト GET /manager/html/stop?path=/&org.apache.catalina.filters.csr F_NONCE=1A740989DB7347FB6FE1FF02EC6A2C59 HTTP/1.1 Host www.example.com Cookie JSESSIONID=F11C4A2BDEE2759214A79D46F69B283E Authorization Basic Og== if (!skipnoncecheck) { String previousnonce = req.getparameter(constants.csrf_nonce_request_param); previousnonce トークン if (noncecache!= null &&!noncecache.contains(previousnonce)) { res.senderror(httpservletresponse.sc_forbidden); //エラー 処 理 return; 文 字 列 "org.apache.catalina.filters.csrf_nonce" 9
dofilterメソッドによるトークンの 取 得 トークンの 比 較 CsrfPreventionFilter.java トークンの 検 証 処 理 を 実 行 する public class CsrfPreventionFilter extends FilterBase 下 記 の2つの { 条 件 が 両 方 とも 成 り 立 つ 場 合 は エラーとして 処 理 される public void dofilter(servletrequest request, ServletResponse 1 変 数 noncecacheの response,filterchain 値 に 変 数 chain) throws IOException, ServletException { 2 previousnonceが 含 まれていない 変 数 noncecacheがnullでない HttpServletRequest req = (HttpServletRequest) request; @SuppressWarnings("unchecked") noncecache LruCache<String> noncecache = (LruCache<String>) req.getsession(true).getattribute( トークン Constants.CSRF_NONCE_SESSION_ATTR_NAME); 条 件 1nonceCacheにpreviousNonce の 値 が 含 まれているか? previousnonc e トークン 条 件 2nonceCacheはnullでないか? if (!skipnoncecheck) { String previousnonce = req.getparameter(constants.csrf_nonce_request_param); noncecache トークン Is not null? if (noncecache!= null &&!noncecache.contains(previousnonce)) { res.senderror(httpservletresponse.sc_forbidden); //エラー 処 理 return; // 正 常 処 理 条 件 1 2がともに 成 立 しないときに 正 常 処 理 となる 10
攻 撃 コード ( 正 常 なリクエストとの 比 較 ) 通 常 のリクエスト GET トークン /manager/html/stop?path=/&org.apache.catalina.filters.csrf_nonce=1a74098 9DB7347FB6FE1FF02EC6A2C59 HTTP/1.1 Host www.example.com セッション Cookie JSESSIONID=F11C4A2BDEE2759214A79D46F69B283E Authorization Basic Og== 攻 撃 コード GET /manager/html/stop?path=/ HTTP/1.1 Host www.example.com Authorization Basic Og== ついていない 攻 撃 コードのポイント リクエストに 含 まれるトークン (GETパラメータの ord.apache.catalina.filters.csrf_nonce) とセッション(Cookieヘッダ)が 削 除 さ れている 11
攻 撃 コード 実 行 時 の 処 理 フロー サイト 停 止 機 能 を 実 行 する 際 のApache Tomcatの 処 理 フロー 1 Tomcat Managerにクライアント(サイト 管 理 者 )がBasic 認 証 でログイン する 2 管 理 者 画 面 にアクセス 時 に アプリケーションはトークンを 発 行 しForm 要 素 に 埋 め 込 む さらにセッション 変 数 にトークンを 格 納 する 3 クライアントがサイト 停 止 機 能 を 実 行 しリクエストが 送 信 される 4 アプリケーションは 送 信 されてきたトークンとセッション 変 数 に 格 納 さ れているトークンが 同 一 かを 検 証 する 5 アプリケーションがリクエストを 受 信 し 処 理 を 実 行 する 6 結 果 を 含 むレスポンスがクライアント(サイト 管 理 者 )へ 送 信 される 4の 検 証 処 理 が 不 十 分 だった!! 12
4 送 信 されてきたトークンとセッション 変 数 のトークンの 検 証 CsrfPreventionFilter.java public class CsrfPreventionFilter extends FilterBase { public void dofilter(servletrequest request, ServletResponse response,filterchain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; @SuppressWarnings("unchecked") LruCache<String> noncecache = (LruCache<String>) req.getsession(true).getattribute( Constants.CSRF_NONCE_SESSION_ATTR_NAME); リクエストのセッションからセッ ション 変 数 を 取 得 し 変 数 noncecacheに 格 納 する セッション オブジェクト トークン noncecache トークン if (!skipnoncecheck) { 文 字 列 "org.apache.catalina.filters.csrf_nonce" String previousnonce = req.getparameter(constants.csrf_nonce_request_param);...(b) noncecache null if (noncecache!= null &&!noncecache.contains(previousnonce)) {...(C) res.senderror(httpservletresponse.sc_forbidden); //エラー 処 理 return; 攻 撃 コードではセッ ションが 削 除 されて おり セッションオ ブジェクトが 存 在 し ないため nullが 格 納 される 13
4 送 信 されてきたトークンとセッション 変 数 のトークンの 検 証 CsrfPreventionFilter.java public class CsrfPreventionFilter extends FilterBase { public void dofilter(servletrequest request, ServletResponse response,filterchain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; @SuppressWarnings("unchecked") LruCache<String> noncecache = (LruCache<String>) req.getsession(true).getattribute( Constants.CSRF_NONCE_SESSION_ATTR_NAME); if (!skipnoncecheck) { String previousnonce = req.getparameter(constants.csrf_nonce_request_param); if (noncecache!= null &&!noncecache.contains(previousnonce)) {...(C) res.senderror(httpservletresponse.sc_forbidden); //エラー 処 理 return; リクエストのパラメータから 変 数 を 取 得 し 変 数 previousnonceに 格 納 する HTTPリクエスト GET /manager/html/stop?path=/ HTTP/1.1 Host www.example.com Authorization Basic Og== previousnonce null 攻 撃 コードにはトーク ンが 含 まれていないの でnullが 格 納 される 文 字 列 "org.apache.catalina.filters.csrf_nonce" 14
4 送 信 されてきたトークンとセッション 変 数 のトークンの 検 証 CsrfPreventionFilter.java public class CsrfPreventionFilter extends FilterBase { public void dofilter(servletrequest request, ServletResponse response,filterchain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; @SuppressWarnings("unchecked") 1 LruCache<String> noncecache = (LruCache<String>) req.getsession(true).getattribute( 2 Constants.CSRF_NONCE_SESSION_ATTR_NAME); if (!skipnoncecheck) { String previousnonce = req.getparameter(constants.csrf_nonce_request_param); if (noncecache!= null &&!noncecache.contains(previousnonce)) { res.senderror(httpservletresponse.sc_forbidden); //エラー 処 理 return; // 正 常 処 理 トークンの 検 証 処 理 を 実 行 する 下 記 の2つの 条 件 が 両 方 とも 成 り 立 つ 場 合 は エラーとして 処 理 される 変 数 noncecacheの 値 に 変 数 previousnonceが 含 まれていない 変 数 noncecacheがnullでない 15
4 送 信 されてきたトークンとセッション 変 数 のトークンの 検 証 CsrfPreventionFilter.java 1は 正 常 なトークンの 検 証 処 理 のため 問 題 ないが 問 題 は2 public class CsrfPreventionFilter extends FilterBase { の 条 件 変 数 noncecacheがnull = セッションがそのものが 存 在 public void dofilter(servletrequest request, ServletResponse response,filterchain chain) しない であり セッション throws IOException, 自 体 を ServletException 削 除 する(Cookieヘッダを { 削 除 す る)ことで 変 数 noncecacheがnullとなり このトークンの 検 トークンの 検 証 処 理 を 実 行 する 証 HttpServletRequest 処 理 をバイパスすることができる!! req = (HttpServletRequest) request; 下 記 の2つの 条 件 が 両 方 とも 成 り 立 つ 場 合 は エラーとして 処 理 される @SuppressWarnings("unchecked") 1 変 数 noncecacheの 値 に 変 数 LruCache<String> noncecache = previousnonceが 含 まれていない (LruCache<String>) req.getsession(true).getattribute( 2 変 数 noncecacheがnullでない Constants.CSRF_NONCE_SESSION_ATTR_NAME); if (!skipnoncecheck) { String previousnonce = req.getparameter(constants.csrf_nonce_request_param); if (noncecache!= null &&!noncecache.contains(previousnonce)) { res.senderror(httpservletresponse.sc_forbidden); //エラー 処 理 return; // 正 常 処 理 16
セッションを 削 除 したリクエストの 処 理 通 常 セッションを 削 除 (Cookieヘッダを 削 除 )してしまうと Webアプリケー ションはユーザーを 認 識 できなくなり 認 証 エラーが 発 生 する 通 常 の Webサイト 一 般 のWebアプリ セッション(Cookie)なしの HTTPリクエスト しかし Apache TomcatのTomcat WebアプリケーションマネージャはBasic 認 証 を 使 用 しており 認 証 にセッション(Cookieヘッダ)を 使 用 していない Basic 認 証 ではAuthorizationヘッダを 使 用 する セッション(Cookie)なしの HTTPリクエスト CSRF 対 策 であるトークン 検 証 処 理 にてセッション(Cookieヘッダ)が 無 い 場 合 を 想 定 していなかったため 認 証 エラーが 発 生 せず 機 能 が 実 行 されてしまう!! 17
今 回 のアプリケーションにおける 具 体 的 な 問 題 点 セッションが 存 在 しないケース(Cookie 以 外 によるセッ ション 管 理 )を 想 定 していなかった セッションが 存 在 しない 場 合 は CSRF 対 策 をパスしてし まっていた 問 題 点 に 対 してどうすべきだったか アプリケーションの 仕 様 ( 今 回 の 場 合 はセッションの 管 理 方 式 )を 確 認 し 適 切 なCSRF 対 策 を 選 択 する 必 要 があっ た 18
修 正 版 コード 脆 弱 性 はバージョン7.0.32 6.0.36にて 修 正 が 適 用 されている サイト 停 止 機 能 を 実 行 する 際 のApache Tomcatの 処 理 フロー 1 Tomcat Managerにクライアント(サイト 管 理 者 )がBasic 認 証 でログイ ンする 2 管 理 者 画 面 にアクセス 時 に アプリケーションはトークンを 発 行 しForm 要 素 に 埋 め 込 む さらにセッション 変 数 にトークンを 格 納 する 3 クライアントがサイト 停 止 機 能 を 実 行 しリクエストが 送 信 される 4 アプリケーションは 送 信 されてきたトークンとセッション 変 数 に 格 納 さ れているトークンが 同 一 かを 検 証 する 5 アプリケーションがリクエストを 受 信 し 処 理 を 実 行 する 6 結 果 を 含 むレスポンスがクライアント(サイト 管 理 者 )へ 送 信 される 4の 処 理 におけるコードが 修 正 されている 19
修 正 版 コード public class CsrfPreventionFilter extends FilterBase { public void dofilter(servletrequest request, ServletResponse response,filterchain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpSession session = req.getsession(false); @SuppressWarnings("unchecked") LruCache<String> noncecache = (session == null)? null (LruCache<String>) session.getattribute( Constants.CSRF_NONCE_SESSION_ATTR_NAME); if (!skipnoncecheck) { String previousnonce = req.getparameter(constants.csrf_nonce_request_param); CsrfPreventionFilter.java if (noncecache == null previousnonce == null!noncecache.contains(previousnonce)) { res.senderror(denystatus); return; // 正 常 処 理 セッションがnullである 場 合 またはリクエス トにトークンが 含 まれ ていない 場 合 はエラー として 処 理 する 20
参 考 文 献 OWASP CSRFGuard Project https//www.owasp.org/index.php/categoryowasp_csrfguard_project IPA ISEC セキュア プログラミング 講 座 Webアプリケーション 編 第 4 章 セッション 対 策 リクエスト 強 要 (CSRF) 対 策 https//www.ipa.go.jp/security/awareness/vendor/programmingv2/contents/301.html 安 全 なウェブサイトの 作 り 方 IPA https//www.ipa.go.jp/security/vuln/websecurity.html 21
著 作 権 引 用 や 二 次 利 用 について 本 資 料 の 著 作 権 はJPCERT/CCに 帰 属 します 本 資 料 あるいはその 一 部 を 引 用 転 載 再 配 布 する 際 は 引 用 元 名 資 料 名 および URL の 明 示 を お 願 いします 記 載 例 引 用 元 一 般 社 団 法 人 JPCERTコーディネーションセンター Java アプリケーション 脆 弱 性 事 例 解 説 資 料 Apache Tomcat における CSRF 保 護 メカニズム 回 避 の 脆 弱 性 https//www.jpcert.or.jp/securecoding/2012/no.09_apache_tomcat.pdf 本 資 料 を 引 用 転 載 再 配 布 をする 際 は 引 用 先 文 書 時 期 内 容 等 の 情 報 を JPCERT コーディ ネーションセンター 広 報 (office@jpcert.or.jp)までメールにてお 知 らせください なお この 連 絡 により 取 得 した 個 人 情 報 は 別 途 定 めるJPCERT コーディネーションセンターの プライバシーポ リシー に 則 って 取 り 扱 います 本 資 料 の 利 用 方 法 等 に 関 するお 問 い 合 わせ JPCERTコーディネーションセンター 広 報 担 当 E-mailoffice@jpcert.or.jp 本 資 料 の 技 術 的 な 内 容 に 関 するお 問 い 合 わせ JPCERTコーディネーションセンター セキュアコーディング 担 当 E-mailsecure-coding@jpcert.or.jp 22