Bir yöntem (vasıtasıyla bir komutu yerine getirdikten execve()
sistem çağrısı), bellek silinir. Bazı bilgileri yürütmeye aktarmak için execve()
sistem çağrıları bunun için iki argüman alır: argv[]
veenvp[]
diziler.
Bunlar iki dizi dizisidir:
argv[]
argümanları içerir
envp[]
ortam değişkeni tanımlarını var=value
biçimdeki dizeler olarak içerir (kural gereği).
Yaptığınızda:
export SECRET=value; cmd "$SECRET"
(burada parametre genişletmesinin etrafına eksik tırnak işaretleri eklenmiştir).
Sen infaz konum cmd
sır ile ( value
) içinde de geçti argv[]
ve envp[]
. argv[]
olacak ["cmd", "value"]
ve envp[]
benzeri bir şey [..., "PATH=/bin:...", "HOME=...", ..., "SECRET=value", "TERM=xterm", ...]
. Gibi cmd
herhangi çıkmıyor getenv("SECRET")
bundan sırrı değerini almak için veya eşdeğeriSECRET
ortam değişkeninden onu ortama koymak yararlı değildir.
argv[]
kamuoyunun bilgisidir. Bu çıktı gösterir ps
. envp[]
günümüzde değil. Linux'ta bunu gösterir /proc/pid/environ
. Bu çıkış gösterir ps ewww
BSD üzerinde (ve 's PROCPS-ng ps
Linux), ancak bunların aynı etkili uidA ile çalışan işlemler için (ve Setuid / setgid yürütülebilir daha fazla kısıtlamalarla). Bazı denetim günlüklerinde gösterilebilir, ancak bu denetim günlüklerine yalnızca yöneticiler tarafından erişilebilir olmalıdır.
Kısacası, bir çalıştırılabilir dosyaya geçirilen ortam, özel veya en azından yaklaşık olarak bir sürecin dahili belleği kadar özeldir (bazı durumlarda doğru ayrıcalıklara sahip başka bir işlem de örneğin bir hata ayıklayıcı ile erişebilir ve ayrıca diske dökülebilir).
Yana argv[]
kamu bilgisi, komut satırında gizli olması gerekiyordu beklediği veri tasarımı ile kırık olduğunu bir komut vardır.
Genellikle, bir sır verilmesi gereken komutlar, bunu bir ortam değişkeni aracılığıyla olduğu gibi size başka bir arabirim sağlar. Örneğin:
IPMI_PASSWORD=secret ipmitool -I lan -U admin...
Veya stdin gibi özel bir dosya tanımlayıcı aracılığıyla:
echo secret | openssl rsa -passin stdin ...
( echo
yerleşik olarak, çıktılarında gösterilmez ps
)
Veya .netrc
for ftp
ve birkaç komut gibi bir dosya veya
mysql --defaults-extra-file=/some/file/with/password ....
Gibi bazı uygulamalar curl
(ve bu da @meuh tarafından alınan yaklaşım ) argv[]
meraklı gözlerden aldıkları şifreyi gizlemeye çalışır (bazı sistemlerde, belleğin argv[]
dizelerin depolandığı kısmının üzerine yazarak ). Ama bu gerçekten yardımcı olmuyor ve sahte bir güvenlik vaadi veriyor. Bu, execve()
ve ps
sırrının üzerine hala bir sır gösterecek bir pencere bırakır .
Örneğin, bir saldırgan bir komut dosyası çalıştırdığınızı bilirse curl -u user:somesecret https://...
(örneğin bir cron işinde), tek yapması gereken önbellekten curl
(örneğin sh -c 'a=a;while :; do a=$a$a;done'
) a ( until grep 'curl.*[-]u' /proc/*/cmdline; do :; done
testlerimde bu şifreyi yakalamak için çok verimsiz bir işlem yapmak bile yeterlidir.
Sırları komutlara verebilmenin tek yolu argümanlarsa, deneyebileceğiniz bazı şeyler olabilir.
Linux'un eski sürümleri de dahil olmak üzere bazı sistemlerde, dizelerin yalnızca ilk birkaç baytı (Linux 4.1 ve öncesi 4096) argv[]
sorgulanabilir.
Orada şunları yapabilirsiniz:
(exec -a "$(printf %-4096s cmd)" cmd "$secret")
Ve sır saklanacaktı çünkü ilk 4096 baytı geçti. Şimdi bu yöntemi kullanan insanlar, Linux'tan beri 4.2'den beri artık argümanlar listesini kesmediğinden pişman olmalılar /proc/pid/cmdline
. Ayrıca ps
, bir komut satırının (2048 ile sınırlı olduğu görünen FreeBSD'de olduğu gibi) çok fazla bayttan daha fazlasını göstermeyeceği için, aynı API'nın ps
daha fazlasını elde etmek için kullanamayacağını unutmayın . Ancak bu yaklaşım, ps
normal bir kullanıcının bu bilgiyi almasının tek yolunun bulunduğu sistemlerde geçerlidir (API ayrıcalıklı olduğunda veps
için setgid veya setuid olması gibi) tek yol olduğu, ancak hala potansiyel olarak geleceğe yönelik olmadığı sistemler için geçerlidir.
Diğer bir yaklaşım olacaktır değil de gizli geçmesi argv[]
programa (kullanarak içine ancak iğne kodunu gdb
veya $LD_PRELOAD
onun öncesinde hack) main()
başlatıldığını ekler içine sır argv[]
alınan execve()
.
İle LD_PRELOAD
, Setuid olmayan / ifadesini bir GNU sistemi üzerinde dinamik bağlantılı yürütülebilir 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);
}
Sonra:
$ 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 *****
Hiçbir noktada olurdu ps
göstermiştir ps -opid,args
(orada -opid,args
bu örnekte gizli olmak üzere). İşaretçilerargv[]
dizisinin öğelerini değiştirdiğimizi , bu işaretçiler tarafından işaret edilen dizeleri geçersiz kılmadığımızı unutmayın, bu nedenle değişikliklerin çıktısında görünmüyor .ps
İle gdb
Setuid olmayan / dinamik bağlantılı yürütülebilir ve GNU sistemlerinde setgid hala için,:
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"
Yine de gdb
, yürütülebilir dosyalara dinamik olarak bağlı olan veya hata ayıklama sembollerine sahip olmayan ve Linux'ta yürütülebilir herhangi bir ELF için çalışması gereken GNU'ya özgü olmayan bir yaklaşım:
#! /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
Statik bağlantılı bir yürütülebilir dosya ile test etme:
$ SECRET=/proc/self/cmdline ./replace_secret busybox cat '*****' | tr '\0' '\n'
/bin/busybox
cat
*****
Yürütülebilir dosya statik olduğunda, sırrı saklamak için bellek ayırmanın güvenilir bir yolu yoktur, bu yüzden sırrı zaten işlem belleğinde olan başka bir yerden almamız gerekir. Bu yüzden ortam burada bariz bir seçimdir. Ayrıca , işlemin çevresini bir nedenle boşaltmaya veya güvenilmeyen uygulamaları yürütmeye karar vermesi durumunda sızmasını önlemek için SECRET
env değişkenini sürece değiştiririz (değiştirerek SECRE=
).
Bu da Solaris 11 (sağlanan gdb üzerinde çalışır ve GNU binutils (Yeniden adlandırmak gerekebilir yüklenir objdump
için gobjdump
).
FreeBSD'de (en azından x86_64), ilk 24 baytın (gdb (8.0.1) gdb'de bir hata olabileceğini düşündüren etkileşimli olduğunu) yığının ne olduğundan emin değilim, değiştirin argc
ve argv
tanımları ile:
set $argc = *((int*)($sp + 24))
set $argv = &((char**)$sp)[4]
( gdb
aksi halde sistemle birlikte gelen sürüm eski olduğundan paketi / bağlantı noktasını yüklemeniz gerekebilir ).