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 權限相結合?
換句話說,我能否以某種方式掩蓋我傳遞給某個任意shell 命令的參數出現在輸出中
/proc
或ps
輸出中(我認為是同一件事) ?(我猜這個答案是“不”,但似乎值得包括這個額外的半個問題。)
當核心執行一個程序時,它會將命令行參數複製到屬於該程序的讀寫記憶體(在堆棧上,至少在 Linux 上)。該程序可以像任何其他記憶體一樣寫入該記憶體。當
ps
顯示參數時,它會讀回儲存在程序記憶體中該特定地址的任何內容。大多數程序保留原始參數,但可以更改它們。POSIX 描述ps
的狀態未指定表示的字元串是參數列表的版本,因為它是在命令啟動時傳遞給命令的,還是參數的版本,因為它們可能已被應用程序修改。應用程序不能依賴於能夠修改其參數列表並將該修改反映在 ps 的輸出中。
提到這一點的原因是大多數 unix 變體確實反映了這種變化,但其他類型的作業系統上的 POSIX 實現可能不會。
此功能的用途有限,因為該程序無法進行任意更改。至少,參數的總長度不能增加,因為程序不能改變
ps
獲取參數的位置,也不能擴展超出其原始大小的區域。通過在末尾放置空字節可以有效地減少長度,因為參數是 C 風格的以空結尾的字元串(這與末尾有一堆空參數沒有區別)。如果你真的想深挖,可以看看開源實現的源碼。在 Linux 上, 的來源
ps
並不有趣,您只會看到它從proc 文件系統中讀取命令行參數,在. 生成此文件內容的程式碼在核心中,在. 程序的記憶體部分(用 訪問)從地址到; 這些地址在程序啟動時記錄在核心中,之後無法更改。/proc/*PID*/cmdline
proc_pid_cmdline_read``fs/proc/base.c
access_remote_vm``mm->arg_start``mm->arg_end
一些守護程序使用這種能力來反映它們的狀態,例如,它們將它們更改
argv[1]
為類似starting
oravailable
or的字元串exiting
。許多 unix 變體具有setproctitle
執行此操作的功能。一些程序使用此功能來隱藏機密數據。請注意,這是有限的用途,因為命令行參數在程序啟動時是可見的。大多數高級語言將參數複製到字元串對象,並且不提供修改原始儲存的方法。
argv
這是一個通過直接更改元素來展示這種能力的 C 程序。#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 定義了一個函式cleanarg
,src/tool_paramhlp.c
用於將參數更改為使用memset
. 在src/tool_getparam.c
此功能中使用了幾次,例如通過編輯使用者密碼。由於該函式是從參數解析中呼叫的,因此它會在 curl 呼叫的早期發生,但在此之前轉儲命令行仍會顯示任何密碼。由於參數儲存在程序自己的記憶體中,因此它們不能從外部更改,除非使用調試器。