Bugtraq mailing list archives

ASA-0000: GV Execution of Arbitrary Shell Commands


From: Marc Bevand <bevand_m () epita fr>
Date: 1 Oct 2002 00:00:02 -0000



                      "After" Security Advisory

        Title: GV Execution of Arbitrary Shell Commands
      Affects: gv-3.5.8 and probably older versions
  Advisory ID: ASA-0000
 Release Date: 2002-10-01
       Author: Marc Bevand <bevand_m (at) epita.fr>
          URL: http://www.epita.fr/~bevand_m/asa/asa-0000


--oOo-- 0. Table of Contents

0. Table of Contents
1. Introduction
2. Problem
3. Solution
4. Conclusion
5. References
6. Attached files


--oOo-- 1. Introduction

  GV [0] is a PostScript and PDF previewer available on
many unix
systems and even on some non-unix systems. Technically,
it is a user
interface for Ghostscript [1], which is a PostScript
and PDF language
interpreter. GV is also able to automatically
decompress GZip'ed [2]
files on-the-fly before reading them.

  When GV detects that the document is either a PDF
file or a
GZip compressed file, it executes some commands with
the help of the
system() function. Unfortunately, these commands
contain the
filename, which can be considered as untrusted user
input. It is then
possible to distribute a file (with a meticulously
choosed filename,
that can even seems innocent) that causes execution of
arbitrary
shell commands when it is read with GV.


--oOo-- 2. Problem

  GV detects PDF files or GZip compressed files by
reading the first
bytes of datas:

  o when "%PDF-" is read, GV assumes it is a PDF file
and call
    system() with the following argument (default value
of the
    GV.gsCmdScanPDF X11 ressource):
    "gs -dNODISPLAY -dQUIET -sPDFname=%s -sDSCname=%s
pdf2dsc.ps -c quit"
    The 1st "%s" corresponds to the PDF filename, and
the 2nd to a
    temporary filename.

  o when "\037\235" or "\037\213" is read, GV assumes
it is a GZip
    compressed file and call system() with the
following argument
    (default value of the GV.uncompressCommand X11
ressource):
    "gzip -d -c %s > %s"
    The 1st "%s" corresponds to the GZip compressed
filename, and the
    2nd to a temporary filename.

  In these conditions, trying to open, for example, a
PDF file named
"xxx & echo hello & xxx" leads to execution of
"gs -dNODISPLAY -dQUIET -sPDFname=xxx & echo hello &
xxx ...". Thus,
"echo hello" (a part of the filename) is executed:

      $ file "xxx & echo hello & xxx"
      xxx & echo hello & xxx: PDF document, version 1.2
      $ gv "xxx & echo hello & xxx" 
  --> hello
      sh: xxx: command not found
      GS>hello
      sh: xxx..tmp: command not found

The error messages ("sh: xxx: command not found", etc)
are just
results of the garbage introduced in the system()
argument by the
unusual "xxx & echo hello & xxx" filename. Moreover, GV
displays a
dialog box explaining that execution of Ghostscript failed.

  But all these "inconvenients" (from the malicious
user point-of-
view) can be easily avoided. Imagine a site where each
host access a
file server through the mount point "/sgoinfre", and
suppose that
someone (Charly) creates 2 files in this directory:

  o a PDF file named 'Huhu_"`source evil`".pdf'
(doublequotes and
    backquotes are part of the filename)
  o a shell script named 'evil' that contains:
      #!/bin/sh
      echo '"`source evil`"'
      touch _it_works_

Now, here is what happens if someone else (Alice) wants
to read the
PDF file:

  $ cd /sgoinfre
  $ gv 'Huhu_"`source evil`".pdf'

All works fine for Alice (no error messages, no dialog
box), except
that she hasn't realized that 'evil' has been executed
under her
identity:

  $ ls -l _it_works_
  -rw-------    1 alice   users         0 Jul 30 05:56
_it_works_

Note: this example works only if the /bin/sh shell
executed by
system() supports the 'source' builtin; that's the case
when /bin/sh
is a link to bash.


--oOo-- 3. Solution

  The GV maintainer, Johannes Plass <plass (at)
thep.physik.uni-mainz.de>, has been e-mailed twice
about this
problem. Unfortunately, no response has been received,
it seems that
he has stopped his work on GV since 1997.

  However, I propose a temporary fix: the attached patch
("asa-0000.gv-3.5.8.patch"), done against GV 3.5.8,
checks if
the filename contains only allowed characters
(alphanumeric and
``+,-./:=@\^_''). If this is not the case, an error
message is
displayed and system() is not called.


--oOo-- 4. Conclusion

  GV contains a security hole allowing execution of
arbitrary shell
commands. Since the author seems to have stopped his
work on GV, a
temporary fix has been developped: see the attached
patch done
against GV 3.5.8.


--oOo-- 5. References

[0] GV
    http://wwwthep.physik.uni-mainz.de/~plass/gv/

[1] Ghostscript
    http://www.cs.wisc.edu/~ghost/index.html

[2] GNU Zip
    http://www.gzip.org

--oOo-- 6. Attached files

The following file is also available at:
http://www.epita.fr/~bevand_m/asa/asa-0000.gv-3.5.8.patch

---8<------------------ asa-0000.gv-3.5.8.patch
-------------------------
diff -ur gv-3.5.8.orig/source/file.c gv-3.5.8/source/file.c
--- gv-3.5.8.orig/source/file.c 1997-06-07
00:00:00.000000000 +0200
+++ gv-3.5.8/source/file.c      2002-09-26
23:56:00.000000000 +0200
@@ -285,6 +285,22 @@
 }
 
 /*############################################################*/
+/* file_nameIsDangerous */
+/*############################################################*/
+
+char *file_charsAllowedInName = "+,-./:=@\\^_";
+
+int
+file_nameIsDangerous(fn)
+  char *fn;
+{
+  for (; *fn; fn++)
+    if (!isalnum(*fn) &&
!strchr(file_charsAllowedInName, *fn))
+      return(1);
+  return(0);
+}
+
+/*############################################################*/
 /* file_pdfname2psname */
 /* If the file ends in .pdf, change this to .ps.*/
 /* Return pointer to temp copy if changed, else to
input string. */
diff -ur gv-3.5.8.orig/source/file.h gv-3.5.8/source/file.h
--- gv-3.5.8.orig/source/file.h 1997-04-26
00:00:00.000000000 +0200
+++ gv-3.5.8/source/file.h      2002-09-26
23:28:38.000000000 +0200
@@ -70,6 +70,14 @@
 #endif
 );
 
+extern char *file_charsAllowedInName;
+
+extern int                     file_nameIsDangerous (
+#if NeedFunctionPrototypes
+   char *
+#endif
+);
+
 extern char*                   file_pdfname2psname (
 #if NeedFunctionPrototypes
    char *      /* name */
diff -ur gv-3.5.8.orig/source/ps.c gv-3.5.8/source/ps.c
--- gv-3.5.8.orig/source/ps.c   1997-06-07
00:00:00.000000000 +0200
+++ gv-3.5.8/source/ps.c        2002-09-27
00:29:35.000000000 +0200
@@ -420,6 +420,16 @@
       char cmd[512];
       char s[512];
       filename_unc=file_getTmpFilename(NULL,filename_raw);
+      if (file_nameIsDangerous(filename))
+       {
+         INFMESSAGE(the filename is dangerous)
+         sprintf(s, "The filename \"%s\" is dangerous:
only alphanumeric "
+                 "characters and \"%s\" are allowed.\n",
+                 filename, file_charsAllowedInName);
+         NotePopupShowMessage(s);
+         ENDMESSAGE(psscan)
+         return(NULL);
+       }
       sprintf(cmd,cmd_uncompress,filename,filename_unc);
       INFMESSAGE(is compressed)
       INFSMESSAGE(uncompress command,cmd)
@@ -491,6 +501,16 @@
       char cmd[512];
       char s[512];
       filename_dsc=file_getTmpFilename(NULL,filename_raw);
+      if (file_nameIsDangerous(filename))
+       {
+         INFMESSAGE(the filename is dangerous)
+         sprintf(s, "The filename \"%s\" is dangerous:
only alphanumeric "
+                 "characters and \"%s\" are allowed.\n",
+                 filename, file_charsAllowedInName);
+         NotePopupShowMessage(s);
+         ENDMESSAGE(psscan)
+         return(NULL);
+       }
       sprintf(cmd,cmd_scan_pdf,filename,filename_dsc);
       INFMESSAGE(is PDF)
       INFSMESSAGE(scan command,cmd)
---8<------------------ asa-0000.gv-3.5.8.patch
-------------------------


Current thread: