I noticed some time ago that usernames and passwords given to curl
as command line arguments don't appear in ps
output (although of course they may appear in your bash history).
They likewise don't appear in /proc/PID/cmdline
.
(The length of the combined username/password argument can be derived, though.)
Demonstration below:
[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 ~]#
How is this effect achieved? Is it somewhere in the source code of curl
? (I assume it is a curl
feature, not a ps
feature? Or is it a kernel feature of some sort?)
Also: can this be achieved from outside the source code of a binary executable? E.g. by using shell commands, probably combined with root permissions?
In other words could I somehow mask an argument from appearing in /proc
or in ps
output (same thing, I think) that I passed to some arbitrary shell command? (I would guess the answer to this is "no" but it seems worth including this extra half-a-question.)
Best Answer
When the kernel executes a process, it copies the command line arguments to read-write memory belonging to the process (on the stack, at least on Linux). The process can write to that memory like any other memory. When
ps
displays the argument, it reads back whatever is stored at that particular address in the process's memory. Most programs keep the original arguments, but it's possible to change them. The POSIX description ofps
states thatThe reason this is mentioned is that most unix variants do reflect the change, but POSIX implementations on other types of operating systems may not.
This feature is of limited use because the process can't make arbitrary changes. At the very least, the total length of the arguments cannot be increased, because the program can't change the location where
ps
will fetch the arguments and can't extend the area beyond its original size. The length can effectively be decreased by putting null bytes at the end, because arguments are C-style null-terminated strings (this is indistinguishable from having a bunch of empty arguments at the end).If you really want to dig, you can look at the source of an open-source implementation. On Linux, the source of
ps
isn't interesting, all you'll see there is that it reads the command line arguments from the proc filesystem, in/proc/PID/cmdline
. The code that generates the content of this file is in the kernel, inproc_pid_cmdline_read
infs/proc/base.c
. The part of the process's memory (accessed withaccess_remote_vm
) goes from the addressmm->arg_start
tomm->arg_end
; these addresses are recorded in the kernel when the process starts and can't be changed afterwards.Some daemons use this ability to reflect their status, e.g. they change their
argv[1]
to a string likestarting
oravailable
orexiting
. Many unix variants have asetproctitle
function to do this. Some programs use this ability to hide confidential data. Note that this is of limited use since the command line arguments are visible while the process starts.Most high-level languages copy the arguments to string objects and don't give a way to modify the original storage. Here's a C program that demonstrates this ability by changing
argv
elements directly.Sample output:
You can see
argv
modification in the curl source code. Curl defines a functioncleanarg
insrc/tool_paramhlp.c
which is used to change an argument to all spaces usingmemset
. Insrc/tool_getparam.c
this function is used a few times, e.g. by redacting the user password. Since the function is called from the parameter parsing, it happens early in a curl invocation, but dumping the command line before this happens will still show any passwords.Since the arguments are stored in the process's own memory, they cannot be changed from the outside except by using a debugger.