我前段时间注意到,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/cmdline
proc_pid_cmdline_read
在fs/proc/base.c
。进程内存的一部分(通过 访问access_remote_vm
)从地址mm->arg_start
到mm->arg_end
;这些地址在进程启动时就记录在内核中,之后无法更改。
某些守护进程使用此功能来反映其状态,例如,它们将其更改为类似or或 的argv[1]
字符串。许多 UNIX 变体都有一个starting
available
exiting
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源代码中看到修改。卷曲定义了一个函数cleanarg
于src/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
进程不仅可以读取其参数,还可以写入它们。
我不是开发人员,所以我不熟悉这些东西,但从外部可以使用类似于更改环境参数的方法: