SqliteAccountInfo profile name validation uses "re.match", so only the first character is checked
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 75/100
調査の方向性
b2sdk/_internal/account_info/sqlite_account_info.pyを読み、特にget_user_account_info_pathとB2_ACCOUNT_INFO_PROFILE_NAME_REGEXPを確認し、その後、既存のtest_invalid_profile_nameテストを調べてください。プロファイル内のどこにあっても無効な文字を拒否するように検証を更新し、有効な先頭文字の後に悪意のあるサフィックスが続くケースをテストに追加してください。それらの名前でValueErrorが発生し、有効なプロファイルが引き続き機能すれば完了です。
索引モデルが issue の本文から書いたものです。
説明
Description
get_user_account_info_path validates profile with B2_ACCOUNT_INFO_PROFILE_NAME_REGEXP.match(profile) (b2sdk/_internal/account_info/sqlite_account_info.py:112), and the pattern at :30 is unanchored, so re.match accepts any string whose first character is in [a-zA-Z0-9_\-] and never looks at the rest. The value is then interpolated into the database path at :115, :136 and :142 and handed to os.open and sqlite3.connect without normalisation, so a name like a/../../../../../tmp/planted reaches the filesystem with its .. components intact and the credential database, which holds a live application key, is created outside the directory the SDK selected. Over a corpus of 108 names built from valid leading characters and 21 hostile suffixes, 103 were accepted and 50 of those resolved outside {XDG_CONFIG_HOME}/b2. Two things keep this low rather than worse: nothing inside the SDK passes untrusted input as profile, and on POSIX the first injected path component has to already exist as a directory, which the SDK never creates, so an attacker who can create it already has write access to the config directory. We are filing it in the open for that reason. It still seems worth fixing, because the pattern exists specifically to constrain this value and currently does not, and the one test covering it, test_invalid_profile_name, uses '&@(*$', whose first character is already outside the class, so it passes against the unanchored pattern.
Reproduction
On master @ ffa02b32 with Python 3.10.19, in a container built from a fresh clone at that ref, with the ~/.config/b2/db-a directory created to satisfy the POSIX precondition. Build with this Dockerfile, save the script below as poc.py, then run docker build -t b2poc . && docker run --rm -u 1001 -v "$PWD/poc.py:/poc.py:ro" b2poc python /poc.py:
FROM python:3.10.19-slim-bookworm
RUN apt-get update -qq && apt-get install -y -qq --no-install-recommends git >/dev/null
RUN git clone -q https://github.com/Backblaze/b2-sdk-python.git /src \
&& git -C /src checkout -q ffa02b3264bc0e7069ef2c8b25facd9a467e65a6 \
&& pip install --no-cache-dir -q /src
RUN useradd -m -u 1001 victim
import os, sqlite3
from b2sdk.v3 import SqliteAccountInfo
PROFILE = 'a/../../../../../tmp/planted' # accepted by .match(), rejected by .fullmatch()
print('resolved :', SqliteAccountInfo.get_user_account_info_path(profile=PROFILE))
os.makedirs(os.path.expanduser('~/.config/b2/db-a')) # POSIX needs the first component
info = SqliteAccountInfo(profile=PROFILE)
info.set_auth_data(
account_id='acct', auth_token='tok', api_url='https://api', download_url='https://dl',
absolute_minimum_part_size=5000000, recommended_part_size=100, realm='production',
application_key='VICTIM-SECRET-KEY', s3_api_url='https://s3', application_key_id='keyid',
allowed=SqliteAccountInfo.DEFAULT_ALLOWED)
print('created :', os.path.realpath(info.filename))
print('key there:', sqlite3.connect('/tmp/planted.sqlite')
.execute('select application_key from account').fetchone()[0])
resolved : /home/victim/.config/b2/db-a/../../../../../tmp/planted.sqlite
created : /tmp/planted.sqlite
key there: VICTIM-SECRET-KEY
Suggested remediation
Validate the whole string:
--- a/b2sdk/_internal/account_info/sqlite_account_info.py
+++ b/b2sdk/_internal/account_info/sqlite_account_info.py
@@ -109,7 +109,7 @@ class SqliteAccountInfo(UrlPoolAccountInfo):
@classmethod
def get_user_account_info_path(cls, file_name: str | None = None, profile: str | None = None):
- if profile and not B2_ACCOUNT_INFO_PROFILE_NAME_REGEXP.match(profile):
+ if profile and not B2_ACCOUNT_INFO_PROFILE_NAME_REGEXP.fullmatch(profile):
raise ValueError(f'Invalid profile name: {profile}')
One adjacent thing I noticed while reproducing this, which is separate and not addressed by the above: _validate_database (:144) only calls _create_database when the file does not already exist, and _create_database is the only place mode 0600 is set, so a database file that is already present keeps its permissions. Starting from a mode 0666 file, the mode after credentials were stored was still 0o666 and the application key was readable from it.
Credits
- 主要言語
- Python
- スター
- 214
- フォーク
- 68
- 平均マージ
- 51分
- マージ済み PR(30日)
- 6
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
Backblaze/b2-sdk-python のほかの issue
-
Download and copy sync actions print remote file names without escape_control_chars対応中かも @dreyfus92 が 15 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
Backblaze/b2-sdk-python#631 ·
メンテナーはふだん 1 日以内に返信
-
Adopt the AI-native SDLC and repository policies対応中かも @goanpeca が 18 日前に担当しました。 オープンenhancement
難易度 5/5 1週間以上 初心者へのやさしさ 20/100
Backblaze/b2-sdk-python#634 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 50/100
Backblaze/b2-sdk-python#630 ·
メンテナーはふだん 1 日以内に返信
-
ci: cli-compat fails — 4 sse/sync integration tests broken in B2_Command_Line_Tool (all environments)再び着手できるかも @ffumero2003 が 37 日前に担当しましたが、オープン中のプルリクエストはありません。 オープンbug testing
Backblaze/b2-sdk-python#627 · コメント 2 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
難易度 3/5 半日 初心者へのやさしさ 62/100
Backblaze/b2-sdk-python#626 ·
メンテナーはふだん 1 日以内に返信
Backblaze/b2-sdk-python の issue をすべて見る
似ている issue
-
難易度 1/5 1時間未満 初心者へのやさしさ 72/100
btclib-org/btclib-node#2505 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
open-webui/open-webui#34462 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
gorakhargosh/watchdog#1462 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
openwisp/openwisp-monitoring#889 ·
メンテナーはふだん 2 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
IETF-Hackathon/pqc-certificates#351 ·
メンテナーはふだん 2 日以内に返信