linux-environment-variables.md (15808B)
1 --- 2 title: "Linux Environment Variables" 3 section: "Linux" 4 sectionSlug: "linux-hardening" 5 sourcePath: "src/linux-hardening/linux-basics/linux-environment-variables.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/linux-hardening/linux-basics/linux-environment-variables.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Linux Environment Variables 14 15 ## Global variables 16 17 The global variables **will be** inherited by **child processes**. 18 19 You can create a global variable for your current session doing: 20 21 ```bash 22 export MYGLOBAL="hello world" 23 echo $MYGLOBAL #Prints: hello world 24 ``` 25 26 This variable will be accessible by your current sessions and its child processes. 27 28 You can **remove** a variable doing: 29 30 ```bash 31 unset MYGLOBAL 32 ``` 33 34 ## Local variables 35 36 The **local variables** can only be **accessed** by the **current shell/script**. 37 38 ```bash 39 LOCAL="my local" 40 echo $LOCAL 41 unset LOCAL 42 ``` 43 44 ## List current variables 45 46 ```bash 47 set 48 env 49 printenv 50 cat /proc/$$/environ 51 cat /proc/`python -c "import os; print(os.getppid())"`/environ 52 ``` 53 54 The contents of `/proc/*/environ` are **NUL-separated**, so these variants are usually easier to read: 55 56 ```bash 57 tr '\0' '\n' </proc/$$/environ | sort -u 58 tr '\0' '\n' </proc/<PID>/environ | sort -u 59 ``` 60 61 If you are looking for **credentials** or **interesting service configuration** inside inherited environments, also check [Linux Post Exploitation](/hacktricks/linux-hardening/post-exploitation/linux-post-exploitation/overview). 62 63 ## Common variables 64 65 From: [https://geek-university.com/linux/common-environment-variables/](https://geek-university.com/linux/common-environment-variables/).<sup>[[5]](#references)</sup> 66 67 - **DISPLAY** – the display used by **X**. This variable is usually set to **:0.0**, which means the first display on the current computer. 68 - **EDITOR** – the user’s preferred text editor. 69 - **HISTFILESIZE** – the maximum number of lines contained in the history file. 70 - **HISTSIZE** – Number of lines added to the history file when the user finish his session 71 - **HOME** – your home directory. 72 - **HOSTNAME** – the hostname of the computer. 73 - **LANG** – your current language. 74 - **MAIL** – the location of the user’s mail spool. Usually **/var/spool/mail/USER**. 75 - **MANPATH** – the list of directories to search for manual pages. 76 - **OSTYPE** – the type of operating system. 77 - **PS1** – the default prompt in bash. 78 - **PATH** – stores the path of all the directories which holds binary files you want to execute just by specifying the name of the file and not by relative or absolute path. 79 - **PWD** – the current working directory. 80 - **SHELL** – the path to the current command shell (for example, **/bin/bash**). 81 - **TERM** – the current terminal type (for example, **xterm**). 82 - **TZ** – your time zone. 83 - **USER** – your current username. 84 85 ## Interesting variables for hacking 86 87 Not every variable is equally useful. From an offensive perspective, prioritize variables that change **search paths**, **startup files**, **dynamic linker behavior**, or **audit/logging**. 88 89 ### **HISTFILESIZE** 90 91 Change the **value of this variable to 0**, so when you **end your session** the **history file** (\~/.bash_history) will be **truncated to 0 lines**. 92 93 ```bash 94 export HISTFILESIZE=0 95 ``` 96 97 ### **HISTSIZE** 98 99 Change the **value of this variable to 0**, so commands are **not kept in the in-memory history** and won't be written back to the **history file** (\~/.bash_history). 100 101 ```bash 102 export HISTSIZE=0 103 ``` 104 105 ### **HISTCONTROL** 106 107 If the **value of this variable is set to `ignorespace` or `ignoreboth`**, any command prepended with an extra space will not be saved in the history. 108 109 ```bash 110 export HISTCONTROL=ignorespace 111 ``` 112 113 ```bash 114 $ echo "to save or" 115 $ echo "not to save" 116 ``` 117 118 ### **HISTFILE** 119 120 Point the **history file** to **`/dev/null`** or unset it completely. This is usually more reliable than only changing the history size. 121 122 ```bash 123 export HISTFILE=/dev/null 124 unset HISTFILE 125 ``` 126 127 ### http_proxy & https_proxy 128 129 The processes will use the **proxy** declared here to connect to internet through **http or https**. 130 131 ```bash 132 export http_proxy="http://10.10.10.10:8080" 133 export https_proxy="http://10.10.10.10:8080" 134 ``` 135 136 ### all_proxy & no_proxy 137 138 - `all_proxy`: default proxy for tools/protocols that honor it. 139 - `no_proxy`: bypass list (hosts/domains/CIDRs) that should connect directly. 140 141 ```bash 142 export all_proxy="socks5h://10.10.10.10:1080" 143 export no_proxy="localhost,127.0.0.1,.corp.local,10.0.0.0/8" 144 ``` 145 146 Both lowercase and uppercase variants may be used depending on the tool (`http_proxy`/`HTTP_PROXY`, `no_proxy`/`NO_PROXY`). 147 148 ### SSL_CERT_FILE & SSL_CERT_DIR 149 150 The processes will trust the certificates indicated in **these env variables**. This is useful to make tools such as **`curl`**, **`git`**, Python HTTP clients, or package managers trust a CA controlled by the attacker (for example, to make an interception proxy look legitimate). 151 152 ```bash 153 export SSL_CERT_FILE=/path/to/ca-bundle.pem 154 export SSL_CERT_DIR=/path/to/ca-certificates 155 ``` 156 157 ### **PATH** 158 159 If a privileged wrapper/script executes commands **without absolute paths**, the **first attacker-controlled directory** in `PATH` wins. This is the primitive behind many **PATH hijacks** in `sudo`, cron jobs, shell wrappers, and custom SUID helpers. Look for `env_keep+=PATH`, weak `secure_path`, or wrappers that call `tar`, `service`, `cp`, `python`, etc. by name. 160 161 ```bash 162 mkdir -p /dev/shm/bin 163 cat > /dev/shm/bin/tar <<'EOF' 164 #!/bin/sh 165 echo '[+] PATH hijack reached' >&2 166 id 167 EOF 168 chmod +x /dev/shm/bin/tar 169 PATH=/dev/shm/bin:$PATH vulnerable-wrapper 170 ``` 171 172 For full privilege-escalation chains abusing `PATH`, check [Linux Privilege Escalation](/hacktricks/linux-hardening/linux-basics/linux-privilege-escalation/overview). 173 174 ### **HOME & XDG_CONFIG_HOME** 175 176 `HOME` is not only a directory reference: many tools automatically load **dotfiles**, **plugins**, and **per-user configuration** from `$HOME` or `$XDG_CONFIG_HOME`. If a privileged workflow preserves these values, **config injection** may be easier than binary hijacking. 177 178 ```bash 179 export HOME=/dev/shm/fakehome 180 export XDG_CONFIG_HOME=/dev/shm/fakehome/.config 181 mkdir -p "$XDG_CONFIG_HOME" 182 ``` 183 184 Interesting targets include `.gitconfig`, `.wgetrc`, `.curlrc`, `.inputrc`, `.pythonrc.py`, and tool-specific files such as `.terraformrc`. 185 186 ### **LD_PRELOAD, LD_LIBRARY_PATH & LD_AUDIT** 187 188 These variables influence the **dynamic linker**: 189 190 - `LD_PRELOAD`: force extra shared objects to be loaded first. 191 - `LD_LIBRARY_PATH`: prepend library search directories. 192 - `LD_AUDIT`: load auditor libraries that observe library loading and symbol resolution. 193 194 They are extremely valuable for **hooking**, **instrumentation**, and **privilege escalation** if a privileged command preserves them. In **secure-execution** mode (`AT_SECURE`, e.g. setuid/setgid/capabilities), the loader strips or restricts many of these variables. However, parser bugs in that early loader stage are still high-impact because they run **before** the target program.<sup>[[2]](#references)</sup> 195 196 ```bash 197 env | grep -E '^LD_' 198 ldso=$(ls /lib64/ld-linux-*.so.* /lib/*-linux-gnu/ld-linux-*.so.* 2>/dev/null | head -n1) 199 "$ldso" --list-diagnostics /bin/true | head 200 "$ldso" --list-tunables /bin/true | head 201 ``` 202 203 ### **GLIBC_TUNABLES** 204 205 `GLIBC_TUNABLES` changes early glibc behavior (for example, allocator tunables) and is very handy in exploit labs. It also matters from a security perspective because the **dynamic loader parses it very early**. The 2023 **Looney Tunables** bug was a good reminder that a single environment variable parsed in the loader can become a **local privilege-escalation primitive** against SUID programs.<sup>[[6]](#references)</sup> 206 207 ```bash 208 GLIBC_TUNABLES=glibc.malloc.tcache_count=0 ./binary 209 ``` 210 211 ### **BASH_ENV & ENV** 212 213 If **Bash** is started **non-interactively**, it checks `BASH_ENV` and sources that file before running the target script. When Bash is invoked as `sh`, or in POSIX-style interactive mode, `ENV` may also be consulted. This is a classic way to turn a shell wrapper into code execution if the environment is attacker-controlled. 214 215 ```bash 216 cat > /tmp/pre.sh <<'EOF' 217 echo '[+] sourced before the target script' 218 EOF 219 BASH_ENV=/tmp/pre.sh bash -c 'echo target' 220 ``` 221 222 Bash ignores these startup files when the **real/effective IDs differ**; `-p` preserves the effective ID but does not enable those startup files, so the exact behavior depends on how the wrapper invokes the shell. Be careful with privileged wrappers that call `setuid()`/`setgid()` **before** launching Bash: once the IDs match again, Bash may trust `BASH_ENV`, `ENV`, and related shell state that would otherwise be ignored.<sup>[[1]](#references)</sup> 223 224 ### **PYTHONPATH, PYTHONHOME, PYTHONSTARTUP & PYTHONINSPECT** 225 226 These variables change how Python starts: 227 228 - `PYTHONPATH`: prepend import search paths. 229 - `PYTHONHOME`: relocate the standard library tree. 230 - `PYTHONSTARTUP`: execute a file before the interactive prompt. 231 - `PYTHONINSPECT=1`: drop into interactive mode after a script finishes. 232 233 They are useful against maintenance scripts, debuggers, shells, and wrappers that call Python with a controllable environment. `python -E` and `python -I` ignore all `PYTHON*` variables. 234 235 ```bash 236 mkdir -p /tmp/pylib 237 printf 'print("owned from PYTHONPATH")\n' > /tmp/pylib/htmod.py 238 PYTHONPATH=/tmp/pylib python3 -c 'import htmod' 239 PYTHONPATH=/tmp/pylib python3 -I -c 'import htmod' # ignored in isolated mode 240 ``` 241 242 A recent real-world example was the 2024 **needrestart** LPE on Ubuntu/Debian systems: the root-owned scanner copied an unprivileged process's `PYTHONPATH` from `/proc/<PID>/environ` and then executed Python. The published exploit planted `importlib/__init__.so` in the attacker-controlled path so Python executed attacker code during its own initialization, before the helper's hard-coded script even mattered.<sup>[[3]](#references)</sup> 243 244 ### **PERL5OPT & PERL5LIB** 245 246 Perl has equally useful startup variables: 247 248 - `PERL5LIB`: prepend library directories. 249 - `PERL5OPT`: inject switches as if they were on every `perl` command line. 250 251 This can force **automatic module loading** or change interpreter behavior before the target script does anything interesting. Perl ignores these variables in **taint / setuid / setgid** contexts, but they still matter a lot for normal root-run wrappers, CI jobs, installers, and custom sudoers rules. 252 253 ```bash 254 mkdir -p /tmp/perllib 255 cat > /tmp/perllib/HT.pm <<'EOF' 256 package HT; 257 BEGIN { print "PERL5OPT_TRIGGERED\n" } 258 1; 259 EOF 260 PERL5LIB=/tmp/perllib PERL5OPT=-MHT perl -e 'print "target\n"' 261 ``` 262 263 ### **NODE_OPTIONS** 264 265 `NODE_OPTIONS` prepends **Node.js CLI flags** to every `node` process that inherits the environment. This makes it useful against wrappers, CI jobs, Electron helpers, and sudo rules that eventually invoke Node. The most interesting flags offensively are usually: 266 267 - `--require <file>`: preload a CommonJS file before the target script. 268 - `--import <module>`: preload an ES module before the target script. 269 270 Node rejects some dangerous flags in `NODE_OPTIONS`, but `--require` and `--import` are explicitly allowed and are processed **before** the regular command-line arguments.<sup>[[4]](#references)</sup> 271 272 ```bash 273 cat > /tmp/preload.js <<'EOF' 274 console.error('[+] NODE_OPTIONS preload reached') 275 EOF 276 NODE_OPTIONS='--require /tmp/preload.js' node -e 'console.log("target")' 277 ``` 278 279 For remote gadget chains that set `NODE_OPTIONS` indirectly (for example, prototype-pollution to RCE), check [this other page](/hacktricks/pentesting-web/deserialization/nodejs-proto-prototype-pollution/prototype-pollution-to-rce). 280 281 ### **RUBYLIB & RUBYOPT** 282 283 Ruby offers the same class of startup abuse: 284 285 - `RUBYLIB`: prepend directories to Ruby's load path. 286 - `RUBYOPT`: inject command-line options such as `-r` into every `ruby` invocation. 287 288 ```bash 289 mkdir -p /tmp/rubylib 290 printf 'warn "[+] RUBYOPT preload reached"\n' > /tmp/rubylib/ht.rb 291 RUBYLIB=/tmp/rubylib RUBYOPT='-rht' ruby -e 'puts :target' 292 ``` 293 294 The 2024 **needrestart** vulnerabilities showed that this is not just a lab trick: the same root-owned helper that was vulnerable to `PYTHONPATH` abuse could also be coerced into running Ruby with an attacker-controlled `RUBYLIB`, loading `enc/encdb.so` from an attacker directory.<sup>[[3]](#references)</sup> 295 296 ### **PAGER, MANPAGER, GIT_PAGER, GIT_EDITOR & LESSOPEN** 297 298 Some tools do not just read a path from the environment; they pass the value to a **shell**, an **editor**, or an **input preprocessor**. This makes the following variables especially interesting when a privileged wrapper runs `git`, `man`, `less`, or similar text viewers: 299 300 - `PAGER`, `MANPAGER`, `GIT_PAGER`: choose the pager command. 301 - `GIT_EDITOR`, `VISUAL`, `EDITOR`: choose the editor command, often with arguments. 302 - `LESSOPEN`, `LESSCLOSE`: define pre/post-processors that run when `less` opens a file. 303 304 ```bash 305 PAGER='sh -c "exec sh 0<&1 1>&1"' man man 306 307 cat > /tmp/lesspipe.sh <<'EOF' 308 #!/bin/sh 309 echo '[+] LESSOPEN triggered' >&2 310 cat "$1" 311 EOF 312 chmod +x /tmp/lesspipe.sh 313 LESSOPEN='|/tmp/lesspipe.sh %s' less /etc/hosts 314 ``` 315 316 Git also supports **env-only config injection** without touching disk via `GIT_CONFIG_COUNT`, `GIT_CONFIG_KEY_<n>`, and `GIT_CONFIG_VALUE_<n>`: 317 318 ```bash 319 GIT_CONFIG_COUNT=1 \ 320 GIT_CONFIG_KEY_0=core.pager \ 321 GIT_CONFIG_VALUE_0='sh -c "exec sh 0<&1 1>&1"' \ 322 git -p help 323 ``` 324 325 From a post-exploitation perspective, also remember that inherited environments often contain **credentials**, **proxy settings**, **service tokens**, or **cloud keys**. Check [Linux Post Exploitation](/hacktricks/linux-hardening/post-exploitation/linux-post-exploitation/overview) for `/proc/<PID>/environ` and `systemd` `Environment=` hunting. 326 327 ### PS1 328 329 Change how your prompt looks. 330 331 [**This is an example**](https://gist.github.com/carlospolop/43f7cd50f3deea972439af3222b68808) 332 333 Root: 334 335  336 337 Regular user: 338 339  340 341 One, two and three backgrounded jobs: 342 343  344 345 One background job, one stopped and last command didn't finish correctly: 346 347  348 349 ## References 350 351 - [1] [GNU Bash Manual - Bash Startup Files](https://www.gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html) 352 - [2] [ld.so(8) - Linux manual page](https://man7.org/linux/man-pages/man8/ld.so.8.html) 353 - [3] [Qualys - LPEs in needrestart](https://www.qualys.com/2024/11/19/needrestart/needrestart.txt) 354 - [4] [Node.js CLI documentation - `NODE_OPTIONS`](https://nodejs.org/api/cli.html) 355 - [5] [Common environment variables - Geek University](https://geek-university.com/linux/common-environment-variables/) 356 - [6] [CVE-2023-4911: Looney Tunables - Local Privilege Escalation in the glibc's ld.so - Qualys](https://blog.qualys.com/vulnerabilities-threat-research/2023/10/03/cve-2023-4911-looney-tunables-local-privilege-escalation-in-the-glibcs-ld-so)