Skip to content
KitploitKITPLOIT
ツールエクスプロイトブログ
Log in
提出
ツールエクスプロイトブログ
提出

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

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

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

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2021-3156 — CVE-2021-3156 POC、Docker、および解析レポート | Kitploit
ツール/GitHubGitHub/chenaotian/cve-2021-3156
特権昇格脆弱性分析コード分析エクスプロイトリバースエンジニアリングデバッガファジングペネトレーションテスト学習と教育バイナリエクスプロイトラボと実践
112154年前未レビュー

人気

すべて見る →

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

すべてのツールを探索

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

すべてのツールを見る →
共有
GitHub
chenaotian/cve-2021-3156

CVE-2021-3156

CVE-2021-3156 POC、Docker、および解析レポート

リポジトリを見る

CVE-2021-3156

[toc]

脆弱性の概要

脆弱性番号: CVE-2021-3156

脆弱性スコア:

脆弱性製品: linux sudo

影響範囲: 1.8.2-1.8.31sp12; 1.9.0-1.9.5sp1

利用条件: linux ローカル;sudoがSUIDであり実行可能

利用効果: ローカル権限昇格

ソースコード入手: https://www.sudo.ws/getting/source/

環境構築

docker 環境: chenaotian/cve-2021-3156

私が自分で構築したdockerでは、以下を提供しています:

  1. 自分でコンパイルしたソースデバッグ可能なsudo
  2. デバッグシンボル付きのglibc
  3. gdb とgdbプラグインpwngdb & pwndbg
  4. exp.c とそのコンパイル済みexp

すべてのファイルは/root ディレクトリにあります:

image-20220124223312224

  • exp ディレクトリはexpコードとコンパイル済みファイルが置かれているディレクトリで、そのままdocker内で実行できます
  • glibc-2.27 はこの環境におけるlibcバージョンのソースコードディレクトリです
  • sudo-1.8.21 はこの環境におけるsudoのソースコードディレクトリで、これを使ってコンパイルしました。

テストexp:``` cd exp su test ./exp whoami

デバッグ関連の内容は後述の[いくつかのデバッグコマンド](#一些调试命令)


## 脆弱性の原理

脆弱性トリガーペイロード```shell
sudoedit -s '\' `python3 -c "print('A'*80)"`

ソースコード分析(sudo-1.8.21): まずはsudo.cのmain関数(sudo.c: 133):```c int main(int argc, char *argv[], char *envp[]) { int nargc, ok, status = 0; char **nargv, **env_add; char **user_info, **command_info, **argv_out, **user_env_out; struct sudo_settings *settings; struct plugin_container *plugin, *next; sigset_t mask; debug_decl_vars(main, SUDO_DEBUG_MAIN)

··· ···
··· ···

/* Parse command line arguments. */
//在这里处理输入参数,设置sudo_mode
sudo_mode = parse_args(argc, argv, &nargc, &nargv, &settings, &env_add);

··· ···
··· ···
    
switch (sudo_mode & MODE_MASK) {
··· ···
··· ···
case MODE_EDIT:
case MODE_RUN:
    ok = policy_check(&policy_plugin, nargc, nargv, env_add,
	&command_info, &argv_out, &user_env_out);
    ··· ···
    ··· ···
}

··· ···
··· ···

}

- 最初に parse_args 関数を呼び出して入力されたパラメータを処理します。ここでは `-s` のみを入力しており、設定できるものはありません。sudo_mode を MODE_EDIT および MODE_SHELL に設定します。

- 次に sudo_mode に応じて、MODE_EDIT は policy_check を呼び出します。

次に、sudo.c 内の policy_check 関数 (sudo.c: 1136) です。```c
static int
policy_check(struct plugin_container *plugin, int argc, char * const argv[],
    char *env_add[], char **command_info[], char **argv_out[],
    char **user_env_out[])
{
    ··· ···
    ··· ···
    ret = plugin->u.policy->check_policy(argc, argv, env_add, command_info,
	argv_out, user_env_out);
    ···
}

コールバック関数 plugin->u.policy->check_policy が呼び出されました、デバッグによりこの関数の実際の関数を確認できます:

image-20220123113326096

呼び出されているのは、policy.c 内の sudoers_policy_check 関数(policy.c: 760) です:```c static int sudoers_policy_check(int argc, char * const argv[], char *env_add[], char **command_infop[], char **argv_out[], char **user_env_out[]) { ··· ···

exec_args.argv = argv_out;
exec_args.envp = user_env_out;
exec_args.info = command_infop;

ret = sudoers_policy_main(argc, argv, 0, env_add, &exec_args);
··· ···
··· ···

}

次に、sudoers.c 内の sudoers_policy_main 関数(sudoers.c: 224)を呼び出します:```c
int
sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
    void *closure)
{
    ··· ···
    ··· ···

    /*
     * Make a local copy of argc/argv, with special handling
     * for pseudo-commands and the '-i' option.
     */
    if (argc == 0) {
	··· ···
    } else {
	/* Must leave an extra slot before NewArgv for bash's --login */
	NewArgc = argc;
	NewArgv = reallocarray(NULL, NewArgc + 2, sizeof(char *));
	··· ···
	}
	memcpy(++NewArgv, argv, argc * sizeof(char *));
	NewArgv[NewArgc] = NULL;
	··· ···
	}
    }
	··· ···
    cmnd_status = set_cmnd();
    ··· ···
    ··· ···
    ··· ···
}

ここではいくつかのグローバル変数が設定されています。NewArgc と NewArgv は以下の通りで、実際には渡されたパラメータです。

image-20220123113819116

その後、sudoers.c の set_cmnd 関数 (sudoers.c: 796) に入ります:```c static int set_cmnd(void) { ··· ··· ··· ···

/* set user_args */
if (NewArgc > 1) {
    char *to, *from, **av;
    size_t size, n;

    /* Alloc and build up user_args. */
    //根据参数总长度计算size, 后续malloc 申请,没有问题
    for (size = 0, av = NewArgv + 1; *av; av++)
	size += strlen(*av) + 1;
    if (size == 0 || (user_args = malloc(size)) == NULL) {
	sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
	debug_return_int(-1);
    }
    if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
	/*
	 * When running a command via a shell, the sudo front-end
	 * escapes potential meta chars.  We unescape non-spaces
	 * for sudoers matching and logging purposes.
	 */
     //将所有参数拷贝到一起放到堆中,逻辑是遇到'\'加非空格类型字符则只拷贝非空格字符
     //但这里\x00 并不算空格类型字符
     //他没有考虑参数如果只有一个'\'或以'\'结尾并且下两个字符后就是另一个字符串情况
	for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
	    while (*from) {
		if (from[0] == '\\' && !isspace((unsigned char)from[1]))
		    from++;
		*to++ = *from++;
	    }
	    *to++ = ' ';
	}
	*--to = '\0';
    } 
    ··· ···
}
}
··· ···
··· ···

}

溢出もここで発生します。コード中のコメントから分かるように、ヒープオーバーフローはヒープへのコピー時に発生します。このコードの本来の意図は、NewArgvのすべての引数をヒープにコピーし、スペースで分割し、`\+非スペース文字` の場合はその文字だけをコピーすることです。

**しかし、考慮されていないケースがあります。NewArgvの要素が `\` で終わる場合、つまり `\+\x00` のような構造であり、`\x00` はスペース文字に該当しません(おかしな話ですが)。つまり、`\x00` をヒープにコピーした後、from 変数をさらにインクリメント(ループ内で2回加算)することで、while ループの終了マーカー `\x00` をチェックする機会を飛ばしてしまい、引数がコピーされ終わったと判断せずに、次の `\x00` に出会うまでコピーを続けてしまいます。**

このシナリオでは、`\+\x00` の直後に次の引数 `A*80` があるため、`A*80` の末尾までコピーが続行されます。しかし、この後で実際に `A*80` という引数が正規に処理されることも忘れてはいけません。その際にもう一度コピーが行われるため、結果として `A*80` は合計2回コピーされることになります。しかし、chunk の割り当ては `A*80` という文字列1つ分のサイズで行われているため、その長さをはるかに超えてしまいます。

イメージ-20220123113907744

その結果、オーバーフローが発生します。コピー前:

イメージ-20220123114036691

コピー後:

イメージ-20220123114137794

全体的な脆弱性のトリガーパスは次の通りです(デバッグ時にはこれらの関数に直接ブレークポイントを設定すればよいです)。

- sudo.c : main
  - sudo.c : policy_check
    - policy.c : sudoerrs_policy_check
      - sudoers.c : sudoers_policy_main
        - sudoers.c : set_cmnd
          - sudoers.c : 859

## 脆弱性の悪用原理

[blasty/CVE-2021-3156](https://github.com/blasty/CVE-2021-3156) を参考にしました。**しかし、彼のヒープレイアウト方式は誰でも実現できるものではなく、ここではヒープレイアウトの方法を詳細に分析します**。環境変数 `LC_*` を渡すことでヒープをレイアウトし、オーバーフローした chunk が nss_load_library 関数で so をロードする際の構造体 service_user をちょうど上書きするようにします。その構造体内の so 名文字列を上書きし、プログラムに指定した so をロードさせることで任意のコード実行を実現します。

ロジックは明快に見えますが、解決すべき細かい点はかなり多いです。

1. nss_load_library 内の関連データ構造とメカニズム
2. setlocale が環境変数 `LC_*` によってどのようにヒープレイアウトを行うか

以降では、脆弱性が発生するオーバーフロー可能な chunk を **vuln chunk**、オーバーフローの標的となる chunk を **target chunk** と呼びます。

### nss の原理

まず、脆弱性悪用の鍵となるコードを見てみましょう。
ツールをダウンロード