1
0
mirror of https://github.com/phpbb/phpbb.git synced 2025-02-25 04:23:38 +01:00
Andreas Fischer f894da0d25 [ticket/10633] Stop leaking filename of attachments when thumbnail is requested
While filenames are chosen at random and there is no correlation between the
original filename and the new filesystem filename, there is a correlation
between filesystem filename and filesytem filename of thumbnails.

Adjust error message to no longer include the physical filename and make it
consistent with the error message that is shown when there is no attachment at
all.

This information was mostly useless for regular users (i.e. non-admins) anyway.

PHPBB3-10633
2012-02-09 15:17:42 +01:00

712 lines
18 KiB
PHP

<?php
/**
*
* @package phpBB3
* @version $Id$
* @copyright (c) 2005 phpBB Group
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*
*/
/**
* @ignore
*/
define('IN_PHPBB', true);
$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './../';
$phpEx = substr(strrchr(__FILE__, '.'), 1);
// Thank you sun.
if (isset($_SERVER['CONTENT_TYPE']))
{
if ($_SERVER['CONTENT_TYPE'] === 'application/x-java-archive')
{
exit;
}
}
else if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'Java') !== false)
{
exit;
}
if (isset($_GET['avatar']))
{
require($phpbb_root_path . 'includes/startup.' . $phpEx);
require($phpbb_root_path . 'config.' . $phpEx);
if (!defined('PHPBB_INSTALLED') || empty($dbms) || empty($acm_type))
{
exit;
}
require($phpbb_root_path . 'includes/acm/acm_' . $acm_type . '.' . $phpEx);
require($phpbb_root_path . 'includes/cache.' . $phpEx);
require($phpbb_root_path . 'includes/db/' . $dbms . '.' . $phpEx);
require($phpbb_root_path . 'includes/constants.' . $phpEx);
require($phpbb_root_path . 'includes/functions.' . $phpEx);
$db = new $sql_db();
$cache = new cache();
// Connect to DB
if (!@$db->sql_connect($dbhost, $dbuser, $dbpasswd, $dbname, $dbport, false, false))
{
exit;
}
unset($dbpasswd);
// worst-case default
$browser = (!empty($_SERVER['HTTP_USER_AGENT'])) ? htmlspecialchars((string) $_SERVER['HTTP_USER_AGENT']) : 'msie 6.0';
$config = $cache->obtain_config();
$filename = request_var('avatar', '');
$avatar_group = false;
$exit = false;
if (isset($filename[0]) && $filename[0] === 'g')
{
$avatar_group = true;
$filename = substr($filename, 1);
}
// '==' is not a bug - . as the first char is as bad as no dot at all
if (strpos($filename, '.') == false)
{
send_status_line(403, 'Forbidden');
$exit = true;
}
if (!$exit)
{
$ext = substr(strrchr($filename, '.'), 1);
$stamp = (int) substr(stristr($filename, '_'), 1);
$filename = (int) $filename;
$exit = set_modified_headers($stamp, $browser);
}
if (!$exit && !in_array($ext, array('png', 'gif', 'jpg', 'jpeg')))
{
// no way such an avatar could exist. They are not following the rules, stop the show.
send_status_line(403, 'Forbidden');
$exit = true;
}
if (!$exit)
{
if (!$filename)
{
// no way such an avatar could exist. They are not following the rules, stop the show.
send_status_line(403, 'Forbidden');
}
else
{
send_avatar_to_browser(($avatar_group ? 'g' : '') . $filename . '.' . $ext, $browser);
}
}
file_gc();
}
// implicit else: we are not in avatar mode
include($phpbb_root_path . 'common.' . $phpEx);
$download_id = request_var('id', 0);
$mode = request_var('mode', '');
$thumbnail = request_var('t', false);
// Start session management, do not update session page.
$user->session_begin(false);
$auth->acl($user->data);
$user->setup('viewtopic');
if (!$download_id)
{
send_status_line(404, 'Not Found');
trigger_error('NO_ATTACHMENT_SELECTED');
}
if (!$config['allow_attachments'] && !$config['allow_pm_attach'])
{
send_status_line(404, 'Not Found');
trigger_error('ATTACHMENT_FUNCTIONALITY_DISABLED');
}
$sql = 'SELECT attach_id, in_message, post_msg_id, extension, is_orphan, poster_id, filetime
FROM ' . ATTACHMENTS_TABLE . "
WHERE attach_id = $download_id";
$result = $db->sql_query_limit($sql, 1);
$attachment = $db->sql_fetchrow($result);
$db->sql_freeresult($result);
if (!$attachment)
{
send_status_line(404, 'Not Found');
trigger_error('ERROR_NO_ATTACHMENT');
}
if ((!$attachment['in_message'] && !$config['allow_attachments']) || ($attachment['in_message'] && !$config['allow_pm_attach']))
{
send_status_line(404, 'Not Found');
trigger_error('ATTACHMENT_FUNCTIONALITY_DISABLED');
}
$row = array();
if ($attachment['is_orphan'])
{
// We allow admins having attachment permissions to see orphan attachments...
$own_attachment = ($auth->acl_get('a_attach') || $attachment['poster_id'] == $user->data['user_id']) ? true : false;
if (!$own_attachment || ($attachment['in_message'] && !$auth->acl_get('u_pm_download')) || (!$attachment['in_message'] && !$auth->acl_get('u_download')))
{
send_status_line(404, 'Not Found');
trigger_error('ERROR_NO_ATTACHMENT');
}
// Obtain all extensions...
$extensions = $cache->obtain_attach_extensions(true);
}
else
{
if (!$attachment['in_message'])
{
//
$sql = 'SELECT p.forum_id, f.forum_password, f.parent_id
FROM ' . POSTS_TABLE . ' p, ' . FORUMS_TABLE . ' f
WHERE p.post_id = ' . $attachment['post_msg_id'] . '
AND p.forum_id = f.forum_id';
$result = $db->sql_query_limit($sql, 1);
$row = $db->sql_fetchrow($result);
$db->sql_freeresult($result);
// Global announcement?
$f_download = (!$row) ? $auth->acl_getf_global('f_download') : $auth->acl_get('f_download', $row['forum_id']);
if ($auth->acl_get('u_download') && $f_download)
{
if ($row && $row['forum_password'])
{
// Do something else ... ?
login_forum_box($row);
}
}
else
{
send_status_line(403, 'Forbidden');
trigger_error('SORRY_AUTH_VIEW_ATTACH');
}
}
else
{
$row['forum_id'] = false;
if (!$auth->acl_get('u_pm_download'))
{
send_status_line(403, 'Forbidden');
trigger_error('SORRY_AUTH_VIEW_ATTACH');
}
// Check if the attachment is within the users scope...
$sql = 'SELECT user_id, author_id
FROM ' . PRIVMSGS_TO_TABLE . '
WHERE msg_id = ' . $attachment['post_msg_id'];
$result = $db->sql_query($sql);
$allowed = false;
while ($user_row = $db->sql_fetchrow($result))
{
if ($user->data['user_id'] == $user_row['user_id'] || $user->data['user_id'] == $user_row['author_id'])
{
$allowed = true;
break;
}
}
$db->sql_freeresult($result);
if (!$allowed)
{
send_status_line(403, 'Forbidden');
trigger_error('ERROR_NO_ATTACHMENT');
}
}
// disallowed?
$extensions = array();
if (!extension_allowed($row['forum_id'], $attachment['extension'], $extensions))
{
send_status_line(404, 'Forbidden');
trigger_error(sprintf($user->lang['EXTENSION_DISABLED_AFTER_POSTING'], $attachment['extension']));
}
}
if (!download_allowed())
{
send_status_line(403, 'Forbidden');
trigger_error($user->lang['LINKAGE_FORBIDDEN']);
}
$download_mode = (int) $extensions[$attachment['extension']]['download_mode'];
// Fetching filename here to prevent sniffing of filename
$sql = 'SELECT attach_id, is_orphan, in_message, post_msg_id, extension, physical_filename, real_filename, mimetype, filetime
FROM ' . ATTACHMENTS_TABLE . "
WHERE attach_id = $download_id";
$result = $db->sql_query_limit($sql, 1);
$attachment = $db->sql_fetchrow($result);
$db->sql_freeresult($result);
if (!$attachment)
{
send_status_line(404, 'Not Found');
trigger_error('ERROR_NO_ATTACHMENT');
}
$attachment['physical_filename'] = utf8_basename($attachment['physical_filename']);
$display_cat = $extensions[$attachment['extension']]['display_cat'];
if (($display_cat == ATTACHMENT_CATEGORY_IMAGE || $display_cat == ATTACHMENT_CATEGORY_THUMB) && !$user->optionget('viewimg'))
{
$display_cat = ATTACHMENT_CATEGORY_NONE;
}
if ($display_cat == ATTACHMENT_CATEGORY_FLASH && !$user->optionget('viewflash'))
{
$display_cat = ATTACHMENT_CATEGORY_NONE;
}
if ($thumbnail)
{
$attachment['physical_filename'] = 'thumb_' . $attachment['physical_filename'];
}
else if (($display_cat == ATTACHMENT_CATEGORY_NONE/* || $display_cat == ATTACHMENT_CATEGORY_IMAGE*/) && !$attachment['is_orphan'])
{
// Update download count
$sql = 'UPDATE ' . ATTACHMENTS_TABLE . '
SET download_count = download_count + 1
WHERE attach_id = ' . $attachment['attach_id'];
$db->sql_query($sql);
}
if ($display_cat == ATTACHMENT_CATEGORY_IMAGE && $mode === 'view' && (strpos($attachment['mimetype'], 'image') === 0) && ((strpos(strtolower($user->browser), 'msie') !== false) && (strpos(strtolower($user->browser), 'msie 8.0') === false)))
{
wrap_img_in_html(append_sid($phpbb_root_path . 'download/file.' . $phpEx, 'id=' . $attachment['attach_id']), $attachment['real_filename']);
file_gc();
}
else
{
// Determine the 'presenting'-method
if ($download_mode == PHYSICAL_LINK)
{
// This presenting method should no longer be used
if (!@is_dir($phpbb_root_path . $config['upload_path']))
{
send_status_line(500, 'Internal Server Error');
trigger_error($user->lang['PHYSICAL_DOWNLOAD_NOT_POSSIBLE']);
}
redirect($phpbb_root_path . $config['upload_path'] . '/' . $attachment['physical_filename']);
file_gc();
}
else
{
send_file_to_browser($attachment, $config['upload_path'], $display_cat);
file_gc();
}
}
/**
* A simplified function to deliver avatars
* The argument needs to be checked before calling this function.
*/
function send_avatar_to_browser($file, $browser)
{
global $config, $phpbb_root_path;
$prefix = $config['avatar_salt'] . '_';
$image_dir = $config['avatar_path'];
// Adjust image_dir path (no trailing slash)
if (substr($image_dir, -1, 1) == '/' || substr($image_dir, -1, 1) == '\\')
{
$image_dir = substr($image_dir, 0, -1) . '/';
}
$image_dir = str_replace(array('../', '..\\', './', '.\\'), '', $image_dir);
if ($image_dir && ($image_dir[0] == '/' || $image_dir[0] == '\\'))
{
$image_dir = '';
}
$file_path = $phpbb_root_path . $image_dir . '/' . $prefix . $file;
if ((@file_exists($file_path) && @is_readable($file_path)) && !headers_sent())
{
header('Pragma: public');
$image_data = @getimagesize($file_path);
header('Content-Type: ' . image_type_to_mime_type($image_data[2]));
if (strpos(strtolower($browser), 'msie') !== false && strpos(strtolower($browser), 'msie 8.0') === false)
{
header('Content-Disposition: attachment; ' . header_filename($file));
if (strpos(strtolower($browser), 'msie 6.0') !== false)
{
header('Expires: -1');
}
else
{
header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', time() + 31536000));
}
}
else
{
header('Content-Disposition: inline; ' . header_filename($file));
header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', time() + 31536000));
}
$size = @filesize($file_path);
if ($size)
{
header("Content-Length: $size");
}
if (@readfile($file_path) == false)
{
$fp = @fopen($file_path, 'rb');
if ($fp !== false)
{
while (!feof($fp))
{
echo fread($fp, 8192);
}
fclose($fp);
}
}
flush();
}
else
{
send_status_line(404, 'Not Found');
}
}
/**
* Wraps an url into a simple html page. Used to display attachments in IE.
* this is a workaround for now; might be moved to template system later
* direct any complaints to 1 Microsoft Way, Redmond
*/
function wrap_img_in_html($src, $title)
{
echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-Strict.dtd">';
echo '<html>';
echo '<head>';
echo '<meta http-equiv="content-type" content="text/html; charset=UTF-8" />';
echo '<title>' . $title . '</title>';
echo '</head>';
echo '<body>';
echo '<div>';
echo '<img src="' . $src . '" alt="' . $title . '" />';
echo '</div>';
echo '</body>';
echo '</html>';
}
/**
* Send file to browser
*/
function send_file_to_browser($attachment, $upload_dir, $category)
{
global $user, $db, $config, $phpbb_root_path;
$filename = $phpbb_root_path . $upload_dir . '/' . $attachment['physical_filename'];
if (!@file_exists($filename))
{
send_status_line(404, 'Not Found');
trigger_error('ERROR_NO_ATTACHMENT');
}
// Correct the mime type - we force application/octetstream for all files, except images
// Please do not change this, it is a security precaution
if ($category != ATTACHMENT_CATEGORY_IMAGE || strpos($attachment['mimetype'], 'image') !== 0)
{
$attachment['mimetype'] = (strpos(strtolower($user->browser), 'msie') !== false || strpos(strtolower($user->browser), 'opera') !== false) ? 'application/octetstream' : 'application/octet-stream';
}
if (@ob_get_length())
{
@ob_end_clean();
}
// Now send the File Contents to the Browser
$size = @filesize($filename);
// To correctly display further errors we need to make sure we are using the correct headers for both (unsetting content-length may not work)
// Check if headers already sent or not able to get the file contents.
if (headers_sent() || !@file_exists($filename) || !@is_readable($filename))
{
// PHP track_errors setting On?
if (!empty($php_errormsg))
{
send_status_line(500, 'Internal Server Error');
trigger_error($user->lang['UNABLE_TO_DELIVER_FILE'] . '<br />' . sprintf($user->lang['TRACKED_PHP_ERROR'], $php_errormsg));
}
send_status_line(500, 'Internal Server Error');
trigger_error('UNABLE_TO_DELIVER_FILE');
}
// Now the tricky part... let's dance
header('Pragma: public');
/**
* Commented out X-Sendfile support. To not expose the physical filename within the header if xsendfile is absent we need to look into methods of checking it's status.
*
* Try X-Sendfile since it is much more server friendly - only works if the path is *not* outside of the root path...
* lighttpd has core support for it. An apache2 module is available at http://celebnamer.celebworld.ws/stuff/mod_xsendfile/
*
* Not really ideal, but should work fine...
* <code>
* if (strpos($upload_dir, '/') !== 0 && strpos($upload_dir, '../') === false)
* {
* header('X-Sendfile: ' . $filename);
* }
* </code>
*/
// Send out the Headers. Do not set Content-Disposition to inline please, it is a security measure for users using the Internet Explorer.
$is_ie8 = (strpos(strtolower($user->browser), 'msie 8.0') !== false);
header('Content-Type: ' . $attachment['mimetype']);
if ($is_ie8)
{
header('X-Content-Type-Options: nosniff');
}
if ($category == ATTACHMENT_CATEGORY_FLASH && request_var('view', 0) === 1)
{
// We use content-disposition: inline for flash files and view=1 to let it correctly play with flash player 10 - any other disposition will fail to play inline
header('Content-Disposition: inline');
}
else
{
if (empty($user->browser) || (!$is_ie8 && (strpos(strtolower($user->browser), 'msie') !== false)))
{
header('Content-Disposition: attachment; ' . header_filename(htmlspecialchars_decode($attachment['real_filename'])));
if (empty($user->browser) || (strpos(strtolower($user->browser), 'msie 6.0') !== false))
{
header('expires: -1');
}
}
else
{
header('Content-Disposition: ' . ((strpos($attachment['mimetype'], 'image') === 0) ? 'inline' : 'attachment') . '; ' . header_filename(htmlspecialchars_decode($attachment['real_filename'])));
if ($is_ie8 && (strpos($attachment['mimetype'], 'image') !== 0))
{
header('X-Download-Options: noopen');
}
}
}
if ($size)
{
header("Content-Length: $size");
}
// Close the db connection before sending the file
$db->sql_close();
if (!set_modified_headers($attachment['filetime'], $user->browser))
{
// Try to deliver in chunks
@set_time_limit(0);
$fp = @fopen($filename, 'rb');
if ($fp !== false)
{
while (!feof($fp))
{
echo fread($fp, 8192);
}
fclose($fp);
}
else
{
@readfile($filename);
}
flush();
}
file_gc();
}
/**
* Get a browser friendly UTF-8 encoded filename
*/
function header_filename($file)
{
$user_agent = (!empty($_SERVER['HTTP_USER_AGENT'])) ? htmlspecialchars((string) $_SERVER['HTTP_USER_AGENT']) : '';
// There be dragons here.
// Not many follows the RFC...
if (strpos($user_agent, 'MSIE') !== false || strpos($user_agent, 'Safari') !== false || strpos($user_agent, 'Konqueror') !== false)
{
return "filename=" . rawurlencode($file);
}
// follow the RFC for extended filename for the rest
return "filename*=UTF-8''" . rawurlencode($file);
}
/**
* Check if downloading item is allowed
*/
function download_allowed()
{
global $config, $user, $db;
if (!$config['secure_downloads'])
{
return true;
}
$url = (!empty($_SERVER['HTTP_REFERER'])) ? trim($_SERVER['HTTP_REFERER']) : trim(getenv('HTTP_REFERER'));
if (!$url)
{
return ($config['secure_allow_empty_referer']) ? true : false;
}
// Split URL into domain and script part
$url = @parse_url($url);
if ($url === false)
{
return ($config['secure_allow_empty_referer']) ? true : false;
}
$hostname = $url['host'];
unset($url);
$allowed = ($config['secure_allow_deny']) ? false : true;
$iplist = array();
if (($ip_ary = @gethostbynamel($hostname)) !== false)
{
foreach ($ip_ary as $ip)
{
if ($ip)
{
$iplist[] = $ip;
}
}
}
// Check for own server...
$server_name = $user->host;
// Forcing server vars is the only way to specify/override the protocol
if ($config['force_server_vars'] || !$server_name)
{
$server_name = $config['server_name'];
}
if (preg_match('#^.*?' . preg_quote($server_name, '#') . '.*?$#i', $hostname))
{
$allowed = true;
}
// Get IP's and Hostnames
if (!$allowed)
{
$sql = 'SELECT site_ip, site_hostname, ip_exclude
FROM ' . SITELIST_TABLE;
$result = $db->sql_query($sql);
while ($row = $db->sql_fetchrow($result))
{
$site_ip = trim($row['site_ip']);
$site_hostname = trim($row['site_hostname']);
if ($site_ip)
{
foreach ($iplist as $ip)
{
if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($site_ip, '#')) . '$#i', $ip))
{
if ($row['ip_exclude'])
{
$allowed = ($config['secure_allow_deny']) ? false : true;
break 2;
}
else
{
$allowed = ($config['secure_allow_deny']) ? true : false;
}
}
}
}
if ($site_hostname)
{
if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($site_hostname, '#')) . '$#i', $hostname))
{
if ($row['ip_exclude'])
{
$allowed = ($config['secure_allow_deny']) ? false : true;
break;
}
else
{
$allowed = ($config['secure_allow_deny']) ? true : false;
}
}
}
}
$db->sql_freeresult($result);
}
return $allowed;
}
/**
* Check if the browser has the file already and set the appropriate headers-
* @returns false if a resend is in order.
*/
function set_modified_headers($stamp, $browser)
{
// let's see if we have to send the file at all
$last_load = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? strtotime(trim($_SERVER['HTTP_IF_MODIFIED_SINCE'])) : false;
if ((strpos(strtolower($browser), 'msie 6.0') === false) && (strpos(strtolower($browser), 'msie 8.0') === false))
{
if ($last_load !== false && $last_load >= $stamp)
{
send_status_line(304, 'Not Modified');
// seems that we need those too ... browsers
header('Pragma: public');
header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', time() + 31536000));
return true;
}
else
{
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $stamp) . ' GMT');
}
}
return false;
}
function file_gc()
{
global $cache, $db;
if (!empty($cache))
{
$cache->unload();
}
$db->sql_close();
exit;
}
?>