私は、たとえば次のようないくつかの機能のロックを解除するために、パスフレーズを入力する特定のアクションを必要とするソフトウェアデーモンを実行しています。
$ darkcoind masternode start <mypassphrase>
これで、ヘッドレスのdebianサーバーにセキュリティ上の懸念が生じました。
たとえばCtrl+R
を使用してbash履歴を検索すると、この強力なパスワードが表示されます。今、私は自分のサーバーが危険にさらされており、侵入者がシェルにアクセスでき、単純にCtrl+R
を使用して履歴からパスフレーズを見つけることができると想像します。
パスフレーズを入力せずに、bashの履歴、ps
、/proc
などに表示する方法はありますか?
Update 1:デーモンにパスワードを渡さないと、エラーがスローされます。これはオプションではありません。
アップデート2:ソフトウェアや、開発者をぶら下げるなどの役立つヒントを削除するように言わないでください。私はこれがベストプラクティスの例ではないことを知っていますが、このソフトウェアは bitcoin に基づいており、すべてのビットコインベースのクライアントは、これらのコマンドをリッスンするjson rpcサーバーの一種であり、既知のセキュリティ問題はまだ議論されています( a 、 b 、 c )。
Update 3:デーモンはすでに起動しており、コマンドで実行されています
$ darkcoind -daemon
ps
を実行すると、起動コマンドのみが表示されます。
$ ps aux | grep darkcoin
user 12337 0.0 0.0 10916 1084 pts/4 S+ 09:19 0:00 grep darkcoin
user 21626 0.6 0.3 1849716 130292 ? SLl May02 6:48 darkcoind -daemon
したがって、パスフレーズを使用してコマンドを渡しても、ps
または/proc
にはまったく表示されません。
$ darkcoind masternode start <mypassphrase>
$ ps aux | grep darkcoin
user 12929 0.0 0.0 10916 1088 pts/4 S+ 09:23 0:00 grep darkcoin
user 21626 0.6 0.3 1849716 130292 ? SLl May02 6:49 darkcoind -daemon
これは、歴史がどこに現れるのかという疑問を残しますか? .bash_history
のみ
本当に、これはアプリケーション自体で修正する必要があります。そして、そのようなアプリケーションはオープンソースである必要がありますので、アプリ自体で問題を修正することはオプションです。この種の間違いを犯すセキュリティ関連のアプリケーションは他の間違いもするかもしれないので、私はそれを信用しません。
しかし、別の方法を求めていたので、ここに1つあります。
#define _GNU_SOURCE
#include <dlfcn.h>
int __libc_start_main(
int (*main) (int, char * *, char * *),
int argc, char * * ubp_av,
void (*init) (void),
void (*fini) (void),
void (*rtld_fini) (void),
void (* stack_end)
)
{
int (*next)(
int (*main) (int, char * *, char * *),
int argc, char * * ubp_av,
void (*init) (void),
void (*fini) (void),
void (*rtld_fini) (void),
void (* stack_end)
) = dlsym(RTLD_NEXT, "__libc_start_main");
ubp_av[argc - 1] = "secret password";
return next(main, argc, ubp_av, init, fini, rtld_fini, stack_end);
}
これをコンパイルする
gcc -O2 -fPIC -shared -o injectpassword.so injectpassword.c -ldl
次に、プロセスを実行します
LD_PRELOAD=$PWD/injectpassword.so darkcoind masternode start fakepasshrase
インターポーザライブラリは、アプリケーションのmain
関数が実行される前にこのコードを実行します。 mainの呼び出しで、最後のコマンドライン引数を実際のパスワードに置き換えます。ただし、/proc/*/cmdline
に出力される(したがって、ps
などのツールで表示される)コマンドラインには、偽の引数が含まれます。明らかに、ソースコードとそれからコンパイルしたライブラリを自分だけが読めるようにする必要があるので、chmod 0700
ディレクトリで操作するのが最善です。また、パスワードはコマンド呼び出しの一部ではないため、bashの履歴も安全です。
より複雑なことを行う場合は、ランタイムライブラリが適切に初期化される前に__libc_start_main
が実行されることに注意してください。ですから、絶対に必要でない限り、関数呼び出しを避けることをお勧めします。心ゆくまで関数を呼び出すことができるようにしたい場合は、main
自体が呼び出される直前に、すべての初期化が完了した後で呼び出してください。次の例では、指摘したGrubermenschに感謝する必要があります コマンドライン引数として渡されたパスワードを非表示にする方法 は、getpass
に注目しました。
#define _GNU_SOURCE
#include <dlfcn.h>
#include <unistd.h>
static int (*real_main) (int, char * *, char * *);
static int my_main(int argc, char * * argv, char * * env) {
char *pass = getpass(argv[argc - 1]);
if (pass == NULL) return 1;
argv[argc - 1] = pass;
return real_main(argc, argv, env);
}
int __libc_start_main(
int (*main) (int, char * *, char * *),
int argc, char * * ubp_av,
void (*init) (void),
void (*fini) (void),
void (*rtld_fini) (void),
void (* stack_end)
)
{
int (*next)(
int (*main) (int, char * *, char * *),
int argc, char * * ubp_av,
void (*init) (void),
void (*fini) (void),
void (*rtld_fini) (void),
void (* stack_end)
) = dlsym(RTLD_NEXT, "__libc_start_main");
real_main = main;
return next(my_main, argc, ubp_av, init, fini, rtld_fini, stack_end);
}
これによりパスワードの入力が求められるため、インターポーザーライブラリを秘密にする必要はありません。プレースホルダー引数はパスワードプロンプトとして再利用されるため、次のように呼び出します。
LD_PRELOAD=$PWD/injectpassword.so darkcoind masternode start "Password: "
別の代替手段は、ファイル記述子(たとえばgpg --passphrase-fd
が行うような)、またはx11-ssh-askpass
などからパスワードを読み取ります。
歴史だけではありません。 psの出力にも表示されます。
そのソフトウェアを書いた人はだれでも掛けられ、引き分けられ、四分の一にされるべきです。ソフトウェアに関係なく、コマンドラインでパスワードを入力する必要はありません。
デーモンプロセスの場合、それはさらに許されません...
ソフトウェア自体のrm -fの他に、これに対する解決策はわかりません。正直なところ、仕事を成し遂げるために他のソフトウェアを見つけてください。そのようなジャンクを使用しないでください。
これにより、ps
出力がクリアされます。
非常に注意してください:これはアプリケーションを破壊する可能性があります。あなたはここにドラゴンがいると警告されています。
今、あなたはこれらの悲惨な警告について正式に通知されています。これにより、ps
に表示される出力がクリアされます。履歴は消去されず、bashジョブの履歴も消去されません(myprocess myargs &
などのプロセスの実行など)。しかし、ps
は引数を表示しなくなります。
#!/usr/bin/python
import os, sys
import re
PAGESIZE=4096
if __name__ == "__main__":
if len(sys.argv) < 2:
sys.stderr.write("Must provide a pid\n")
sys.exit(1)
pid = sys.argv[1]
try:
cmdline = open("/proc/{0}/cmdline".format(pid)).read(8192)
## On linux, at least, argv is located in the stack. This is likely o/s
## independent.
## Open the maps file and obtain the stack address.
maps = open("/proc/{0}/maps".format(pid)).read(65536)
m = re.search('([0-9a-f]+)-([0-9a-f]+)\s+rw.+\[stack\]\n', maps)
if not m:
sys.stderr.write("Could not find stack in process\n");
sys.exit(1)
start = int("0x"+m.group(1), 0)
end = int("0x"+m.group(2), 0)
## Open the mem file
mem = open('/proc/{0}/mem'.format(pid), 'r+')
## As the stack grows downwards, start at the end. It is expected
## that the value we are looking for will be at the top of the stack
## somewhere
## Seek to the end of the stack minus a couple of pages.
mem.seek(end-(2*PAGESIZE))
## Read this buffer to the end of the stack
stackportion = mem.read(8192)
## look for a string matching cmdline. This is pretty dangerous.
## HERE BE DRAGONS
m = re.search(cmdline, stackportion)
if not m:
## cause this is an example dont try to search exhaustively, just give up
sys.stderr.write("Could not find command line in the stack. Giving up.")
sys.exit(1)
## Else, we got a hit. Rewind our file descriptor, plus where we found the first argument.
mem.seek(end-(2*PAGESIZE)+m.start())
## Additionally, we'll keep arg0, as thats the program name.
arg0len = len(cmdline.split("\x00")[0]) + 1
mem.seek(arg0len, 1)
## lastly overwrite the remaining region with nulls.
writeover = "\x00" * (len(cmdline)-arg0len)
mem.write(writeover)
## cleanup
mem.close()
except OSError, IOError:
sys.stderr.write("Cannot find pid\n")
sys.exit(1)
chmod +x
を保存して、プログラムを起動します。次に、./whatever <pidoftarget>
を実行します。これが機能する場合、出力は生成されません。失敗すると、何かについて文句を言い、終了します。
Rootまたは必要なユーザーのみがアクセスできるファイルから引数を渡すことができますか?
コンソールにパスワードを入力するのは大変ですが、最後の手段として、行をスペースで始めて履歴に表示されないようにしてください。
たぶんこれは動作しますか?
darkcoind masternode start `cat password.txt`
残念ながら、darkcoind
コマンドがパスワードをコマンドライン引数として期待している場合、ps
などのユーティリティを介して公開されます。唯一の実際の解決策は 開発者を教育する です。
ps
の公開は避けられないかもしれませんが、少なくともパスワードがシェルの履歴ファイルに書き出されないようにすることができます。
$ xargs darkcoind masternode start
password⏎
CtrlD
履歴ファイルはxargs darkcoind masternode start
、パスワードではありません。
ビットコインの場合、公式の開発者の回答は、提供されているpython contrib/bitrpc/bitrpc.py
のラッパーを使用することです( github ):
たとえば、コマンド
walletpassphrase
を使用すると、安全な方法でパスワードを要求します。bitcoin-cli
にインタラクティブ機能を追加する予定はありません。
そして:
bitcoin-cli
は現状のままであり、インタラクティブな機能はありません。
出典: #2318
ウォレットのロックを解除します。
$ python bitrpc.py walletpassphrase
パスフレーズを変更:
$ python bitrpc.py walletpassphrasechange
https://github.com/bitcoin/bitcoin/tree/master/contrib/bitrpc
Darkcoinの場合、それはアナログで動作します:
https://github.com/darkcoin/darkcoin/tree/master/contrib/bitrpc
他の人が述べたように、履歴から情報を隠すためにシェルの履歴コントロールを調べてください。
しかし、まだ誰も提案していないように見えることの1つは、hidepid
パラメータを使用して/proc
をマウントすることです。次のように、/proc
の/etc/fstab
行を変更して、hidepid
を含めるようにしてください。
# <file system> <mount point> <type> <options> <dump> <pass>
proc /proc proc defaults,hidepid=2 0 0
新しいシェルプロセスからコマンドを実行することにより、シェルの履歴にパスワードが含まれないようにすることができ、その後すぐに終了します。例えば:
bash$ sh
sh$ darkcoind masternode start 'correct horse battery staple'
sh$ exit
bash$
sh
が設定されていることを確認してくださいnotその履歴をファイルに保存します。
もちろん、これはps
にパスワードが表示されるなど、他の問題には対処しません。 darkcoind
プログラム自体がps
から情報を隠す方法はあると思いますが、それは脆弱性の時間枠を短くするだけです。