जब कोई प्रक्रिया एक कमांड ( execve()
सिस्टम कॉल के माध्यम से ) निष्पादित करती है , तो इसकी मेमोरी मिटा दी जाती है। निष्पादन के दौरान कुछ जानकारी पारित करने के लिए, execve()
सिस्टम कॉल उसके लिए दो तर्क लेता है: ए argv[]
और envp[]
सरणियाँ।
वे तार के दो सरणियाँ हैं:
argv[]
तर्क होते हैं
envp[]
पर्यावरण चर परिभाषाओं में शामिल है var=value
प्रारूप में तार (सम्मेलन द्वारा)।
जब तुम करोगे:
export SECRET=value; cmd "$SECRET"
(यहां पैरामीटर विस्तार के आसपास लापता उद्धरण जोड़े गए)।
आप को क्रियान्वित कर रहे हैं cmd
गुप्त के साथ ( value
) में दोनों पारित कर दिया argv[]
और envp[]
। argv[]
होगा ["cmd", "value"]
और envp[]
कुछ ऐसा होगा [..., "PATH=/bin:...", "HOME=...", ..., "SECRET=value", "TERM=xterm", ...]
। जैसा कि उस पर्यावरण चर से रहस्य के मूल्य को प्राप्त करने के लिए cmd
कोई भी getenv("SECRET")
या समकक्ष नहीं कर रहा है SECRET
, इसे पर्यावरण में डालना उपयोगी नहीं है।
argv[]
जनता का ज्ञान है। यह आउटपुट में दिखाता है ps
। envp[]
आजकल नहीं है। लिनक्स पर, यह दिखाता है /proc/pid/environ
। यह ps ewww
BSDs (और ps
लिनक्स पर procps- एनजी के साथ ) के आउटपुट में दिखाता है , लेकिन केवल एक ही प्रभावी यूआईडी (और सेतु / सेटगाइड निष्पादन योग्य के लिए अधिक प्रतिबंधों के साथ) चलने वाली प्रक्रियाओं के लिए। यह कुछ ऑडिट लॉग में दिखा सकता है, लेकिन उन ऑडिट लॉग को केवल प्रशासकों द्वारा ही एक्सेस किया जाना चाहिए।
संक्षेप में, एक निष्पादन योग्य को पारित किए जाने वाले ई-मेल का मतलब निजी या कम से कम निजी के रूप में एक प्रक्रिया की आंतरिक मेमोरी के रूप में होता है (जो कुछ परिस्थितियों में सही विशेषाधिकार के साथ एक अन्य प्रक्रिया भी उदाहरण के लिए डीबगर के साथ उपयोग कर सकती है और कर सकती है भी डिस्क के लिए फेंक दिया)।
चूंकि argv[]
सार्वजनिक ज्ञान है, एक कमांड जो डेटा की अपेक्षा करता है कि इसकी कमांड लाइन पर गुप्त होने का मतलब है डिजाइन द्वारा टूट गया है।
आमतौर पर, ऐसे कमांड जिन्हें गुप्त रखने की आवश्यकता होती है, आपको ऐसा करने के लिए एक अन्य इंटरफ़ेस प्रदान करता है, जैसे पर्यावरण चर के माध्यम से। उदाहरण के लिए:
IPMI_PASSWORD=secret ipmitool -I lan -U admin...
या स्टडिन की तरह एक समर्पित फ़ाइल डिस्क्रिप्टर के माध्यम से:
echo secret | openssl rsa -passin stdin ...
(बनाया echo
जा रहा है, यह के उत्पादन में नहीं दिखा ps
)
या एक फ़ाइल, जैसे .netrc
के लिए ftp
और कुछ अन्य आदेशों या
mysql --defaults-extra-file=/some/file/with/password ....
कुछ एप्लिकेशन जैसे curl
(और यह @meuh द्वारा यहां भी लिया गया दृष्टिकोण है ) पासवर्ड को छिपाने के लिए उन्हें argv[]
prying आँखों से प्राप्त करने की कोशिश करते हैं (कुछ सिस्टम स्मृति के उस हिस्से को अधिलेखित करके जहां argv[]
तार संग्रहीत किए गए थे)। लेकिन यह वास्तव में मदद नहीं कर रहा है और सुरक्षा का झूठा वादा करता है। यह execve()
ओवरराइटिंग के बीच एक खिड़की छोड़ देता है जहां ps
अभी भी रहस्य दिखाई देगा।
उदाहरण के लिए, यदि एक हमलावर जानता है कि आप एक स्क्रिप्ट कर रहे हैं curl -u user:somesecret https://...
(उदाहरण के लिए एक क्रॉन जॉब में), तो उसे बस इतना करना होगा कि कैश से बेदखल कर दिया जाए (कई) पुस्तकालयों का curl
उपयोग करता है (उदाहरण के लिए sh -c 'a=a;while :; do a=$a$a;done'
) के रूप में अपने स्टार्टअप को धीमा करने के लिए, और यहां तक कि बहुत ही अक्षम until grep 'curl.*[-]u' /proc/*/cmdline; do :; done
करने के लिए मेरे परीक्षणों में उस पासवर्ड को पकड़ने के लिए पर्याप्त है।
यदि तर्क ही एकमात्र तरीका है, तो आप गुप्त को आदेशों को पारित कर सकते हैं, फिर भी कुछ चीजें हो सकती हैं जिन्हें आप आज़मा सकते हैं।
कुछ प्रणालियों पर, लिनक्स के पुराने संस्करणों सहित, केवल पहले कुछ बाइट्स (लिनक्स 4.1 पर 4096 और उससे पहले) के तार को argv[]
बुझाया जा सकता है।
वहाँ, आप कर सकते हैं:
(exec -a "$(printf %-4096s cmd)" cmd "$secret")
और यह रहस्य छिपा होगा क्योंकि यह पहले 4096 बाइट्स है। अब जिन लोगों ने उस पद्धति का उपयोग किया है, उन्हें अब लिनक्स पर पछतावा करना चाहिए क्योंकि 4.2 के बाद अब args की सूची को काट नहीं सकता है /proc/pid/cmdline
। यह भी ध्यान दें कि ऐसा नहीं है क्योंकि ps
कमांड लाइन के बहुत से बाइट्स से अधिक नहीं दिखाई देंगे (जैसे FreeBSD पर जहां यह 2048 तक सीमित लगता है) जो कि ps
अधिक प्राप्त करने के लिए एक ही एपीआई का उपयोग नहीं कर सकता है । हालांकि यह दृष्टिकोण उन प्रणालियों पर मान्य ps
है, जहां एक नियमित उपयोगकर्ता के लिए एकमात्र तरीका है उस जानकारी को पुनः प्राप्त करना (जैसे कि जब एपीआई विशेषाधिकार प्राप्त होता है और ps
इसका उपयोग करने के लिए सेटगिड या सेतु होता है), लेकिन अभी भी संभावित रूप से भविष्य के सबूत नहीं हैं।
एक और दृष्टिकोण के लिए होगा नहीं में गुप्त पारित argv[]
लेकिन प्रोग्राम (का उपयोग करते हुए इंजेक्षन कोड gdb
या एक $LD_PRELOAD
हैक) से पहले अपने main()
शुरू कर दिया जाता है कि आवेषण में गुप्त argv[]
से प्राप्त execve()
।
साथ LD_PRELOAD
, के लिए गैर setuid / setgid एक जीएनयू सिस्टम पर गतिशील रूप से जुड़ा हुआ निष्पादनयोग्य:
/*
* replace ***** with secret read from fd 9
* gcc -Wall -fpic -shared -o inject_secret.so inject_secret.c -ldl
* LD_PRELOAD=/.../inject_secret.so cmd -p '*****' 9<<< secret
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>
#define PLACEHOLDER "*****"
static char secret[1024];
int __libc_start_main(int (*main) (int, char**, char**),
int argc,
char **argv,
void (*init) (void),
void (*fini)(void),
void (*rtld_fini)(void),
void (*stack_end)){
static int (*real_libc_start_main)() = NULL;
int n;
if (!real_libc_start_main) {
real_libc_start_main = dlsym(RTLD_NEXT, "__libc_start_main");
if (!real_libc_start_main) abort();
}
n = read(9, secret, sizeof(secret));
if (n > 0) {
int i;
if (secret[n - 1] == '\n') secret[--n] = '\0';
for (i = 1; i < argc; i++)
if (strcmp(argv[i], PLACEHOLDER) == 0)
argv[i] = secret;
}
return real_libc_start_main(main, argc, argv, init, fini,
rtld_fini, stack_end);
}
फिर:
$ gcc -Wall -fpic -shared -o inject_secret.so inject_secret.c -ldl
$ LD_PRELOAD=$PWD/inject_secret.so ps '*****' 9<<< "-opid,args"
PID COMMAND
7659 /bin/zsh
8828 ps *****
किसी भी बिंदु ps
पर ps -opid,args
वहाँ नहीं दिखाया गया है ( -opid,args
इस उदाहरण में रहस्य होने के नाते)। ध्यान दें कि हम पॉइंटर्स के argv[]
सरणी के तत्वों को प्रतिस्थापित कर रहे हैं , न कि उन पॉइंटर्स द्वारा बताए गए स्ट्रिंग्स को ओवरराइड करने के कारण जो हमारे संशोधनों के आउटपुट में नहीं दिखते हैं ।ps
साथ gdb
, अभी भी गैर-setuid / setgid गतिशील रूप से जुड़े हुए निष्पादनयोग्य और जीएनयू सिस्टम पर:
tmp=$(mktemp) && cat << EOF > "$tmp" &&
break __libc_start_main
commands 1
set argv[1]="-opid,args"
continue
end
run
EOF
gdb -n --batch-silent --return-child-result -x "$tmp" --args ps '*****'
rm -f -- "$tmp"
फिर भी gdb
, एक गैर-जीएनयू विशिष्ट दृष्टिकोण जो निष्पादन योग्य गतिशील रूप से जुड़े होने या डिबग प्रतीकों पर भरोसा नहीं करता है और कम से कम लिनक्स पर निष्पादन योग्य किसी भी ईएलएफ के लिए काम करना चाहिए:
#! /bin/sh -
# gdb+sh polyglot script to replace "*****" arguments with the content
# of the SECRET environment variable *after* execve and before calling
# the executable's main() function.
#
# Usage: SECRET=somesecret cmd --password '*****'
if ':' - ':'
then
# running in sh
# retrieve the start address for the executable
start=$(
LC_ALL=C objdump -f -- "$(command -v -- "${1?}")" |
sed -n 's/^start address //p'
)
[ -n "$start" ] || exit
# re-exec ourself with gdb.
exec gdb -n --batch-silent --return-child-result -iex "set \$start = $start" -x "$0" --args "$@"
exit 1
fi
end
# running in gdb
break *$start
commands 1
# The stack on startup contains:
# argc argv[0]... argv[argc-1] 0 envp[0] envp[1]... 0 argv[] and envp[] strings
set $argc = *((int*)$sp)
set $argv = &((char**)$sp)[1]
set $envp = &($argv[$argc+1])
set $i = 0
while $envp[$i]
# look for an envp[] string starting with "SECRET=". We can't use strcmp()
# here as there's no guarantee that the debugged executable has such
# a function
set $e = $envp[$i]
if $e[0] == 'S' && \
$e[1] == 'E' && \
$e[2] == 'C' && \
$e[3] == 'R' && \
$e[4] == 'E' && \
$e[5] == 'T' && \
$e[6] == '='
set $secret = &($e[7])
# replace SECRET=xxx<NUL> with SECRE=<NUL>
set $e[5] = '='
set $e[6] = '\0'
# not calling loop_break as that causes a SEGV with my version of gdb
end
set $i = $i + 1
end
if $secret
# now looking for argv[] strings being "*****" and replace them with
# the secret identified earlier
set $i = 0
while $i < $argc
set $a = $argv[$i]
if $a[0] == '*' && \
$a[1] == '*' && \
$a[2] == '*' && \
$a[3] == '*' && \
$a[4] == '*' && \
$a[5] == '\0'
set $argv[$i] = $secret
end
set $i = $i + 1
end
end
# using "continue" as "detach" causes a SEGV with my version of gdb.
continue
end
run
सांख्यिकीय रूप से जुड़े निष्पादन योग्य के साथ परीक्षण:
$ SECRET=/proc/self/cmdline ./replace_secret busybox cat '*****' | tr '\0' '\n'
/bin/busybox
cat
*****
जब निष्पादन योग्य स्थिर हो सकता है, तो हमारे पास गुप्त स्टोर करने के लिए मेमोरी आवंटित करने का एक विश्वसनीय तरीका नहीं है, इसलिए हमें कहीं और से रहस्य प्राप्त करना होगा जो पहले से ही प्रक्रिया मेमोरी में है। यही कारण है कि यहाँ environ स्पष्ट विकल्प है। यदि प्रक्रिया किसी कारण से अपने वातावरण को डंप करने या अविशिष्ट अनुप्रयोगों को निष्पादित करने का निर्णय लेती है, तो हम इसे लीक करने से बचने के SECRET
लिए (इसे बदलकर SECRE=
) प्रक्रिया में छिपाते हैं ।
यही कारण है कि यह भी सोलारिस 11 (बशर्ते gdb पर काम करता है और GNU binutils स्थापित कर रहे हैं (आप नाम बदलना पड़ सकता है objdump
करने के लिए gobjdump
)।
FreeBSD पर (कम से कम x86_64, मुझे यकीन नहीं है कि उन पहले 24 बाइट्स (जो 16 हो जाते हैं जब gdb (8.0.1) इंटरेक्टिव होता है, यह सुझाव देना कि स्टैक पर) वहाँ gdb में बग हो सकता है), argc
और argv
परिभाषाओं को बदलें साथ में:
set $argc = *((int*)($sp + 24))
set $argv = &((char**)$sp)[4]
(आपको gdb
पैकेज / पोर्ट को संस्करण के रूप में स्थापित करने की आवश्यकता हो सकती है जो अन्यथा सिस्टम के साथ आता है प्राचीन है)।