mirror of
https://github.com/Lastorder-DC/rhymix.git
synced 2026-06-13 05:27:14 +09:00
Merge branch 'security/rve-2026-7'
This commit is contained in:
commit
aa0f3f0200
9 changed files with 85 additions and 21 deletions
|
|
@ -873,6 +873,45 @@ class Storage
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent access to a directory by creating .htaccess and index.html files in it.
|
||||
*
|
||||
* This is a best-effort measure only, and depends on web server configuration.
|
||||
* It is recommended to use proper server configuration to protect sensitive directories.
|
||||
*
|
||||
* @param string $dirname
|
||||
* @return bool
|
||||
*/
|
||||
public static function protectDirectory(string $dirname): bool
|
||||
{
|
||||
$dirname = rtrim($dirname, '/\\');
|
||||
if (!self::isDirectory($dirname) || !self::isWritable($dirname))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = self::write($dirname . '/index.html', '');
|
||||
if (!$result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$result = self::write($dirname . '/.htaccess', preg_replace('/\\t/', '', <<<END
|
||||
<IfModule authz_core_module>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
<IfModule !authz_core_module>
|
||||
Order deny,allow
|
||||
Deny from all
|
||||
</IfModule>
|
||||
END));
|
||||
if (!$result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current umask.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ class Advanced extends Base
|
|||
// Thumbnail settings
|
||||
$oDocumentModel = getModel('document');
|
||||
$config = $oDocumentModel->getDocumentConfig();
|
||||
Context::set('thumbnail_target', $config->thumbnail_target ?: 'all');
|
||||
Context::set('thumbnail_target', $config->thumbnail_target ?: 'attachment');
|
||||
Context::set('thumbnail_type', $config->thumbnail_type ?: 'fill');
|
||||
Context::set('thumbnail_quality', $config->thumbnail_quality ?: 75);
|
||||
if ($config->thumbnail_type === 'none')
|
||||
|
|
@ -190,7 +190,7 @@ class Advanced extends Base
|
|||
// Thumbnail settings
|
||||
$oDocumentModel = getModel('document');
|
||||
$document_config = $oDocumentModel->getDocumentConfig();
|
||||
$document_config->thumbnail_target = $vars->thumbnail_target ?: 'all';
|
||||
$document_config->thumbnail_target = $vars->thumbnail_target ?: 'attachment';
|
||||
$document_config->thumbnail_type = $vars->thumbnail_type ?: 'fill';
|
||||
$document_config->thumbnail_quality = intval($vars->thumbnail_quality) ?: 75;
|
||||
$oModuleController = getController('module');
|
||||
|
|
|
|||
|
|
@ -338,8 +338,9 @@ $lang->mobile_viewport = 'Mobile viewport Setting';
|
|||
$lang->about_mobile_viewport = 'The settings entered above will be output in a <meta name="viewport"> tag for mobile visitors.';
|
||||
$lang->restore_default_viewport = 'Restore Default';
|
||||
$lang->thumbnail_target = 'Extract Thumbnail From';
|
||||
$lang->thumbnail_target_all = 'All images';
|
||||
$lang->thumbnail_target_content = 'All embedded images, including external images';
|
||||
$lang->thumbnail_target_attachment = 'Attached images only';
|
||||
$lang->about_thumbnail_target = 'Using external images to generate thumbnails may cause various issues with respect to page load speed, copyright, and security. Please be careful.';
|
||||
$lang->thumbnail_type = 'Thumbnail Type';
|
||||
$lang->input_header_script = 'Header Script';
|
||||
$lang->detail_input_header_script = 'Content added here will be printed at the top of every page, except the admin module.';
|
||||
|
|
|
|||
|
|
@ -334,8 +334,9 @@ $lang->mobile_viewport = '모바일 viewport 설정';
|
|||
$lang->about_mobile_viewport = '모바일 접속시 여기 입력한 내용이 <meta name="viewport"> 태그로 출력됩니다.';
|
||||
$lang->restore_default_viewport = '기본값 복원';
|
||||
$lang->thumbnail_target = '썸네일 생성 대상';
|
||||
$lang->thumbnail_target_all = '모든 이미지 (외부 이미지 포함)';
|
||||
$lang->thumbnail_target_content = '모든 이미지 (외부 이미지 포함)';
|
||||
$lang->thumbnail_target_attachment = '첨부된 이미지';
|
||||
$lang->about_thumbnail_target = '외부 이미지를 사용하여 썸네일을 생성하는 경우 페이지 로딩 속도, 저작권, 보안 등 다양한 문제가 발생할 수 있으니 주의하시기 바랍니다.';
|
||||
$lang->thumbnail_type = '썸네일 생성 방식';
|
||||
$lang->input_header_script = '상단(헤더) 스크립트';
|
||||
$lang->detail_input_header_script = '모든 페이지의 최상단에 코드를 삽입합니다. 관리자 화면에는 적용되지 않습니다.';
|
||||
|
|
|
|||
|
|
@ -126,18 +126,19 @@
|
|||
<div class="x_control-group">
|
||||
<label class="x_control-label">{$lang->thumbnail_target}</label>
|
||||
<div class="x_controls">
|
||||
<label for="thumbnail_target_all" class="x_inline">
|
||||
<input type="radio" name="thumbnail_target" id="thumbnail_target_all" value="all" checked="checked"|cond="$thumbnail_target == 'all' || !$thumbnail_target" />
|
||||
{$lang->thumbnail_target_all}
|
||||
</label>
|
||||
<label for="thumbnail_target_attachment" class="x_inline">
|
||||
<input type="radio" name="thumbnail_target" id="thumbnail_target_attachment" value="attachment" checked="checked"|cond="$thumbnail_target == 'attachment'" />
|
||||
<input type="radio" name="thumbnail_target" id="thumbnail_target_attachment" value="attachment" checked="checked"|cond="$thumbnail_target == 'attachment' || !$thumbnail_target" />
|
||||
{$lang->thumbnail_target_attachment}
|
||||
</label>
|
||||
<label for="thumbnail_target_content" class="x_inline">
|
||||
<input type="radio" name="thumbnail_target" id="thumbnail_target_content" value="content" checked="checked"|cond="$thumbnail_target == 'content'" />
|
||||
{$lang->thumbnail_target_content}
|
||||
</label>
|
||||
<label for="thumbnail_target_none" class="x_inline">
|
||||
<input type="radio" name="thumbnail_target" id="thumbnail_target_none" value="none" checked="checked"|cond="$thumbnail_target == 'none'" />
|
||||
{$lang->thumbnail_none}
|
||||
</label>
|
||||
<p class="x_help-block">{$lang->about_thumbnail_target}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="x_control-group">
|
||||
|
|
|
|||
|
|
@ -924,28 +924,34 @@ class CommentItem extends BaseObject
|
|||
$target_src = Context::getRequestUri().$target_src;
|
||||
}
|
||||
|
||||
$tmp_file = sprintf('./files/cache/tmp/%d', md5(rand(111111, 999999) . $this->comment_srl));
|
||||
if(!is_dir('./files/cache/tmp'))
|
||||
$tmp_file = sprintf('./files/cache/tmp/%s', Rhymix\Framework\Security::getRandom(32));
|
||||
if (!Rhymix\Framework\Storage::exists(\RX_BASEDIR . 'files/cache/tmp'))
|
||||
{
|
||||
FileHandler::makeDir('./files/cache/tmp');
|
||||
Rhymix\Framework\Storage::createDirectory(\RX_BASEDIR . 'files/cache/tmp');
|
||||
}
|
||||
if (!Rhymix\Framework\Storage::exists(\RX_BASEDIR . 'files/cache/tmp/.htaccess'))
|
||||
{
|
||||
Rhymix\Framework\Storage::protectDirectory(\RX_BASEDIR . 'files/cache/tmp');
|
||||
}
|
||||
FileHandler::getRemoteFile($target_src, $tmp_file);
|
||||
if(!file_exists($tmp_file))
|
||||
if (!Rhymix\Framework\Storage::exists($tmp_file))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if($is_img = @getimagesize($tmp_file))
|
||||
if ($is_img = @getimagesize($tmp_file))
|
||||
{
|
||||
list($_w, $_h, $_t, $_a) = $is_img;
|
||||
if($_w < ($external_image_min_width) && ($height === 'auto' || $_h < ($external_image_min_height)))
|
||||
{
|
||||
Rhymix\Framework\Storage::delete($tmp_file);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Rhymix\Framework\Storage::delete($tmp_file);
|
||||
continue;
|
||||
}
|
||||
$source_file = $tmp_file;
|
||||
|
|
|
|||
|
|
@ -1257,28 +1257,34 @@ class DocumentItem extends BaseObject
|
|||
$target_src = Context::getRequestUri().$target_src;
|
||||
}
|
||||
|
||||
$tmp_file = sprintf('./files/cache/tmp/%d', md5(rand(111111,999999).$this->document_srl));
|
||||
if(!is_dir('./files/cache/tmp'))
|
||||
$tmp_file = sprintf('./files/cache/tmp/%s', Rhymix\Framework\Security::getRandom(32));
|
||||
if (!Rhymix\Framework\Storage::exists(\RX_BASEDIR . 'files/cache/tmp'))
|
||||
{
|
||||
FileHandler::makeDir('./files/cache/tmp');
|
||||
Rhymix\Framework\Storage::createDirectory(\RX_BASEDIR . 'files/cache/tmp');
|
||||
}
|
||||
if (!Rhymix\Framework\Storage::exists(\RX_BASEDIR . 'files/cache/tmp/.htaccess'))
|
||||
{
|
||||
Rhymix\Framework\Storage::protectDirectory(\RX_BASEDIR . 'files/cache/tmp');
|
||||
}
|
||||
FileHandler::getRemoteFile($target_src, $tmp_file);
|
||||
if(!file_exists($tmp_file))
|
||||
if (!Rhymix\Framework\Storage::exists($tmp_file))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if($is_img = @getimagesize($tmp_file))
|
||||
if ($is_img = @getimagesize($tmp_file))
|
||||
{
|
||||
list($_w, $_h, $_t, $_a) = $is_img;
|
||||
if($_w < ($external_image_min_width) && ($height === 'auto' || $_h < ($external_image_min_height)))
|
||||
{
|
||||
Rhymix\Framework\Storage::delete($tmp_file);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Rhymix\Framework\Storage::delete($tmp_file);
|
||||
continue;
|
||||
}
|
||||
$source_file = $tmp_file;
|
||||
|
|
|
|||
|
|
@ -1036,9 +1036,9 @@ class DocumentModel extends Document
|
|||
{
|
||||
self::$_config = ModuleModel::getModuleConfig('document') ?: new stdClass;
|
||||
}
|
||||
if (!isset(self::$_config->thumbnail_target))
|
||||
if (!isset(self::$_config->thumbnail_target) || self::$_config->thumbnail_target === 'all')
|
||||
{
|
||||
self::$_config->thumbnail_target = 'all';
|
||||
self::$_config->thumbnail_target = 'attachment';
|
||||
}
|
||||
if (!isset(self::$_config->thumbnail_type))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -346,6 +346,16 @@ class StorageTest extends \Codeception\Test\Unit
|
|||
$this->assertFalse(Rhymix\Framework\Storage::deleteDirectory($nonexistent));
|
||||
}
|
||||
|
||||
public function testProtectDirectory()
|
||||
{
|
||||
$dir = \RX_BASEDIR . 'tests/_output/protectdir';
|
||||
mkdir($dir);
|
||||
$this->assertTrue(Rhymix\Framework\Storage::protectDirectory($dir));
|
||||
$this->assertTrue(file_exists($dir . '/index.html'));
|
||||
$this->assertTrue(file_exists($dir . '/.htaccess'));
|
||||
$this->assertStringContainsString('Require all denied', file_get_contents($dir . '/.htaccess'));
|
||||
}
|
||||
|
||||
public function testDeleteDirectoryKeepRoot()
|
||||
{
|
||||
$sourcedir = \RX_BASEDIR . 'tests/_output/sourcedir';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue