diff --git a/DOCUMENTATION/SYSTEM_ADMINISTRATORS/CONFIGURE.md b/DOCUMENTATION/SYSTEM_ADMINISTRATORS/CONFIGURE.md index 5346f1f378..f3ed5e589b 100644 --- a/DOCUMENTATION/SYSTEM_ADMINISTRATORS/CONFIGURE.md +++ b/DOCUMENTATION/SYSTEM_ADMINISTRATORS/CONFIGURE.md @@ -667,6 +667,12 @@ in php.ini to be large enough to handle your upload. In httpd.conf (if you're using apache), check that the LimitRequestBody directive isn't set too low (it's optional, so it may not be there at all). +* `extblacklist`: associative array to either deny certain extensions or + change them to a different one. For example: + $config['attachments']['extblacklist']['php'] = 'phps'; // this turns .php into .phps + $config['attachments']['extblacklist']['exe'] = false; // this would deny any uploads + // of files with the "exe" extension + * `process_links`: follow redirects and save all available file information (mimetype, date, size, oembed, etc.). Defaults to true. diff --git a/README.md b/README.md index 0746163e87..c4529dd6b0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# GNU social 1.21.x +# GNU social 1.22.x (c) 2010-2019 Free Software Foundation, Inc This is the README file for GNU social, the free diff --git a/classes/File.php b/classes/File.php index 1af73d28be..4bf91cebff 100644 --- a/classes/File.php +++ b/classes/File.php @@ -336,6 +336,31 @@ class File extends Managed_DataObject return $filename; } + /** + * @param string $filename + * @return string|bool Value from the 'extblacklist' array, in the config + */ + public static function getSafeExtension(string $filename) { + if (preg_match('/^.+?\.([A-Za-z0-9]+)$/', $filename, $matches)) { + // we matched on a file extension, so let's see if it means something. + $ext = mb_strtolower($matches[1]); + $blacklist = common_config('attachments', 'extblacklist'); + // If we got an extension from $filename we want to check if it's in a blacklist + // so we avoid people uploading restricted files + if (array_key_exists($ext, $blacklist)) { + if (!is_string($blacklist[$ext])) { + return false; + } + // return a safe replacement extension ('php' => 'phps' for example) + return $blacklist[$ext]; + } + // the attachment extension based on its filename was not blacklisted so it's ok to use it + return $ext; + } else { + return false; + } + } + /** * @param $mimetype string The mimetype we've discovered for this file. * @param $filename string An optional filename which we can use on failure. @@ -351,28 +376,16 @@ class File extends Managed_DataObject return $ext; } catch (UnknownMimeExtensionException $e) { // We don't know the extension for this mimetype, but let's guess. - // If we can't recognize the extension from the MIME, we try // to guess based on filename, if one was supplied. - if (!is_null($filename) && preg_match('/^.+\.([A-Za-z0-9]+)$/', $filename, $matches)) { - // we matched on a file extension, so let's see if it means something. - $ext = mb_strtolower($matches[1]); - - $blacklist = common_config('attachments', 'extblacklist'); - // If we got an extension from $filename we want to check if it's in a blacklist - // so we avoid people uploading .php files etc. - if (array_key_exists($ext, $blacklist)) { - if (!is_string($blacklist[$ext])) { - // we don't have a safe replacement extension - throw new ClientException(_('Blacklisted file extension.')); - } - common_debug('Found replaced extension for filename '._ve($filename).': '._ve($ext)); - - // return a safe replacement extension ('php' => 'phps' for example) - return $blacklist[$ext]; + if (!is_null($filename)) { + $ext = getSafeExtension($filename); + if ($ext === false) { + // we don't have a safe replacement extension + throw new ClientException(_('Blacklisted file extension.')); + } else { + return $ext; } - // the attachment extension based on its filename was not blacklisted so it's ok to use it - return $ext; } } catch (Exception $e) { common_log(LOG_INFO, 'Problem when figuring out extension for mimetype: '._ve($e)); @@ -381,9 +394,9 @@ class File extends Managed_DataObject // If nothing else has given us a result, try to extract it from // the mimetype value (this turns .jpg to .jpeg for example...) $matches = array(); - // FIXME: try to build a regexp that will get jpeg from image/jpeg as well as json from application/jrd+json - if (!preg_match('/\/([a-z0-9]+)/', mb_strtolower($mimetype), $matches)) { - throw new Exception('Malformed mimetype: '.$mimetype); + // Will get jpeg from image/jpeg as well as json from application/jrd+json + if (!preg_match('/[\/+-\.]([a-z0-9]+)/', mb_strtolower($mimetype), $matches)) { + throw new Exception("Malformed mimetype: {$mimetype}"); } return mb_strtolower($matches[1]); } diff --git a/lib/default.php b/lib/default.php index 754d03aa6f..c68c31c7c5 100644 --- a/lib/default.php +++ b/lib/default.php @@ -273,10 +273,7 @@ $default = 'show_html' => false, // show (filtered) text/html attachments (and oEmbed HTML etc.). Doesn't affect AJAX calls. 'show_thumbs' => true, // show thumbnails in notice lists for uploaded images, and photos and videos linked remotely that provide oEmbed info 'process_links' => true, // check linked resources for embeddable photos and videos; this will hit referenced external web sites when processing new messages. - 'extblacklist' => [ - 'php' => 'phps', // this turns .php into .phps - 'exe' => false, // this would deny any uploads to keep the "exe" file extension - ], + 'extblacklist' => [], 'memory_limit' => '1024M' // PHP's memory limit to use temporarily when handling images ), 'thumbnail' => [ diff --git a/lib/framework.php b/lib/framework.php index 77e1b19076..1077256605 100644 --- a/lib/framework.php +++ b/lib/framework.php @@ -32,12 +32,12 @@ defined('GNUSOCIAL') || die(); define('GNUSOCIAL_ENGINE', 'GNU social'); define('GNUSOCIAL_ENGINE_URL', 'https://www.gnu.org/software/social/'); -define('GNUSOCIAL_BASE_VERSION', '1.21.3'); +define('GNUSOCIAL_BASE_VERSION', '1.22.0'); define('GNUSOCIAL_LIFECYCLE', 'dev'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release' define('GNUSOCIAL_VERSION', GNUSOCIAL_BASE_VERSION . '-' . GNUSOCIAL_LIFECYCLE); -define('GNUSOCIAL_CODENAME', 'The Invicta Crusade'); +define('GNUSOCIAL_CODENAME', 'Undecided'); define('AVATAR_PROFILE_SIZE', 96); define('AVATAR_STREAM_SIZE', 48); diff --git a/lib/mediafile.php b/lib/mediafile.php index e429ec7f91..b38bab6436 100644 --- a/lib/mediafile.php +++ b/lib/mediafile.php @@ -351,6 +351,12 @@ class MediaFile File::respectsQuota($scoped, $_FILES[$param]['size']); } + // Gets a replacement extension if configured in the config, returns false if it's blocked + $ext = File::getSafeExtension($_FILES[$param]['name']); + if ($ext === false) { + throw new ClientException(_('Blacklisted file extension.')); + } + $mimetype = self::getUploadedMimeType($_FILES[$param]['tmp_name'], $_FILES[$param]['name']); $media = common_get_mime_media($mimetype); @@ -371,7 +377,7 @@ class MediaFile } // New file name format - $original_filename = bin2hex("{$basename}.{$ext}"); + $original_filename = bin2hex($basename); $filename = "{$original_filename}-{$filehash}"; $filepath = File::path($filename); @@ -609,22 +615,20 @@ class MediaFile */ public static function getDisplayName(File $file) : string { // New file name format is "{bin2hex(original_name.ext)}-{$hash}" - $ret = preg_match('/^([^\-]+)-.+$/', $file->filename, $matches); + $ret = preg_match('/^([^\.-]+)-.+$/', $file->filename, $matches); // If there was an error in the match, something's wrong with some piece // of code (could be a file with utf8 chars in the name) - $user_error_mesg = "Invalid file name ({$file->filename})."; - $log_error_msg = "Invalid file name for File with id={$file->id} " . - "({$file->filename}). Some plugin probably did something wrong."; + $log_error_msg = "Invalid file name for File with id={$file->id} " . + "({$file->filename}). Some plugin probably did something wrong."; if ($ret === false) { common_log(LOG_ERR, $log_error_msg); - throw new ServerException($user_error_msg); } elseif ($ret === 1) { $filename = hex2bin($matches[1]); } else { // The old file name format was "{hash}.{ext}" // This estracts the extension - $ret = preg_match('/^[^\.]+\.(.+)$/', $file->filename, $matches); + $ret = preg_match('/^.+?\.(.+)$/', $file->filename, $matches); if ($ret !== 1) { common_log(LOG_ERR, $log_error_msg); return _('Untitled attachment');