
CVE-2024-27198の詳細分析:JetBrains TeamCityにおける認証バイパスがリモートコード実行につながる、コードレベルの説明とエクスプロイトデモを含む
TeamCityは、継続的インテグレーションおよびデプロイメントサーバであり、ビルド問題に関する継続的な単体テスト、コード品質分析、早期レポートを提供します。
この脆弱性は、攻撃者が認証されていない任意のエンドポイントにアクセスできるようにするライブラリに存在します。
脆弱なコードは、directory to the lib内のweb-openapi.jarライブラリにあります。
クラスjetbrains.buildServer.controllers.BaseControllerがリクエストとレスポンスの処理を担当しますが、実装が不適切です。コードを見てみましょう:
public abstract class BaseController extends AbstractController {
//////
public final ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
ModelAndView modelAndView = this.doHandle(request, response);
if (modelAndView != null) {
if (modelAndView.getView() instanceof RedirectView) {
modelAndView.getModel().clear();
} else {
this.updateViewIfRequestHasJspParameter(request, modelAndView);
}
}
ModelAndViewメソッドの主な目的は、リクエストされたページをUIにレンダリングすることです。ここからバグが始まります。リクエストがリダイレクトされていない場合、updateViewIfRequestHasJspParameterが呼び出されることに注意してください。脆弱性の根本原因を見つけるためには、さらに深く調査する必要があります。
private void updateViewIfRequestHasJspParameter(@NotNull HttpServletRequest request, @NotNull ModelAndView modelAndView) {
boolean isControllerRequestWithViewName = modelAndView.getViewName() != null && !request.getServletPath().endsWith(".jsp");
String jspFromRequest = this.getJspFromRequest(request);
if (isControllerRequestWithViewName && StringUtil.isNotEmpty(jspFromRequest) && !modelAndView.getViewName().equals(jspFromRequest)) {
modelAndView.setViewName(jspFromRequest);
}
}
このメソッドは、特定の条件を満たした場合にModelAndViewオブジェクトのビュー名を更新するために使用されます。isControllerRequestWithViewNameは、modelAndViewオブジェクトに名前があり、URLのパスが.jspで終わらない場合にTrueになります。例えば、条件を満たすURLはhttp://localhost:8111/random_stringであり、無効なものはhttp://localhost:8111/admin/admin.htmlやhttp://localhost:8111/admin.jspです。前述の通り、リダイレクトを引き起こすもの(通常は認証が必要なページ)にアクセスしてはいけません。プログラムはその後、jspFromRequestという変数にgetJspFromRequest()メソッドの呼び出し結果を代入します。次にそのメソッドに移り、残りのコードを後で説明します。 if文があり、isControllerRequestWithViewNameがtrueで、getJspFromRequest()メソッドの呼び出し結果が空でなく、かつmodelAndViewオブジェクトの名前がリクエストしようとしているページと等しくないことをチェックします。
protected String getJspFromRequest(@NotNull HttpServletRequest request) { String jspFromRequest = request.getParameter("jsp"); return jspFromRequest == null || jspFromRequest.endsWith(".jsp") && !jspFromRequest.contains("admin/") ? jspFromRequest : null; }
この関数はまず、jspというリクエストパラメータの値を取得します。チェックにより、jspは.jspで終わり、かつ/adminを含んではならないことが保証されます。上記のif文と組み合わせると:
if (isControllerRequestWithViewName && StringUtil.isNotEmpty(jspFromRequest) && !modelAndView.getViewName().equals(jspFromRequest)) {
modelAndView.setViewName(jspFromRequest);
}
URLがどのようになるかの全体像が見えてきます:
.jspを含んではならない。jspリクエストパラメータはpathと等しくてはならない。例えば、/random?jsp=/randomは無効。jspが.jspで終わらなければならないこと。TeamCityは、外部アプリケーションを統合し、TeamCityサーバとのスクリプトインタラクションを作成するためのREST APIを提供しています。URLパスを介してリソースにアクセスできます。 REST APIの使用を開始するには、ブラウザで
http://<TeamCity Server host>:<port>/app/rest/serverURLを開いてください。このページには、APIを探索するためのいくつかのポインタが表示されます。
TeamCityは、認証をバイパスできれば機密リソースにアクセスできるREST APIを提供しています。この場合、例えば/app/rest/serverにアクセスしてみます。

通常、/app/rest/serverにリクエストすると、サーバは/login.htmlにリダイレクトします。この制限を回避するための完璧なURLを構築しましょう。まず、パスは404ステータスコードまたは200(例:login.html)を返すものなら何でも構いません。次に、jspを使用して/app/rest/serverにリクエストしますが、.jspで終わらなければなりません。この状況では、このチェックをバイパスするために2つのトリックを使用できます。セミコロン;をパラメータ区切り文字として使用する方法:/app/rest/server;.jsp。この場合、.jspは2番目のパラメータとして扱われます。2つ目のバイパスは、URIフラグメント#を使用する方法です:/app/rest/server%23.jsp。URIフラグメントの後ろにあるものはルーティングに使用されず、ページ内のナビゲーションにのみ使用されます。なお、この文字はURLエンコードする必要があります。そうしないと、ブラウザが最初に無視してしまいます。

この手法を使えば、管理者権限を持つ新しいユーザーを作成することもできます。TeamCityのドキュメントによると、エンドポイント/app/rest/usersを介して新しいユーザーを作成できます。

管理者パネルに移動して、新しいユーザーが作成されたか確認しましょう。

予想通り、管理者権限を持つ新しいユーザーが作成されました。管理者権限があれば、サーバーを完全に制御できます。しかし、さらにリモートコード実行を獲得することでさらに先に進むことができます。このCVEは2023.11.4より前のすべてのバージョンに影響しますが、このRCEは2023.11より前のバージョンでのみ可能です。管理者権限を持つユーザーが任意のコマンドを実行できる、文書化されていないエンドポイント/app/rest/debug/processesがあります。リクエストURLに2つのリクエストパラメータを付けてPOSTリクエストを送信します。Windowsの場合は?exePath=cmd.exe¶ms=/c%20[コマンド]、Linuxの場合は?exePath=/bin/sh¶ms=-c%20[コマンド]です。私はTeamcityをWindowsで実行しているので、完全なパスは/app/rest/debug/processes?exePath=cmd.exe¶ms=/c%20whoamiになります。

サーバーはリクエストにcsrfトークンが不足しているという403エラーを返します。TeamCityのドキュメントには、トークンを取得するためのエンドポイント/authenticationTest.html?csrfも記載されています。
トークンを取得したら、リクエストヘッダーX-TC-CSRF-TokenまたはHTTPパラメータtc-csrf-tokenに追加します。ここではX-TC-CSRF-Tokenを選択します。

csrfトークンを提供した後、リモートでコマンドを実行することに成功し、サーバーを完全に制御できるようになりました。
このCVEでは、セキュリティの欠陥は入力検証ではなく、コードのロジックにありました。このアプリケーションは非常に複雑で、開発者は何らかのミスを犯してしまいます。また、認証をバイパスする新しいテクニックを学びました。この分析から何か役立つことを学んでいただければ幸いです。Happy hacking!!!