본문으로 건너뛰기

웹FTP 파일 목록 파일명 보안 강화

PHP 8 · GODO26
변경 파일 수: 3개
변경 파일 목록
총 3개
변경 파일 코드
추가삭제
Admin/share/popup_webftp.php+29-17
변경 전popup_webftp.php
</thead>
<tbody>
<?php foreach ($dirArray as $name => $fileInfo) { ?>
<tr class="text-center" data-name="<?= $name; ?>" data-href="<?= $fileInfo['url_path']; ?>">
<td data-sort="<?= $name; ?>" class="text-left">
<?php if ($fileInfo['is_image']) { ?>
<a href="<?php echo $fileInfo['url_path']; ?>" class="btn btn-link <?= $fileInfo['icon_class']; ?>" data-title="<?= basename($fileInfo['file_path']) ?>" data-gallery="godomall5Image"
data-toggle="lightbox"><?= $name; ?></a>
<?php } elseif ($fileInfo['is_html']) { ?>
<span class="<?= $fileInfo['icon_class']; ?>"><?= $name; ?></span>
<?php } else { ?>
<a href="<?php echo $fileInfo['url_path']; ?>" class="btn btn-link <?= $fileInfo['icon_class']; ?>"><?= $name; ?></a>
<?php } ?>
</td>
<td data-sort="<?= $fileInfo['file_size'] ?>" class="font-num"><?= $fileInfo['file_size']; ?></td>
<td data-sort="<?= $fileInfo['mod_time'] ?>" class="font-date"><?= $fileInfo['mod_time'] ?></td>
<td>
<?php if ($name != '..') { ?>
<button type="button" class="btn btn-sm btn-gray js-clipboard" title="주소복사하기" data-clipboard-text="/<?= $fileInfo['file_path'] ?>">주소복사</button>
<?php if (is_file($fileInfo['file_path'])) { ?>
<a href="popup_webftp_ps.php?mode=download&file=<?= $fileInfo['file_path'] ?>" class="btn btn-sm btn-white js-file-download">다운로드</a>
<?php } ?>
<button type="button" data-dirpath="<?= $currentDirectory ?>" data-file="<?= $fileInfo['file_path'] ?>" class="btn btn-sm btn-white js-file-rename">이름변경</button>
<button type="button" data-file="<?= $fileInfo['file_path'] ?>" class="btn btn-sm btn-white js-file-delete">삭제</button>
<?php } ?>
</td>
</tr>
var $btn_search = $('#btnSearch');
var $search_text = $('#' + $btn_search.data('target-id'));
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 검색함수
*/
$item.addClass('display-none');
}
var $child = $(item).find('td:first a.fa-folder,a.fa-picture-o,span.fa-code');
$child.html(name);
var child_html = $child.text();
$child.html(exec_search(child_html, search_text));
});
var result = '';
var breakCnt = 0;
if (search == '') {
return html;
}
while (html != '' && breakCnt < 10) {
var exec = regexp.exec(html);
if (exec == null) {
result += html.substring(0, html.length);
break;
}
if (exec.index > 0) {
result += html.substring(0, exec.index);
result += append_highlight(exec[0]);
html = html.substring(exec.index + exec[0].length, html.length);
} else {
* @returns {string} 하이라이트처리된 html
*/
function append_highlight(text) {
return '<span style="background-color:#ffff00; color:#000000;">' + text + '</span>';
}
 
// 검색 버튼 이벤트
var filename = $(this).data('file');
BootstrapDialog.confirm({
title: '파일 삭제하기',
message: '"' + filename + '" 을(를) 정말로 삭제하시겠습니까?<br>삭제 후 복구하실 수 없습니다.',
callback: function (result) {
if (result) {
var params = {
var filename = $(this).data('file');
var compiled = _.template($('#fileRenameForm').html());
var datas = {
filename: filename
};
 
BootstrapDialog.show({
 
변경 후popup_webftp.php
</thead>
<tbody>
<?php foreach ($dirArray as $name => $fileInfo) { ?>
<tr class="text-center" data-name="<?= gd_htmlspecialchars($name); ?>" data-href="<?= $fileInfo['url_path']; ?>">
<td data-sort="<?= gd_htmlspecialchars($name); ?>" class="text-left">
<?php if ($fileInfo['is_image']) { ?>
<a href="<?php echo $fileInfo['url_path']; ?>" class="btn btn-link <?= $fileInfo['icon_class']; ?>" data-title="<?= gd_htmlspecialchars(basename($fileInfo['file_path'])) ?>" data-gallery="godomall5Image"
data-toggle="lightbox"><?= gd_htmlspecialchars($name); ?></a>
<?php } elseif ($fileInfo['is_html']) { ?>
<span class="<?= $fileInfo['icon_class']; ?>"><?= gd_htmlspecialchars($name); ?></span>
<?php } else { ?>
<a href="<?php echo $fileInfo['url_path']; ?>" class="btn btn-link <?= $fileInfo['icon_class']; ?>"><?= gd_htmlspecialchars($name); ?></a>
<?php } ?>
</td>
<td data-sort="<?= $fileInfo['file_size'] ?>" class="font-num"><?= $fileInfo['file_size']; ?></td>
<td data-sort="<?= $fileInfo['mod_time'] ?>" class="font-date"><?= $fileInfo['mod_time'] ?></td>
<td>
<?php if ($name != '..') { ?>
<button type="button" class="btn btn-sm btn-gray js-clipboard" title="주소복사하기" data-clipboard-text="/<?= gd_htmlspecialchars($fileInfo['file_path']) ?>">주소복사</button>
<?php if (is_file($fileInfo['file_path'])) { ?>
<a href="popup_webftp_ps.php?mode=download&file=<?= gd_htmlspecialchars($fileInfo['file_path']) ?>" class="btn btn-sm btn-white js-file-download">다운로드</a>
<?php } ?>
<button type="button" data-dirpath="<?= $currentDirectory ?>" data-file="<?= gd_htmlspecialchars($fileInfo['file_path']) ?>" class="btn btn-sm btn-white js-file-rename">이름변경</button>
<button type="button" data-file="<?= gd_htmlspecialchars($fileInfo['file_path']) ?>" class="btn btn-sm btn-white js-file-delete">삭제</button>
<?php } ?>
</td>
</tr>
var $btn_search = $('#btnSearch');
var $search_text = $('#' + $btn_search.data('target-id'));
 
/**
* HTML 특수문자 escape (파일명을 DOM 에 주입할 때 XSS 방지)
*/
function htmlEscape(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
 
/**
* 검색함수
*/
$item.addClass('display-none');
}
var $child = $(item).find('td:first a.fa-folder,a.fa-picture-o,span.fa-code');
$child.text(name);
var child_html = $child.text();
$child.html(exec_search(child_html, search_text));
});
var result = '';
var breakCnt = 0;
if (search == '') {
return htmlEscape(html);
}
while (html != '' && breakCnt < 10) {
var exec = regexp.exec(html);
if (exec == null) {
result += htmlEscape(html.substring(0, html.length));
break;
}
if (exec.index > 0) {
result += htmlEscape(html.substring(0, exec.index));
result += append_highlight(exec[0]);
html = html.substring(exec.index + exec[0].length, html.length);
} else {
* @returns {string} 하이라이트처리된 html
*/
function append_highlight(text) {
return '<span style="background-color:#ffff00; color:#000000;">' + htmlEscape(text) + '</span>';
}
 
// 검색 버튼 이벤트
var filename = $(this).data('file');
BootstrapDialog.confirm({
title: '파일 삭제하기',
message: '"' + htmlEscape(filename) + '" 을(를) 정말로 삭제하시겠습니까?<br>삭제 후 복구하실 수 없습니다.',
callback: function (result) {
if (result) {
var params = {
var filename = $(this).data('file');
var compiled = _.template($('#fileRenameForm').html());
var datas = {
filename: htmlEscape(filename)
};
 
BootstrapDialog.show({
 
Component/File/Webftp.php+88-94
변경 전Webftp.php
*/
class Webftp
{
 
 
 
 
 
/**
* @var null
*/
// Cleanup directory path
$directory = $this->setDirectoryPath($directory);
 
if ($directory != '.' && $this->_isHidden($directory)) {
echo "Access denied.";
}
 
$filenameNoExt = basename($directory);
 
if ($directory == '.') {
$filenameNoExt = 'Home';
}
 
// We deliver a zip file
header('Content-Type: archive/zip');
 
{
 
// Build the path
if ($this->_directory == '.') {
$path = $this->_appURL;
} else {
$path = $this->_appURL . $this->_directory;
}
 
// Return the path
return $path;
 
// Check for an empty variable
if (empty($dir) || $dir == '.') {
return '.';
}
 
// Eliminate double slashes
// Set the error message
$this->setSystemMessage('danger', '<b>ERROR:</b> File path does not exist');
 
// Return the web root
return '.';
 
 
 
 
 
 
 
 
}
 
// Prevent access to hidden files
// Set the error message
$this->setSystemMessage('danger', '<b>ERROR:</b> Access denied');
 
// Set the directory to web root
return '.';
}
 
// Prevent access to parent folders
// Set the error message
$this->setSystemMessage('danger', '<b>ERROR:</b> An invalid path string was detected');
 
// Set the directory to web root
return '.';
} else {
// Should stop all URL wrappers (Thanks to Hexatex)
$directoryPath = $dir;
$relativePath = substr($relativePath, 2);
}
 
// Don't check parent dir if we're in the root dir
if ($this->_directory == '.' && $file == '..') {
continue;
 
 
 
 
} else {
// Get files absolute path
$realPath = realpath($relativePath);
 
// Determine file type by extension
if (is_dir($realPath)) {
$iconClass = 'fa-folder';
$sort = 1;
} else {
// Get file extension
$fileExt = strtolower(pathinfo($realPath, PATHINFO_EXTENSION));
 
if (isset($this->_fileTypes[$fileExt])) {
$iconClass = $this->_fileTypes[$fileExt];
} else {
$iconClass = $this->_fileTypes['blank'];
}
 
$sort = 2;
}
 
 
}
 
if ($file == '..') {
 
if ($this->_directory != '.') {
// Get parent directory path
$pathArray = explode('/', $relativePath);
unset($pathArray[gd_count($pathArray) - 1]);
unset($pathArray[gd_count($pathArray) - 1]);
$directoryPath = gd_implode('/', $pathArray);
 
if (!empty($directoryPath)) {
$directoryPath = '?dir=' . rawurlencode($directoryPath);
}
 
// 한글명 패치
$directoryPath = iconv('CP949', 'UTF-8', $directoryPath);
 
// Add file info to the array
$directoryArray['..'] = [
'file_path' => $directoryPath,
'url_path' => $this->_appURL . $directoryPath,
'file_size' => '-',
'file_permission' => $this->getPermission($realPath),
'mod_time' => date('Y-m-d H:i:s', filemtime($realPath)),
'icon_class' => 'fa-level-up',
'sort' => 0,
];
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
} elseif (!$this->_isHidden($relativePath)) {
 
// Add all non-hidden files to the array
if ($this->_directory != '.' || $file != 'index.php') {
 
// Build the file path
$urlPath = gd_implode('/', array_map('rawurlencode', explode('/', $relativePath)));
 
if (is_dir($relativePath)) {
$urlPath = '?dir=' . $urlPath;
} else {
$urlPath = '/' . $urlPath;
}
 
// 한글명 패치
$relativePath = iconv('CP949', 'UTF-8', $relativePath);
 
// Add the info to the main array
$directoryArray[pathinfo($relativePath, PATHINFO_BASENAME)] = [
'file_path' => $relativePath,
'url_path' => $urlPath,
'file_size' => is_dir($realPath) ? '-' : $this->getFileSize($realPath),
'file_permission' => $this->getPermission($realPath),
'mod_time' => date('Y-m-d H:i:s', filemtime($realPath)),
'icon_class' => $iconClass,
'sort' => $sort,
'is_deleteable' => is_dir($realPath) ? $this->isRecursivelyDeleteable($realPath) : is_writable($realPath),
'is_image' => FileUtils::isImageFile($realPath),
'is_html' => gd_in_array(
$fileExt, [
'html',
'css',
'js',
]
),
];
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
unset($fileExt);
}
 
변경 후Webftp.php
*/
class Webftp
{
/**
* WebFTP 샌드박스 루트 — 잘못된 경로의 폴백 목적지이자 초기 진입 기본 디렉토리
*/
public const DEFAULT_DIRECTORY = 'data';
 
/**
* @var null
*/
// Cleanup directory path
$directory = $this->setDirectoryPath($directory);
 
if ($this->_isHidden($directory)) {
echo "Access denied.";
}
 
$filenameNoExt = basename($directory);
 
 
 
 
 
// We deliver a zip file
header('Content-Type: archive/zip');
 
{
 
// Build the path
$path = $this->_appURL . $this->_directory;
 
 
 
 
 
// Return the path
return $path;
 
// Check for an empty variable
if (empty($dir) || $dir == '.') {
return self::DEFAULT_DIRECTORY;
}
 
// Eliminate double slashes
// Set the error message
$this->setSystemMessage('danger', '<b>ERROR:</b> File path does not exist');
 
// 잘못된 경로는 webftp 루트(data)로 폴백 — 도큐먼트 루트 노출 방지
return self::DEFAULT_DIRECTORY;
}
 
// 정규화 후 실제 경로가 도큐먼트 루트로 귀결되면 data 로 폴백
// (상단 empty/'.' 가드는 정규화 전이라 './', './/', './.' 등이 통과해 웹루트가 노출됨)
if (realpath($dir) === realpath('.')) {
$this->setSystemMessage('danger', '<b>ERROR:</b> An invalid path string was detected');
 
return self::DEFAULT_DIRECTORY;
}
 
// Prevent access to hidden files
// Set the error message
$this->setSystemMessage('danger', '<b>ERROR:</b> Access denied');
 
// 잘못된 경로는 webftp 루트(data)로 폴백 — 도큐먼트 루트 노출 방지
return self::DEFAULT_DIRECTORY;
}
 
// Prevent access to parent folders
// Set the error message
$this->setSystemMessage('danger', '<b>ERROR:</b> An invalid path string was detected');
 
// 잘못된 경로는 webftp 루트(data)로 폴백 — 도큐먼트 루트 노출 방지
return self::DEFAULT_DIRECTORY;
} else {
// Should stop all URL wrappers (Thanks to Hexatex)
$directoryPath = $dir;
$relativePath = substr($relativePath, 2);
}
 
// Get files absolute path
$realPath = realpath($relativePath);
 
// Determine file type by extension
if (is_dir($realPath)) {
$iconClass = 'fa-folder';
$sort = 1;
} else {
// Get file extension
$fileExt = strtolower(pathinfo($realPath, PATHINFO_EXTENSION));
 
if (isset($this->_fileTypes[$fileExt])) {
$iconClass = $this->_fileTypes[$fileExt];
 
 
} else {
$iconClass = $this->_fileTypes['blank'];
 
 
 
 
 
 
 
 
 
}
 
$sort = 2;
}
 
if ($file == '..') {
 
// Get parent directory path
$pathArray = explode('/', $relativePath);
unset($pathArray[gd_count($pathArray) - 1]);
unset($pathArray[gd_count($pathArray) - 1]);
$directoryPath = gd_implode('/', $pathArray);
 
if (!empty($directoryPath)) {
$directoryPath = '?dir=' . rawurlencode($directoryPath);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
// 한글명 패치
$directoryPath = iconv('CP949', 'UTF-8', $directoryPath);
 
// Add file info to the array
$directoryArray['..'] = [
'file_path' => $directoryPath,
'url_path' => $this->_appURL . $directoryPath,
'file_size' => '-',
'file_permission' => $this->getPermission($realPath),
'mod_time' => date('Y-m-d H:i:s', filemtime($realPath)),
'icon_class' => 'fa-level-up',
'sort' => 0,
];
 
} elseif (!$this->_isHidden($relativePath)) {
 
// Build the file path
$urlPath = gd_implode('/', array_map('rawurlencode', explode('/', $relativePath)));
 
if (is_dir($relativePath)) {
$urlPath = '?dir=' . $urlPath;
} else {
$urlPath = '/' . $urlPath;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
// 한글명 패치
$relativePath = iconv('CP949', 'UTF-8', $relativePath);
 
// Add the info to the main array
$directoryArray[pathinfo($relativePath, PATHINFO_BASENAME)] = [
'file_path' => $relativePath,
'url_path' => $urlPath,
'file_size' => is_dir($realPath) ? '-' : $this->getFileSize($realPath),
'file_permission' => $this->getPermission($realPath),
'mod_time' => date('Y-m-d H:i:s', filemtime($realPath)),
'icon_class' => $iconClass,
'sort' => $sort,
'is_deleteable' => is_dir($realPath) ? $this->isRecursivelyDeleteable($realPath) : is_writable($realPath),
'is_image' => FileUtils::isImageFile($realPath),
'is_html' => gd_in_array(
$fileExt, [
'html',
'css',
'js',
]
),
];
}
unset($fileExt);
}
 
Controller/Admin/Share/PopupWebftpController.php+2-2
변경 전PopupWebftpController.php
$dirArray = $webftp->listDirectory(Request::get()->get('dir'));
$currentDirectory = Request::get()->get('dir');
} else {
$dirArray = $webftp->listDirectory('data');
$currentDirectory = 'data';
}
}
$this->setData('currentDirectory', $currentDirectory);
 
변경 후PopupWebftpController.php
$dirArray = $webftp->listDirectory(Request::get()->get('dir'));
$currentDirectory = Request::get()->get('dir');
} else {
$dirArray = $webftp->listDirectory(Webftp::DEFAULT_DIRECTORY);
$currentDirectory = Webftp::DEFAULT_DIRECTORY;
}
}
$this->setData('currentDirectory', $currentDirectory);