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=valuebiç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 cmdsı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 cmdherhangi çı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 ewwwBSD üzerinde (ve 's PROCPS-ng psLinux), 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 ...
( echoyerleşik olarak, çıktılarında gösterilmez ps)
Veya .netrcfor ftpve 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 pssı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 :; donetestlerimde 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 psdaha fazlasını elde etmek için kullanamayacağını unutmayın . Ancak bu yaklaşım, psnormal 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 gdbveya $LD_PRELOADonun ö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 psgöstermiştir ps -opid,args(orada -opid,argsbu ö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 gdbSetuid 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 SECRETenv 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 objdumpiç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 argcve argvtanımları ile:
set $argc = *((int*)($sp + 24))
set $argv = &((char**)$sp)[4]
( gdbaksi halde sistemle birlikte gelen sürüm eski olduğundan paketi / bağlantı noktasını yüklemeniz gerekebilir ).