curl如何防止密码出现在ps输出中?


68

我前段时间注意到,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权限?

换句话说,我能以某种方式掩盖传递给任意 shell命令的输出中/procps输出中的参数(我想是一样)吗?(我想对此的答案是“否”,但似乎值得将这个额外的半个问题包括在内。)



16
没有答案,但是请注意,这种方法并不安全。在程序启动和清除参数字符串之间有一个竞赛窗口,在此期间任何用户都可以读取密码。不要在命令行上接受敏感密码。
R.,

1
松散相关:环境变量属于谁?以及实践中是否有人environ直接使用访问环境变量?—最重要的是:参数列表与环境变量列表一样,位于读/写用户进程内存中,并且可以由用户进程修改。
斯科特,

1
@ JPhi1618,只需将grep模式的第一个字符设为字符类。EGps -ef | grep '[c]url'
通配符

1
@mpy,不是很复杂。有些正则表达式匹配自己,有些则不匹配。 curl匹配curl[c]url不匹配[c]url。如果您需要更多细节,请提出一个新问题,我很乐意回答。
通配符

Answers:


78

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

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

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

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

如果您真的想挖掘,可以查看开源实现的源代码。在Linux上,源ps不感兴趣,你会看到有它读取由命令行参数proc文件系统中。生成此文件内容的代码在内核的in 。进程内存的一部分(通过访问)从地址到;这些地址在进程启动时记录在内核中,以后不能更改。/proc/PID/cmdlineproc_pid_cmdline_readfs/proc/base.caccess_remote_vmmm->arg_startmm->arg_end

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

大多数高级语言将参数复制到字符串对象,而没有提供修改原始存储的方法。这是一个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源代码中看到修改。Curl定义一个函数cleanargsrc/tool_paramhlp.c函数用于使用来将参数更改为所有空格memset。在src/tool_getparam.c此功能中使用了几次,例如通过编辑用户密码。由于该函数是从参数解析中调用的,因此它会在curl调用的早期发生,但是在此之前转储命令行仍会显示任何密码。

由于参数存储在进程自己的内存中,因此只能使用调试器从外部进行更改。


大!因此,关于该规格的片断,我明白它的意思,这将是POSIX兼容,让您的内核店的过程的原始命令行参数进程的读写内存(除了在读写存储器拷贝) ?然后ps从该内核内存中获取报告参数,而忽略进程的读写内存中的任何更改?但是(如果我做对了?)大多数UNIX变体甚至都不使用前者,因此如果ps没有内核修改,就不能实现后者的实现,因为原始数据没有保存在任何地方?
通配符

1
@通配符正确。可能有Unix的实现保留了原始的实现,但我认为没有任何一种常见的实现。C语言允许argv更改条目的内容(您不能设置argv[i],但是可以argv[i][0]通过写入argv[i][strlen(argv[i])]),因此该进程的内存中必须有一个副本。
吉尔斯


4
@ Wildcard,Solaris执行此操作。/ usr / ucb / ps看到的命令行是进程拥有的(可变的)副本。/ usr / bin / ps看到的命令行是内核拥有的(不可变的)副本。内核只保留前80个字符。其他所有内容都将被截断。
BowlOfRed

1
@Wildcard实际上,尾随的null是空参数。在ps输出中,很多空参数看起来好像什么都没有,但是是的,如果您检查有多少空格,确实会有所作为,并且可以直接从观察更多/proc/PID/cmdline
吉尔斯

14

其他答案通常可以很好地回答问题。要具体回答“ 如何实现这种效果?它在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
}

因此,我们可以明确看到in的username:password参数argv被空格覆盖,如其他答案所述。


我喜欢评论中的内容cleanarg明确指出它正在执行所要问的问题!
弗洛里斯(Floris)

3

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

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

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


好的,但是运行例如bash -c 'awk 1 /proc/$$/cmdline; set -- something; awk 1 /proc/$$/cmdline'显示至少在外壳程序中,设置参数与修改内核视为进程参数的方法不同。
通配符

4
Shell脚本中的@Wildcard位置参数最初一些 Shell进程的命令行参数的副本。大多数shell不允许脚本更改原始参数。
吉尔斯

@吉尔斯,是的,这就是我的评论的重点。:)关于进程可以做到这一点的一般性声明(此答案的第一句话)无法回答是否可以通过现有的shell功能来实现。答案似乎是“否”,这是我在问题的最底层猜到的。
通配符
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.