CVE-2014-1932: Improper Link Resolution Before File Access ('Link Following') (CWE-59)
value: The (1) load_djpeg function in JpegImagePlugin.py, (2) Ghostscript function in EpsImagePlugin.py, (3) load function in IptcImagePlugin.py, and (4) _copy function in Image.py in Python Image Library (PIL) 1.1.7 and earlier and Pillow before 2.3.1 do not properly create temporary files, which allow local users to overwrite arbitrary files and obtain sensitive information via a symlink attack on the temporary file.
CWE description: The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.
Published: 2014-04-17T14:55Z
Repository: https://github.com/python-imaging/Pillow
Language: Python
Files changed: PIL/EpsImagePlugin.py, PIL/Image.py, PIL/IptcImagePlugin.py, PIL/JpegImagePlugin.py
Commit date: 2014-03-14 16:40:45 -0700
Commit message: Removed tempfile.mktemp, fixes CVE-2014-1932 CVE-2014-1933, debian bug #737059

Diff: diff --git a/PIL/EpsImagePlugin.py b/PIL/EpsImagePlugin.py
index a8706b0..fcfae8a 100644
--- a/PIL/EpsImagePlugin.py
+++ b/PIL/EpsImagePlugin.py
@@ -65,11 +65,12 @@ def Ghostscript(tile, size, fp, scale=1):
     bbox = [bbox[0], bbox[1], bbox[2] * scale, bbox[3] * scale]
     #print("Ghostscript", scale, size, orig_size, bbox, orig_bbox)
 
     import tempfile, os, subprocess
 
-    file = tempfile.mktemp()
+    out_fd, file = tempfile.mkstemp()
+    os.close(out_fd)
 
     # Build ghostscript command
     command = ["gs",
                "-q",                    # quite mode
                "-g%dx%d" % size,        # set output geometry (pixels)
diff --git a/PIL/Image.py b/PIL/Image.py
index 87631d6..915f5b1 100644
--- a/PIL/Image.py
+++ b/PIL/Image.py
@@ -493,18 +493,21 @@ class Image:
         self.load()
         self.im = self.im.copy()
         self.readonly = 0
 
     def _dump(self, file=None, format=None):
-        import tempfile
+        import tempfile, os
         if not file:
-            file = tempfile.mktemp()
+            f, file = tempfile.mkstemp(format or '')
+            os.close(f)
+            
         self.load()
         if not format or format == "PPM":
             self.im.save_ppm(file)
         else:
-            file = file + "." + format
+            if file.endswith(format):
+                file = file + "." + format
             self.save(file, format)
         return file
 
     def __repr__(self):
         return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % (
diff --git a/PIL/IptcImagePlugin.py b/PIL/IptcImagePlugin.py
index 157b735..1041530 100644
--- a/PIL/IptcImagePlugin.py
+++ b/PIL/IptcImagePlugin.py
@@ -170,12 +170,12 @@ class IptcImageFile(ImageFile.ImageFile):
         encoding, offset = tile
 
         self.fp.seek(offset)
 
         # Copy image data to temporary file
-        outfile = tempfile.mktemp()
-        o = open(outfile, "wb")
+        o_fd, outfile = tempfile.mkstemp(text=False)
+        o = os.fdopen(o_fd)
         if encoding == "raw":
             # To simplify access to the extracted file,
             # prepend a PPM header
             o.write("P5\n%d %d\n255\n" % self.size)
         while True:
diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py
index 90d3b51..67dc5ba 100644
--- a/PIL/JpegImagePlugin.py
+++ b/PIL/JpegImagePlugin.py
@@ -342,17 +342,21 @@ class JpegImageFile(ImageFile.ImageFile):
     def load_djpeg(self):
 
         # ALTERNATIVE: handle JPEGs via the IJG command line utilities
 
         import tempfile, os
-        file = tempfile.mktemp()
-        os.system("djpeg %s >%s" % (self.filename, file))
+        f, path = tempfile.mkstemp()
+        os.close(f)
+        if os.path.exists(self.filename):
+            os.system("djpeg '%s' >'%s'" % (self.filename, path))
+        else:
+            raise ValueError("Invalid Filename")
 
         try:
-            self.im = Image.core.open_ppm(file)
+            self.im = Image.core.open_ppm(path)
         finally:
-            try: os.unlink(file)
+            try: os.unlink(path)
             except: pass
 
         self.mode = self.im.mode
         self.size = self.im.size
 

Vulnerable code:     file = tempfile.mktemp()
        import tempfile
            file = tempfile.mktemp()
            file = file + "." + format
        outfile = tempfile.mktemp()
        o = open(outfile, "wb")
        file = tempfile.mktemp()
        os.system("djpeg %s >%s" % (self.filename, file))
            self.im = Image.core.open_ppm(file)
            try: os.unlink(file)
Fixed code:     out_fd, file = tempfile.mkstemp()
    os.close(out_fd)
        import tempfile, os
            f, file = tempfile.mkstemp(format or '')
            os.close(f)
            
            if file.endswith(format):
                file = file + "." + format
        o_fd, outfile = tempfile.mkstemp(text=False)
        o = os.fdopen(o_fd)
        f, path = tempfile.mkstemp()
        os.close(f)
        if os.path.exists(self.filename):
            os.system("djpeg '%s' >'%s'" % (self.filename, path))
        else:
            raise ValueError("Invalid Filename")
            self.im = Image.core.open_ppm(path)
            try: os.unlink(path)