Unter Linux können Sie den Wert der Umgebungszeichenfolgen auf dem Stack überschreiben.
Sie können den Eintrag also ausblenden, indem Sie ihn mit Nullen oder irgendetwas anderem überschreiben:
#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;
}
Ausführen als:
$ 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
der k=v
wurde mit \0\0\0
überschrieben .
Beachten Sie, dass setenv("k", "", 1)
den Wert zu überschreiben funktioniert nicht, da in diesem Fall ein neuer "k="
Zeichenfolge zugewiesen.
Wenn Sie den k
nicht anderweitig geändert haben Umgebungsvariable mit setenv()
/putenv()
, dann sollten Sie auch so etwas tun können, um die Adresse der k=v
zu erhalten String auf dem Stack (naja, von einem von ihnen):
#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;
}
Beachten Sie jedoch, dass nur eine entfernt wird der k=v
Einträge in der Umgebung empfangen. Normalerweise gibt es nur einen, aber nichts hindert jemanden daran, beide k=v1
zu bestehen und k=v2
(oder k=v
zweimal) in der env-Liste, die an execve()
übergeben wird . Dies war in der Vergangenheit die Ursache für Sicherheitslücken wie CVE-2016-2381. Es könnte wirklich mit bash
passieren vor Shellshock, wenn sowohl eine Variable als auch eine Funktion mit demselben Namen exportiert werden.
In jedem Fall wird es immer ein kleines Fenster geben, in dem der env var-String noch nicht überschrieben wurde, also möchten Sie vielleicht einen anderen Weg finden, um das Geheimnis zu übergeben Informationen zum Befehl (wie zum Beispiel eine Pipe), wenn sie über /proc/pid/environ
verfügbar gemacht werden ist ein Anliegen.
Beachten Sie auch das Gegenteil von /proc/pid/cmdline
, /proc/pid/environment
ist nur für Prozesse mit der gleichen euid oder root zugänglich (oder root nur, wenn euid und ruid des Prozesses anscheinend nicht gleich sind).
Sie können diesen Wert in /proc/pid/environ
vor ihnen verbergen , aber sie können möglicherweise immer noch jede andere Kopie erhalten, die Sie von der Zeichenfolge im Speicher erstellt haben, beispielsweise indem sie einen Debugger daran anhängen.
Unter https://www.kernel.org/doc/Documentation/security/Yama.txt finden Sie Möglichkeiten, zumindest Nicht-Root-Benutzer daran zu hindern.
Es war nicht notwendig, die obigen Zeichenfolgen zu überschreiben (nicht wirklich on ) der Stack des Haupt-Threads unter Linux seit 2010.
Beide /proc/self/cmdline
und /proc/self/environ
können vom Prozess selbst zur Laufzeit geändert werden, indem prctl()
aufgerufen wird Funktion jeweils mit PR_SET_MM_ARG_START
+PR_SET_MM_ARG_END
oder PR_SET_MM_ENV_START
+PR_SET_MM_ENV_END
. Diese setzen die Speicherzeiger direkt in den Anwendungsspeicherbereich des Prozesses, der vom Kernel für jeden Prozess gehalten wird, die verwendet werden, um den Inhalt von /proc/${PID}/cmdline
abzurufen und /proc/${PID}/environ
, und daher die Befehlszeile und Umgebung, die von ps
gemeldet werden Befehl.
Man muss also einfach ein neues Argument oder eine neue Umgebungszeichenfolge konstruieren (nicht Vektor, Achtung – der Speicher, auf den gezeigt wird, muss die eigentliche Zeichenfolge sein, verkettet und ␀
-delimited) und teilen Sie dem Kernel mit, wo es sich befindet.
Dies ist in der Linux-Handbuchseite für prctl(2)
dokumentiert Funktion sowie der environ(7)
Handbuchseite. Was ist nicht dokumentiert ist, dass der Kernel jeden Versuch ablehnt, die Startadresse über die Endadresse oder die Endadresse unter die Startadresse zu setzen; oder um eine der Adressen (zurück) auf Null zu setzen. Außerdem ist dies nicht der ursprüngliche Mechanismus, der 2009 von Bryan Donlan vorgeschlagen wurde und der es ermöglichte, Start und Ende in einem einzigen Vorgang atomar festzulegen. Außerdem bietet der Kernel keine Möglichkeit, get die aktuellen Werte dieser Zeiger.
Das macht es schwierig, zu ändern die Umgebungs- und Kommandozeilenbereiche mit prctl()
. Man muss die prctl()
anrufen Funktion bis zu viermal, da die ersten Versuche dazu führen können, dass versucht wird, den Startzeiger höher als den Endzeiger zu setzen, je nachdem, wo sich die alten und neuen Daten im Speicher befinden. Man muss es weiter nennen viermal, wenn man sicherstellen will, dass dies nicht zu einem Gelegenheitsfenster für andere Prozesse auf dem System führt, um einen beliebigen Bereich des Speicherplatzes des Prozesses in dem Zeitraum zu inspizieren, in dem der neue Start/das neue Ende gesetzt wurde, aber das neue Ende /start war nicht.
Ein einzelner atomarer Systemaufruf, der den gesamten Bereich auf einmal festlegt, wäre für Anwendungsprogramme viel einfacher gewesen, ihn sicher zu verwenden.
Ein weiterer Wermutstropfen ist, dass ohne wirklich triftigen Grund (angesichts der Prüfungen im Kernel) die Überschreibbarkeit der Originaldatenbereiche sowieso , und die Tatsache, dass die Äquivalente auf keinem der BSDs privilegierte Operationen sind), erfordert dies unter Linux Superuser-Privilegien.
Ich habe ziemlich einfach setprocargv()
geschrieben und setprocenvv()
Funktionen für meine Toolsets, die dies verwenden. Kettenladen von Programmen aus den integrierten Toolsets, wie setenv
und foreground
, spiegeln also die verketteten Befehlsargumente und die Umgebung wider, wo Linux dies zulässt.
# /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 #
Beachten Sie, dass dies nicht gegen Dinge spricht, die den Prozess verfolgen und auf andere Weise (statt über diese beiden Pseudodateien) direkt auf seinen Speicher zugreifen, und natürlich ein Fenster hinterlässt, bevor die Zeichenfolgen geändert werden, in dem diese Informationen nur angezeigt werden können wie das Überschreiben der Daten über dem Stack des Haupt-Threads. Und genau wie beim Überschreiben der Daten berücksichtigt dies keine Sprachlaufzeitbibliotheken, die unter verschiedenen Umständen Kopien der Umgebung (auf dem Heap) erstellen. Betrachten Sie dies im Allgemeinen nicht als einen so guten Mechanismus, um "Geheimnisse" an ein Programm zu übergeben, wie (sagen wir), es einen offenen Dateideskriptor an das Leseende einer unbenannten Pipe erben zu lassen, der vollständig unter Ihrer Kontrolle in einen Eingabepuffer eingelesen wird die Sie dann löschen.
Weiterführende Literatur
- Timo Sirainen (2009-10-02). Option PR_SET_PROCTITLE_AREA für prctl() hinzugefügt . Linux-Kernel-Mailingliste.
- https://unix.stackexchange.com/a/432681/5132
- Daniel J. Bernstein. Das checkpassword-Interface . cr.yp.to.
- https://github.com/jdebp/nosh/blob/master/source/setprocargv.cpp
- https://github.com/jdebp/nosh/blob/master/source/setprocenvv.cpp