TIP게시판

제목 xss_clean이 WYSIWYG의 style을 제거하는 부분 처리
글쓴이 배강민 작성시각 2013/06/07 13:53:00
댓글 : 3 추천 : 0 스크랩 : 0 조회수 : 18426   RSS
배강민
WYSIWYG를 쓰다보면 style 태그가 삽입이 되곤하죠.

그런데, CI의 xss_clean 이 style 도 xss 공격이 가능하므로 없애버리게 됩니다.

물론, 없애야 맞긴 하겠지만, 위지윅을 쓰려면 어쩔 수 없어서...

style 이 안박히는 위지윅이 있는지 모르겠지만, 대부분 박히는듯...

해서 security.php 를 오버라이딩했습니다.

application/core/MY_security.php 로 생성하면 되고요..

사용은 forme_validation 에서는 xss_clean 을 빼고요.

만약 해당 변수가 $contents 라면

$this->security->xss_clean($contents, FALSE, TRUE); 로 하면 됩니다.

2번째 인자값은 원래 있는 값이고, 3번째가 TRUE 이면 wysiwyg 이므로 style 처리부분을 제거하겠다입니다.

좀 더 좋은 방법이 있을지 고민중이긴한데, 일단 공유드려봅니다. 더 좋은 의견을 기둘려보며...ㅋ

<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class MY_Security extends CI_Security 
{
 public function __construct() 
 {
  parent::__construct();
 }

 /**
  * XSS Clean
  *
  * Sanitizes data so that Cross Site Scripting Hacks can be
  * prevented.  This function does a fair amount of work but
  * it is extremely thorough, designed to prevent even the
  * most obscure XSS attempts.  Nothing is ever 100% foolproof,
  * of course, but I haven't been able to get anything passed
  * the filter.
  *
  * Note: This function should only be used to deal with data
  * upon submission.  It's not something that should
  * be used for general runtime processing.
  *
  * This function was based in part on some code and ideas I
  * got from Bitflux: http://channel.bitflux.ch/wiki/XSS_Prevention
  *
  * To help develop this script I used this great list of
  * vulnerabilities along with a few other hacks I've
  * harvested from examining vulnerabilities in other programs:
  * http://ha.ckers.org/xss.html
  *
  * @param mixed string or array
  * @param  bool
  * @return string
  */
 public function xss_clean($str, $is_image = FALSE, $is_wysiwyg = FALSE)
 {
  /*
   * Is the string an array?
   *
   */
  if (is_array($str))
  {
   while (list($key) = each($str))
   {
    $str[$key] = $this->xss_clean($str[$key]);
   }

   return $str;
  }

  /*
   * Remove Invisible Characters
   */
  $str = remove_invisible_characters($str);

  // Validate Entities in URLs
  $str = $this->_validate_entities($str);

  /*
   * URL Decode
   *
   * Just in case stuff like this is submitted:
   *
   * <a href="http://www.google.com">Google</a>
   *
   * Note: Use rawurldecode() so it does not remove plus signs
   *
   */
  $str = rawurldecode($str);

  /*
   * Convert character entities to ASCII
   *
   * This permits our tests below to work reliably.
   * We only convert entities that are within tags since
   * these are the ones that will pose security problems.
   *
   */

  $str = preg_replace_callback("/[a-z]+=([\'\"]).*?\\1/si", array($this, '_convert_attribute'), $str);

  $str = preg_replace_callback("/<\w+.*?(?=>|<|$)/si", array($this, '_decode_entity'), $str);

  /*
   * Remove Invisible Characters Again!
   */
  $str = remove_invisible_characters($str);

  /*
   * Convert all tabs to spaces
   *
   * This prevents strings like this: javascript
   * NOTE: we deal with spaces between characters later.
   * NOTE: preg_replace was found to be amazingly slow here on
   * large blocks of data, so we use str_replace.
   */

  if (strpos($str, "\t") !== FALSE)
  {
   $str = str_replace("\t", ' ', $str);
  }

  /*
   * Capture converted string for later comparison
   */
  $converted_string = $str;

  // Remove Strings that are never allowed
  $str = $this->_do_never_allowed($str);

  /*
   * Makes PHP tags safe
   *
   * Note: XML tags are inadvertently replaced too:
   *
   * <?xml
   *
   * But it doesn't seem to pose a problem.
   */
  if ($is_image === TRUE)
  {
   // Images have a tendency to have the PHP short opening and
   // closing tags every so often so we skip those and only
   // do the long opening tags.
   $str = preg_replace('/<\?(php)/i', "<?\\1", $str);
  }
  else
  {
   $str = str_replace(array('<?', '?'.'>'),  array('<?', '?>'), $str);
  }

  /*
   * Compact any exploded words
   *
   * This corrects words like:  javascript
   * These words are compacted back to their correct state.
   */
  $words = array(
   'javascript', 'expression', 'vbscript', 'script', 'base64',
   'applet', 'alert', 'document', 'write', 'cookie', 'window'
  );

  foreach ($words as $word)
  {
   $temp = '';

   for ($i = 0, $wordlen = strlen($word); $i < $wordlen; $i++)
   {
    $temp .= substr($word, $i, 1)."\s*";
   }

   // We only want to do this when it is followed by a non-word character
   // That way valid stuff like "dealer to" does not become "dealerto"
   $str = preg_replace_callback('#('.substr($temp, 0, -3).')(\W)#is', array($this, '_compact_exploded_words'), $str);
  }

  /*
   * Remove disallowed Javascript in links or img tags
   * We used to do some version comparisons and use of stripos for PHP5,
   * but it is dog slow compared to these simplified non-capturing
   * preg_match(), especially if the pattern exists in the string
   */
  do
  {
   $original = $str;

   if (preg_match("/<a/i", $str))
   {
    $str = preg_replace_callback("#<a\s+([^>]*?)(>|$)#si", array($this, '_js_link_removal'), $str);
   }

   if (preg_match("/<img/i", $str))
   {
    $str = preg_replace_callback("#<img\s+([^>]*?)(\s?/?>|$)#si", array($this, '_js_img_removal'), $str);
   }

   if (preg_match("/script/i", $str) OR preg_match("/xss/i", $str))
   {
    $str = preg_replace("#<(/*)(script|xss)(.*?)\>#si", '[removed]', $str);
   }
  }
  while($original != $str);

  unset($original);

  // Remove evil attributes such as style, onclick and xmlns
  $str = $this->_remove_evil_attributes($str, $is_image, $is_wysiwyg);

  /*
   * Sanitize naughty HTML elements
   *
   * If a tag containing any of the words in the list
   * below is found, the tag gets converted to entities.
   *
   * So this: <blink>
   * Becomes: <blink>
   */
  $naughty = 'alert|applet|audio|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|isindex|layer|link|meta|object|plaintext|style|script|textarea|title|video|xml|xss';
  $str = preg_replace_callback('#<(/*\s*)('.$naughty.')([^><]*)([><]*)#is', array($this, '_sanitize_naughty_html'), $str);

  /*
   * Sanitize naughty scripting elements
   *
   * Similar to above, only instead of looking for
   * tags it looks for PHP and JavaScript commands
   * that are disallowed.  Rather than removing the
   * code, it simply converts the parenthesis to entities
   * rendering the code un-executable.
   *
   * For example: eval('some code')
   * Becomes:  eval('some code')
   */
  $str = preg_replace('#(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si', "\\1\\2(\\3)", $str);


  // Final clean up
  // This adds a bit of extra precaution in case
  // something got through the above filters
  $str = $this->_do_never_allowed($str);

  /*
   * Images are Handled in a Special Way
   * - Essentially, we want to know that after all of the character
   * conversion is done whether any unwanted, likely XSS, code was found.
   * If not, we return TRUE, as the image is clean.
   * However, if the string post-conversion does not matched the
   * string post-removal of XSS, then it fails, as there was unwanted XSS
   * code found and removed/changed during processing.
   */

  if ($is_image === TRUE)
  {
   return ($str == $converted_string) ? TRUE: FALSE;
  }

  log_message('debug', "XSS Filtering completed");
  return $str;
 }

 // --------------------------------------------------------------------

 /*
  * Remove Evil HTML Attributes (like evenhandlers and style)
  *
  * It removes the evil attribute and either:
  *  - Everything up until a space
  *  For example, everything between the pipes:
  *  <a |style=[removed]('hello');alert('world');| class=link>
  *  - Everything inside the quotes
  *  For example, everything between the pipes:
  *  <a |style="[removed]('hello'); alert('world');"| class="link">
  *
  * @param string $str The string to check
  * @param boolean $is_image TRUE if this is an image
  * @return string The string with the evil attributes removed
  */
 protected function _remove_evil_attributes($str, $is_image, $is_wysiwyg)
 {
  // All javascript event handlers (e.g. onload, onclick, onmouseover), style, and xmlns
  $evil_attributes = array('on\w*', 'style', 'xmlns', 'formaction');

  if($is_wysiwyg === TRUE)
  {
   unset($evil_attributes[1]); //wysiwyg 인 경우 style 처리 제외
  }

  if ($is_image === TRUE)
  {
   /*
    * Adobe Photoshop puts XML metadata into JFIF images, 
    * including namespacing, so we have to allow this for images.
    */
   unset($evil_attributes[array_search('xmlns', $evil_attributes)]);
  }

  do {
   $count = 0;
   $attribs = array();

   // find occurrences of illegal attribute strings without quotes
   preg_match_all('/('.implode('|', $evil_attributes).')\s*=\s*([^\s>]*)/is', $str, $matches, PREG_SET_ORDER);

   foreach ($matches as $attr)
   {

    $attribs[] = preg_quote($attr[0], '/');
   }

   // find occurrences of illegal attribute strings with quotes (042 and 047 are octal quotes)
   preg_match_all("/(".implode('|', $evil_attributes).")\s*=\s*(\042|\047)([^\\2]*?)(\\2)/is",  $str, $matches, PREG_SET_ORDER);

   foreach ($matches as $attr)
   {
    $attribs[] = preg_quote($attr[0], '/');
   }

   // replace illegal attribute strings that are inside an html tag
   if (count($attribs) > 0)
   {
    $str = preg_replace("/<(\/?[^><]+?)([^A-Za-z<>\-])(.*?)(".implode('|', $attribs).")(.*?)([\s><])([><]*)/i", '<$1 $3$5$6$7', $str, -1, $count);
   }

  } while ($count);

  return $str;
 }
}

//EOF

 다음글 폼검증에서 select 박스 값 복원 (2)
 이전글 사진정보 읽고 쓰기

댓글

배강민 / 2013/06/07 13:56:32 / 추천 0
단순히 파라메터 추가하고, style만 처리대상 배열에서 빼버립니다.
criuce / 2013/06/07 23:29:23 / 추천 0
그런데 style 안에 javascript를 넣어도 동작을 하게 되는데, style을 예외처리하면 xss_clean 자체가 의미가 없어질 것 같아요.
배강민 / 2013/06/08 13:46:14 / 추천 0
그렇긴하죠. 근데 그럴라면 일반적인 위지윅을 쓰지 말던가 style을 포기하던가 style이 안박히는 위지윅을 찾던가 만들던가 해야하는 상황이란게 문제죠...쩝..

좀 뒤져봐도 대부분 xss_clean 을 포기해버리던데, 그래도 style만 빼고는 써보고자 해봤던...