curl 如何保护密码不出现在 ps 输出中?

curl 如何保护密码不出现在 ps 输出中?

我前段时间注意到,curl作为命令行参数给出的用户名和密码不会出现在ps输出中(尽管它们当然可能出现在您的 bash 历史记录中)。

它们同样不会出现在/proc/PID/cmdline.

(不过,可以导出组合的用户名/密码参数的长度。)

演示如下:

[root@localhost ~]# nc -l 80 &
[1] 3342
[root@localhost ~]# curl -u iamsam:samiam localhost &
[2] 3343
[root@localhost ~]# GET / HTTP/1.1
Authorization: Basic aWFtc2FtOnNhbWlhbQ==
User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.15.3 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
Host: localhost
Accept: */*



[1]+  Stopped                 nc -l 80
[root@localhost ~]# jobs
[1]+  Stopped                 nc -l 80
[2]-  Running                 curl -u iamsam:samiam localhost &
[root@localhost ~]# ps -ef | grep curl
root      3343  3258  0 22:37 pts/1    00:00:00 curl -u               localhost
root      3347  3258  0 22:38 pts/1    00:00:00 grep curl
[root@localhost ~]# od -xa /proc/3343/cmdline 
0000000    7563    6c72    2d00    0075    2020    2020    2020    2020
          c   u   r   l nul   -   u nul  sp  sp  sp  sp  sp  sp  sp  sp
0000020    2020    2020    0020    6f6c    6163    686c    736f    0074
         sp  sp  sp  sp  sp nul   l   o   c   a   l   h   o   s   t nul
0000040
[root@localhost ~]# 

这种效果是如何实现的呢? 它在源代码中的某个地方吗curl? (我认为它是一个curl功能,而不是一个ps功能?或者它是某种内核功能?)


还:这可以从二进制可执行文件的源代码外部实现吗? 例如,通过使用 shell 命令,可能与 root 权限结合使用?

换句话说,我是否可以以某种方式掩盖我传递给某些人的参数出现在输出中/proc或输出中ps(我认为是同一件事)随意的外壳命令? (我猜这个问题的答案是“不”,但似乎值得包括这个额外的半个问题。)

答案1

当内核执行一个进程时,它会将命令行参数复制到属于该进程的读写内存(在堆栈上,至少在 Linux 上)。该进程可以像写入任何其他内存一样写入该内存。当ps显示参数时,它会读回进程内存中该特定地址处存储的所有内容。大多数程序都会保留原始参数,但可以更改它们。这POSIX 描述ps指出

未指定所表示的字符串是参数列表的版本(因为它在启动时传递给命令),还是参数的版本(因为它们可能已被应用程序修改)。应用程序不能依赖于能够修改其参数列表并将该修改反映在 ps 的输出中。

之所以提到这一点,是因为大多数 UNIX 变体确实反映了这一变化,但其他类型操作系统上的 POSIX 实现可能不会。

此功能的用途有限,因为进程无法进行任意更改。至少,参数的总长度不能增加,因为程序无法更改ps获取参数的位置,也不能将区域扩展到超出其原始大小。通过在末尾放置空字节可以有效地减少长度,因为参数是 C 风格的空终止字符串(这与末尾有一堆空参数没有区别)。

如果你真的想挖掘,你可以查看开源实现的源代码。在 Linux 上, 的来源ps并不有趣,您所看到的只是它从进程文件系统, 在。生成这个文件内容的代码在内核中,在/proc/PID/cmdlineproc_pid_cmdline_readfs/proc/base.c。进程内存的一部分(通过 访问access_remote_vm)从地址mm->arg_startmm->arg_end;这些地址在进程启动时就记录在内核中,之后无法更改。

某些守护进程使用此功能来反映其状态,例如,它们将其更改为类似or或 的argv[1]字符串。许多 UNIX 变体都有一个startingavailableexitingsetproctitle函数来执行此操作。某些程序使用此功能来隐藏机密数据。请注意,这是有限的用途,因为命令行参数在进程启动时是可见的。

大多数高级语言将参数复制到字符串对象,并且不提供修改原始存储的方法。下面是一个 C 程序,它通过argv直接更改元素来演示这种能力。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
    int i;
    system("ps -p $PPID -o args=");
    for (i = 0; i < argc; i++)
    {
        memset(argv[i], '0' + (i % 10), strlen(argv[i]));
    }
    system("ps -p $PPID -o args=");
    return 0;
}

示例输出:

./a.out hello world
0000000 11111 22222

您可以argv在curl源代码中看到修改。卷曲定义了一个函数cleanargsrc/tool_paramhlp.c它用于使用 更改所有空格的参数memset。在src/tool_getparam.c此函数中使用了几次,例如通过编辑用户密码。由于该函数是从参数解析中调用的,因此它发生在curl调用的早期,但在此发生之前转储命令行仍然会显示任何密码。

由于参数存储在进程自己的内存中,因此除非使用调试器,否则无法从外部更改它们。

答案2

其他答案很好地回答了这个问题。具体回答“这种效果是如何实现的呢?它在curl源代码中的某个地方吗?”:

在里面curl 源代码的参数解析部分,该-u选项的处理方式如下:

    case 'u':
      /* user:password  */
      GetStr(&config->userpwd, nextarg);
      cleanarg(nextarg);
      break;

还有cleanarg()函数已定义如下:

void cleanarg(char *str)
{
#ifdef HAVE_WRITABLE_ARGV
  /* now that GetStr has copied the contents of nextarg, wipe the next
   * argument out so that the username:password isn't displayed in the
   * system process list */
  if(str) {
    size_t len = strlen(str);
    memset(str, ' ', len);
  }
#else
  (void)str;
#endif
}

因此,我们可以明确地看到,中的用户名:密码参数argv被空格覆盖,如其他答案所述。

答案3

进程不仅可以读取其参数,还可以写入它们。

我不是开发人员,所以我不熟悉这些东西,但从外部可以使用类似于更改环境参数的方法:

https://stackoverflow.com/questions/205064/is-there-a-way-to-change-another-processs-environment-variables

相关内容