CVE-2021-3156:Baron Samedit

【CVE-2021-3156:Baron Samedit】パスワード認証なしでroot権限に昇格できるsudo脆弱性の仕組みと対策

投稿日: カテゴリー: セキュリティ
Pocket

CVE-2021-3156:Baron Samediとはどんな脆弱性か

CVE-2021-3156通称Baron Sameditは「どのユーザーでもパスワード認証をせずroot権限に昇格できる」という脆弱性です。
まず今回のCVE-2021-3156の脆弱性が皆さんの管理するサーバに該当しないかを簡単1コマンドで確認しましょう。
$sudoedit -s /」を実行します。メッセージが「sudoedit:」から始まった場合は脆弱性があるので対応が必要です。
sudoの食パンのキャラクターもだいぶ怖い(公式ページ)ですが、この脆弱性の方が怖いのでまず該当したらアップデートを考えましょう。
※ここで解説するsudoのソースコードは公式のSudo Main Pageを参照しております。

CVE-2021-3156:Baron Samediの影響と対策(軽減策)

バージョン1.9.5p2より前のsudo
Linux(Amazon Linux含む)

対策:修正バージョンにアップデートしましょう。

Sudo直接の場合はこちら
Amazon Linuxはこちら
Red Hat Enterprise Linux(RHEL 5は脆弱性なし)はこちら
Ciscoはこちら

軽減策:systemtap による緩和策

Red Hat Enterprise Linuxではsystemtap による緩和策が利用可能です。
STEP1:「systemtap yum-utils kernel-devel-“$(uname -r)”」でsystemtapをインストールします。
STEP2:Red Hat Enterprise Linuxによってコマンドが変わります。
RHEL 6または8の場合「debuginfo-install sudo」でdebuginfoをインストールします。
RHEL 7の場合「debuginfo-install -y kernel-“$(uname -r)” 」でdebuginfoをインストールします。
STEP3: systemtapスクリプトを書きます。

probe process("/usr/bin/sudo").function("main") {
  command = cmdline_args(0,0,"");
  if (strpos(command, "edit") >= 0) {
    raise(9);
  }
}

STEP4:特権ユーザで以下を実行します。

# nohup stap -g sudoedit-block.stap &

アップデート後、systemtapプロセスをkillするとsystemtapスクリプトを削除できます。

# kill -s SIGTERM XXXX

CVE-2021-3156:Baron Samediの仕組みとPoC

そもそも「$sudoedit -s /」って何をしているの?

まず「$sudoedit -s /」のコマンドを知らない方向けに説明します。
sudoedit とは、sudo -e fileと同じ意味です。sudoeditコマンドを利用した場合、vim や emacs などのエディタを起動せずにファイル編集が可能というコマンドです(起動エディターの指定も可能)
そして、オプションの-sは対象のユーザとしてシェルコマンドを実行という意味になります。

SUID(Set User ID)はLinuxパーミッション

通常コマンドの実行者の権限と実行したファイルの権限は一緒になりますが、SUID(Set User ID)が設定されたファイルはその実行ファイルの所有者のアカウント権限で実行される事になります。sudoコマンドの他にpasswdコマンドなどもSUIDです(sの箇所)。「chmod u+s targetfile」とすることで任意のファイルにも設定可能です。

$ ls -l /bin/sudo
-rwsr-xr-x 1 root root 586560 10月 30 10:49 /bin/sudo
$ ls -l /usr/bin/passwd
-r-s--x--x 1 root root 26520 2月 1 2021 /usr/bin/passwd*

CVE-2021-3156の脆弱性のある関数のソースコードの解説

mainの先頭の関数であるparse_args()ではフラグがチェックされます。MODE_SHELLとは-sのことです。(SudoのMODE_SHELLフラグの-sオプションとMODE_LOGIN_SHELLの-iオプションでも可能です)

if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) {
    char **av, *cmnd = NULL;
    int ac = 1;

すべてのコマンドライン引数を連結し「\\」をつけてメタ文字をエスケープしていることがわかります。

    for (av = argv; *av != NULL; av++) {
        for (src = *av; *src != '\0'; src++) {
       /* quote potential meta characters */
          if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
             *dst++ = '\\';
             *dst++ = *src;
         }
         *dst++ = ' ';
    }

その後set_cmnd()のコマンドライン引数をヒープベースのバッファ「user_args」に連結される時に「\\」をとってメタ文字のエスケープを解除します

if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
...ちょっと間開きます。
for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
while (*from) {
if (from[0] == '\\' && !isspace((unsigned char)from[1]))
from++;
*to++ = *from++;
}
*to++ = ' ';
}

ここでコマンドの終端が一つだけの「/」で終端している場合、from [0]は「/」,from [1]は「NULL終端(¥0)」となります。
この時「*to++ = *from++;」は「NULL終端(¥0)」が「user_args」バッファにコピーされ引数の範囲外となることがわかります。

ではどのようなコマンドであれば到達可能なのか
まず、parse_args()では
if (ISSET(mode, MODE_RUN) && ISSET(flags, MODE_SHELL)) {
さらにset_cmnd()では
if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
かつ
if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
sudoedit
結論からいうとsudoではそのような組み合わせは存在しませんが、sudoeditは自動的にMODE_EDITを設定され「valid_flags」はリセットされないため「sudoedit -s」を実行すると、MODE_EDITとMODE_SHELLの両方を設定できエスケープコードを回避しコマンドラインからヒープベースのバッファ「user_args」をオーバーフローさせルことができます。
sudoコマンドでオプションを指定した場合は「MODE_SHELL」を含まないよう初期化がなされていますが、sudoeditコマンドにはこうした初期化処理が行われていなかったため、valid_flagsにはMODE_SHELLを含む「DEFAULT_VALID_FLAGS」が指定できルことが脆弱性の原因と理解できたと思います。
後はアーキテクチャに合わせて「user_args」バッファーのサイズを調整することで目的のコマンドを実行させることができます。

PoCやexploitは公開されていますが、部分的な上書き技術を利用してASLRをバイパスす必要があり4000回程度の試行が必要となります。「$sudoedit -s /」で確認するのが手っ取り早いです。

 

以上です。参考になれば幸いです。