cross site scripting 취약점 문의
본문
아래와 같은 취약점을 해결해 주시면 고마움의 뜻으로 스타벅스 커피 쿠폰을 보내드릴께요.^^
1. 문제점 : 홈페이지에 그누보드를 사용하고 있으며 외부기관 의뢰 홈페이지 취약점 점검 결과
Cross Site Scripting 취약점이 발견되어 개선을 요구함
2. 취약점 #1 내용
○ 취약점 경로 : http://홈페이지주소/board/bbs/download.php?bo_table=resume&wr_id=2213320&no=1
○ 취약점 내용 : 헤더에 악성스크립트를 삽입하여 악성코드 유포 및 피싱사이트 유도 공격 시도 가능
○ 대응방안 : 모든 사용자 입력 값에 불필요한 특수문자(',",<,>)를 서버단에서 치환해야 함.
(필수적으로 모든 페이지의 입력 값 파라미터에 일괄 적용해야 함)
3. 취약점 #2 내용
○ 취약점 경로 : http://홈페이지주소/board/bbs/board.php?bo_table=resume&wr_id=2213327
○ 취약점 내용 : 게시판에 악성스크립트를 삽입하여 악성코드 유포 및 피싱사이트 유도 공격 시도 가능
○ 대응방안 : 모든 사용자 입력 값에 불필요한 특수문자(',",<,>)를 서버단에서 치환해야 함.
(필수적으로 모든 페이지의 입력 값 파라미터에 일괄 적용해야 함)
4. 취약점 #3 내용
○ 취약점 경로 : http://홈페이지주소/board/bbs/login.php?url=/%27%20a=b%27%3E%3Cscript%3Ealert
(1)%3C/script%3E%3Cb%3E%3C%27
○ 취약점 내용 : Reflected XSS 공격을 통해 악성코드 유포 및 피싱사이트 유도 공격 시도 가능
○ 대응방안 : 모든 사용자 입력 값에 불필요한 특수문자(',",<,>)를 서버단에서 치환해야 함.
(필수적으로 모든 페이지의 입력 값 파라미터에 일괄 적용해야 함)
많은 관심부탁드립니다. 감사합니다.
답변 5
감사합니다
쓰시는 그누보드 버전이 어떤지 모르겠지만,
최신 버전의 common.php만 바꿔도 해결 될 겁니다.
lib/common.lib.php도 교체하거나 변경 작업해야 합니다.
소스를 눈으로 봐서는 힘들고
서버에 적용한 후에 에러를 잡는 형식으로 해야 합니다.
그누보드 최신 버전을 받아 common.php파일과 common.lib.php파일을 대체해 봤더니 홈페이지가 열리지 않네요. 현재 원상복구 시켰더니 홈페이지는 정상으로 운영됩니다.
혹시나 아래에 common.php 파일과 common.lib.php파일의 소스를 올렸는데 수정 가능한지요?
부탁드립니다.
common.php파일
<?php
/*******************************************************************************
** 공통 변수, 상수, 코드
*******************************************************************************/
error_reporting( E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_ERROR | E_WARNING | E_PARSE | E_USER_ERROR | E_USER_WARNING );
// 보안설정이나 프레임이 달라도 쿠키가 통하도록 설정
header('P3P: CP="ALL CURa ADMa DEVa TAIa OUR BUS IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC OTC"');
if (!defined('G5_SET_TIME_LIMIT')) define('G5_SET_TIME_LIMIT', 0);
@set_time_limit(G5_SET_TIME_LIMIT);
//==========================================================================================================================
// extract($_GET); 명령으로 인해 page.php?_POST[var1]=data1&_POST[var2]=data2 와 같은 코드가 _POST 변수로 사용되는 것을 막음
// 081029 : letsgolee 님께서 도움 주셨습니다.
//--------------------------------------------------------------------------------------------------------------------------
$ext_arr = array ('PHP_SELF', '_ENV', '_GET', '_POST', '_FILES', '_SERVER', '_COOKIE', '_SESSION', '_REQUEST',
'HTTP_ENV_VARS', 'HTTP_GET_VARS', 'HTTP_POST_VARS', 'HTTP_POST_FILES', 'HTTP_SERVER_VARS',
'HTTP_COOKIE_VARS', 'HTTP_SESSION_VARS', 'GLOBALS');
$ext_cnt = count($ext_arr);
for ($i=0; $i<$ext_cnt; $i++) {
// POST, GET 으로 선언된 전역변수가 있다면 unset() 시킴
if (isset($_GET[$ext_arr[$i]])) unset($_GET[$ext_arr[$i]]);
if (isset($_POST[$ext_arr[$i]])) unset($_POST[$ext_arr[$i]]);
}
//==========================================================================================================================
function g5_path()
{
$result['path'] = str_replace('\\', '/', dirname(__FILE__));
// $result['path'] = str_replace('\\', '/', dirname(__FILE__));
$tilde_remove = preg_replace('/^\/\~[^\/]+(.*)$/', '$1', $_SERVER['SCRIPT_NAME']);
$document_root = str_replace($tilde_remove, '', $_SERVER['SCRIPT_FILENAME']);
$root = str_replace($document_root, '', $result['path']);
$port = $_SERVER['SERVER_PORT'] != 80 ? ':'.$_SERVER['SERVER_PORT'] : '';
$http = 'http' . ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']=='on') ? 's' : '') . '://';
$user = str_replace(str_replace($document_root, '', $_SERVER['SCRIPT_FILENAME']), '', $_SERVER['SCRIPT_NAME']);
$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'];
if(isset($_SERVER['HTTP_HOST']) && preg_match('/:[0-9]+$/', $host))
$host = preg_replace('/:[0-9]+$/', '', $host);
//$result['url'] = $http.$host.$port.$user.$root;
$result['url'] = $http.$host.$port.$user;
//echo $result['url'];exit;
//$result['url'] = $http.$host."/board";
return $result;
}
$g5_path = g5_path();
//echo $g5_path['url'];exit;
include_once($g5_path['path'].'/config.php'); // 설정 파일
unset($g5_path);
// multi-dimensional array에 사용자지정 함수적용
function array_map_deep($fn, $array)
{
if(is_array($array)) {
foreach($array as $key => $value) {
if(is_array($value)) {
$array[$key] = array_map_deep($fn, $value);
} else {
$array[$key] = call_user_func($fn, $value);
}
}
} else {
$array = call_user_func($fn, $array);
}
return $array;
}
// SQL Injection 대응 문자열 필터링
function sql_escape_string($str)
{
if(defined('G5_ESCAPE_PATTERN') && defined('G5_ESCAPE_REPLACE')) {
$pattern = G5_ESCAPE_PATTERN;
$replace = G5_ESCAPE_REPLACE;
if($pattern)
$str = preg_replace($pattern, $replace, $str);
}
$str = call_user_func('addslashes', $str);
return $str;
}
//==============================================================================
// SQL Injection 등으로 부터 보호를 위해 sql_escape_string() 적용
//------------------------------------------------------------------------------
// magic_quotes_gpc 에 의한 backslashes 제거
if (get_magic_quotes_gpc()) {
$_POST = array_map_deep('stripslashes', $_POST);
$_GET = array_map_deep('stripslashes', $_GET);
$_COOKIE = array_map_deep('stripslashes', $_COOKIE);
$_REQUEST = array_map_deep('stripslashes', $_REQUEST);
}
// sql_escape_string 적용
$_POST = array_map_deep(G5_ESCAPE_FUNCTION, $_POST);
$_GET = array_map_deep(G5_ESCAPE_FUNCTION, $_GET);
$_COOKIE = array_map_deep(G5_ESCAPE_FUNCTION, $_COOKIE);
$_REQUEST = array_map_deep(G5_ESCAPE_FUNCTION, $_REQUEST);
//==============================================================================
// PHP 4.1.0 부터 지원됨
// php.ini 의 register_globals=off 일 경우
@extract($_GET);
@extract($_POST);
@extract($_SERVER);
// 완두콩님이 알려주신 보안관련 오류 수정
// $member 에 값을 직접 넘길 수 있음
$config = array();
$member = array();
$board = array();
$group = array();
$g5 = array();
//==============================================================================
// 공통
//------------------------------------------------------------------------------
$dbconfig_file = G5_DATA_PATH.'/'.G5_DBCONFIG_FILE;
if (file_exists($dbconfig_file)) {
include_once($dbconfig_file);
include_once(G5_LIB_PATH.'/common.lib.php'); // 공통 라이브러리
$connect_db = sql_connect(G5_MYSQL_HOST, G5_MYSQL_USER, G5_MYSQL_PASSWORD) or die('MySQL Connect Error!!!');
$select_db = sql_select_db(G5_MYSQL_DB, $connect_db) or die('MySQL DB Error!!!');
// mysql connect resource $g5 배열에 저장 - 명랑폐인님 제안
$g5['connect_db'] = $connect_db;
sql_query(" set names utf8 ");
if(defined('G5_MYSQL_SET_MODE') && G5_MYSQL_SET_MODE) sql_query("SET SESSION sql_mode = ''");
if (defined(G5_TIMEZONE)) sql_query(" set time_zone = '".G5_TIMEZONE."'");
} else {
?>
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<title>오류! <?php echo G5_VERSION ?> 설치하기</title>
<link rel="stylesheet" href="install/install.css">
</head>
<body>
<div id="ins_bar">
<span id="bar_img">GNUBOARD5</span>
<span id="bar_txt">Message</span>
</div>
<h1>그누보드5를 먼저 설치해주십시오.</h1>
<div class="ins_inner">
<p>다음 파일을 찾을 수 없습니다.</p>
<ul>
<li><strong><?php echo G5_DATA_DIR.'/'.G5_DBCONFIG_FILE ?></strong></li>
</ul>
<p>그누보드 설치 후 다시 실행하시기 바랍니다.</p>
<div class="inner_btn">
<a href="<?php echo G5_URL; ?>/install/"><?php echo G5_VERSION ?> 설치하기</a>
</div>
</div>
<div id="ins_ft">
<strong>GNUBOARD5</strong>
<p>GPL! OPEN SOURCE GNUBOARD</p>
</div>
</body>
</html>
common.lib.php파일
<?php
if (!defined('_GNUBOARD_')) exit;
/*************************************************************************
**
** 일반 함수 모음
**
*************************************************************************/
// 마이크로 타임을 얻어 계산 형식으로 만듦
function get_microtime()
{
list($usec, $sec) = explode(" ",microtime());
return ((float)$usec + (float)$sec);
}
// 한페이지에 보여줄 행, 현재페이지, 총페이지수, URL
function get_paging($write_pages, $cur_page, $total_page, $url, $add="")
{
//$url = preg_replace('#&page=[0-9]*(&page=)$#', '$1', $url);
$url = preg_replace('#&page=[0-9]*#', '', $url) . '&page=';
$str = '';
if ($cur_page > 1) {
$str .= '<a href="'.$url.'1'.$add.'" class="pg_page pg_start">처음</a>'.PHP_EOL;
}
$start_page = ( ( (int)( ($cur_page - 1 ) / $write_pages ) ) * $write_pages ) + 1;
$end_page = $start_page + $write_pages - 1;
if ($end_page >= $total_page) $end_page = $total_page;
if ($start_page > 1) $str .= '<a href="'.$url.($start_page-1).$add.'" class="pg_page pg_prev">이전</a>'.PHP_EOL;
if ($total_page > 1) {
for ($k=$start_page;$k<=$end_page;$k++) {
if ($cur_page != $k)
$str .= '<a href="'.$url.$k.$add.'" class="pg_page">'.$k.'<span class="sound_only">페이지</span></a>'.PHP_EOL;
else
$str .= '<span class="sound_only">열린</span><strong class="pg_current">'.$k.'</strong><span class="sound_only">페이지</span>'.PHP_EOL;
}
}
if ($total_page > $end_page) $str .= '<a href="'.$url.($end_page+1).$add.'" class="pg_page pg_next">다음</a>'.PHP_EOL;
if ($cur_page < $total_page) {
$str .= '<a href="'.$url.$total_page.$add.'" class="pg_page pg_end">맨끝</a>'.PHP_EOL;
}
if ($str)
return "<nav class=\"pg_wrap\"><span class=\"pg\">{$str}</span></nav>";
else
return "";
}
// 페이징 코드의 <nav><span> 태그 다음에 코드를 삽입
function page_insertbefore($paging_html, $insert_html)
{
if(!$paging_html)
$paging_html = '<nav class="pg_wrap"><span class="pg"></span></nav>';
return preg_replace("/^(<nav[^>]+><span[^>]+>)/", '$1'.$insert_html.PHP_EOL, $paging_html);
}
// 페이징 코드의 </span></nav> 태그 이전에 코드를 삽입
function page_insertafter($paging_html, $insert_html)
{
if(!$paging_html)
$paging_html = '<nav class="pg_wrap"><span class="pg"></span></nav>';
if(preg_match("#".PHP_EOL."</span></nav>#", $paging_html))
$php_eol = '';
else
$php_eol = PHP_EOL;
return preg_replace("#(</span></nav>)$#", $php_eol.$insert_html.'$1', $paging_html);
}
// 변수 또는 배열의 이름과 값을 얻어냄. print_r() 함수의 변형
function print_r2($var)
{
ob_start();
print_r($var);
$str = ob_get_contents();
ob_end_clean();
$str = str_replace(" ", " ", $str);
echo nl2br("<span style='font-family:Tahoma, 굴림; font-size:9pt;'>$str</span>");
}
// 메타태그를 이용한 URL 이동
// header("location:URL") 을 대체
function goto_url($url)
{
$url = str_replace("&", "&", $url);
//echo "<script> location.replace('$url'); </script>";
if (!headers_sent())
header('Location: '.$url);
else {
echo '<script>';
echo 'location.replace("'.$url.'");';
echo '</script>';
echo '<noscript>';
echo '<meta http-equiv="refresh" content="0;url='.$url.'" />';
echo '</noscript>';
}
exit;
}
// 세션변수 생성
function set_session($session_name, $value)
{
if (PHP_VERSION < '5.3.0')
session_register($session_name);
// PHP 버전별 차이를 없애기 위한 방법
$$session_name = $_SESSION[$session_name] = $value;
}
// 세션변수값 얻음
function get_session($session_name)
{
return isset($_SESSION[$session_name]) ? $_SESSION[$session_name] : '';
}
// 쿠키변수 생성
function set_cookie($cookie_name, $value, $expire)
{
global $g5;
setcookie(md5($cookie_name), base64_encode($value), G5_SERVER_TIME + $expire, '/', G5_COOKIE_DOMAIN);
}
// 쿠키변수값 얻음
function get_cookie($cookie_name)
{
$cookie = md5($cookie_name);
if (array_key_exists($cookie, $_COOKIE))
return base64_decode($_COOKIE[md5($cookie_name)]);
else
return "";
}
// 경고메세지를 경고창으로
function alert($msg='', $url='', $error=true, $post=false)
{
global $g5, $config, $member;
global $is_admin;
if (!$msg) $msg = '올바른 방법으로 이용해 주십시오.';
$header = '';
if (isset($g5['title'])) {
$header = $g5['title'];
}
include_once(G5_BBS_PATH.'/alert.php');
exit;
}
// 경고메세지 출력후 창을 닫음
function alert_close($msg, $error=true)
{
global $g5;
$header = '';
if (isset($g5['title'])) {
$header = $g5['title'];
}
include_once(G5_BBS_PATH.'/alert_close.php');
exit;
}
// confirm 창
function confirm($msg, $url1='', $url2='', $url3='')
{
global $g5;
if (!$msg) {
$msg = '올바른 방법으로 이용해 주십시오.';
alert($msg);
}
if(!trim($url1) || !trim($url2)) {
$msg = '$url1 과 $url2 를 지정해 주세요.';
alert($msg);
}
if (!$url3) $url3 = $_SERVER['HTTP_REFERER'];
$msg = str_replace("\\n", "<br>", $msg);
$header = '';
if (isset($g5['title'])) {
$header = $g5['title'];
}
include_once(G5_BBS_PATH.'/confirm.php');
exit;
}
// way.co.kr 의 wayboard 참고
function url_auto_link($str)
{
global $g5;
global $config;
// 140326 유창화님 제안코드로 수정
// http://sir.co.kr/bbs/board.php?bo_table=pg_lecture&wr_id=461
// http://sir.co.kr/bbs/board.php?bo_table=pg_lecture&wr_id=463
$str = str_replace(array("<", ">", "&", """, " "), array("\t_lt_\t", "\t_gt_\t", "&", "\"", "\t_nbsp_\t"), $str);
$str = preg_replace("`(?:(?:(?:href|src)\s*=\s*(?:\"|'|)){0})((http|https|ftp|telnet|news|mms)://[^\"'\s()]+)`", "<A HREF=\"\\1\" TARGET='{$config['cf_link_target']}'>\\1</A>", $str);
$str = preg_replace("/(^|[\"'\s(])(www\.[^\"'\s()]+)/i", "\\1<A HREF=\"http://\\2\" TARGET='{$config['cf_link_target']}'>\\2</A>", $str);
$str = preg_replace("/[0-9a-z_-]+@[a-z0-9._-]{4,}/i", "<a href='mailto:\\0'>\\0</a>", $str);
$str = str_replace(array("\t_nbsp_\t", "\t_lt_\t", "\t_gt_\t"), array(" ", "<", ">"), $str);
/*
// 속도 향상 031011
$str = preg_replace("/</", "\t_lt_\t", $str);
$str = preg_replace("/>/", "\t_gt_\t", $str);
$str = preg_replace("/&/", "&", $str);
$str = preg_replace("/"/", "\"", $str);
$str = preg_replace("/ /", "\t_nbsp_\t", $str);
$str = preg_replace("/([^(http:\/\/)]|\(|^)(www\.[^[:space:]]+)/i", "\\1<A HREF=\"http://\\2\" TARGET='{$config['cf_link_target']}'>\\2</A>", $str);
//$str = preg_replace("/([^(HREF=\"?'?)|(SRC=\"?'?)]|\(|^)((http|https|ftp|telnet|news|mms):\/\/[a-zA-Z0-9\.-]+\.[\xA1-\xFEa-zA-Z0-9\.:=_\?\/~\+%@;\-\|\,]+)/i", "\\1<A HREF=\"\\2\" TARGET='$config['cf_link_target']'>\\2</A>", $str);
// 100825 : () 추가
// 120315 : CHARSET 에 따라 링크시 글자 잘림 현상이 있어 수정
$str = preg_replace("/([^(HREF=\"?'?)|(SRC=\"?'?)]|\(|^)((http|https|ftp|telnet|news|mms):\/\/[a-zA-Z0-9\.-]+\.[가-힣\xA1-\xFEa-zA-Z0-9\.:=_\?\/~\+%@;\-\|\,\(\)]+)/i", "\\1<A HREF=\"\\2\" TARGET='{$config['cf_link_target']}'>\\2</A>", $str);
// 이메일 정규표현식 수정 061004
//$str = preg_replace("/(([a-z0-9_]|\-|\.)+@([^[:space:]]*)([[:alnum:]-]))/i", "<a href='mailto:\\1'>\\1</a>", $str);
$str = preg_replace("/([0-9a-z]([-_\.]?[0-9a-z])*@[0-9a-z]([-_\.]?[0-9a-z])*\.[a-z]{2,4})/i", "<a href='mailto:\\1'>\\1</a>", $str);
$str = preg_replace("/\t_nbsp_\t/", " " , $str);
$str = preg_replace("/\t_lt_\t/", "<", $str);
$str = preg_replace("/\t_gt_\t/", ">", $str);
*/
return $str;
}
// url에 http:// 를 붙인다
function set_http($url)
{
if (!trim($url)) return;
if (!preg_match("/^(http|https|ftp|telnet|news|mms)\:\/\//i", $url))
$url = "http://" . $url;
return $url;
}
// 파일의 용량을 구한다.
//function get_filesize($file)
function get_filesize($size)
{
//$size = @filesize(addslashes($file));
if ($size >= 1048576) {
$size = number_format($size/1048576, 1) . "M";
} else if ($size >= 1024) {
$size = number_format($size/1024, 1) . "K";
} else {
$size = number_format($size, 0) . "byte";
}
return $size;
}
// 게시글에 첨부된 파일을 얻는다. (배열로 반환)
function get_file($bo_table, $wr_id)
{
global $g5, $qstr;
$file['count'] = 0;
$sql = " select * from {$g5['board_file_table']} where bo_table = '$bo_table' and wr_id = '$wr_id' order by bf_no ";
$result = sql_query($sql);
while ($row = sql_fetch_array($result))
{
$no = $row['bf_no'];
$file[$no]['href'] = G5_BBS_URL."/download.php?bo_table=$bo_table&wr_id=$wr_id&no=$no" . $qstr;
$file[$no]['download'] = $row['bf_download'];
// 4.00.11 - 파일 path 추가
$file[$no]['path'] = G5_DATA_URL.'/file/'.$bo_table;
$file[$no]['size'] = get_filesize($row['bf_filesize']);
$file[$no]['datetime'] = $row['bf_datetime'];
$file[$no]['source'] = addslashes($row['bf_source']);
$file[$no]['bf_content'] = $row['bf_content'];
$file[$no]['content'] = get_text($row['bf_content']);
//$file[$no]['view'] = view_file_link($row['bf_file'], $file[$no]['content']);
$file[$no]['view'] = view_file_link($row['bf_file'], $row['bf_width'], $row['bf_height'], $file[$no]['content']);
$file[$no]['file'] = $row['bf_file'];
$file[$no]['image_width'] = $row['bf_width'] ? $row['bf_width'] : 640;
$file[$no]['image_height'] = $row['bf_height'] ? $row['bf_height'] : 480;
$file[$no]['image_type'] = $row['bf_type'];
$file['count']++;
}
return $file;
}
// 폴더의 용량 ($dir는 / 없이 넘기세요)
function get_dirsize($dir)
{
$size = 0;
$d = dir($dir);
while ($entry = $d->read()) {
if ($entry != '.' && $entry != '..') {
$size += filesize($dir.'/'.$entry);
}
}
$d->close();
return $size;
}
/*************************************************************************
**
** 그누보드 관련 함수 모음
**
*************************************************************************/
// 게시물 정보($write_row)를 출력하기 위하여 $list로 가공된 정보를 복사 및 가공
function get_list($write_row, $board, $skin_url, $subject_len=40)
{
global $g5, $config;
global $qstr, $page;
//$t = get_microtime();
// 배열전체를 복사
$list = $write_row;
unset($write_row);
$board_notice = array_map('trim', explode(',', $board['bo_notice']));
$list['is_notice'] = in_array($list['wr_id'], $board_notice);
if ($subject_len)
$list['subject'] = conv_subject($list['wr_subject'], $subject_len, '…');
else
$list['subject'] = conv_subject($list['wr_subject'], $board['bo_subject_len'], '…');
// 목록에서 내용 미리보기 사용한 게시판만 내용을 변환함 (속도 향상) : kkal3(커피)님께서 알려주셨습니다.
if ($board['bo_use_list_content'])
{
$html = 0;
if (strstr($list['wr_option'], 'html1'))
$html = 1;
else if (strstr($list['wr_option'], 'html2'))
$html = 2;
$list['content'] = conv_content($list['wr_content'], $html);
}
$list['comment_cnt'] = '';
if ($list['wr_comment'])
$list['comment_cnt'] = "<span class=\"cnt_cmt\">".$list['wr_comment']."</span>";
// 당일인 경우 시간으로 표시함
$list['datetime'] = substr($list['wr_datetime'],0,10);
$list['datetime2'] = $list['wr_datetime'];
if ($list['datetime'] == G5_TIME_YMD)
$list['datetime2'] = substr($list['datetime2'],11,5);
else
$list['datetime2'] = substr($list['datetime2'],5,5);
// 4.1
$list['last'] = substr($list['wr_last'],0,10);
$list['last2'] = $list['wr_last'];
if ($list['last'] == G5_TIME_YMD)
$list['last2'] = substr($list['last2'],11,5);
else
$list['last2'] = substr($list['last2'],5,5);
$list['wr_homepage'] = get_text(addslashes($list['wr_homepage']));
$tmp_name = get_text(cut_str($list['wr_name'], $config['cf_cut_name'])); // 설정된 자리수 만큼만 이름 출력
if ($board['bo_use_sideview'])
$list['name'] = get_sideview($list['mb_id'], $tmp_name, $list['wr_email'], $list['wr_homepage']);
else
$list['name'] = '<span class="'.($list['mb_id']?'sv_member':'sv_guest').'">'.$tmp_name.'</span>';
$reply = $list['wr_reply'];
$list['reply'] = strlen($reply)*10;
$list['icon_reply'] = '';
if ($list['reply'])
$list['icon_reply'] = '<img src="'.$skin_url.'/img/icon_reply.gif" style="margin-left:'.$list['reply'].'px;" alt="답변글">';
$list['icon_link'] = '';
if ($list['wr_link1'] || $list['wr_link2'])
$list['icon_link'] = '<img src="'.$skin_url.'/img/icon_link.gif" alt="관련링크">';
// 분류명 링크
$list['ca_name_href'] = G5_BBS_URL.'/board.php?bo_table='.$board['bo_table'].'&sca='.urlencode($list['ca_name']);
$list['href'] = G5_BBS_URL.'/board.php?bo_table='.$board['bo_table'].'&wr_id='.$list['wr_id'].$qstr;
$list['comment_href'] = $list['href'];
$list['icon_new'] = '';
if ($board['bo_new'] && $list['wr_datetime'] >= date("Y-m-d H:i:s", G5_SERVER_TIME - ($board['bo_new'] * 3600)))
$list['icon_new'] = '<img src="'.$skin_url.'/img/icon_new.gif" alt="새글">';
$list['icon_hot'] = '';
if ($board['bo_hot'] && $list['wr_hit'] >= $board['bo_hot'])
$list['icon_hot'] = '<img src="'.$skin_url.'/img/icon_hot.gif" alt="인기글">';
$list['icon_secret'] = '';
if (strstr($list['wr_option'], 'secret'))
$list['icon_secret'] = '<img src="'.$skin_url.'/img/icon_secret.gif" alt="비밀글">';
// 링크
for ($i=1; $i<=G5_LINK_COUNT; $i++) {
$list['link'][$i] = set_http(get_text($list["wr_link{$i}"]));
$list['link_href'][$i] = G5_BBS_URL.'/link.php?bo_table='.$board['bo_table'].'&wr_id='.$list['wr_id'].'&no='.$i.$qstr;
$list['link_hit'][$i] = (int)$list["wr_link{$i}_hit"];
}
// 가변 파일
if ($board['bo_use_list_file'] || ($list['wr_file'] && $subject_len == 255) /* view 인 경우 */) {
$list['file'] = get_file($board['bo_table'], $list['wr_id']);
} else {
$list['file']['count'] = $list['wr_file'];
}
if ($list['file']['count'])
$list['icon_file'] = '<img src="'.$skin_url.'/img/icon_file.gif" alt="첨부파일">';
return $list;
}
// get_list 의 alias
function get_view($write_row, $board, $skin_url)
{
return get_list($write_row, $board, $skin_url, 255);
}
// set_search_font(), get_search_font() 함수를 search_font() 함수로 대체
function search_font($stx, $str)
{
global $config;
// 문자앞에 \ 를 붙입니다.
$src = array('/', '|');
$dst = array('\/', '\|');
if (!trim($stx)) return $str;
// 검색어 전체를 공란으로 나눈다
$s = explode(' ', $stx);
// "/(검색1|검색2)/i" 와 같은 패턴을 만듬
$pattern = '';
$bar = '';
for ($m=0; $m<count($s); $m++) {
if (trim($s[$m]) == '') continue;
// 태그는 포함하지 않아야 하는데 잘 안되는군. ㅡㅡa
//$pattern .= $bar . '([^<])(' . quotemeta($s[$m]) . ')';
//$pattern .= $bar . quotemeta($s[$m]);
//$pattern .= $bar . str_replace("/", "\/", quotemeta($s[$m]));
$tmp_str = quotemeta($s[$m]);
$tmp_str = str_replace($src, $dst, $tmp_str);
$pattern .= $bar . $tmp_str . "(?![^<]*>)";
$bar = "|";
}
// 지정된 검색 폰트의 색상, 배경색상으로 대체
$replace = "<b class=\"sch_word\">\\1</b>";
return preg_replace("/($pattern)/i", $replace, $str);
}
// 제목을 변환
function conv_subject($subject, $len, $suffix='')
{
return cut_str(get_text($subject), $len, $suffix);
}
// 내용을 변환
function conv_content($content, $html, $filter=true)
{
global $config, $board;
if ($html)
{
$source = array();
$target = array();
$source[] = "//";
$target[] = "";
if ($html == 2) { // 자동 줄바꿈
$source[] = "/\n/";
$target[] = "<br/>";
}
// 테이블 태그의 개수를 세어 테이블이 깨지지 않도록 한다.
$table_begin_count = substr_count(strtolower($content), "<table");
$table_end_count = substr_count(strtolower($content), "</table");
for ($i=$table_end_count; $i<$table_begin_count; $i++)
{
$content .= "</table>";
}
$content = preg_replace($source, $target, $content);
if($filter)
$content = html_purifier($content);
}
else // text 이면
{
// & 처리 : & 등의 코드를 정상 출력함
$content = html_symbol($content);
// 공백 처리
//$content = preg_replace("/ /", " ", $content);
$content = str_replace(" ", " ", $content);
$content = str_replace("\n ", "\n ", $content);
$content = get_text($content, 1);
$content = url_auto_link($content);
}
return $content;
}
// http://htmlpurifier.org/
// Standards-Compliant HTML Filtering
// Safe : HTML Purifier defeats XSS with an audited whitelist
// Clean : HTML Purifier ensures standards-compliant output
// Open : HTML Purifier is open-source and highly customizable
function html_purifier($html)
{
$f = file(G5_PLUGIN_PATH.'/htmlpurifier/safeiframe.txt');
$domains = array();
foreach($f as $domain){
// 첫행이 # 이면 주석 처리
if (!preg_match("/^#/", $domain)) {
$domain = trim($domain);
if ($domain)
array_push($domains, $domain);
}
}
// 내 도메인도 추가
array_push($domains, $_SERVER['HTTP_HOST'].'/');
$safeiframe = implode('|', $domains);
include_once(G5_PLUGIN_PATH.'/htmlpurifier/HTMLPurifier.standalone.php');
$config = HTMLPurifier_Config::createDefault();
// data/cache 디렉토리에 CSS, HTML, URI 디렉토리 등을 만든다.
$config->set('Cache.SerializerPath', G5_DATA_PATH.'/cache');
$config->set('HTML.SafeEmbed', true);
$config->set('HTML.SafeObject', true);
$config->set('HTML.SafeIframe', true);
$config->set('URI.SafeIframeRegexp','%^(https?:)?//('.$safeiframe.')%');
$config->set('Attr.AllowedFrameTargets', array('_blank'));
$purifier = new HTMLPurifier($config);
return $purifier->purify($html);
}
// 검색 구문을 얻는다.
function get_sql_search($search_ca_name, $search_field, $search_text, $search_operator='and')
{
global $g5;
$str = "";
if ($search_ca_name)
$str = " ca_name = '$search_ca_name' ";
$search_text = strip_tags(($search_text));
$search_text = trim(stripslashes($search_text));
if (!$search_text) {
if ($search_ca_name) {
return $str;
} else {
return '0';
}
}
if ($str)
$str .= " and ";
// 쿼리의 속도를 높이기 위하여 ( ) 는 최소화 한다.
$op1 = "";
// 검색어를 구분자로 나눈다. 여기서는 공백
$s = array();
$s = explode(" ", $search_text);
// 검색필드를 구분자로 나눈다. 여기서는 +
$tmp = array();
$tmp = explode(",", trim($search_field));
$field = explode("||", $tmp[0]);
$not_comment = "";
if (!empty($tmp[1]))
$not_comment = $tmp[1];
$str .= "(";
for ($i=0; $i<count($s); $i++) {
// 검색어
$search_str = trim($s[$i]);
if ($search_str == "") continue;
// 인기검색어
insert_popular($field, $search_str);
$str .= $op1;
$str .= "(";
$op2 = "";
for ($k=0; $k<count($field); $k++) { // 필드의 수만큼 다중 필드 검색 가능 (필드1+필드2...)
// SQL Injection 방지
// 필드값에 a-z A-Z 0-9 _ , | 이외의 값이 있다면 검색필드를 wr_subject 로 설정한다.
$field[$k] = preg_match("/^[\w\,\|]+$/", $field[$k]) ? $field[$k] : "wr_subject";
$str .= $op2;
switch ($field[$k]) {
case "mb_id" :
case "wr_name" :
$str .= " $field[$k] = '$s[$i]' ";
break;
case "wr_hit" :
case "wr_good" :
case "wr_nogood" :
$str .= " $field[$k] >= '$s[$i]' ";
break;
// 번호는 해당 검색어에 -1 을 곱함
case "wr_num" :
$str .= "$field[$k] = ".((-1)*$s[$i]);
break;
case "wr_ip" :
case "wr_password" :
$str .= "1=0"; // 항상 거짓
break;
// LIKE 보다 INSTR 속도가 빠름
default :
if (preg_match("/[a-zA-Z]/", $search_str))
$str .= "INSTR(LOWER($field[$k]), LOWER('$search_str'))";
else
$str .= "INSTR($field[$k], '$search_str')";
break;
}
$op2 = " or ";
}
$str .= ")";
$op1 = " $search_operator ";
}
$str .= " ) ";
if ($not_comment)
$str .= " and wr_is_comment = '0' ";
return $str;
}
// 게시판 테이블에서 하나의 행을 읽음
function get_write($write_table, $wr_id)
{
return sql_fetch(" select * from $write_table where wr_id = '$wr_id' ");
}
// 게시판의 다음글 번호를 얻는다.
function get_next_num($table)
{
// 가장 작은 번호를 얻어
$sql = " select min(wr_num) as min_wr_num from $table ";
$row = sql_fetch($sql);
// 가장 작은 번호에 1을 빼서 넘겨줌
return (int)($row['min_wr_num'] - 1);
}
// 그룹 설정 테이블에서 하나의 행을 읽음
function get_group($gr_id)
{
global $g5;
return sql_fetch(" select * from {$g5['group_table']} where gr_id = '$gr_id' ");
}
// 회원 정보를 얻는다.
function get_member($mb_id, $fields='*')
{
global $g5;
return sql_fetch(" select $fields from {$g5['member_table']} where mb_id = TRIM('$mb_id') ");
}
// 날짜, 조회수의 경우 높은 순서대로 보여져야 하므로 $flag 를 추가
// $flag : asc 낮은 순서 , desc 높은 순서
// 제목별로 컬럼 정렬하는 QUERY STRING
function subject_sort_link($col, $query_string='', $flag='asc')
{
global $sst, $sod, $sfl, $stx, $page;
$q1 = "sst=$col";
if ($flag == 'asc')
{
$q2 = 'sod=asc';
if ($sst == $col)
{
if ($sod == 'asc')
{
$q2 = 'sod=desc';
}
}
}
else
{
$q2 = 'sod=desc';
if ($sst == $col)
{
if ($sod == 'desc')
{
$q2 = 'sod=asc';
}
}
}
$arr_query = array();
$arr_query[] = $query_string;
$arr_query[] = $q1;
$arr_query[] = $q2;
$arr_query[] = 'sfl='.$sfl;
$arr_query[] = 'stx='.$stx;
$arr_query[] = 'page='.$page;
$qstr = implode("&", $arr_query);
return "<a href=\"{$_SERVER['PHP_SELF']}?{$qstr}\">";
}
// 관리자 정보를 얻음
function get_admin($admin='super', $fields='*')
{
global $config, $group, $board;
global $g5;
$is = false;
if ($admin == 'board') {
$mb = sql_fetch("select {$fields} from {$g5['member_table']} where mb_id in ('{$board['bo_admin']}') limit 1 ");
$is = true;
}
if (($is && !$mb['mb_id']) || $admin == 'group') {
$mb = sql_fetch("select {$fields} from {$g5['member_table']} where mb_id in ('{$group['gr_admin']}') limit 1 ");
$is = true;
}
if (($is && !$mb['mb_id']) || $admin == 'super') {
$mb = sql_fetch("select {$fields} from {$g5['member_table']} where mb_id in ('{$config['cf_admin']}') limit 1 ");
}
return $mb;
}
// 관리자인가?
function is_admin($mb_id)
{
global $config, $group, $board;
if (!$mb_id) return;
if ($config['cf_admin'] == $mb_id) return 'super';
if (isset($group['gr_admin']) && ($group['gr_admin'] == $mb_id)) return 'group';
if (isset($board['bo_admin']) && ($board['bo_admin'] == $mb_id)) return 'board';
return '';
}
// 분류 옵션을 얻음
// 4.00 에서는 카테고리 테이블을 없애고 보드테이블에 있는 내용으로 대체
function get_category_option($bo_table='', $ca_name='')
{
global $g5, $board, $is_admin;
$categories = explode("|", $board['bo_category_list'].($is_admin?"|공지":"")); // 구분자가 , 로 되어 있음
$str = "";
for ($i=0; $i<count($categories); $i++) {
$category = trim($categories[$i]);
if (!$category) continue;
$str .= "<option value=\"$categories[$i]\"";
if ($category == $ca_name) {
$str .= ' selected="selected"';
}
$str .= ">$categories[$i]</option>\n";
}
return $str;
}
// 게시판 그룹을 SELECT 형식으로 얻음
function get_group_select($name, $selected='', $event='')
{
global $g5, $is_admin, $member;
$sql = " select gr_id, gr_subject from {$g5['group_table']} a ";
if ($is_admin == "group") {
$sql .= " left join {$g5['member_table']} b on (b.mb_id = a.gr_admin)
where b.mb_id = '{$member['mb_id']}' ";
}
$sql .= " order by a.gr_id ";
$result = sql_query($sql);
$str = "<select id=\"$name\" name=\"$name\" $event>\n";
for ($i=0; $row=sql_fetch_array($result); $i++) {
if ($i == 0) $str .= "<option value=\"\">선택</option>";
$str .= option_selected($row['gr_id'], $selected, $row['gr_subject']);
}
$str .= "</select>";
return $str;
}
function option_selected($value, $selected, $text='')
{
if (!$text) $text = $value;
if ($value == $selected)
return "<option value=\"$value\" selected=\"selected\">$text</option>\n";
else
return "<option value=\"$value\">$text</option>\n";
}
// '예', '아니오'를 SELECT 형식으로 얻음
function get_yn_select($name, $selected='1', $event='')
{
$str = "<select name=\"$name\" $event>\n";
if ($selected) {
$str .= "<option value=\"1\" selected>예</option>\n";
$str .= "<option value=\"0\">아니오</option>\n";
} else {
$str .= "<option value=\"1\">예</option>\n";
$str .= "<option value=\"0\" selected>아니오</option>\n";
}
$str .= "</select>";
return $str;
}
// 포인트 부여
function insert_point($mb_id, $point, $content='', $rel_table='', $rel_id='', $rel_action='', $expire=0)
{
global $config;
global $g5;
global $is_admin;
// 포인트 사용을 하지 않는다면 return
if (!$config['cf_use_point']) { return 0; }
// 포인트가 없다면 업데이트 할 필요 없음
if ($point == 0) { return 0; }
// 회원아이디가 없다면 업데이트 할 필요 없음
if ($mb_id == '') { return 0; }
$mb = sql_fetch(" select mb_id from {$g5['member_table']} where mb_id = '$mb_id' ");
if (!$mb['mb_id']) { return 0; }
// 회원포인트
$mb_point = get_point_sum($mb_id);
// 이미 등록된 내역이라면 건너뜀
if ($rel_table || $rel_id || $rel_action)
{
$sql = " select count(*) as cnt from {$g5['point_table']}
where mb_id = '$mb_id'
and po_rel_table = '$rel_table'
and po_rel_id = '$rel_id'
and po_rel_action = '$rel_action' ";
$row = sql_fetch($sql);
if ($row['cnt'])
return -1;
}
// 포인트 건별 생성
$po_expire_date = '9999-12-31';
if($config['cf_point_term'] > 0) {
if($expire > 0)
$po_expire_date = date('Y-m-d', strtotime('+'.($expire - 1).' days', G5_SERVER_TIME));
else
$po_expire_date = date('Y-m-d', strtotime('+'.($config['cf_point_term'] - 1).' days', G5_SERVER_TIME));
}
$po_expired = 0;
if($point < 0) {
$po_expired = 1;
$po_expire_date = G5_TIME_YMD;
}
$po_mb_point = $mb_point + $point;
$sql = " insert into {$g5['point_table']}
set mb_id = '$mb_id',
po_datetime = '".G5_TIME_YMDHIS."',
po_content = '".addslashes($content)."',
po_point = '$point',
po_use_point = '0',
po_mb_point = '$po_mb_point',
po_expired = '$po_expired',
po_expire_date = '$po_expire_date',
po_rel_table = '$rel_table',
po_rel_id = '$rel_id',
po_rel_action = '$rel_action' ";
sql_query($sql);
// 포인트를 사용한 경우 포인트 내역에 사용금액 기록
if($point < 0) {
insert_use_point($mb_id, $point);
}
// 포인트 UPDATE
$sql = " update {$g5['member_table']} set mb_point = '$po_mb_point' where mb_id = '$mb_id' ";
sql_query($sql);
return 1;
}
// 사용포인트 입력
function insert_use_point($mb_id, $point, $po_id='')
{
global $g5, $config;
if($config['cf_point_term'])
$sql_order = " order by po_expire_date asc, po_id asc ";
else
$sql_order = " order by po_id asc ";
$point1 = abs($point);
$sql = " select po_id, po_point, po_use_point
from {$g5['point_table']}
where mb_id = '$mb_id'
and po_id <> '$po_id'
and po_expired = '0'
and po_point > po_use_point
$sql_order ";
$result = sql_query($sql);
for($i=0; $row=sql_fetch_array($result); $i++) {
$point2 = $row['po_point'];
$point3 = $row['po_use_point'];
if(($point2 - $point3) > $point1) {
$sql = " update {$g5['point_table']}
set po_use_point = po_use_point + '$point1'
where po_id = '{$row['po_id']}' ";
sql_query($sql);
break;
} else {
$point4 = $point2 - $point3;
$sql = " update {$g5['point_table']}
set po_use_point = po_use_point + '$point4',
po_expired = '100'
where po_id = '{$row['po_id']}' ";
sql_query($sql);
감사합니다