Kaynak kodu olmadan programlanacak bağımsız değişkenleri gizle


15

Çalıştığım bir program için bazı hassas bağımsız değişkenleri gizlemem gerekiyor, ancak kaynak koduna erişimim yok. Ayrıca hidepidsudo ayrıcalıklarına sahip olmadığım için böyle bir şey kullanamam bu yüzden paylaşılan bir sunucuda çalıştırıyorum .

İşte denediğim bazı şeyler:

  • export SECRET=[my arguments], ardından bir çağrı gelir ./program $SECRET, ancak bu yardımcı olmaz.

  • ./program `cat secret.txt`nerede secret.txtbenim argümanlar içerir, ancak her şeye kadir pssırlarımı koklayarak edebilmektedir.

Yönetici müdahalesi içermeyen argümanlarımı gizlemenin başka bir yolu var mı?


Bu özel program nedir? Eğer olağan bir
emirse

14
Neler olduğunu anlıyorsunuz, denediğiniz şeylerin çalışma şansı yok, çünkü kabuk, ortam değişkenlerini genişletmekten ve programı başlatmadan önce komut yerine koyma işleminden sorumludur . ps"sırlarını koklamak" için büyülü bir şey yapmıyor. Her neyse, makul yazılmış programlar bunun yerine, doğrudan bir argüman olarak almak yerine belirli bir dosyadan veya stdin'den bir sırrı okumak için bir komut satırı seçeneği sunmalıdır.
jamesdlin

Özel bir şirket tarafından yazılmış bir hava durumu simülasyon programı yürütüyorum. Kaynak kodlarını paylaşmazlar ve dokümanları bir dosyadan bir sır paylaşmak için herhangi bir yol sunmazlar. Burada seçenekler dışında olabilir
MS

Yanıtlar:


25

Açıklandığı gibi burada , Linux programın veri uzayda bir programın argümanlar koyar ve bu alanda başlangıcına bir işaretçi tutar. psProgram argümanlarını bulmak ve göstermek için bu şekilde kullanılır .

Veriler programın alanında olduğundan, onu manipüle edebilir. Bunu, programın kendisini değiştirmeden yapmak, programın main()gerçek ana kısmından önce çağrılacak bir fonksiyonla bir şim yüklemeyi içerir . Bu şim, gerçek argümanları yeni bir alana kopyalayabilir, daha sonra orijinal argümanların üzerine yazabilir, böylece pssadece nuls görür.

Aşağıdaki C kodu bunu yapar.

/* /unix//a/403918/119298
 * capture calls to a routine and replace with your code
 * gcc -Wall -O2 -fpic -shared -ldl -o shim_main.so shim_main.c
 * LD_PRELOAD=/.../shim_main.so theprogram theargs...
 */
#define _GNU_SOURCE /* needed to get RTLD_NEXT defined in dlfcn.h */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <dlfcn.h>

typedef int (*pfi)(int, char **, char **);
static pfi real_main;

/* copy argv to new location */
char **copyargs(int argc, char** argv){
    char **newargv = malloc((argc+1)*sizeof(*argv));
    char *from,*to;
    int i,len;

    for(i = 0; i<argc; i++){
        from = argv[i];
        len = strlen(from)+1;
        to = malloc(len);
        memcpy(to,from,len);
        memset(from,'\0',len);    /* zap old argv space */
        newargv[i] = to;
        argv[i] = 0;
    }
    newargv[argc] = 0;
    return newargv;
}

static int mymain(int argc, char** argv, char** env) {
    fprintf(stderr, "main argc %d\n", argc);
    return real_main(argc, copyargs(argc,argv), env);
}

int __libc_start_main(pfi main, int argc,
                      char **ubp_av, void (*init) (void),
                      void (*fini)(void),
                      void (*rtld_fini)(void), void (*stack_end)){
    static int (*real___libc_start_main)() = NULL;

    if (!real___libc_start_main) {
        char *error;
        real___libc_start_main = dlsym(RTLD_NEXT, "__libc_start_main");
        if ((error = dlerror()) != NULL) {
            fprintf(stderr, "%s\n", error);
            exit(1);
        }
    }
    real_main = main;
    return real___libc_start_main(mymain, argc, ubp_av, init, fini,
            rtld_fini, stack_end);
}

Müdahale etmek mümkün değildir main(), ancak __libc_start_mainmain çağırmaya devam eden standart C kütüphane işlevine müdahale edebilirsiniz . Bu dosyayı shim_main.cbaşlangıçtaki yorumda belirtildiği gibi derleyin ve gösterildiği gibi çalıştırın. Ben printfaslında bir arandığını kontrol böylece kodda bir bıraktım . Örneğin,

LD_PRELOAD=/tmp/shim_main.so /bin/sleep 100

sonra a yapın psve boş bir komut ve argümanların gösterildiğini göreceksiniz.

Komut argümanlarının görülebilmesi için hala az bir süre vardır. Bundan kaçınmak için, örneğin, bir dosyayı sırrınızı okumak için şim ayarını değiştirebilir ve programa aktarılan argümanlara ekleyebilirsiniz.


12
Ancak yine de /proc/pid/cmdlinesırrı gösterecek kısa bir pencere olacaktır ( curlkomut satırında verilen şifreyi gizlemeye çalıştığında olduğu gibi ). LD_PRELOAD kullanırken, sırrı ortamdan main'un aldığı argv'ye kopyalamak için ana öğeyi kapatabilirsiniz. Varlık ile aradığınız LD_PRELOAD=x SECRET=y cmdyer gibimain()argv[][argv[0], getenv("SECRET")]
Stéphane Chazelas

Ortamı bir sırrı görünür olduğu için gizlemek için kullanamazsınız /proc/pid/environ. Bu, argümanlarla aynı şekilde yazılabilir, ancak aynı pencereden ayrılır.
meuh

11
/proc/pid/cmdlineherkese açık, /proc/pid/environdeğil. (Orada pssetuid çalıştırılabilir) herhangi bir sürecin ortamını maruz bazı sistemler vardı , ama ben bugünlerde herhangi bir rastlamak sanmıyorum. Çevre genellikle yeterince güvenli kabul edilir . Aynı euid ile işlemlerden meraklı olmak güvenli değildir, ancak bunlar genellikle aynı euid ile işlemlerin belleğini okuyabilir, bu nedenle bu konuda yapabileceğiniz çok şey yoktur.
Stéphane Chazelas

4
@ StéphaneChazelas: Eğer kişi sırları iletmek için ortamı kullanırsa, ideal olarak onu mainsarılmış programın yöntemine ileten sarıcı ayrıca alt süreçlere kazara sızmayı önlemek için ortam değişkenini de kaldırır. Alternatif olarak, sarıcı bir dosyadaki tüm komut satırı bağımsız değişkenlerini okuyabilir .
David Foerster

@DavidFoerster, iyi bir nokta. Bunu dikkate almak için cevabımı güncelledim.
Stéphane Chazelas

16
  1. Söz konusu uygulamanın komut satırı arabiriminin belgelerini okuyun. Sırrı bir argüman olarak doğrudan bir dosyadan sağlama seçeneği de olabilir.

  2. Bu başarısız olursa, bir sırrı sağlamanın güvenli bir yolu olmadığı gerekçesiyle uygulamaya karşı bir hata raporu gönderin.

  3. Meuh'un özel ihtiyaçlarına cevap olarak çözümü her zaman dikkatli bir şekilde (!) Uyarlayabilirsiniz . Özel önem Öde Stéphane'nın yorumun ve takibi.


12

Programın çalışmasını sağlamak için argüman hidepidiletmeniz gerekiyorsa, procfs'de kullanamazsanız ne yaparsanız yapın şansınız kalmaz.

Bunun bir bash betiği olduğundan bahsettiğiniz için, bash derlenmiş bir dil olmadığından zaten kaynak kodunuz olmalıdır.

Bu gerçekleşmediği takdirde sen olabilir kullanılarak sürecin cmdline yeniden muktedir gdbveya benzer ve uğraşırken argc/ ' argvo çoktan başladı kere, ama:

  1. Bu güvenli değildir, çünkü program argümanlarınızı değiştirmeden önce başlangıçta göstermeye devam edersiniz
  2. Bu oldukça hacky, eğer işe alabilirseniz bile ona güvenmenizi tavsiye etmem

Gerçekten sadece kaynak kodunu almanızı veya kodun değiştirilmesi için satıcıyla konuşmanızı tavsiye ederim. POSIX işletim sistemindeki komut satırında sır sağlamak güvenli işlemle uyumsuz.


11

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 ).


Re (burada parametre genişletmesi etrafındaki eksik tırnaklar eklendi): Tırnakları kullanmamanın nesi yanlış? Gerçekten bir fark var mı?
yukashima huksay


3

Yapabilecekleriniz

 export SECRET=somesecretstuff

sonra, ./programC harfinizi yazdığınızı varsayarsak (veya başkası yaparsa ve sizin için değiştirebilir veya iyileştirebilir ) , bu programda getenv (3) kullanın ,

char* secret= getenv("SECRET");

ve sonra export sadece ./programaynı kabukta koşuyorsun . Veya ortam değişkeni adı ona aktarılabilir (çalıştırılarak./program --secret-var=SECRET vb ...)

pssırrını anlatmayacak, ama proc (5) hala çok fazla bilgi verebilir (en azından aynı kullanıcının diğer süreçlerine).

Ayrıca bkz bu programın argümanları geçen daha iyi bir yolunu tasarlamak yardımına.

Bu cevaba bakınGlobbing ve bir kabuğun rolü hakkında daha iyi bir açıklama için .

Belki de programveri almanın (veya süreçler arası iletişimi kullanmanın başka yolları vardır) basit program argümanlarından (hassas bilgileri işlemek isteniyorsa kesinlikle gerekir) daha akıllıca kullanmanın) . Belgelerini okuyun. Ya da belki de (gizli verileri işlemek için tasarlanmamış olan) programı kötüye kullanıyorsunuzdur.

Gizli verileri gizlemek gerçekten zor. Program argümanlarından geçirmemek yeterli değildir.


5
Onun bile sahip olmadığı sorusuna gelen oldukça açık kaynak kodu için ./programbu cevabın ilk yarısında alakalı görünmüyor böylece.
boru
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.