소스 코드없이 프로그래밍 할 인수 숨기기


15

실행중인 프로그램에 민감한 인수를 숨길 필요가 있지만 소스 코드에 액세스 할 수 없습니다. 공유 서버에서이를 실행 hidepid중이므로 sudo 권한이 없기 때문에 같은 것을 사용할 수 없습니다 .

내가 시도한 몇 가지 사항은 다음과 같습니다.

  • export SECRET=[my arguments]에 대한 호출 ./program $SECRET이 이어지지 만 도움이되지 않는 것 같습니다.

  • ./program `cat secret.txt`어디에 secret.txt내 주장 이 포함되어 있지만 전능하신 ps분이 내 비밀을 알아낼 수 있습니다.

관리자의 개입이없는 내 주장을 숨길 수있는 다른 방법이 있습니까?


그 특정 프로그램은 무엇입니까? 일반적인 명령이라면 어떤 명령인지 알려야합니다 (그리고 다른 접근 방법이있을 수도 있습니다)
Basile Starynkevitch

14
따라서 쉘은 환경 변수를 확장 하고 프로그램을 호출 하기 전에 명령 대체를 수행하기 때문에 진행중인 작업을 이해할 수 있습니다. ps"비밀을 알아 내기"위해 마법적인 일을하지 않습니다. 어쨌든, 합리적으로 작성된 프로그램은 대신 직접 인수로 사용하지 않고 지정된 파일이나 stdin에서 비밀을 읽는 명령 행 옵션을 제공해야합니다.
jamesdlin

개인 회사에서 작성한 날씨 시뮬레이션 프로그램을 실행하고 있습니다. 소스 코드를 공유하지 않으며 문서에서 파일의 비밀을 공유 할 수있는 방법도 제공하지 않습니다. 여기서 옵션에서 벗어날 수 있습니다.
MS

답변:


25

여기 에서 설명하는 것처럼 Linux는 프로그램의 데이터 공간에 프로그램의 인수를 넣고이 영역의 시작 부분에 대한 포인터를 유지합니다. 이것은 ps프로그램 인수를 찾고 보여주기 위해 사용되는 것입니다 .

데이터는 프로그램 공간에 있으므로 조작 할 수 있습니다. 프로그램 자체를 변경하지 않고이 작업을 수행하려면 프로그램 main()의 실제 메인 전에 호출 될 함수 로 shim을로드 해야합니다. 이 심은 실제 인수를 새 공간에 복사 한 다음 원래 인수를 덮어 ps써서 nuls 만 표시 할 수 있습니다.

다음 C 코드가이를 수행합니다.

/* /unix//a/403918/119298
 * capture calls to a routine and replace with your code
 * gcc -Wall -O2 -fpic -shared -ldl -o shim_main.so shim_main.c
 * LD_PRELOAD=/.../shim_main.so theprogram theargs...
 */
#define _GNU_SOURCE /* needed to get RTLD_NEXT defined in dlfcn.h */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <dlfcn.h>

typedef int (*pfi)(int, char **, char **);
static pfi real_main;

/* copy argv to new location */
char **copyargs(int argc, char** argv){
    char **newargv = malloc((argc+1)*sizeof(*argv));
    char *from,*to;
    int i,len;

    for(i = 0; i<argc; i++){
        from = argv[i];
        len = strlen(from)+1;
        to = malloc(len);
        memcpy(to,from,len);
        memset(from,'\0',len);    /* zap old argv space */
        newargv[i] = to;
        argv[i] = 0;
    }
    newargv[argc] = 0;
    return newargv;
}

static int mymain(int argc, char** argv, char** env) {
    fprintf(stderr, "main argc %d\n", argc);
    return real_main(argc, copyargs(argc,argv), env);
}

int __libc_start_main(pfi main, int argc,
                      char **ubp_av, void (*init) (void),
                      void (*fini)(void),
                      void (*rtld_fini)(void), void (*stack_end)){
    static int (*real___libc_start_main)() = NULL;

    if (!real___libc_start_main) {
        char *error;
        real___libc_start_main = dlsym(RTLD_NEXT, "__libc_start_main");
        if ((error = dlerror()) != NULL) {
            fprintf(stderr, "%s\n", error);
            exit(1);
        }
    }
    real_main = main;
    return real___libc_start_main(mymain, argc, ubp_av, init, fini,
            rtld_fini, stack_end);
}

개입 main()할 수는 없지만 __libc_start_mainmain을 호출 하는 표준 C 라이브러리 함수에 개입 할 수 있습니다 . shim_main.c시작시 주석에 언급 된 대로이 파일 을 컴파일 하고 표시된대로 실행하십시오. printf코드에 a 를 남겼 으므로 실제로 호출되는지 확인하십시오. 예를 들어

LD_PRELOAD=/tmp/shim_main.so /bin/sleep 100

그런 다음 a를 수행하면 ps빈 명령과 args가 표시됩니다.

명령 인수가 표시 될 수있는 시간은 여전히 ​​적습니다. 이를 피하기 위해 예를 들어 파일에서 비밀을 읽도록 shim을 변경하고 프로그램에 전달 된 인수에 추가 할 수 있습니다.


12
그러나 /proc/pid/cmdline비밀 키를 표시 하는 짧은 창이 여전히 있습니다 ( curl암호를 숨기려고 할 때 명령 줄에 지정된 암호 와 동일 ). LD_PRELOAD를 사용하는 동안 환경에서 main이 수신하는 argv로 비밀이 복사되도록 main을 랩핑 할 수 있습니다. 전화처럼 LD_PRELOAD=x SECRET=y cmd당신이 전화 main()argv[][argv[0], getenv("SECRET")]
스테판 Chazelas가

를 통해 볼 수있는 환경을 사용하여 비밀을 숨길 수는 없습니다 /proc/pid/environ. 이것은 인수와 같은 방식으로 덮어 쓸 수 있지만 동일한 창을 남깁니다.
meuh

11
/proc/pid/cmdline공개 /proc/pid/environ되지 않습니다. pssetuid 실행 파일이 프로세스의 환경을 노출시키는 일부 시스템이 있었지만 요즘에는 당신이 올 것이라고 생각하지 않습니다. 환경은 일반적으로 충분히 안전하다고 간주됩니다 . 동일한 euid를 가진 프로세스에서 감시하는 것은 안전하지 않지만 어쨌든 동일한 euid로 프로세스의 메모리를 읽을 수 있기 때문에 할 수있는 일이 많지 않습니다.
Stéphane Chazelas

4
@ StéphaneChazelas : 환경을 사용하여 비밀을 전달하는 main경우 랩핑 된 프로그램 의 메소드로 전달하는 래퍼 도 환경 변수를 제거하여 실수로 자식 프로세스로 누출되는 것을 방지합니다. 또는 랩퍼가 파일에서 모든 명령 행 인수를 읽을 수 있습니다.
David Foerster

@DavidFoerster, 좋은 지적입니다. 나는 그것을 고려하기 위해 대답을 업데이트했습니다.
Stéphane Chazelas

16
  1. 해당 응용 프로그램의 명령 행 인터페이스 문서를 읽으십시오. 인수 대신 파일에서 비밀을 제공하는 옵션이있을 수도 있습니다.

  2. 실패하면 비밀을 제공 할 안전한 방법이 없다는 이유로 애플리케이션에 대해 버그 보고서를 제출하십시오.

  3. 당신은 항상 당신의 특정 요구에 대한 meuh의 대답 으로 솔루션을 신중하게 (!) 조정할 수 있습니다 . 스테판의 의견 과 후속 조치 를 특별히 고려하십시오 .


12

프로그램을 작동시키기 위해 인수를 프로그램에 전달해야하는 경우, hidepidprocfs에서 사용할 수 없다면 무엇을 하든지 운이 나빠질 것입니다.

bash 스크립트라고 언급 했으므로 bash는 컴파일 된 언어가 아니기 때문에 이미 소스 코드를 사용할 수 있어야합니다.

실패 하면 프로세스를 시작 하거나 사용하여 비슷한 cmdline을 다시 작성 하거나 /를 사용하여 재생할 있지만 다음을 수행 할 수 있습니다 .gdbargcargv

  1. 초기에 프로그램 인수를 변경하기 전에 여전히 노출시키기 때문에 이것은 안전하지 않습니다.
  2. 당신이 그것을 작동시킬 수 있다고해도 이것은 해키입니다. 나는 그것에 의존하지 않는 것이 좋습니다

소스 코드를 얻거나 공급 업체에 문의하여 코드를 수정하는 것이 좋습니다. POSIX 운영 체제에서 명령 행에 비밀을 제공하는 것은 보안 조작과 호환되지 않습니다.


11

프로세스가 execve()시스템 호출을 통해 명령을 실행하면 메모리가 지워집니다. 실행에서 몇 가지 정보를 전달하기 위해, execve(): 시스템 호출이 개 그의 인수 소요 argv[]envp[]배열을.

두 개의 문자열 배열입니다.

  • argv[] 인수를 포함
  • envp[]환경 변수 정의를 var=value형식 으로 문자열로 포함 합니다 (일반적으로).

할 때 :

export SECRET=value; cmd "$SECRET"

(여기서 매개 변수 확장에 누락 된 따옴표를 추가했습니다).

에서 및로 전달 된 cmd비밀 ( value)로 실행 중 입니다 . 될 것입니다 및 같은 . 바와 같이 어떤 일을하지 않는 것과 비밀의 값 검색 또는 그에 상당하는 환경에 넣어, 환경 변수 것은 유용하지 않습니다.argv[]envp[]argv[]["cmd", "value"]envp[][..., "PATH=/bin:...", "HOME=...", ..., "SECRET=value", "TERM=xterm", ...]cmdgetenv("SECRET")SECRET

argv[]공개 지식입니다. 의 출력에 표시 ps됩니다. envp[]요즘은 그렇지 않습니다. Linux에서는에 표시됩니다 /proc/pid/environ. ps ewwwBSD (및 psLinux 에서는 procps-ng 를 사용하여)의 출력에 표시 되지만 동일한 유효한 uid (setuid / setgid 실행 파일에 대한 추가 제한 사항)로 실행중인 프로세스에만 표시됩니다. 일부 감사 로그에 표시 될 수 있지만 해당 감사 로그는 관리자 만 액세스 할 수 있어야합니다.

간단히 말해서 실행 파일로 전달되는 환경은 프로세스의 내부 메모리와 같이 개인용이거나 최소한 개인용이어야합니다 (일부 상황에서 올바른 권한을 가진 다른 프로세스도 디버거를 사용하여 액세스 할 수 있음) 또한 디스크에 덤프됩니다.

argv[]공개 지식 이기 때문에 명령 줄에서 데이터를 비밀로하는 명령은 의도적으로 손상되었습니다.

일반적으로 비밀을 부여해야하는 명령은 환경 변수와 같은 다른 인터페이스를 제공합니다. 예를 들어 :

IPMI_PASSWORD=secret ipmitool -I lan -U admin...

또는 stdin과 같은 전용 파일 디스크립터를 통해 :

echo secret | openssl rsa -passin stdin ...

( echo내장되어 있지만의 출력에 표시되지 않습니다 ps)

또는 .netrcfor ftp및 몇 가지 다른 명령 또는

mysql --defaults-extra-file=/some/file/with/password ....

curl( @meuh가 취한 접근 방식 과 같은) 일부 응용 프로그램 argv[]은 엿보기 눈에서 받은 암호를 숨기려고합니다 (일부 시스템에서는 argv[]문자열이 저장된 메모리 부분을 덮어 씁니다 ). 그러나 그것은 실제로 도움이되지 않으며 보안에 대한 거짓 약속을합니다. 그러면 비밀을 보여줄 execve()덮어 쓰기와 사이에 창이 남습니다 ps.

예를 들어, 침입자가 curl -u user:somesecret https://...(예를 들어 cron 작업에서) 스크립트를 실행하고 있다는 것을 알고 있다면, (예를 들어 cron curl을 실행하여 sh -c 'a=a;while :; do a=$a$a;done') 사용 하는 (많은) 라이브러리 캐시에서 제거 해야합니다. 시작 속도를 늦추고 심지어 매우 비효율적 인 until grep 'curl.*[-]u' /proc/*/cmdline; do :; done것으로 내 테스트에서 해당 암호를 잡기에 충분합니다.

인수가 비밀을 명령에 전달할 수있는 유일한 방법 인 경우 시도 할 수있는 사항이 여전히있을 수 있습니다.

이전 버전의 Linux를 포함하여 일부 시스템에서는 문자열의 처음 몇 바이트 (Linux 4.1 이하에서는 4096) argv[]만 쿼리 할 수 ​​있습니다.

거기에서 할 수있는 일 :

(exec -a "$(printf %-4096s cmd)" cmd "$secret")

비밀은 처음 4096 바이트를 지났기 때문에 숨겨져있었습니다. 이제이 방법을 사용한 사람들은 4.2 이후 리눅스가 더 이상 인수 목록을 자르지 않기 때문에 후회해야한다 /proc/pid/cmdline. 또한 ps동일한 API에 사용할 수없는 FreeBSD와 같이 많은 바이트 ps를 얻는 데 많은 바이트를 넘지 않기 때문 은 아닙니다 . 그러나이 방법은 ps일반 사용자가 해당 정보를 검색 할 수있는 유일한 방법 (예 : API가 특권을 받고 ps이를 사용하기 위해 setgid 또는 setuid 인 경우) 인 시스템에서는 유효하지만 여전히 미래에 보장되지는 않습니다.

또 다른 방법은하는 것입니다 하지 의 비밀 패스 argv[]프로그램 (사용에 있지만 분사 코드 gdb또는 $LD_PRELOAD그 전에 해킹을) main()시작되었는지를 삽입에 비밀 argv[]에서 수신 execve().

LD_PRELOAD, 비의 setuid /을위한 GNU 시스템에 동적으로 링크 된 실행 파일을 setgid를 :

/* 
 * replace ***** with secret read from fd 9
 * gcc -Wall -fpic -shared -o inject_secret.so inject_secret.c -ldl 
 * LD_PRELOAD=/.../inject_secret.so cmd -p '*****' 9<<< secret
 */
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>

#define PLACEHOLDER "*****"
static char secret[1024];

int __libc_start_main(int (*main) (int, char**, char**),
                      int argc,
                      char **argv,
                      void (*init) (void),
                      void (*fini)(void),
                      void (*rtld_fini)(void),
                      void (*stack_end)){
    static int (*real_libc_start_main)() = NULL;
    int n;

    if (!real_libc_start_main) {
        real_libc_start_main = dlsym(RTLD_NEXT, "__libc_start_main");
        if (!real_libc_start_main) abort();
    }

    n = read(9, secret, sizeof(secret));
    if (n > 0) {
      int i;

      if (secret[n - 1] == '\n') secret[--n] = '\0'; 
      for (i = 1; i < argc; i++)
        if (strcmp(argv[i], PLACEHOLDER) == 0)
          argv[i] = secret;
    }

    return real_libc_start_main(main, argc, argv, init, fini,
                                rtld_fini, stack_end);
}

그때:

$ gcc -Wall -fpic -shared -o inject_secret.so inject_secret.c -ldl
$ LD_PRELOAD=$PWD/inject_secret.so  ps '*****' 9<<< "-opid,args"
  PID COMMAND
 7659 /bin/zsh
 8828 ps *****

어느 시점 ps에서도 ps -opid,args그곳 을 보여 주지 않았을 것입니다 ( -opid,args이 예에서는 비밀 임). 우리는 포인터argv[] 배열이 가리키는 포인터가리키는 것이 아니라 포인터 배열의 요소를 대체하고 있기 때문에 수정 사항이의 출력에 표시되지 않습니다 ps.

를 사용하면 gdbsetuid / setgid가 아닌 동적으로 링크 된 실행 파일 및 GNU 시스템의 경우 :

tmp=$(mktemp) && cat << EOF > "$tmp" &&
break __libc_start_main
commands 1
set argv[1]="-opid,args"
continue
end
run
EOF

gdb -n --batch-silent --return-child-result -x "$tmp" --args ps '*****'
rm -f -- "$tmp"

그래도 gdb동적으로 링크되거나 디버그 기호가있는 실행 파일에 의존하지 않고 Linux에서 ELF 실행 파일에 대해 작동해야하는 GNU 이외의 접근 방식은 다음과 같습니다.

#! /bin/sh -
# gdb+sh polyglot script to replace "*****" arguments with the content
# of the SECRET environment variable *after* execve and before calling
# the executable's main() function.
#
# Usage: SECRET=somesecret cmd --password '*****'

if ':' - ':'
then
  # running in sh
  # retrieve the start address for the executable
  start=$(
    LC_ALL=C objdump -f -- "$(command -v -- "${1?}")" |
    sed -n 's/^start address //p'
  )
  [ -n "$start" ] || exit
  # re-exec ourself with gdb.
  exec gdb -n --batch-silent --return-child-result -iex "set \$start = $start" -x "$0" --args "$@"
  exit 1
fi
end
# running in gdb
break *$start
commands 1
  # The stack on startup contains:
  # argc argv[0]... argv[argc-1] 0 envp[0] envp[1]... 0 argv[] and envp[] strings
  set $argc = *((int*)$sp)
  set $argv = &((char**)$sp)[1]
  set $envp = &($argv[$argc+1])
  set $i = 0
  while $envp[$i]
    # look for an envp[] string starting with "SECRET=". We can't use strcmp()
    # here as there's no guarantee that the debugged executable has such
    # a function
    set $e = $envp[$i]
    if $e[0] == 'S' && \
       $e[1] == 'E' && \
       $e[2] == 'C' && \
       $e[3] == 'R' && \
       $e[4] == 'E' && \
       $e[5] == 'T' && \
       $e[6] == '='
      set $secret = &($e[7])
      # replace SECRET=xxx<NUL> with SECRE=<NUL>
      set $e[5] = '='
      set $e[6] = '\0'
      # not calling loop_break as that causes a SEGV with my version of gdb
    end
    set $i = $i + 1
  end
  if $secret
    # now looking for argv[] strings being "*****" and replace them with
    # the secret identified earlier
    set $i = 0
    while $i < $argc
      set $a = $argv[$i]
      if $a[0] == '*' && \
       $a[1] == '*' && \
       $a[2] == '*' && \
       $a[3] == '*' && \
       $a[4] == '*' && \
       $a[5] == '\0'
        set $argv[$i] = $secret
      end
      set $i = $i + 1
    end
  end
  # using "continue" as "detach" causes a SEGV with my version of gdb.
  continue
end
run

정적으로 링크 된 실행 파일로 테스트 :

$ SECRET=/proc/self/cmdline ./replace_secret busybox cat '*****' | tr '\0' '\n'
/bin/busybox
cat
*****

실행 파일이 정적 인 경우 비밀을 저장하기 위해 메모리를 할당 할 수있는 확실한 방법이 없으므로 프로세스 메모리에 이미있는 다른 곳에서 비밀을 가져와야합니다. 이것이 바로 환경이 명백한 선택 인 이유입니다. 또한 프로세스가 어떤 이유로 환경을 덤프하거나 신뢰할 수없는 응용 프로그램을 실행하기로 결정한 경우 누출을 피하기 위해 SECRET프로세스에 해당 env var를 숨 깁니다 (로 변경하여 SECRE=).

Solaris 11에서도 작동합니다 (제공된 gdb 및 GNU binutils가 설치되어있는 경우 이름 objdump을 변경해야 할 수도 있음 gobjdump).

FreeBSD의에서 교체 (적어도 x86_64에, 나는 GDB (8.0.1)이있는 스택에 GDB가 버그)가있을 수 있습니다 제안 상호 작용하는 경우 확인 될 그 첫 24 바이트 (16되지 무엇을 해요) argcargv정의를 와:

set $argc = *((int*)($sp + 24))
set $argv = &((char**)$sp)[4]

( gdb시스템과 함께 제공되는 버전이 고대 버전이므로 패키지 / 포트 를 설치해야 할 수도 있습니다 ).


다시 (여기서 매개 변수 확장에 누락 된 따옴표를 추가했습니다) : 따옴표를 사용하지 않는 것이 무엇입니까? 실제로 차이가 있습니까?
yukashima huksay 2018 년


3

당신이 할 수있는 일은

 export SECRET=somesecretstuff

그런 다음 ./programC 로 작성한다고 가정 하거나 다른 사람이 변경하고 개선 할 수 있다고 가정하면 해당 프로그램에서 getenv (3) 를 사용하십시오.

char* secret= getenv("SECRET");

그리고 export 당신은 방금 ./program같은 쉘에서 실행 합니다. 또는 환경 변수 이름을 전달할 수 있습니다 ( ./program --secret-var=SECRET등 을 실행하여 ...).

ps비밀에 대해서는 말하지 않지만 proc (5) 는 여전히 많은 정보를 제공 할 수 있습니다 (적어도 동일한 사용자의 다른 프로세스에).

프로그램 인수를 전달하는 더 나은 방법을 디자인 하는데도 도움 됩니다.

글 러빙 및 쉘의 역할에 대한 자세한 설명은 이 답변 을 참조하십시오 .

아마도 당신 program은 평범한 프로그램 인수 (민감한 정보를 처리하려는 경우 반드시 있어야 함)보다 데이터를 얻는 (또는 프로세스 간 통신을 보다 현명하게 사용하는) 다른 방법이있을 수 있습니다 . 설명서를 읽으십시오. 또는 해당 프로그램을 악용하고있을 수 있습니다 (비밀 데이터를 처리하지 않음).

비밀 데이터를 숨기는 것은 정말 어렵습니다. 프로그램 인수를 통해 전달하지 않으면 충분하지 않습니다.


5
그가조차하지 않는 것을 질문에서 꽤 분명 소스 코드 를 들어 ./program이 답변의 첫 번째 절반은 관련하지 않는 것, 그래서.
파이프
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.