V Linuxu můžete přepsat hodnotu řetězců prostředí v zásobníku.
Položku tedy můžete skrýt tak, že ji přepíšete nulami nebo čímkoli jiným:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char* argv[], char* envp[]) {
char cmd[100];
while (*envp) {
if (strncmp(*envp, "k=", 2) == 0)
memset(*envp, 0, strlen(*envp));
envp++;
}
sprintf(cmd, "cat /proc/%u/environ", getpid());
system(cmd);
return 0;
}
Spustit jako:
$ env -i a=foo k=v b=bar ./wipe-env | hd
00000000 61 3d 66 6f 6f 00 00 00 00 00 62 3d 62 61 72 00 |a=foo.....b=bar.|
00000010
k=v
byl přepsán \0\0\0
.
Všimněte si, že setenv("k", "", 1)
pro přepsání hodnoty nebude fungovat jako v tomto případě nový "k="
řetězec je přidělen.
Pokud jste jinak neupravili k
proměnná prostředí s setenv()
/putenv()
, pak byste také měli být schopni udělat něco takového, abyste získali adresu k=v
řetězec na zásobníku (tedy na jednom z nich):
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char* argv[]) {
char cmd[100];
char *e = getenv("k");
if (e) {
e -= strlen("k=");
memset(e, 0, strlen(e));
}
sprintf(cmd, "cat /proc/%u/environ", getpid());
system(cmd);
return 0;
}
Upozorňujeme však, že odstraní pouze jeden z k=v
záznamy přijaté v prostředí. Obvykle je pouze jeden, ale nic nikomu nebrání projít oběma k=v1
a k=v2
(nebo k=v
dvakrát) v seznamu env předaném execve()
. To bylo v minulosti příčinou zranitelností zabezpečení, jako je CVE-2016-2381. To by se skutečně mohlo stát s bash
před shellshock při exportu proměnné i funkce se stejným názvem.
V každém případě bude vždy existovat malé okno, během kterého ještě nebyl přepsán řetězec env var, takže možná budete chtít najít jiný způsob, jak předat tajemství informace do příkazu (jako je například roura), pokud je vystavujete pomocí /proc/pid/environ
je znepokojující.
Všimněte si také, že na rozdíl od /proc/pid/cmdline
, /proc/pid/environment
je přístupný pouze procesům se stejným euid nebo root (nebo root pouze v případě, že euid a ruid procesu nejsou stejné, jak se zdá).
Tuto hodnotu před nimi můžete skrýt v /proc/pid/environ
, ale stále mohou být schopni získat jakoukoli jinou kopii, kterou jste vytvořili z řetězce v paměti, například tím, že k ní připojíte debugger.
Viz https://www.kernel.org/doc/Documentation/security/Yama.txt, kde najdete způsoby, jak v tom zabránit alespoň uživatelům bez oprávnění root.
Výše uvedené řetězce nebylo nutné přepisovat (ve skutečnosti ne zapnuto ) zásobník hlavního vlákna v Linuxu od roku 2010.
Oba /proc/self/cmdline
a /proc/self/environ
jsou modifikovatelné samotným procesem za běhu, voláním prctl()
funkce s příslušným PR_SET_MM_ARG_START
+PR_SET_MM_ARG_END
nebo PR_SET_MM_ENV_START
+PR_SET_MM_ENV_END
. Ty přímo nastavují ukazatele paměti do aplikačního paměťového prostoru procesu, drženého jádrem pro každý proces, které se používají k načtení obsahu /proc/${PID}/cmdline
a /proc/${PID}/environ
, a tedy příkazový řádek a prostředí uváděné ps
příkaz.
Stačí tedy vytvořit nový argument nebo řetězec prostředí (ne vektor, všimněte si – paměť, na kterou ukazuje, musí být skutečná data řetězce, zřetězená a ␀
-delimited) a sdělte jádru, kde se nachází.
To je zdokumentováno v manuálové stránce Linuxu pro prctl(2)
stejně jako environ(7)
manuálová stránka. Co není dokumentováno je, že jádro odmítá jakýkoli pokus nastavit počáteční adresu nad koncovou adresu nebo koncovou adresu pod počáteční adresou; nebo (znovu)nastavit kteroukoli adresu na nulu. Toto také není původní mechanismus navržený Bryanem Donlanem v roce 2009, který umožňoval nastavit začátek a konec v jediné operaci, atomicky. Navíc jádro neposkytuje žádný způsob, jak získat aktuální hodnoty těchto ukazatelů.
Díky tomu je obtížné jej upravit prostředí a oblasti příkazového řádku s prctl()
. Musíte zavolat prctl()
fungovat až čtyřikrát, protože první pokusy mohou vést k pokusům nastavit počáteční ukazatel výše než koncový ukazatel v závislosti na tom, odkud jsou stará a nová data v paměti. Člověk tomu musí říkat další čtyřikrát, pokud chceme zajistit, aby to nevedlo k oknu příležitosti pro ostatní procesy v systému zkontrolovat libovolný rozsah paměťového prostoru procesu v období, kdy byl nastaven nový začátek/konec, ale nový konec /start nebyl.
Jediné atomické systémové volání, které nastaví celý rozsah najednou, by bylo pro aplikační programy mnohem snazší bezpečně používat.
Další vráska je, že z žádného opravdu dobrého důvodu (vzhledem k kontrolám v jádře, přepisovatelnosti původních datových oblastí stejně a skutečnost, že ekvivalenty nejsou privilegované operace na žádném z BSD), v Linuxu to vyžaduje oprávnění superuživatele.
Napsal jsem poměrně jednoduché setprocargv()
a setprocenvv()
funkce pro mé sady nástrojů, které toto využívají. Řetězové načítání programů ze sad nástrojů, které jsou vestavěné, jako je setenv
a foreground
, tedy odrážejí zřetězené argumenty a prostředí, kde to Linux dovoluje.
# /package/admin/nosh/command/clearenv setenv WIBBLE wobble foreground pause \; true & [1] 1057 # hexdump -C /proc/1057/cmdline 00000000 66 6f 72 65 67 72 6f 75 6e 64 00 70 61 75 73 65 |foreground.pause| 00000010 00 3b 00 74 72 75 65 00 |.;.true.| 00000018 # hexdump -C /proc/1057/environ 00000000 57 49 42 42 4c 45 3d 77 6f 62 62 6c 65 00 |WIBBLE=wobble.| 0000000e # hexdump -C /proc/1058/cmdline 00000000 70 61 75 73 65 00 |pause.| 00000006 # hexdump -C /proc/1058/environ 00000000 57 49 42 42 4c 45 3d 77 6f 62 62 6c 65 00 |WIBBLE=wobble.| 0000000e #
Všimněte si, že to nebrání věcem, které sledují proces a přistupují k jeho paměti přímo jinými prostředky (spíše než prostřednictvím těchto dvou pseudosouborů), a samozřejmě před úpravou řetězců ponechává okno, kde lze tyto informace vidět, jen jako přepisování dat nad zásobníkem hlavního vlákna. A stejně jako v případě přepisování dat se nepočítá s knihovnami jazykového běhu, které za různých okolností vytvářejí kopie prostředí (na hromadě). Obecně to nepovažujte za tak dobrý mechanismus pro předávání „tajemství“ programu jako (řekněme) to, že zdědí deskriptor otevřeného souboru na čtecí konec nepojmenované roury, načtený do vstupní vyrovnávací paměti plně pod vaší kontrolou. které pak vymažete.
Další čtení
- Timo Sirainen (2009-10-02). Přidána možnost PR_SET_PROCTITLE_AREA pro prctl() . Linux Kernel Mailing List.
- https://unix.stackexchange.com/a/432681/5132
- Daniel J. Bernstein. Rozhraní kontroly hesla . cr.yp.to.
- https://github.com/jdebp/nosh/blob/master/source/setprocargv.cpp
- https://github.com/jdebp/nosh/blob/master/source/setprocenvv.cpp