セキュリティブログ

Web開発で見落とされがちなMatrix URIの解釈差異の脆弱性:Tomcatにおける認証バイパスやSSRFを実例と共に解説

Web開発で見落とされがちなMatrix URIの解釈差異の脆弱性:Tomcatにおける認証バイパスやSSRFを実例と共に解説

公開日:2025.10.22 更新日:2025.10.21

こんにちは。アセスメントサービス部の川根です。

Webアプリケーションのセキュリティにおいて、SQLインジェクションやクロスサイトスクリプティング(XSS)のような脆弱性は広く知られていますが、URIの解析処理やパス正規化における細かな挙動や解釈の差異に起因する脆弱性については、まだ十分に認知されていないように感じます。URIは認証や認可と密接に関わる重要な要素である一方、その仕様の複雑さや実装の多様性により、開発者の意図しない挙動が原因で脆弱性が生じることがあります。

本記事では、あまり一般的ではないURI構文の1つであるMatrix URIの解釈の差異によって引き起こされる脆弱性について詳しく解説します。

Matrix URIとは?

Matrix URIは、URIのパス部分にセミコロン(;)を使ってパラメータを記述する形式です。通常のクエリパラメータが「?key=value」の形式でURIの末尾に付くのに対し、Matrix URIではパスの各セグメントに「;key=value」の形式でパラメータを埋め込みます。このセミコロン区切りのパラメータはMatrixパラメータと呼ばれており、RFC3986のセクション3.3では、URI構文の中での予約文字としてセミコロン(;)とイコール(=)が規定されています。Matrixパラメータは次のようにパスのセグメントの中で使うことができます。

https://example.com/product;id=123/detail;lang=ja
https://example.com/users;id=123;role=admin/profile;lang=ja

ただし、Matrix URI(Matrixパラメータ)は広く使われているわけではなく、正式な標準仕様としては定着していません。そのため、サーバやフレームワークによって解釈が大きく異なる場合があり、この解釈の差異がセキュリティ上の脆弱性を引き起こす恐れがあります。あるサーバではセミコロン区切りのパラメータを認識して処理する一方で、別のサーバではそれを単にパスの一部として扱ったり、無視したりするためです。その結果、認証やアクセス制御のバイパスなどの脆弱性につながることがあります。

Matrix URIの解釈とアクセス制御のバイパス

TomcatのようなJavaサーブレットコンテナにおいては、アプリケーションのコードとTomcat本体でのリクエストパス解釈に差異があることが原因で、アクセス制御が意図せず破られてしまうケースがあります。

たとえば、あるWebアプリケーションで、ログインしていないユーザが管理機能にアクセスできないよう、「/admin/」以下のパスに対してアクセス制御を設けているとします。アクセス制御の実装には、Tomcatのフィルタ機能を用いて必要に応じて認証チェックを行う設計になっています。このフィルタでは、クエリ文字列を除いたリクエストされたパス全体を返すgetRequestURI()を使ってリクエストパスを取得し、「/admin/」で始まる場合にのみ認証されているかを確認しています。

public class AuthFilter implements Filter {
  @Override
  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
    throws IOException, ServletException {
      HttpServletRequest request = (HttpServletRequest) req;
      HttpServletResponse response = (HttpServletResponse) res;
      String uri = request.getRequestURI();

      if (uri.startsWith("/admin/")) {
        if (!isAuthenticated(request)) {
          response.sendError(HttpServletResponse.SC_FORBIDDEN);
          return;
        }
      }

      chain.doFilter(req, res);
    }
}

このコードは一見すると問題がないように見えますが、Matrix URIの解釈差異によるアクセス制御バイパスの脆弱性が潜んでいます。たとえば、次のようなリクエストが送られた場合を考えてみましょう。

GET /admin;key=value/settings HTTP/1.1
Host: localhost:8080

このリクエストでは、「/admin」のパスにセミコロン区切りで「key=value」というパラメータが付いており、続けて「/settings」が指定されています。TomcatはMatrix URIを解釈するため、このリクエストのルーティング時に「;key=value」の部分を無視します。つまり、「/admin/settings」にマッピングされたServletで処理を実行することになります。ところが、getRequestURI()はセミコロン区切りのパラメータも含む「/admin;key=value/settings」を返すため、Tomcatとアプリケーションコードとの間でパスの解釈に差異が生じます。「uri.startsWith(“/admin/”)」はfalseになるため、アクセス制御のロジックが適用されなくなってしまうのです。

このような解釈の差異による脆弱性は、軽微な実装上の問題のように見えますが、攻撃者にとってはアクセス制御を簡単に回避するための強力な手段となります。特に、Matrix URIのように一般的にあまり意識されない仕様を悪用する攻撃は、セキュリティレビューの観点からも見落とされやすく、実運用中のシステムであっても気づかれないまま残存しているケースがあります。

Cisco Identity Services Engineで発見した脆弱性

実際にMatrix URIの解釈の差異による脆弱性が見つかった例として、Cisco Identity Services Engine(ISE)のCSCwj33447が挙げられます。これは、筆者が以前にCisco ISEを調査していた際に発見した任意ファイルアップロードの脆弱性で、Ciscoにより現在は修正されています。

Cisco ISEはネットワークのアクセス制御を行う製品で、ネットワークに一時的または制限付きでアクセスさせたいユーザのために、Webポータルを通じてアクセスを提供するゲストポータル機能があります。Cisco ISEの管理者ユーザは、ゲストポータルの見た目(UI)を自由にカスタマイズできるようになっており、デフォルトで用意されているテーマを選択する他に、CSSファイルをアップロードして適用することもできます。

ここでアップロードされるファイルの検証はサーバ側で行われていないため、任意の拡張子のファイルをアップロードすることができます。以下は、alert(1)を実行するスクリプトを含むHTMLファイルをアップロードするリクエストの例です。

POST /admin/guestAccessSettingsAction.do HTTP/1.1
Host: cisco ise
=== 省略 ===
Connection: keep-alive

------WebKitFormBoundaryXzcKvZOKJBXsL5SL
Content-Disposition: form-data; name="customCssFileUpload"; filename="test.html"
Content-Type: text/css

<script>alert(1)</script>
------WebKitFormBoundaryXzcKvZOKJBXsL5SL--

アップロードしたファイルは/opt/CSCOcpm/appsrv/apache-tomcat/webapps/previewportal/css/に保存され、Webブラウザからもアクセスすることができます。

これは典型的な任意ファイルアップロードの脆弱性です。Cisco ISEがWebコンテナとしてTomcatを使用していることを考えると、JSPファイルをアップロードすることで任意コード実行が行えそうに思えますが、実際にアップロードしてアクセスしたところ真っ白な画面が表示されるだけで、コードの実行は行われませんでした。

しばらく調査するとアップロードしたJSPへのアクセスが、Tomcatのフィルタによって阻害されていることがわかりました。Cisco ISEでは、JSPファイルに直接アクセスできないようにするためJspFilterが定義されていました。これにより、任意のファイルアップロードのような脆弱性が見つかった場合でも、JSPファイルへアクセスできなくなるため、コードを実行することができなくなります。

JspFilterを見てみると、httpServletRequest.getRequestURI()でリクエストのパスを取得し、アクセスしても問題ないパスであるかを検証しており、アクセスが拒否されるとsendRedirect()によってリダイレクトされます。リダイレクト先を指定しているthis.redirectURIがnullになっているため、リダイレクトが行われずに真っ白な画面が表示されたようです。

public class JspFilter
implements Filter
{
  private Set<String> filters = new HashSet<>();
  private Set<String> jspExceptions = new HashSet<>();
  private String redirectURI = null;
  public void destroy() {}
        
  public void doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse, FilterChain paramFilterChain) throws IOException, ServletException {
    HttpServletRequest httpServletRequest = (HttpServletRequest)paramServletRequest;
    HttpServletResponse httpServletResponse = (HttpServletResponse)paramServletResponse;
    String str = httpServletRequest.getRequestURI();

    if (isRestricted(str))
    {
      if (!isAccessException(str))
      {
        if (!isLoginSession(httpServletRequest)) {
          httpServletResponse.sendRedirect(this.redirectURI);
          System.out.println("Request access denied for URI " + str + " from host " + httpServletRequest.getRemoteHost());
          return;
        } 
      }
    }
    paramFilterChain.doFilter(paramServletRequest, paramServletResponse);
  }
}

isRestricted()は引数のリクエストパスの末尾が「.jsp, .CAB, .jar, .jspx, webconsole.html」であるかを検証し、該当した場合はアクセスを許可しないパスとして判断します。アップロードした「test.jsp」は末尾が「.jsp」であるため、アクセスが拒否されて実行されなかったようです。

private boolean isRestricted(String paramString) {
  for (String str : this.filters) {
    if (paramString.endsWith(str)) {
      return true;
    }
  }
  return false;
}

public void init(FilterConfig paramFilterConfig) throws ServletException {
  this.filters.add(".jsp");
  String str1 = paramFilterConfig.getInitParameter("com.cisco.ise.filters.filterList");

  if (str1 != null && str1.length() > 0) {
    String[] arrayOfString = str1.split(",");
    for (String str : arrayOfString) {
      if (str.length() > 0) {
        this.filters.add(str);
      }
    }
  }

この実装では、リクエストパスをgetRequestURI()で取得して検証していますが、Tomcatはセミコロン以降の部分を無視してルーティングを行うため、アプリケーションコードとTomcatとの間に解釈の差異が生じます。「/test.jsp;」のように末尾にセミコロンを付け足してアクセスすると、isRestricted()はこれをJSP拡張子と認識せずに通しますが、Tomcatはセミコロンを無視して「/test.jsp」を実行します。実際にセミコロンを末尾に追加してアクセスすると、JspFilterを回避してコードを実行できていることがわかります。

Matrix URIを考慮しないことで起こるSSRF

先ほどの例では、リクエストパスの解釈差異によって生じるアクセス制御バイパスの例について紹介しましたが、そのほかにもMatrix URIを考慮しないことで起こる脆弱性があります。次のサンプルコードを見てみましょう。

public class ApiProxyServlet extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException {
      String uri = request.getRequestURI(); 
      String targetUrl = "http://api.example.com" + uri;

      HttpURLConnection conn = (HttpURLConnection) new URL(targetUrl).openConnection();
      conn.setRequestMethod("GET");

      response.setContentType(conn.getContentType());
      try (InputStream in = conn.getInputStream(); OutputStream out = response.getOutputStream()) {
        in.transferTo(out);
      }
  }
}

このコードは、HTTPリクエストのパス部分をgetRequestURI()で取得し、そのURIをもとにAPIサーバ(http://api.example.com)へリクエストを転送するサーブレットの実装例です。リクエストの転送先のURLは、次のように静的なURLとリクエストパスを連結して構築されています。

String uri = request.getRequestURI(); 
String targetUrl = "http://api.example.com" + uri;

これは一見すると、転送先のホストが「api.example.com」に固定されているため、その他の任意のホストへアクセスが行われることはなく、安全な実装のように見えます。しかし、ここで使用されているgetRequestURI()はセミコロンを含んだリクエストパスをそのまま返します。リクエストパスは「/」から始まるという先入観があるかもしれませんが、実際にはそうではありません。次のように「/」の前にセミコロン区切りのパラメータが埋め込まれたリクエストが送信されるとどうなるでしょうか。

GET ;@internal.example.com/api/resource HTTP/1.1
Host: appserver:8080

リクエストの転送先は次のようになります。URLに@が含まれる場合は、@の後が送信先のホストとして扱われるため、この例では「internal.example.com」にリクエストが送信されてしまいます。

http://api.example.com;@internal.example.com/api/resource

このように、転送先のホストを固定しているつもりでも、Matrix URIを考慮しないままリクエストパスを信頼すると、意図しないホストへリクエストが飛んでしまい、SSRF(Server-Side Request Forgery)の脆弱性につながることがあります。

安全に実装するためには

ここまでMatrix URIの解釈差異に起因する脆弱性について紹介しましたが、どのように対策すればこのような問題を防げるのでしょうか。多くのWebアプリケーションではMatrix URIを解釈してパラメータとして受け取っていないため、セミコロンを含むURIを受け付けないようにすることで、予期せぬ解釈差異による脆弱性を未然に防ぐことができます。実装例として、Tomcatの場合はサーブレットやフィルタで以下のようなチェックを追加するのが効果的です。

if (request.getRequestURI().contains(";")) {
    response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Semicolon not allowed in URI");
    return;
}

また、ApacheやNginxなどのリバースプロキシやWAFで、セミコロンを含むURIをアプリケーションサーバに転送しないような設定を行うことも対策として有効です。以下は、リバースプロキシとしてApacheを使用する場合に、セミコロンを含むURLに対して403レスポンスを返す設定例です。

RewriteEngine On
RewriteCond %{REQUEST_URI} \;
RewriteRule .* - [F]

さいごに

本記事では、あまり一般的ではないMatrix URIという特殊なURI構文の解釈差異がもたらす脆弱性について詳しく解説しました。URIはアクセス制御や認証に直結する重要な要素であり、誤った取り扱いは重大なリスクにつながることがあります。

今回取り上げたMatrix URIのように、あまり一般的でない仕様が引き起こす開発者の意図しない挙動は、セキュリティレビューでも注意が向けられにくく、Cisco ISEでの事例のように実運用されている製品で発見されることも少なくありません。このような見落とされがちな脆弱性は、攻撃者に格好の攻撃手段となり得るため、こうした特殊な仕様についても十分な理解と対策が求められます。

弊社では、豊富な診断実績と技術的知見に基づき、お客様のシステムやご予算、運用状況に応じた最適な診断プランをご提案しています。Webアプリケーション脆弱性診断をご検討の際は、ぜひお気軽にお問い合わせください。

Webアプリケーション脆弱性診断

シェア
X FaceBook
セキュリティ診断のことなら
お気軽にご相談ください
セキュリティ診断で発見された脆弱性と、具体的な内容・再現方法・リスク・対策方法を報告したレポートのサンプルをご覧いただけます。

関連記事

経験豊富なエンジニアが
セキュリティの不安を解消します

Webサービスやアプリにおけるセキュリティ上の問題点を解消し、
収益の最大化を実現する相談役としてぜひお気軽にご連絡ください。

疑問点やお見積もり依頼はこちらから

お見積もり・お問い合わせ

セキュリティ診断サービスについてのご紹介

資料ダウンロード