セキュリティ
前回はシステムコールをフックしてsshのパスワード入力のようすをモニタリングするというサンプロを取り上げました。今回はそれをさらに進めて、シェルの実行をモニタリングして、そこに入力された文字列と、出力されたコマンド結果をモニタリングするサンプルを紹介します。
次のスクリプトがそれを実施するためのスクリプトです("DTrace Dynamic Tracing In Oracle Solaris, Mac OS X & FreeBSD", by Brendan Gregg and Jim Mauro"のP.878から掲載されているものです) 。シェルスクリプトとDTraceスクリプトの双方が使われていて、ソースコードのオリジナルはestibi/DTraceToolkit/Apps/shellsnoop|GitHub に掲載されています。このスクリプトのままだとFreeBSDでは実行できないので、FreeBSDで実行できるように一部内容を書き換えてあります。
リスト shellnoop
#!/bin/sh
##############################
# --- Process Arguments ---
#
opt_pid=0; opt_uid=0; opt_time=0; opt_timestr=0; opt_quiet=0; opt_debug=0
filter=0; pid=0; uid=0
while getopts dhp:qsu:v name
do
case $name in
d) opt_debug=1 ;;
p) opt_pid=1; pid=$OPTARG ;;
q) opt_quiet=1 ;;
s) opt_time=1 ;;
u) opt_uid=1; uid=$OPTARG ;;
v) opt_timestr=1 ;;
h|?) cat <<-END >&2
USAGE: shellsnoop [-hqsv] [-p PID] [-u UID]
shellsnoop # default output
-q # quiet, only print data
-s # include start time, us
-v # include start time, string
-p PID # process ID to snoop
-u UID # user ID to snoop
END
exit 1
esac
done
if [ $opt_quiet -eq 1 ]; then
opt_time=0; opt_timestr=0
fi
if [ $opt_pid -eq 1 -o $opt_uid -eq 1 ]; then
filter=1
fi
#################################
# --- Main Program, DTrace ---
#
dtrace -n '
/*
* Command line arguments
*/
inline int OPT_debug = '$opt_debug';
inline int OPT_quiet = '$opt_quiet';
inline int OPT_pid = '$opt_pid';
inline int OPT_uid = '$opt_uid';
inline int OPT_time = '$opt_time';
inline int OPT_timestr = '$opt_timestr';
inline int FILTER = '$filter';
inline int PID = '$pid';
inline int UID = '$uid';
#pragma D option quiet
#pragma D option switchrate=20hz
/*
* Print header
*/
dtrace:::BEGIN /OPT_time == 1/
{
printf("%-14s ","TIME");
}
dtrace:::BEGIN /OPT_timestr == 1/
{
printf("%-20s ","STRTIME");
}
dtrace:::BEGIN /OPT_quiet == 0/
{
printf("%5s %5s %8s %3s %s\n", "PID", "PPID", "CMD", "DIR", "TEXT");
}
/*
* Remember this PID is a shell child
*/
syscall::execve:entry
/execname == "sh" || execname == "ksh" || execname == "csh" ||
execname == "tcsh" || execname == "zsh" || execname == "bash"/
{
child[pid] = 1;
}
syscall::execve:entry
/(OPT_pid == 1 && PID != ppid) || (OPT_uid == 1 && UID != uid)/
{
/* forget if filtered */
child[pid] = 0;
}
/*
* Print shell keystrokes
*/
syscall::write:entry, syscall::read:entry
/(execname == "sh" || execname == "ksh" || execname == "csh" ||
execname == "tcsh" || execname == "zsh" || execname == "bash")
&& (arg0 >= 0 && arg0 <= 2)/
{
self->buf = arg1;
}
syscall::write:entry, syscall::read:entry
/(OPT_pid == 1 && PID != pid) || (OPT_uid == 1 && UID != uid)/
{
self->buf = 0;
}
syscall::write:return, syscall::read:return
/self->buf && child[pid] == 0 && OPT_time == 1/
{
printf("%-14d ", timestamp/1000);
}
syscall::write:return, syscall::read:return
/self->buf && child[pid] == 0 && OPT_timestr == 1/
{
printf("%-20Y ", walltimestamp);
}
syscall::write:return, syscall::read:return
/self->buf && child[pid] == 0 && OPT_quiet == 0/
{
this->text = (char *)copyin(self->buf, arg0);
this->text[arg0] = '\'\\0\'';
printf("%5d %5d %8s %3s %s\n", pid, curpsinfo->pr_ppid, execname,
probefunc == "read" ? "R" : "W", stringof(this->text));
}
syscall::write:return
/self->buf && child[pid] == 0 && OPT_quiet == 1/
{
this->text = (char *)copyin(self->buf, arg0);
this->text[arg0] = '\'\\0\'';
printf("%s", stringof(this->text));
}
syscall::read:return
/self->buf && execname == "sh" && child[pid] == 0 && OPT_quiet == 1/
{
this->text = (char *)copyin(self->buf, arg0);
this->text[arg0] = '\'\\0\'';
printf("%s", stringof(this->text));
}
syscall::write:return, syscall::read:return
/self->buf && child[pid] == 0/
{
self->buf = 0;
}
/*
* Print command output
*/
syscall::write:entry, syscall::read:entry
/child[pid] == 1 && (arg0 == 1 || arg0 == 2)/
{
self->buf = arg1;
}
syscall::write:return, syscall::read:return
/self->buf && OPT_time == 1/
{
printf("%-14d ", timestamp/1000);
}
syscall::write:return, syscall::read:return
/self->buf && OPT_timestr == 1/
{
printf("%-20Y ", walltimestamp);
}
syscall::write:return, syscall::read:return
/self->buf && OPT_quiet == 0/
{
this->text = (char *)copyin(self->buf, arg0);
this->text[arg0] = '\'\\0\'';
printf("%5d %5d %8s %3s %s", pid, curpsinfo->pr_ppid, execname,
probefunc == "read" ? "R" : "W", stringof(this->text));
/* here we check if a newline is needed */
this->length = strlen(this->text);
printf("%s", this->text[this->length - 1] == '\'\\n\'' ? "" : "\n");
self->buf = 0;
}
syscall::write:return, syscall::read:return
/self->buf && OPT_quiet == 1/
{
this->text = (char *)copyin(self->buf, arg0);
this->text[arg0] = '\'\\0\'';
printf("%s", stringof(this->text));
self->buf = 0;
}
/*
* Cleanup
*/
syscall::exit:entry
{
child[pid] = 0;
}
'
実行すると次のような結果が得られます。新しくシェル(sh、ksh、csh、tcsh、zsh、bash)を実行するとそのシェルをトレースし、そのシェルに対する入力と、そこからの出力をモニタリングしています。
図 shellnoopの実行例
% sudo ./shellsnoop
PID PPID CMD DIR TEXT
14843 757 sh W $
14843 757 sh R d
14843 757 sh W d
14843 757 sh R a
14843 757 sh W a
14843 757 sh R t
14843 757 sh W t
14843 757 sh R e
14843 757 sh W e
14843 757 sh R
14843 757 sh W
14844 14843 date W Mon Mar 13 17:50:13 JST 2017
14843 757 sh W $
14843 757 sh R c
14843 757 sh W c
14843 757 sh R a
14843 757 sh W a
14843 757 sh R l
14843 757 sh W l
14843 757 sh R
14843 757 sh W
14845 14843 cal W March 2017
14845 14843 cal W Su Mo Tu We Th Fr Sa
14845 14843 cal W 1 2 3 4
14845 14843 cal W 5 6 7 8 9 10 11
14845 14843 cal W 12 13 14 15 16 17 18
14845 14843 cal W 19 20 21 22 23 24 25
14845 14843 cal W 26 27 28 29 30 31
14845 14843 cal W
14843 757 sh W $
14843 757 sh R l
14843 757 sh W l
14843 757 sh R s
14843 757 sh W s
14843 757 sh R
14843 757 sh W
14846 14843 ls W FREEBSD-20170306.tgz sources typescript.txt
14846 14843 ls W Makefile typescript.gh typescript.xml
14846 14843 ls W commands typescript.html
14843 757 sh W $
14847 9165 bash W [daichi@virt ~/Documents/lwt/20170313]$
14847 9165 bash R p
14847 9165 bash W p
14847 9165 bash R w
14847 9165 bash W w
14847 9165 bash R d
14847 9165 bash W d
14847 9165 bash R
14847 9165 bash W
14847 9165 bash W /Users/daichi/Documents/lwt/20170313
14847 9165 bash W [daichi@virt ~/Documents/lwt/20170313]$
14847 9165 bash R a
14847 9165 bash W a
14847 9165 bash R
14847 9165 bash W
14847 9165 bash R c
14847 9165 bash W c
14847 9165 bash R a
14847 9165 bash W a
14847 9165 bash R l
14847 9165 bash W l
14847 9165 bash R
14847 9165 bash W
14848 14847 cal W March 2017
14848 14847 cal W Su Mo Tu We Th Fr Sa
14848 14847 cal W 1 2 3 4
14848 14847 cal W 5 6 7 8 9 10 11
14848 14847 cal W 12 13 14 15 16 17 18
14848 14847 cal W 19 20 21 22 23 24 25
14848 14847 cal W 26 27 28 29 30 31
14848 14847 cal W
14847 9165 bash W [daichi@virt ~/Documents/lwt/20170313]$
14847 9165 bash R q
14847 9165 bash W q
14847 9165 bash R u
14847 9165 bash W u
14847 9165 bash R i
14847 9165 bash W i
14847 9165 bash R t
14847 9165 bash W t
14847 9165 bash R
14847 9165 bash W
14849 14847 bash W bash: quit: command not found
14847 9165 bash W [daichi@virt ~/Documents/lwt/20170313]$
14847 9165 bash R e
14847 9165 bash W e
14847 9165 bash R x
14847 9165 bash W x
14847 9165 bash R i
14847 9165 bash W i
14847 9165 bash R t
14847 9165 bash W t
14847 9165 bash R
14847 9165 bash W
14847 9165 bash W exit
14843 757 sh R e
14843 757 sh W e
14843 757 sh R x
14843 757 sh W x
14843 757 sh R i
14843 757 sh W i
14843 757 sh R t
14843 757 sh W t
14843 757 sh R
14843 757 sh W
^C
%
このサイズのスクリプトになってくるとぱっと見で何をしているか理解するのはちょっと手間取りますが、それほど複雑なことをしているわけではありません。捕捉対象となるシェルをプロセス番号で記録し、その番号を判別データとして使って対象となるプロセスのみをトレースしています。配列の使い方や収集したデータの出力方法など、実用的で参考になるスクリプトです。こうしたことができるということがわかると、なんとなくDTraceでできることが見えてくるんじゃないでしょうか。
勉強会
第61回 3月23日(木)19:00~FreeBSD勉強会:リキャップ・ザ・AsiaBSDCon 2017 ~日本語でふりかえるABC~
2017年3月9~12日まで東京でAsiaBSDCon 2017 が開催される予定です。ぜひこのカンファレンスにご参加いただきたいわけなのですが、なかにはどうしても仕事の都合で参加できなかったとか、正直英語がよくわからなかったとか、そういった方もいらっしゃるのではないかと思います。
3月のFreeBSD勉強会では、AsiaBSDCon 2017のあとというこのタイミングを活かして、AsiaBSDCon 2017の発表内容を振り返ってみよう、というのをやってみようと思います。AsiaBSDConに参加しているにもかかわらず、これまで一度もプロシーディングを読み返したことすらないというあなた、ぜひプロシーディングを持参してご参加ください :) AsiaBSDConに参加できなかったというあなたも、この機会をお見逃しなく(できればAsiaBSDConそのものに参加した方が絶対的によいです、あしからず) 。
FreeBSD勉強会 発表者募集
FreeBSD勉強会では発表者を募集しています。FreeBSDに関して発表を行いたい場合、@daichigoto までメッセージをお願いします。1時間半~2時間ほどの発表資料を作成していただき発表をお願いできればと思います。