From: lux <lx@shellcodes.org>
To: 59817@debbugs.gnu.org
Subject: bug#59817: [PATCH] Fix etags local command injection vulnerability
Date: Sun, 4 Dec 2022 21:51:13 +0800 [thread overview]
Message-ID: <tencent_6242BE39DC20D890736B46B9E6AB46EACE05@qq.com> (raw)
[-- Attachment #1: Type: text/plain, Size: 1827 bytes --]
Hi, this patch fix a new local command injection vulnerability in the
etags.c.
This vulnerability occurs in the following code:
#if MSDOS || defined (DOS_NT)
char *cmd1 = concat (compr->command, " \"", real_name);
char *cmd = concat (cmd1, "\" > ", tmp_name);
#else
char *cmd1 = concat (compr->command, " '", real_name);
char *cmd = concat (cmd1, "' > ", tmp_name);
#endif
free (cmd1);
inf = (system (cmd) == -1
? NULL
: fopen (tmp_name, "r" FOPEN_BINARY));
free (cmd);
}
Vulnerability #1:
for tmp_name variable, the value from the etags_mktmp() function, this
function takes the value from the environment variable `TMPDIR`, `TEMP`
or `TMP`, but without checking the value. So, if then hacker can
control these environment variables, can execute the shell code.
Attack example:
$ ls
etags.c
$ zip etags.z etags.c
adding: etags.c (deflated 72%)
$ tmpdir="/tmp/;uname -a;/"
$ mkdir $tmpdir
$ TMPDIR=$tmpdir etags *
sh: line 1: /tmp/: Is a directory
Linux mypc 6.0.10-300.fc37.x86_64 #1 SMP PREEMPT_DYNAMIC Sat Nov 26
16:55:13 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux sh: line 1: /etECggCJ:
No such file or directory etags: skipping inclusion of TAGS in self.
Vulnerability #2:
If the target file is a compressed file, execute system commands (such
as gzip, etc.), but do not check the file name.
Attack example:
$ ls
etags.c
$ zip "';uname -a;'test.z" etags.c <--- inject the shell code to
filename
adding: etags.c (deflated 72%)
$ etags *
gzip: .gz: No such file or directory
Linux mypc 6.0.10-300.fc37.x86_64 #1 SMP PREEMPT_DYNAMIC Sat Nov 26
16:55:13 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux sh: line 1: test.z:
command not found
I fix this vulnerability. By create a process, instead of call the
sh or cmd.exe, and this patch work the Linux, BSD and Windows.
[-- Attachment #2: 0001-Fix-etags-local-command-injection-vulnerability.patch --]
[-- Type: text/x-patch, Size: 6233 bytes --]
From 30cec9dcbd67998ce2360d191044e8d97992f794 Mon Sep 17 00:00:00 2001
From: lu4nx <lx@shellcodes.org>
Date: Sun, 4 Dec 2022 21:18:29 +0800
Subject: [PATCH] Fix etags local command injection vulnerability
* lib-src/etags.c:
(decompress_file): New function
---
lib-src/etags.c | 155 +++++++++++++++++++++++++++++++++++++-----------
1 file changed, 121 insertions(+), 34 deletions(-)
diff --git a/lib-src/etags.c b/lib-src/etags.c
index d1d20858cd..7c5faf8d6b 100644
--- a/lib-src/etags.c
+++ b/lib-src/etags.c
@@ -97,6 +97,7 @@ Copyright (C) 1984, 1987-1989, 1993-1995, 1998-2022 Free Software
#ifdef WINDOWSNT
# include <direct.h>
+# include <windows.h>
# undef HAVE_NTGUI
# undef DOS_NT
# define DOS_NT
@@ -124,6 +125,7 @@ Copyright (C) 1984, 1987-1989, 1993-1995, 1998-2022 Free Software
#include <assert.h>
#include <getopt.h>
#include <regex.h>
+#include <sys/wait.h>
/* Define CTAGS to make the program "ctags" compatible with the usual one.
Leave it undefined to make the program "etags", which makes emacs-style
@@ -255,7 +257,7 @@ #define xrnew(op, n, m) ((op) = xnrealloc (op, n, (m) * sizeof *(op)))
typedef struct
{
const char *suffix; /* file name suffix for this compressor */
- const char *command; /* takes one arg and decompresses to stdout */
+ const char *command; /* uncompress command */
} compressor;
typedef struct
@@ -391,6 +393,7 @@ #define xrnew(op, n, m) ((op) = xnrealloc (op, n, (m) * sizeof *(op)))
static _Noreturn void pfatal (const char *);
static void add_node (node *, node **);
+static bool decompress_file (const char *, const char *, const char *);
static void process_file_name (char *, language *);
static void process_file (FILE *, char *, language *);
static void find_entries (FILE *);
@@ -527,16 +530,16 @@ #define STDIN 0x1001 /* returned by getopt_long on --parse-stdin */
};
static compressor compressors[] =
-{
- { "z", "gzip -d -c"},
- { "Z", "gzip -d -c"},
- { "gz", "gzip -d -c"},
- { "GZ", "gzip -d -c"},
- { "bz2", "bzip2 -d -c" },
- { "xz", "xz -d -c" },
- { "zst", "zstd -d -c" },
- { NULL }
-};
+ {
+ { "z", "gzip" },
+ { "Z", "gzip" },
+ { "gz", "gzip" },
+ { "GZ", "gzip" },
+ { "bz2", "bzip2" },
+ { "xz", "xz" },
+ { "zst", "zstd" },
+ { NULL }
+ };
/*
* Language stuff.
@@ -1621,7 +1624,6 @@ process_file_name (char *file, language *lang)
compressor *compr;
char *compressed_name, *uncompressed_name;
char *ext, *real_name UNINIT, *tmp_name UNINIT;
- int retval;
canonicalize_filename (file);
if (streq (file, tagfile) && !streq (tagfile, "-"))
@@ -1712,37 +1714,29 @@ process_file_name (char *file, language *lang)
inf = NULL;
else
{
-#if MSDOS || defined (DOS_NT)
- char *cmd1 = concat (compr->command, " \"", real_name);
- char *cmd = concat (cmd1, "\" > ", tmp_name);
-#else
- char *cmd1 = concat (compr->command, " '", real_name);
- char *cmd = concat (cmd1, "' > ", tmp_name);
-#endif
- free (cmd1);
- inf = (system (cmd) == -1
- ? NULL
- : fopen (tmp_name, "r" FOPEN_BINARY));
- free (cmd);
- }
-
- if (!inf)
- {
- perror (real_name);
- goto cleanup;
+ inf = (!decompress_file (compr->command, real_name, tmp_name)
+ ? NULL
+ : fopen (tmp_name, "r" FOPEN_BINARY));
+ if (!inf)
+ {
+ perror (real_name);
+ goto cleanup;
+ }
}
}
process_file (inf, uncompressed_name, lang);
- retval = fclose (inf);
+ if (fclose (inf) < 0)
+ pfatal (file);
+
if (real_name == compressed_name)
{
- remove (tmp_name);
+ if (remove (tmp_name) == -1)
+ pfatal (tmp_name);
+
free (tmp_name);
}
- if (retval < 0)
- pfatal (file);
cleanup:
if (compressed_name != file)
@@ -1754,6 +1748,99 @@ process_file_name (char *file, language *lang)
return;
}
+/*
+ * Specify a decompression command and write the decompression content to a new file.
+ * On success, true is returned.
+ */
+static bool
+decompress_file (const char *command, const char *input_file, const char *output_file)
+{
+#ifdef DOS_NT
+ SECURITY_ATTRIBUTES sa;
+ sa.nLength = sizeof (SECURITY_ATTRIBUTES);
+ sa.lpSecurityDescriptor = NULL;
+ sa.bInheritHandle = true;
+
+ HANDLE hFile = CreateFile (output_file, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ {
+ perror (output_file);
+ return false;
+ }
+
+ PROCESS_INFORMATION pi;
+ STARTUPINFO si;
+ ZeroMemory (&si, sizeof (si));
+ ZeroMemory (&pi, sizeof (pi));
+ si.cb = sizeof (STARTUPINFO);
+ si.dwFlags = STARTF_USESTDHANDLES;
+ si.hStdInput = NULL;
+ si.hStdOutput = hFile;
+ si.wShowWindow = SW_HIDE;
+
+ char *cmd = concat (command, " -d -c ", input_file);
+ if (!CreateProcess (NULL, cmd, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
+ {
+ perror ("CreateProcess error");
+ return false;
+ }
+
+ WaitForSingleObject(pi.hProcess, INFINITE);
+
+ CloseHandle (pi.hProcess);
+ CloseHandle (pi.hThread);
+ CloseHandle (hFile);
+ return true;
+#else
+ int out_f;
+ if ((out_f = open (output_file, O_CREAT | O_WRONLY)) == -1)
+ {
+ perror (output_file);
+ return false;
+ }
+
+ pid_t pid = fork ();
+ if (pid == -1)
+ {
+ perror ("fork");
+ return false;
+ }
+
+ if (pid == 0)
+ {
+ if (dup2 (out_f, STDOUT_FILENO) == -1)
+ {
+ perror ("dup2 stdout error");
+ exit (EXIT_FAILURE);
+ }
+
+ char *command_args[] = { (char *) command, (char *) "-d", (char *) "-c", (char *) input_file, NULL };
+ if (execvp (command, command_args) == -1)
+ {
+ perror ("cannot execute the decompress command");
+ exit (EXIT_FAILURE);
+ }
+
+ exit (EXIT_SUCCESS);
+ }
+
+ if (waitpid (pid, NULL, 0) == -1)
+ {
+ perror ("waitpid error");
+ return false;
+ }
+
+ if (close (out_f) == -1)
+ {
+ perror ("close error");
+ return false;
+ }
+
+ return true;
+#endif
+}
+
static void
process_file (FILE *fh, char *fn, language *lang)
{
--
2.38.1
next reply other threads:[~2022-12-04 13:51 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-12-04 13:51 lux [this message]
2022-12-04 14:39 ` bug#59817: [PATCH] Fix etags local command injection vulnerability Eli Zaretskii
2022-12-04 16:27 ` Stefan Kangas
2022-12-04 17:04 ` Eli Zaretskii
[not found] ` <tencent_2F6B5EEED2E485C363837738F5661E6AB009@qq.com>
2022-12-05 12:34 ` Eli Zaretskii
2022-12-06 7:48 ` lux
2022-12-06 12:55 ` Eli Zaretskii
2022-12-06 13:11 ` lux
2022-12-06 14:52 ` Eli Zaretskii
2022-12-06 15:05 ` Francesco Potortì
2022-12-06 15:19 ` Francesco Potortì
2022-12-06 15:49 ` lux
2022-12-06 16:14 ` Eli Zaretskii
2022-12-06 13:05 ` Andreas Schwab
2022-12-06 14:33 ` Eli Zaretskii
2022-12-05 0:58 ` lux
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=tencent_6242BE39DC20D890736B46B9E6AB46EACE05@qq.com \
--to=lx@shellcodes.org \
--cc=59817@debbugs.gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).