Làm thế nào để ẩn mật khẩu được truyền dưới dạng đối số dòng lệnh?


43

Tôi đang chạy một trình nền phần mềm yêu cầu một số hành động nhất định để nhập cụm mật khẩu để mở khóa một số tính năng giống như:

$ darkcoind masternode start <mypassphrase>

Bây giờ tôi có một số mối quan tâm bảo mật trên máy chủ debian không đầu của tôi.

Bất cứ khi nào tôi tìm kiếm lịch sử bash của tôi chẳng hạn với Ctrl+Rtôi có thể thấy mật khẩu siêu mạnh này. Bây giờ tôi tưởng tượng máy chủ của tôi bị xâm nhập và một số kẻ xâm nhập có quyền truy cập shell và có thể chỉ cần Ctrl+Rtìm cụm mật khẩu của tôi trong lịch sử.

Có cách nào để nhập cụm mật khẩu mà không được hiển thị trong lịch sử bash ps, /prochoặc bất cứ nơi nào khác không?


Cập nhật 1 : Không có mật khẩu cho daemon sẽ gây ra lỗi. Đây không phải là lựa chọn.


Cập nhật 2 : Đừng bảo tôi xóa phần mềm hoặc các gợi ý hữu ích khác như treo các nhà phát triển. Tôi biết đây không phải là một ví dụ thực tiễn tốt nhất nhưng phần mềm này dựa trên bitcoin và tất cả các máy khách dựa trên bitcoin là một loại máy chủ json rpc lắng nghe các lệnh này và vấn đề bảo mật đã biết vẫn đang được thảo luận ( a , b , c ) .


Cập nhật 3 : Trình nền đã được khởi động và chạy bằng lệnh

$ darkcoind -daemon

Làm pschỉ hiển thị lệnh khởi động.

$ 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

Vì vậy, việc truyền các lệnh với cụm mật khẩu hoàn toàn không hiển thị trong pshoặc /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

Điều này để lại câu hỏi lịch sử hiện lên ở đâu? Chỉ trong .bash_history?


1
Câu hỏi đầu tiên phải là: điều gì xảy ra nếu bạn khởi động trình nền mà không có đối số cụm mật khẩu. Nó chỉ nhắc cho nó?
MadHatter hỗ trợ Monica

31
Tôi không nghĩ rằng có một câu trả lời sẽ làm việc. Không có khả năng nhắc một cụm mật khẩu là một thiếu sót lớn trong trình nền. Nếu đó là phần mềm miễn phí, hãy lập trình viên và sửa nó; đừng quên công bố những thay đổi của bạn Nếu đó là phần mềm độc quyền, hãy gọi cho nhà cung cấp và hét vào mặt họ (điều đó sẽ không khắc phục được gì, nhưng nó sẽ giúp bạn cảm thấy tốt hơn).
MadHatter hỗ trợ Monica

4
Kiểm tra tài liệu của bạn, nó có thể hỗ trợ đọc mật khẩu đó từ một biến môi trường hệ thống.
Elliott Frisch

3
Ngay cả khi mật khẩu không được cung cấp trên dòng lệnh cho daemon, vẫn có vấn đề khi cung cấp nó trên dòng lệnh của bất kỳ lệnh nào khác. Nó chỉ hiển thị trong đầu ra ps trong một thời gian rất ngắn, nhưng một quá trình chạy trong nền vẫn có thể nhận nó. Nhưng tất nhiên nó vẫn còn giá trị làm cho việc lấy mật khẩu trở nên khó khăn hơn.
kasperd

2
Nhìn vào câu trả lời cho câu hỏi này , họ giải quyết chính xác vấn đề này.
dotancohen

Câu trả lời:


68

Thực sự, điều này nên được sửa trong chính ứng dụng. Và các ứng dụng như vậy phải là nguồn mở, để việc khắc phục sự cố trong chính ứng dụng phải là một tùy chọn. Một ứng dụng liên quan đến bảo mật gây ra loại lỗi này cũng có thể gây ra các lỗi khác, vì vậy tôi sẽ không tin tưởng nó.

Giao diện đơn giản

Nhưng bạn đã yêu cầu một cách khác, vì vậy đây là một:

#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);
}

Biên dịch cái này với

gcc -O2 -fPIC -shared -o injectpassword.so injectpassword.c -ldl

sau đó chạy quy trình của bạn với

LD_PRELOAD=$PWD/injectpassword.so darkcoind masternode start fakepasshrase

Thư viện bộ chuyển đổi sẽ chạy mã này trước khi mainchức năng từ ứng dụng của bạn được thực thi. Nó sẽ thay thế đối số dòng lệnh cuối cùng bằng mật khẩu thực tế trong cuộc gọi đến chính. Tuy nhiên, dòng lệnh như được in /proc/*/cmdline(và do đó được nhìn thấy bởi các công cụ như ps) vẫn sẽ chứa đối số giả mạo. Rõ ràng là bạn phải tạo mã nguồn và thư viện mà bạn biên dịch từ nó chỉ có thể đọc được cho chính bạn, để hoạt động tốt nhất trong một chmod 0700thư mục. Và vì mật khẩu không phải là một phần của lệnh gọi, lịch sử bash của bạn cũng an toàn.

Giao diện nâng cao hơn

Nếu bạn muốn làm bất cứ điều gì phức tạp hơn, bạn nên nhớ rằng __libc_start_mainsẽ được thực thi trước khi thư viện thời gian chạy được khởi tạo đúng cách. Vì vậy, tôi khuyên bạn nên tránh bất kỳ cuộc gọi chức năng nào trừ khi chúng thực sự cần thiết. Nếu bạn muốn có thể gọi các chức năng đến nội dung trái tim của bạn, hãy đảm bảo bạn làm như vậy ngay trước khi mainnó được gọi, sau khi tất cả việc khởi tạo được thực hiện. Đối với ví dụ sau tôi phải cảm ơn Grubermensch, người đã chỉ ra cách ẩn mật khẩu được truyền dưới dạng đối số dòng lệnh khiến getpasstôi chú ý.

#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);
}

Điều này nhắc nhập mật khẩu, vì vậy bạn không còn phải giữ bí mật thư viện của người sử dụng. Đối số giữ chỗ được sử dụng lại làm dấu nhắc mật khẩu, vì vậy hãy gọi như thế này

LD_PRELOAD=$PWD/injectpassword.so darkcoind masternode start "Password: "

Một cách khác sẽ đọc mật khẩu từ một bộ mô tả tệp (ví dụ như gpg --passphrase-fd), hoặc từ x11-ssh-askpass, hoặc bất cứ điều gì.


4
Mặc dù tôi không hiểu và không thể kiểm tra mã, tôi hiểu ý chính của nó và đây có vẻ là một câu trả lời thực sự và phải là câu trả lời hàng đầu.
Mark Henderson

Điều này thực sự tuyệt vời.
Waqar Lim

Tuyệt vời. Theo như tôi có thể nói điều này nên làm việc. Tất nhiên bạn cần truy cập vào nguồn và có thể biên dịch lại. Mật khẩu có thể đọc được trong nguồn và (các) tệp được biên dịch nếu bạn sử dụng "chuỗi" hoặc một cái gì đó tương tự để đảm bảo rằng không ai khác có thể đọc chúng.
Tonny

1
Có thể lấy mật khẩu trên STDIN và vẫn có công việc này, giúp loại bỏ stringslỗ hổng. Xem SO: Ẩn mật khẩu nhập trên thiết bị đầu cuối .
Grubermensch

1
@ mulg0r: Tiêu chuẩn bên ngoài "C" nên thực hiện thủ thuật triệt tiêu tên xáo trộn cho chức năng có liên quan, cụ thể là __libc_start_main.
MvG

28

Đó không chỉ là lịch sử. Nó sẽ hiển thị trong đầu ra ps là tốt.

Bất cứ ai đã viết phần mềm đó nên được treo, vẽ và quý. Việc KHÔNG phải cung cấp mật khẩu trên dòng lệnh bất kể đó là phần mềm nào.
Đối với một quy trình daemon, nó thậm chí còn không thể tha thứ hơn ...

Ngoài rm -f trên chính phần mềm, tôi không biết bất kỳ giải pháp nào cho việc này. Thành thật: Tìm phần mềm khác để hoàn thành công việc. Đừng sử dụng những thứ linh tinh như vậy.


9
Cảm ơn vì không hữu ích chút nào. Đây là một vấn đề bảo mật được thảo luận lâu , vẫn chưa được giải quyết và tôi cần một cách giải quyết tốt hơn rm -fbây giờ.
Waqar Lim

17
Thật ra, anh ấy rất hữu ích. Nếu bạn chuyển cụm mật khẩu làm đối số, nó sẽ hiển thị ps. Vì vậy, cho đến khi nhà phát triển có thể khắc phục điều đó, anh ấy đề nghị sử dụng một cái gì đó khác.
Safado

3
Sau đó, bạn tốt hơn nên bắt đầu viết một hệ điều hành khác. Hiện tại KHÔNG có giải pháp nào khác mà tôi biết. Bởi Chúa tôi ước có một. Bạn không phải là người duy nhất gặp vấn đề này.
Tonny

8
vertoe, không nhận được snippy. Bạn có thể yêu cầu một cách để vượt qua nó trên những mẩu giấy nhỏ, nhưng điều đó không có nghĩa là bất kỳ cách nào như vậy tự động tồn tại. read_x vẫn ổn, nhưng vẫn hiển thị cụm mật khẩu thông qua ví dụ ps, vì vậy nó không tốt hơn rmgiải pháp.
MadHatter hỗ trợ Monica

7
Trước khi bạn đi và ném +1 khác vào câu trả lời không thực sự này và phàn nàn rằng điều này là không thể, tôi khuyên bạn nên xem lại câu trả lời của MvG bên dưới
Mark Henderson

19

Điều này sẽ xóa psđầu ra.

ĐƯỢC RẤT TUYỆT VỜI : Điều này có thể phá vỡ ứng dụng. Bạn đang cảnh báo rằng đây là những con rồng.

  • Các tiến trình nước ngoài không nên loay hoay trong bộ nhớ tiến trình.
  • Nếu quá trình dựa vào khu vực này để lấy mật khẩu, bạn có thể phá vỡ ứng dụng của mình.
  • Làm điều này có thể làm hỏng bất kỳ dữ liệu làm việc nào bạn có trong quá trình đó.
  • Đây là một hack điên rồ.

Bây giờ bạn được thông báo hợp lệ về những cảnh báo nghiêm trọng này. Điều này sẽ xóa đầu ra được hiển thị trong ps. Nó sẽ không xóa lịch sử của bạn, cũng sẽ không xóa lịch sử công việc bash (chẳng hạn như chạy quá trình như thế nào myprocess myargs &). Nhưng pssẽ không còn hiển thị các đối số.

#!/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)

Gọi chương trình bằng cách lưu nó, chmod +xnó. Sau đó làm ./whatever <pidoftarget> Nếu điều này hoạt động, nó sẽ không tạo ra đầu ra. Nếu thất bại, nó sẽ phàn nàn về một cái gì đó và bỏ.


18
. . . Điều này vừa sáng tạo vừa đáng sợ.
voretaq7

TUẦN! Giờ tôi sợ rồi.
Janne Pikkarainen

Yikkes, nó có thể hoạt động ... Tôi không chắc nó giống như AppArmor sẽ nắm bắt được điều này? Ngoài ra, virusscanner có thể có khả năng nắm bắt điều này và gây ra sự tàn phá bằng cách chặn tài khoản vi phạm sẽ là 'root'. Thực sự có những con rồng ....
Tonny

@Tonny Đối với các miền được bảo vệ, SELinux sẽ ngăn chặn điều này. Các quyền Unix cơ bản (DAC) của bạn thiếu đủ độ chi tiết của chủ thể để đưa ra bất kỳ sự bảo vệ nào từ hành vi này (cho phép sửa đổi bộ nhớ quy trình trong cùng một UID). Dù sao, nó không phải là một lỗi - đó là một tính năng. Tôi tin rằng đây là cách gdbcó thể sửa đổi bộ nhớ của các quy trình đang chạy (với độ chính xác phẫu thuật cao hơn nhiều so với điều này tôi có thể thêm vào).
Matthew Ife

11

Bạn có thể truyền đối số từ một tệp, chỉ có thể truy cập bằng root hoặc người dùng cần thiết?

Việc gõ mật khẩu trong bảng điều khiển rất LỚN, nhưng lần truy cập cuối cùng ... bắt đầu dòng của bạn bằng một khoảng trắng để nó không xuất hiện trong lịch sử.


Có một tùy chọn shell cho phép nó, nhưng tôi nghĩ nó không được bật theo mặc định.
heinrich5991

export HISTCONTROL=ignorebothbỏ qua cả hai bản sao và dòng với một không gian hàng đầu để đi vào lịch sử. Thêm nó vào .bashrc hoặc .bash_profile của bạn.
Andreas

7

Có lẽ điều này hoạt động (?):

darkcoind masternode start `cat password.txt`

3
Hoặc thậm chí darkcoind masternode start `head -1`, nếu bạn muốn nhập mật khẩu bằng tay.
kasperd

14
Cụm mật khẩu vẫn có sẵn thông qua psvà các tiện ích tương tự.
voretaq7

1
Chuyển từ một mật khẩu văn bản gốc .bash_historysang một mật khẩu văn bản gốc để password.txtđạt được cho bạn những gì, chính xác?
MikeyB

1
@MikeyB: Có một chiến thắng nhỏ: bạn sẽ không vô tình để lộ nó trong khi tìm kiếm trong lịch sử của bạn trong khi ai đó đang nhìn qua vai bạn.
MvG

1
@MikeyB, bạn có thể tạo và xóa tệp đó mỗi lần.
Rịa

4

Thật không may, nếu darkcoindlệnh của bạn mong đợi mật khẩu là đối số dòng lệnh, thì nó sẽ được hiển thị thông qua các tiện ích như ps. Giải pháp thực sự duy nhất là giáo dục các nhà phát triển .

Mặc dù việc pstiếp xúc có thể là không thể tránh khỏi, nhưng ít nhất bạn có thể giữ mật khẩu không bị ghi trong tệp lịch sử shell.

$ xargs darkcoind masternode start

password

CtrlD

Các tập tin lịch sử chỉ nên ghi lại xargs darkcoind masternode start, không phải mật khẩu.


2
Hoặc nếu bạn đang sử dụng bash, hãy đặt ignorespacevào $HISTCONTROL, và sau đó bạn có thể ngăn bất kỳ lệnh nào đi vào lịch sử shell bằng cách thêm tiền tố vào một khoảng trắng.
derobert

3

Như những người khác đã nêu, hãy nhìn vào kiểm soát lịch sử vỏ của bạn để che giấu thông tin khỏi lịch sử.

Nhưng có một điều dường như không ai đề xuất là gắn kết /procvới hidepidtham số. Hãy thử sửa đổi /procdòng của bạn /etc/fstabđể bao gồm hidepid, như thế này:

# <file system> <mount point>   <type>  <options>       <dump>  <pass>
proc            /proc           proc    defaults,hidepid=2        0       0

2

Bạn có thể giữ mật khẩu trong lịch sử shell của mình bằng cách thực thi lệnh từ quy trình shell mới, sau đó bạn sẽ chấm dứt ngay lập tức. Ví dụ:

bash$ sh
sh$ darkcoind masternode start 'correct horse battery staple'
sh$ exit
bash$

Đảm bảo shđược cấu hình để không lưu lịch sử của nó trong một tệp.

Tất nhiên, điều này không giải quyết các vấn đề khác, chẳng hạn như mật khẩu được hiển thị trong ps. Tôi tin rằng có những cách để darkcoindchính chương trình che giấu thông tin ps, nhưng điều đó chỉ rút ngắn cửa sổ dễ bị tổn thương.


1
cụm mật khẩu vẫn có sẵn thông qua psvà các tiện ích tương tự.
voretaq7

3
@ voretaq7: Vâng, như tôi đã thừa nhận rõ ràng trong đoạn cuối của câu trả lời của tôi.
Keith Thompson

3
Thật vậy - bạn là nạn nhân của bản sao bừa bãi về phía tôi :)
voretaq7

2

Đối với Bitcoin, câu trả lời chính thức của nhà phát triển là sử dụng trình bao bọc python được cung cấp trong contrib/bitrpc/bitrpc.py( github ):

Nó yêu cầu mật khẩu một cách an toàn nếu bạn sử dụng lệnh walletpassphrasechẳng hạn. Không có kế hoạch để thêm chức năng tương tác bitcoin-cli.

và:

bitcoin-cli sẽ vẫn như cũ và không đạt được chức năng tương tác.

Nguồn: # 2318

Mở khóa ví:

$ python bitrpc.py walletpassphrase

Thay đổi cụm mật khẩu:

$ python bitrpc.py walletpassphrasechange

https://github.com/bitcoin/bitcoin/tree/master/contrib/bitrpc

Đối với darkcoin, nó hoạt động anlogue:

https://github.com/darkcoin/darkcoin/tree/master/contrib/bitrpc

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.