CVE-2019-14452: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: Sigil before 0.9.16 is vulnerable to a directory traversal, allowing attackers to write arbitrary files via a ../ (dot dot slash) in a ZIP archive entry that is mishandled during extraction.
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVSS v3 base score: 7.5
Published: 2019-07-31T02:15Z
Repository: https://github.com/Sigil-Ebook/Sigil
Language: C++
Files changed: src/Misc/Utility.cpp, src/sigil_exception.h
Commit date: 2019-07-12 14:08:44 -0400
Commit message: harden plugin unzipping to zip-slip attacks

Diff: diff --git a/src/Misc/Utility.cpp b/src/Misc/Utility.cpp
index 81100a75..853c4035 100644
--- a/src/Misc/Utility.cpp
+++ b/src/Misc/Utility.cpp
@@ -1,7 +1,8 @@
 /************************************************************************
 **
+**  Copyright (C) 2019   Kevin B. Hendricks, Stratford, Ontario Canada
 **  Copyright (C) 2009, 2010, 2011  Strahinja Markovic  <strahinja.markovic@gmail.com>
 **
 **  This file is part of Sigil.
 **
 **  Sigil is free software: you can redistribute it and/or modify
@@ -713,10 +714,48 @@ bool Utility::UnZip(const QString &zippath, const QString &destpath)
                 cp437_file_name = cp437->toUnicode(file_name);
             }
 
             // If there is no file name then we can't do anything with it.
             if (!qfile_name.isEmpty()) {
+
+	        // for security reasons against maliciously crafted zip archives
+	        // we need the file path to always be inside the target folder 
+	        // and not outside, so we will remove all illegal backslashes
+	        // and all relative upward paths segments "/../" from the zip's local 
+	        // file name/path before prepending the target folder to create 
+	        // the final path
+
+	        QString original_path = qfile_name;
+	        bool evil_or_corrupt_epub = false;
+
+	        if (qfile_name.contains("\\")) evil_or_corrupt_epub = true; 
+	        qfile_name = "/" + qfile_name.replace("\\","");
+
+	        if (qfile_name.contains("/../")) evil_or_corrupt_epub = true;
+	        qfile_name = qfile_name.replace("/../","/");
+
+	        while(qfile_name.startsWith("/")) { 
+		  qfile_name = qfile_name.remove(0,1);
+	        }
+                
+	        if (cp437_file_name.contains("\\")) evil_or_corrupt_epub = true; 
+	        cp437_file_name = "/" + cp437_file_name.replace("\\","");
+
+	        if (cp437_file_name.contains("/../")) evil_or_corrupt_epub = true;
+	        cp437_file_name = cp437_file_name.replace("/../","/");
+
+	        while(cp437_file_name.startsWith("/")) { 
+		  cp437_file_name = cp437_file_name.remove(0,1);
+	        }
+
+	        if (evil_or_corrupt_epub) {
+		    unzCloseCurrentFile(zfile);
+		    unzClose(zfile);
+		    // throw (UNZIPLoadParseError(QString(QObject::tr("Possible evil or corrupt zip file name: %1")).arg(original_path).toStdString()));
+                    return false;
+	        }
+
                 // We use the dir object to create the path in the temporary directory.
                 // Unfortunately, we need a dir ojbect to do this as it's not a static function.
                 // Full file path in the temporary directory.
                 QString file_path = destpath + "/" + qfile_name;
                 QFileInfo qfile_info(file_path);
diff --git a/src/sigil_exception.h b/src/sigil_exception.h
index a6561d5c..dcc0e0fc 100644
--- a/src/sigil_exception.h
+++ b/src/sigil_exception.h
@@ -1,7 +1,8 @@
 /************************************************************************
 **
+**  Copyright (C) 2019  Kevin B. Hendricks, Stratford, Ontario Canada
 **  Copyright (C) 2015  John Schember <john@nachtimwald.com>
 **  Copyright (C) 2009, 2010, 2011  Strahinja Markovic  <strahinja.markovic@gmail.com>
 **
 **  This file is part of Sigil.
 **
@@ -130,6 +131,15 @@ public:
 class EPUBLoadParseError : public std::runtime_error {
 public:
     EPUBLoadParseError(const std::string &msg) : std::runtime_error(msg) { };
 };
 
+
+/**
+ * Thrown for Invalid EPUB errors while loading and parsing content files.
+ */
+class UNZIPLoadParseError : public std::runtime_error {
+public:
+    UNZIPLoadParseError(const std::string &msg) : std::runtime_error(msg) { };
+};
+
 #endif // SG_EXCEPTION_H

Fixed code: **  Copyright (C) 2019   Kevin B. Hendricks, Stratford, Ontario Canada

	        // for security reasons against maliciously crafted zip archives
	        // we need the file path to always be inside the target folder 
	        // and not outside, so we will remove all illegal backslashes
	        // and all relative upward paths segments "/../" from the zip's local 
	        // file name/path before prepending the target folder to create 
	        // the final path

	        QString original_path = qfile_name;
	        bool evil_or_corrupt_epub = false;

	        if (qfile_name.contains("\\")) evil_or_corrupt_epub = true; 
	        qfile_name = "/" + qfile_name.replace("\\","");

	        if (qfile_name.contains("/../")) evil_or_corrupt_epub = true;
	        qfile_name = qfile_name.replace("/../","/");

	        while(qfile_name.startsWith("/")) { 
		  qfile_name = qfile_name.remove(0,1);
	        }
                
	        if (cp437_file_name.contains("\\")) evil_or_corrupt_epub = true; 
	        cp437_file_name = "/" + cp437_file_name.replace("\\","");

	        if (cp437_file_name.contains("/../")) evil_or_corrupt_epub = true;
	        cp437_file_name = cp437_file_name.replace("/../","/");

	        while(cp437_file_name.startsWith("/")) { 
		  cp437_file_name = cp437_file_name.remove(0,1);
	        }

	        if (evil_or_corrupt_epub) {
		    unzCloseCurrentFile(zfile);
		    unzClose(zfile);
		    // throw (UNZIPLoadParseError(QString(QObject::tr("Possible evil or corrupt zip file name: %1")).arg(original_path).toStdString()));
                    return false;
	        }

**  Copyright (C) 2019  Kevin B. Hendricks, Stratford, Ontario Canada

/**
 * Thrown for Invalid EPUB errors while loading and parsing content files.
 */
class UNZIPLoadParseError : public std::runtime_error {
public:
    UNZIPLoadParseError(const std::string &msg) : std::runtime_error(msg) { };
};