シチュエーション
TCPでサーバにアクセスしたいが、ncなどがインストールされておらず、bashのみ使えるという状況。(UDP通信にも応用もできると思うが、確認はしていない)
経緯
自分の手元にPCが2台あり、1台はWindowsマシンでCygwinがインストールされていて、もう1台にはLinuxがインストールされている。
LinuxからWindowsへssh経由でファイルをコピーしたかったが、Linux側でsshdが起動しなかった。
そこでLinux側でsocatを使ってシェルとソケットをバインドし、そこにWindowsからncで接続、リダイレクトでファイルのコピーをしようとしたがWindowsにはncがインストールされていなかった。
bashを使ってソケット通信ができるということなので、実験したことをまとめる。
(手元にPCがあるんだからUSBとかでコピーすればええやないか、というつっこみは無しで)
使用したコマンド
- bash: Linuxに標準インストールされているシェル。Cygwinにもインストールされている。
- socat: ncの上位互換(?)なツール。接続を待ち受けて、コマンドを実行するのに使う。
以後、bashを使うホストを"ホストB"と、socatを使うホストを"ホストS"と呼ぶ。
自分の経緯においては、WindowsがホストB、LinuxがホストSに相当する。
bashを使ってソケット通信する方法
"/dev/tcp/{IPアドレス or ホスト名}/{ポート番号}"というファイルに対して読み書きすると、それがソケット通信として反映される。
/dev/tcpというファイルは存在しないが、該当ファイルへのアクセスをbashがいい感じに解釈してソケットを割り当ててくれる。
bashをクライアントとして使う
ホストBで入力したコマンドを、ホストSで実行し、結果をホストBに返す。
1. ホストSで接続を待ち受ける
ホストSはsocatで待ち受けておき、ホストBが接続してくるとホストSのシェルを開く。(telnetdの代わりのような使い方)
[S] $ socat tcp-listen:{待ち受けポート番号},reuseaddr,fork exec:/bin/bash
2. ホストBから接続する
hostnameコマンドを入力し、コマンドを実行したのがホストSであることを確認する。
[B] $ hostname
B
[B] $ exec 3<>/dev/tcp/{ホストSのIPアドレス}/{ホストSの待ち受けポート番号}; echo "hostname" >&3; cat <&3
S
C-cを入力して終了
[B] $
ホストSでコマンドを実行すると、以後入力を受け付けてくれなくなる。
ソケットが閉じてしまうのが原因だと思うが、詳しくは分からなかった。
hostnameコマンドに続けて、";"区切りでexitコマンドを入力するとC-cを入力しなくても自動的に終了してくれる。
[B] $ exec 3<>/dev/tcp/{ホストSのIPアドレス}/{ホストSの待ち受けポート番号}; echo "hostname; exit" >&3; cat <&3
S
[B] $
3. コマンドを連続して入力できるようにする
whileループを使うことにより、連続してコマンドを入力できた。
[B] $ while read cmd; do exec 3<>/dev/tcp/{ホストSのIPアドレス}/{ホストSの待ち受けポート番号}; echo "$cmd; exit" >&3; cat <&3; done
hostname # コマンドを入力
S # 結果
uname # 次のコマンドを入力
Linux # 結果
C-cを入力して終了
[B] $
ホストSでコマンドを実行するたびに接続が切れるので、ループのたびにexecを実行する必要がある。
もう少し上手くできないかなぁという感じである。
bashをサーバとして使う
結論から言って、bash単体で接続を待ち受ける機能は無いらしいので、ncやsocatや各種スクリプト言語の力を借りるしかないと思う。
しかし、リバースシェルとしてなら実現bash単体で実現できるので、それを紹介する。
シチュエーションは、ホストSで入力したコマンドを、ホストBで実行し、結果をホストSに返すというものである。
1. ホストSで接続を待ち受ける
機能的にはホストSがクライアントでホストBがサーバであるが、リバースシェルなのであらかじめホストSが接続を待ち受けておく必要がある。
再び、socatを使う。
[S] $ socat tcp-listen:{待ち受けポート番号},reuseaddr,fork stdout
2. ホストBから接続する
ホストBはホストSからコマンドを読み込み、そのコマンドをホストB上で実行し、結果をホストSに返す。
[B] $ exec 3<>/dev/tcp/{ホストSのIPアドレス}/{ホストSの待ち受けポート番号}; while read cmd <&3; do $cmd 2>&3 >&3; done
3. ホストSからコマンドを入力する
hostname # コマンドを入力
B # 結果
ホストB上でコマンドを実行できているのが確認でた。
参考サイト
bash - PIB(2015-04-19: ソケット通信)
ncコマンドとbashの/dev/tcpで通信 - suztomoの日記