S/Key
二段階認証
最近ipadに二段階認証がやってきた。流行りなんだな。
手軽な二段階認証として、Cメール(今は国際的に通用するSMSって言葉に置き換えられたけどね)がある。でも、 SMS認証の代行業者の取締まり強化を全国の警察に指示 こんな話も有るので、うかうかしていられない。で、SMSに変わる方法が推奨される。
オイラーが使ってるさくらさんの所でも、ワンタイムパスワードによる二段階認証がサポートされた。前回の最後を参照
OTP
一口にワンタイムパスワードと言っても、色々有るとな。
お手軽にタイムベースのワンタイムパスワードが勧められている。
ぐるるの奴が筆頭かな。でも、互換機を確認しておかないと、知らず知らずのうちにロックインされちゃうぞ。そんな訳で、 あなたのアカウントを守る、2段階認証アプリ5選 こんな紹介も有ったぞ。
さくらの導入手順書に有ったんだけど、これらの装置が機能しなかった場合の非常手段として、 キーのバックアップを強く勧めるとな。そうだよな、市中の合鍵屋で作って貰う事って出来ないからね。
でも、この文言を良く読むと、バックアップしたキーも一度しか使えないとな。これ、どういう仕組み? 散歩しながら、過去に読んだ記憶を辿ってみたよ。
OPIE in FreeBSD
そしたら、 OPIE - One-time Passwords In Everything なんてのを思い出した(覚えているオイラーは偉い、誉めて遣わそう)。昔の事なんで、何でこんな事を考えるんだとスルーしてたんだ(恥じる事だね)。でも、今の時代に必要になるとは、慧眼だな。
早速試してみたいんだけど、よそ様はどうなってるか、市場調査が先だな。
at linux
ワンタイムパスワードでggじゃなくて、apt-cache search 'one time password' して、目ぼしいものを列挙してみた。
donkey - One Time Password calculator golang-github-pquerna-otp-dev - Google Authenticator compatible one time passwords for Go python3-pyotp - Python One Time Password Library (Python 3) ruby-rotp - Ruby library for generating and verifying one time passwords
donkeyと言うのをやってみる。debianに移植した日本人は偉いぞ。
AUTHOR This program is developed by Kazuhiko Yamamoto <kazu@is.aist-nara.ac.jp>. This manual page was written by Fumitoshi UKAI <ukai@debian.or.jp>, based on the documentation of this program, for the Debian GNU/Linux system (but may be used by others).
作ったはいいけど、リナでは使い道が無いと言う、悲しい現実が。あっ、archLinuxでは、ちょっと手間をかけると S/KEY 認証 出来るようになってた。
debian:~$ donkey -f md5 -i Enter login name [default sakae]: Enter sequence 1 to 999 [default 99]: Enter new seed [default de05302]: Please choose passphrase between 8 and 256 characters. Enter passphrase : Re-enter passphrase : sakae 0099 de05302 8843f0a7ba1fa0f5 Apr 25,2021 08:01:56 GAIN JAN MUD DISK WENT EGAN
このコマンドでmd4かmd5を選択(デフォはmd4なので注意)。自分のログイン情報にSOLTとしてseedを加えるんだな。
debian:~$ donkey -f md5 -n 5 99 de05302 Enter passphrase : 95: THEE TAB OX RAP GUT GOOF 96: OWL GLUE FRED ATE SAL BIAS 97: MUTT WALE NAB WINE SAC ADEN 98: GUS BAIL FEET SIT TEST FEUD 99: GAIN JAN MUD DISK WENT EGAN
同じsoltを使って、5つのパスワードを生成してみた。これを印刷して、財布にしまっておくのさ。
at OpenBSD
OpenBSDで試してみたいんだけど、自分のアカウントでは気が引ける。新しいユーザーを作ろう。えと、adduserだかuseraddだか。。。軽くmanしたら、adduserの方でbatchモードのオプションが有った。ひねくれもののオイラーは、オプション指定しなければ対話モードだろうと予想。たまたまCDでzardを聴いていたので、アカウントはzardにした。
new user
ob# adduser : Enter password again []: sakai2007 Name: zard Password: **** Fullname: zard Uid: 1001 Gid: 1001 (zard) Groups: zard Login Class: default HOME: /home/zard Shell: /bin/ksh OK? (y/n) [y]: y Added user ``zard'' Copy files from /etc/skel to /home/zard Add another user? (y/n) [y]: n Goodbye!
/etc/adduser.confなんて言うのが、初めて場合作られるのね。知らんかった。以前から度々問題になってたshellに何を使うかなんてのも、ここにデフォで指定しておけるのね。
try S/Key
まず初めにrootで、skeyをするための準備が必要とな。
ob# skeyinit -E ob# ls -l /etc/skey/
空のdirが出来るんだな。
ob$ skeyinit Password:sakai2007 [Updating zard with md5] Old seed: [md5] obl45746 Enter new secret passphrase:hello ERROR: Your passphrase must contain more than just lower case letters. Whitespace, numbers, and punctuation are suggested. Enter new secret passphrase:HelloHello Again secret passphrase:HelloHello ID zard skey is otp-md5 100 obl45747 Next login password: CAFE ROOF WATS KENT GAFF CHAR
zard用のskeyを準備。まずは、普通のパスワードで同人か確認。それから、skey用のパスワードを入力。手抜きのやつだと叱られる。
ob# cat /etc/skey/zard zard md5 0100 obl45747 621a3be0d2f87eca
こんな記録が残ったぞ。
ob$ skeyinfo -v otp-md5 99 obl45747 ob$ otp-md5 99 obl45747 Enter secret passphrase:HelloHello SIN IOWA CRY HAND HAY SARA
skeyinfoで、skey/zardの情報を取り出す。それを使ってotp-md5で、ワンタイムパスワードを表示するって寸法なんだな。
ob$ otp-md5 -n 5 99 obl45747 Enter secret passphrase: 95: DUD RUNT GENE GOOF VALE ONE 96: SONG SAC ANEW MEET RUIN GULL 97: WEAN WORM LICK NOEL SULK KENO 98: BAND SURE BITS JOIN POW GRIT 99: SIN IOWA CRY HAND HAY SARA
これを財布の中に仕舞っておけとな。
ob$ ssh -l zard localhost zard@localhost's password:sakai2007 Last login: Sun Apr 25 15:27:19 2021 from 127.0.0.1 OpenBSD 6.8 (GENERIC.MP) #5: Mon Feb 22 04:36:10 MST 2021 :
これが普通のssh login
ob$ ssh -l zard:skey localhost otp-md5 99 obl45747 S/Key Password:SIN IOWA CRY HAND HAY SARA Last login: Sun Apr 25 15:31:25 2021 from 127.0.0.1 OpenBSD 6.8 (GENERIC.MP) #5: Mon Feb 22 04:36:10 MST 2021 :
skeyオプションを付けると、計算しておいたパスワードを入力する必要が出てくる。
zard md5 99 obl45747 3c13e030c9a199ad
シーケンス番号が一つ減ってるし、次のHex値も変わっている。
ob$ ssh -l zard:skey localhost otp-md5 98 obl45747 S/Key Password:BAND SURE BITS JOIN POW GRIT :
もう一度loginすると、違うシーケンス番号をパスワードを要求された。悪い人が99のシーケンスに対するパスワードを盗聴しておいて、それを使おうとしても失敗する。すなわち一度しか使えないパスワードって事だ。
ftp with OTP
今はもうブラウザーにも見放されてしまったftpだけど、ワンタイムパスワードが有れば、鬼に金棒だからね。
OpenBSDは古式なUNIXなので、ftpサーバーを内蔵してる。ってな事で、例に倣って試してみる。
ob# /etc/rc.d/ftpd -f start ftpd(ok)
ob$ ftp localhost Trying 127.0.0.1... Connected to localhost. 220 ob.localhost.jp FTP server ready. Name (localhost:zard): 331 Password required for zard. Password:sakai2007 230- OpenBSD 6.8 (GENERIC.MP) #5: Mon Feb 22 04:36:10 MST 2021 230- 230- Welcome to OpenBSD: The proactively secure Unix-like operating system. 230- 230 User zard logged in.
普通のftp。パスワードただ漏れ
Connected to localhost. 220 ob.localhost.jp FTP server ready. Name (localhost:zard): zard:skey 331 Password required for zard. Password: 530 Login incorrect. ftp: Login zard:skey failed.
仰せに従ってやってみると、駄目じゃん。ユーザー名の一部と見做しているよ。6.5の時代までなのがな? 世間一般の情勢を鑑み、機能削除したのかな?
ob$ skey -md5 -n 5 95 obl45747 Enter secret passphrase:HelloHello 91: MOLT SNAG ANNE NUDE AT BOIL 92: BOP HUNK MAID LINK TACT WARN 93: FIVE LOAM TOUT TOUT MEET JURY 94: WHY VEAL WILD KIRK TEET MOTH 95: DUD RUNT GENE GOOF VALE ONE
ちょいと悔しいので、汎用的なコマンドにも当たっておく。
skey reading
恒例のソース読み。目標は、どんな原理で動いているかを探る事。今までの取り調べで、心理状態を把握しつつあるので、軽めに行きます。
skey enable/disable
まずは大本のメインスイッチだな。使い始める前にrootになって skeyinit -E しろとなってた。じゃ使用を中止する指令も有るはずで、ご想像通りなってる。skeyinit -D 大本なんで、大文字だ。
スイッチはskeyinit.cの中
void enable_db(int op) { if (op == 1) { /* enable */ : if (chmod(_PATH_SKEYDIR, 01730) != 0) err(1, "can't chmod %s", _PATH_SKEYDIR); } else { /* disable */ if (chmod(_PATH_SKEYDIR, 0) != 0 && errno != ENOENT) err(1, "can't chmod %s", _PATH_SKEYDIR); } }
/etc/skey ってdirを読み書き可能にするか、権限を一切剥奪するか(無いに等しい)で制御してる。呆れるぐらい潔い方法だ。
key or seed
donkeyをやった時、シーケンス番号とキーを設定した。シーケンス番号はパスワードを消費する度に減った値をセット。じゃキーは何者? 予想ではSOLTだ。塩で本当のパスワードに味付けして、本当のパスワードを隠すんだろう。どうやって生成してる?
/* Build up a default seed based on the hostname and some randomness */ if (gethostname(hostname, sizeof(hostname)) == -1) err(1, "gethostname"); for (i = 0, p = seed; hostname[i] && i < SKEY_NAMELEN; i++) { if (isalnum((unsigned char)hostname[i])) *p++ = tolower((unsigned char)hostname[i]); } for (i = 0; i < 5; i++) *p++ = arc4random_uniform(10) + '0'; *p = '\0';
ホスト名にランダムは数字を加えたものになってる。
read donkey
折角なので、リナで提供されてたdonkeyで、コードを追ってみる。
(gdb) b main Breakpoint 1 at 0x58bb: file donkey.c, line 271. (gdb) r -f md5 -n 5 99 obl45747 Starting program: /tmp/donkey/donkey -f md5 -n 5 99 obl45747 Breakpoint 1, main (argc=7, argv=0x7f7ffffe3b18) at donkey.c:271 271 { (gdb) b keycrunch Breakpoint 2 at 0x8e8a1e14f7: file skey.c, line 22. (gdb) c Continuing. Breakpoint 2, keycrunch (result=0x7f7ffffe3a90 "", seed=0x7f7ffffe3cb2 "obl4574\ 7", passwd=0x7f7ffffe3990 "HelloHello") at skey.c:22 22 {
seedとパスワードを受け取って処理する部分。
(gdb) p buf $1 = 0x9156ccf620 "obl45747HelloHello"
合体した。
(*mdsw[MD].md_init)(&md); (*mdsw[MD].md_update)(&md, (POINTER)buf, buflen); (*mdsw[MD].md_final)((POINTER)results, &md); free(buf); results[0] ^= results[2]; results[1] ^= results[3]; memcpy(result,(char *)results,8);
そいつのMD5を取っている。MD5は128Bitになるので、それを畳み込んでいる。結果は
(gdb) p result $2 = 0x7f7ffffe3a90 "\265F&/\256\276\257?\353\061\264/\351\211H\277"
後は
=> for (i = 0; i < (seq - n + 1); i++) secure_hash(key); for (; i < seq + 1; i++) { printf("%zu: %-29s\n", i, btoe(key)); secure_hash(key);
最初のforで、必要な回数になるまで、MD5を取り続ける。続いて次のforで、要求されたシーケンス番号から、MD5の結果を表示する。
(gdb) p i $4 = 95 (gdb) p seq $5 = 99 (gdb) p key $6 = "\017\372\216*Go2_"
Enter passphrase : 95: DUD RUNT GENE GOOF VALE ONE 96: SONG SAC ANEW MEET RUIN GULL
すなわち、初期値の文字列(password+seed)に何回もハッシュを取る。ハッシュの結果はバイナリーなんで、btoeで人間が読める6個のワードに変換してるんだ。 このワードをetobで元に戻せるようになっている。
例えば、100回の結果を残しておく。/etc/skeyに中にね。使う時は、99に相当する内容を財布から取り出す。そしてシステムはloginの際中にそれを1回MD5する。100回目の値を求めるわけだ。それと保存してる値が一致すればOK。
次は、99の番号と値を保存。login時には98番目の値を提示して貰うって寸法。動的に提示すべきパスワードが変わっていく。一度しか使えないパスワードシステムの完成だ。
若し、/etc/skeyの中のファイルが盗まれても、それからの本当のパスワードを計算するのは不可能。ああ、ビットコインみたいに、無駄な電気を消費すれば、可能かも。MD5は弱いのでsha1とかrmd160とかの強力なやつで対抗出来るようになってる。
リベンジ ftp login by S/Key
散歩してて、ふと思い付いた。ftpってサーバーが有って成り立つサービスだよな。サーバーがskeyでのログインを許可してない限り使えないはず。だったら、さっさと man ftpd してみろ。
ftpd authenticates users by using the service and type of ftp, as defined in the /etc/login.conf file (see login.conf(5)). An authentication style may be specified by appending with a colon (‘:’) following the authentication style, i.e. “joe:skey”. The allowed authentication styles for ftpd may be explicitly specified by the “auth-ftp” entry in /etc/login.conf.
login.confで許可しておけとな。
# Default allowed authentication styles for authentication type ftp auth-ftp-defaults:auth-ftp=passwd,skey:
passwdの後ろにskeyを追加したよ。そうしておいて再度チャレンジ。
vbox$ ftp localhost Trying 127.0.0.1... Connected to localhost. 220 vbox.local.jp FTP server ready. Name (localhost:sakae): sakae:skey 331- otp-md5 98 vbox08230 331 S/Key Password: Password: 230- OpenBSD 6.8 (GENERIC) #5: Mon Feb 22 03:42:30 MST 2021 230- 230- Welcome to OpenBSD: The proactively secure Unix-like operating system. 230 User sakae logged in. Remote system type is UNIX. Using binary mode to transfer files.
今度はloginの途中で、親切にもS/Keyの調べ方を指南してくれたぞ。別端末で、skey用のパスワードを計算して、それを張り付けたら、無事にログイン出来た。もっと早く気付けよ。
vbox$ otp-md5 98 vbox08230 Enter secret passphrase: SWUM MAY HUNK FEAR MART SURE
debug ftpd
OpenBSDのftpdで観光。-g付きでコンパイル。make installしちゃうとstripされちゃうので手動でコピーする。そして/etc/rc.dから起動。
box# ps awx | grep ftpd 64738 ?? I 0:00.00 /usr/libexec/ftpd -D
-Dでデーモンモードになってる。gdbでこのプロセスにアタッチして、b user , b pass で、それらしい所にBPを置く。ftpでアクセスして来ると、forkされるので、子供を追いかけるようにgdbを設定。
(gdb) set follow-fork-mode child
アクセス中のプロセスは、下記のようになってた。
box# ps awx | grep ftpd 64738 ?? I 0:00.00 /usr/libexec/ftpd -D 95908 ?? S 0:00.01 ftpd: localhost: [priv post-auth] (ftpd) 62022 ?? S 0:00.00 ftpd: localhost: sakae (ftpd)
post-authのプロセスは、 monitor.c:197/handle_cmds()
で待ってた。もう一方は、yyparse()の方だった(多分実働部隊か。なんか、カースト制だな)。
user()の最後の方にこんなのが有った。
B if (as != NULL && (cp = auth_challenge(as)) != NULL) => reply(331, "%s", cp); else B reply(331, "Password required for %s.", name);
そして、指南のメッセージが見えた。
805 reply(331, "%s", cp); (gdb) p cp $1 = 0x5d356000 "otp-md5 96 vbox08230\nS/Key Password:"
後はpass()関数に飛び込む。
(gdb) bt #0 _libc_auth_userresponse (as=0x5d353000, response=0x60eed280 "SIN... VETO", more=0) at /usr/src/lib/libc/gen/authenticate.c:456 #1 0x1b91a004 in pass (passwd=0x60eed280 "SIN... VETO") at ftpd.c:890 #2 0x1b9291f3 in handle_cmds () at monitor.c:286 #3 0x1b928f38 in monitor_init () at monitor.c:197 #4 0x1b918660 in main (argc=2, argv=0xcf7edc84) at ftpd.c:603
そして、この auth_userresponce
の中で、認証が行われている。
実際に認証に使われるのは、
box# ls /usr/libexec/auth/ login_activ login_lchpass login_radius login_snk login_chpass login_ldap login_reject login_token login_crypto login_passwd login_skey login_yubikey
/etc/login.confに列挙されてるのに対応しているんだな。
NAME login_skey – provide S/Key authentication type SYNOPSIS login_skey [-s service] [-v fd=number] user [class] DESCRIPTION The login_skey utility is called by login(1), su(1), ftpd(8), and others to authenticate the user with S/Key authentication. :
ssh by S/Key
sshの場合は、どうなるんだろう? 下記のようにして、どのあたりで動いているかを調べて、それからかな。
ob$ ssh -vvv -l sakae:skey localhost : debug1: Next authentication method: keyboard-interactive debug2: userauth_kbdint debug3: send packet: type 50 debug2: we sent a keyboard-interactive packet, wait for reply debug3: receive packet: type 60 debug2: input_userauth_info_req debug2: input_userauth_info_req: num_prompts 1 otp-md5 97 vbox08230 S/Key Password: :