"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 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 , 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 -------------------------