Summernote

섬머노트는 "이지윅 에디터"로 우리들이 사용하는 게시판등에서 글꼴, bold 혹은 이미지 삽입 등을 사용할 때 사용하는 기능들을 추가해주는 라이브러리이다.

https://summernote.org/

 

티스토리의 이지윅

이와같은 기능을 간편하게 사용할 수 있는 라이브러리 중 하나로 Summernote이다.

 

오늘은 섬머노트를 사용하여 이미지 삽입, 및 css 적용을 진행한다.


writer.php(게시물 등록)

필요한 라이브러리를 import 받는다 이는 css, jquery 등이 포함된다.

....
// 관련 라이브러리를 cdn으로 import
<!-- include libraries(jQuery, bootstrap) -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>

<!-- include summernote css/js -->
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script>
<div style="width: 100%; height:100%">
....

 

섬머노트가 적용될 태그 하위에 섬머노트 태그를 작성

<div style="width: 80%; height:80%; margin: 50px"> 
    <form method="POST" id="write_form">
      <div style="height: 20%;"> 
        <div class="form-floating mb-3">
          <input type="text" name="title" class="form-control" id="floatingInput" placeholder="name@example.com"
          value="<?php
              if ($isSubmit) {
                echo $_POST['title'];
              }
            ?>"/>
          <label for="floatingInput">제목</label>
        </div>
      </div>
  
  
        <div >
          <div class="form-floating">
          // 섬머노트를 textarea 태그로 생성하였으며 이는 데이터를 전송할 form을 사용하기 위함
          // div도 가능하며 id를 "summernote"로 주면 된다.
          <textarea id="summernote" name='content'></textarea >
        </div>
        
          <input hidden/ value="submit" name="is_submit">
        

        <div style="margin-top: 20px;">

          <button onclick="formSubmit()" style="margin-left: 20px" type="button" class="btn btn-primary" >작성</button>
          <span>
            <?php
              if($isSubmit)
              echo "제목, 내용을 확인해주세요";
            ?>
          </span>
        </div>

    </form>

  </div>

 

 

섬머노트의 틀은 적용이 된 모습을 확인 할 수 있다.

그러나 이미지 삽입의 경우 동작하지만 저장되지는 않을 것 이다. 이는 이미지 삽입의 동작 원리를 알아야한다.

 

1. 이미지 삽입

2. 이미지가 서버에 올라가 특정 경로에 저장된다.

3. 이미지가 저장된 경로와 저장된 파일명을 화면으로 반환한다.

4. 경로와 파일명으로 하면에 img태그를 상성한다.

5. 저장버튼을 눌러 저장한다.

6. 저장 내용은 이미지 태그를 포함한 내용이 저장된다.

위 과정을 위해 이미지 업로드 기능을 만들어야한다.

 

writer.php (섬머노트의 업로드 동작을 위한 스크립트)

<script>
    $(document).ready(function() {
    // 높이, 언어, 폰트 등 다양한 설정을 할 수 있는 섬머노트의 옵션설정
        var $summernote = $('#summernote').summernote({
          lang: 'ko-KR',
          height: 400,
          // 콜백 함수 특정 기능들을 할 때 수행할 함수를 지정한다.
          callbacks: {
          // 이미지 업로드를 하면 아래의 함수가 동작한다.
            onImageUpload: function(files) {
              if(files.length > 4) {
                alert('파일은 3개 까지만 등록 할 수 있습니다.')
                return
              }
              for(var i = 0; i < files.length; i++){
              // 각각의 파일들을 sendFile이라는 함수의 인자로 전달한다.
                sendFile($summernote, files[i])
              }
            }
          }
        });
    });

    function sendFile(el, file) {
      console.log('111')
      console.log(el)
    var formData = new FormData();
    formData.append("file", file);
    // file 데이터를 ajax를 통해 uplload_file.php로 Post방식으로 전달한다.
    $.ajax({
        url: '/board/upload_file.php',
        data: formData,
        cache: false,
        contentType: false,
        processData: false,
        type: 'POST',
        success: function (data) {
            if(data==-1){
                alert('용량이 너무크거나 이미지 파일이 아닙니다.');
                return;
            }else{
                console.log(window.location.href+data)
                // 반환 받은 이미지 경로를 통해 summernote의 내부에 img 태그를 생성한다.
                el.summernote('insertImage', data, function ($image) {
                    $image.attr('src', data);
                    $image.attr('class', 'childImg');
                });
                var imgUrl=$("#imgUrl").val();
                if(imgUrl){
                    imgUrl=imgUrl+",";
                }
                $("#imgUrl").val(imgUrl+data);
            }
        }
    });
  }

  function formSubmit() {
    var form = document.getElementById('write_form')
    form.submit()
  }
  </script>

 

upload_file.php (파일 업로드 작업을하는 서버코드)

<?php
// ini_set('display_errors', 1);
// POST 방식일 때만 동작
  if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    if($_FILES['file']['size']>10240000){//10메가
      echo "-1";
      exit;
    }

    $ext = substr(strrchr($_FILES['file']['name'],"."),1);
    $ext = strtolower($ext);

    if ($ext != "jpg" and $ext != "png" and $ext != "jpeg" and $ext != "gif")
    {
      echo "-1";
      exit;
    }

	// 이름의 중복이 있을 수 있기때문에 rand함수를 사용하여 임의의 문자열로 만든다.
    $name = "mp_".$now3.substr(rand(),0,4);
    $filename = $name.'.'.$ext;
    $date = date("Y-m-d",time());
    $dir ='../files/'.$date;
    
    //files의 경로에 오늘 날짜로 된 디렉토리가 없다면 디렉토리를 생성한다.
    // 이는 관리를 수월하게 하기위함일 뿐 없어도 문제는 없다.
    if (!file_exists($dir)) {
      mkdir($dir, 0777, true);
    }
    
    $destination = $dir.'/'.$filename;
    $location =  $_FILES["file"]["tmp_name"];
    // 전달받은 file을 files/오늘날짜/파일명. 확장자 형식으로 저장한다.
    move_uploaded_file($location,$destination);
    
    //이미지 파일의 경로를 반환한다.
    echo '/files/'.$date.'/'.$filename;
  }
?>

 

이미지를 업로드하면 "/files/오늘날짜/파일명.확장자" 형식으로 파일이 저장 되는 것을 확인한다.

 

png 파일 생성 확인

 

summernote 태그 내부에도 이미지가 삽입된 것을 확인

summernote에서 html 태그를 확인 할 수도 있다 "</>" 버튼을 눌러보자

이미지 삽입이 된 것을 확인
이미지 태그에 반환 받은 경로가 들어있는 것을 확인

 

다음에는 XSS방지를위한 html_entites를 적용하여 해당 내용을 저장하는 기능을 추가해보자.

 

Preparedstatement

프리페어드 스테이트먼트(prepared statement), 파라미터라이즈드 스테이트먼트(parameterized statement)는 데이터베이스 관리 시스템(DBMS)에서 동일하거나 비슷한 데이터베이스 문을 높은 효율성으로 반복적으로 실행하기 위해 사용되는 기능이다. 일반적으로 쿼리나 업데이트와 같은 SQL 문과 함께 사용되는 프리페어드 스테이트먼트는 템플릿의 형태를 취하며, 그 템플릿 안으로 특정한 상수값이 매 실행 때마다 대체된다. -위키백과-


Preparedstatement는 보안적 측면에서 SQL Injection을 예방할 수 있는 방법이다. SQL문을 미리 변환하기 때문에 문자 ', " <, > (, ) 와같은 SQL에 사용되는 특수문자들이 SQL 구문이 아닌 문자열로 인식 할 수 있게 해준다.

 

로그인과 게시판 조회에 Preparedstatement를 적용해보자.

login_function.php

<?php
    require_once 'db_connection.php';
   
   ...

    //로그인로직
    function login($id, $pw) {
        global $db_conn;
        $pw = hash("sha256", $pw);
		
        // 바인딩 SQL 구문에 ?를 넣는다.
        $stmtSQL =$db_conn ->prepare("SELECT COUNT(USER_ID) as count 
        FROM user WHERE USER_ID = ? and PW = ?");
        
        // 바인딩할 값을 지정 한다, integer일 경우 i, 문자열의 경우 s 
        // 아래의 예시는 아이디와 비밀번호 모두 문자열이기때문에 첫 인자를 ss로 넣어준다.
        // pw가 integer라면 "si"가 된다.
        $stmtSQL->bind_param("ss", $id, $pw);
        $stmtSQL->execute();
        $result = $stmtSQL->get_result(); 

        $row = mysqli_fetch_array($result);
        var_dump($row);
        
        return $row['count'];
    }
...
?>

 

 

위의 쿼리는 select로 결과값을 가져올 수 있지만 insert, update등은 결과 값이 없기에 성공여부를 가져오기위해 아래와 같은 방법으로 확인한다.

 function register($id, $name, $pw) {
        global $db_conn;
        
        $pw = hash("sha256", $pw);
        // echo "pw : {$pw}";

        $sql = "INSERT INTO user (USER_ID, USER_NM, PW) VALUE(
            '{$id}'
            , '{$name}'
            , '{$pw}'
        )";
        $stmtSQL =$db_conn -> prepare("INSERT INTO user (USER_ID, USER_NM, PW) 
        VALUE( ?, ?, ?)
        ");
        $stmtSQL->bind_param("sss", $id, $name ,$pw);
        
        // excute의 결과는 성공여부에 대한 boolean 값이다.
        $result = $stmtSQL->execute();
        var_dump($result);
       
        return $result;
    }

 

board_function.php(변경 전)

function getBoardList($currentPage, $itemPerPage, $searchType, $searchValue)  {
  $db_conn = getDbConn();
  $token = $_COOKIE['REFRESH_TOKEN'];

  $sql = "SELECT IDX AS idx
  , TITLE as title
  , CONTENT as content
  , FIRST_REG_USER as regUser
  , FIRST_REG_TIME as regTime
   FROM tbl_board ";
  echo "searchValue is {$searchValue}";
  if(!$searchValue=='' && $searchType == 'title') {
    $sql = $sql. "WHERE TITLE like'%{$searchValue}%' ";
  }

  $order = "ORDER BY IDX DESC limit {$currentPage},{$itemPerPage}";

  $sql = $sql.$order;

  echo $sql;
  return mysqli_query($db_conn, $sql);
}

 

board_function.php(변경 후)

function getBoardList($currentPage, $itemPerPage, $searchType, $searchValue)  {
  $db_conn = getDbConn();
  $token = $_COOKIE['REFRESH_TOKEN'];


  $sql = "SELECT IDX AS idx
  , TITLE as title
  , CONTENT as content
  , FIRST_REG_USER as regUser
  , FIRST_REG_TIME as regTime
   FROM tbl_board ";
  
  // 검색입력값이 있다면 적용
  if(!$searchValue == '' && $searchType == 'title') {
    $sql = $sql. "WHERE TITLE like ? ";
  }
  $order = "ORDER BY IDX DESC limit ?,?";
  $sql = $sql.$order;
  $stmtSQL = $db_conn->prepare($sql);
  
  // limit에 들어가는 값은 integer이기 때문에 i
  if(!$searchValue == '' && $searchType == 'title') {
    $stmtSQL->bind_param("sii", $searchValue, $currentPage, $itemPerPage);
  } else {
    $stmtSQL->bind_param("ii",$currentPage, $itemPerPage);
  }
  $stmtSQL->execute();
  $result = $stmtSQL->get_result(); 

  echo $sql;
  return  $result;
}

DOS공격

서비스 거부 공격(-拒否 攻擊, 영어denial-of-service attack, DoS attack) 또는 디오에스 공격/도스 공격(DoS attack)은 시스템을 악의적으로 공격해 해당 시스템의 리소스를 부족하게 하여 원래 의도된 용도로 사용하지 못하게 하는 공격이다.[1] 대량의 데이터 패킷을 통신망으로 보내고 특정 서버에 수많은 접속 시도를 하는 등 다른 이용자가 정상적으로 서비스 이용을 하지 못하게 하거나, 서버의 TCP 연결을 바닥내는 등의 공격이 이 범위에 포함된다. 수단, 동기, 표적은 다양할 수 있지만, 보통 인터넷 사이트 서비스 기능의 일시적 또는 영구적 방해 및 중단을 초래한다. 통상적으로 DoS는 유명한 사이트, 즉 은행, 신용카드 지불 게이트웨이, 또는 심지어 루트 네임 서버(root name server)를 상대로 이루어진다. 2002년 10월 22일과 2007년 2월 6일의 DNS 루트 서버에 대한 DNS 백본 DDoS 공격은 인터넷 URL 주소 체계를 무력화시킨 인터넷 전체에 대한 공격이었다. - 위키백과-


즉 사용자가 해당 서비스를 이용할 수 없게 서비스를 공격하는 방법을 통칭하는 뜻으로 여러 방식의 공격이 있다.

 

크게 두 가지 방식이 있다.

1. 서비스 서버의 리소스를 점유( 어플리 케이션의 취약점)

2. 서비스 서버로 향하는 네트워크 트래픽을 점유

 


1. 서비스 서버의 리소스를 점유( 어플리 케이션의 취약점)

여기서 서버의 리소스는 CPU, Memory, Disk 등이 포함 될 수 있으며 아래와 같이 높은 리소스를 사용하는 작업에 대한 요청으로 서비스 거부(DoS)가 된 사례이다.

 

외에도 업로드의 용량제한이 없다면 큰 용량의 데이터를 대량으로 업로드를 하여 Disk를 점유하는 방식또한 존재 한다.

 


2. 서비스 서버로 향하는 네트워크 트래픽을 점유

서비스를 요청하게 되면 결국 인터넷 망을 통해 서버로 전달 되게 되는데 해당 망에 과도한 요청을 보내어 다른 사용자가 요청할 수 없도록 하는 방법이다.

 

이와같은 방법은 많은 요청을 보내는 것이 필수적인데 이를 위한 공격 방법이 DDoS공격이다.

Distribute Denial of Service Attack의 약자로 분산 서비스 거부 공격이라고 한다.

 

2-1.DDoS

DDoS 공격의 구조는 C&C서버(Cmmand and Control Server)가 여러 좀비 PC에 명령하여 일괄적으로 공격대상에게 요청을 보내는 방식이다.

 

각종 악성코드를 인터넷에 배포하여 좀비PC를 만들어내며 해당 좀비PC를 사용하여 공격자에게 요청하게 된다.

 

2-2.DRDoS(Distribute Reflected Dinal of Service Attack)

이는 IP 스푸핑을 사용한 공격 방법으로 요청을 하면 여러 응답을 주는 반사서버에 요청지를 공격 대상으로 IP를 변조한 패킷을 전달하여 응답을 공격대상서버에 요청하는 공격방법이다.

이를 위해서는 공격자는 반사서버에 요청지 IP를 공격 대상으로 설정(이를 IP 스푸핑이라 한다.)하여 요청을 보내면 반사 서버들은 해당 요청에 대한 응답을 공격대상에게 전달한다.

 

2-3 SYN Flood 공격

이는 TCP의 3 way-handshaking의 구조를 활용한 공격으로 TCP연결을 위한 과정중 공격대상이 ACK요청을 공격 서버로 부터 대기하게 만들어 트래픽을 점유하게 하는 방식이다.

 

3 way-handshaking는 아래와 같은 구조로 가장 마지막의 client가 ACK를 전달하여야 하지만 이를 전달하지 않으므로서 공격대상은 ACK응답을 기다리고 있는 상태를 만든다.

 

이를 무수히 많은 Client(혹은 Zomibe PC)가 요청하게 된다면 서버는 많은 요청들을 대기상태이므로 신규로 받을 트래픽을 할당할 수 없게된다.

 

2-4 Slowloris 공격

클라이언트에서 HTTP 요청 시 데이터를 EOF를 전달하지 않으므로서 서버는 이후 데이터를 받을 준비를 하고 있도록 한다. 이를 대량의 Zombie PC를 사용하여 다량의 사용자가 데이터를 보내는 중으로 인식하게 한다.

 

이를 해결하기 위해 TimeOut시간을 짧게 지정하면 해결 될듯 보인다.


CSRF의 대응방법

- GET Method 대신 POST Mentod 사용

URL을 이용한 CSRF는 GET방식을 이용하기 때문에 주요한 서비스의 경우 GET 방식이 아닌 POST 방식으로 서비스를 구현

단, 해당 방식은 XSS가 가능할경우 script를 통해 POST로 보낼 수 있다.

EX)

<form method="POST" id="myform" action="mypage"
 <input hidden name="password"/>
 <input type="submit">
</form>

<script>
 document.getElementById("myform").submit();
</script>

 

- CSRF 토큰사용

서비스의 요청을 받을 때 CSRF토큰을 사용하여 단순 form의 요청을 차단한다.

단, 해당 방식은 xss로 iframe에서 데이터를 가져온다면 요청할 수 있다.

EX)

<iframe id="myIframe" src="mypage.php">

<form id="myform" action="changePw.php>
 <input name="password">
 <input id="myToken" name="token">
</form>

<script>
 var target = document.getElementById('myIframe');
 var token = target.contentDocuemnt.forms[0].token
 
 document.getElementById('myToken') = token
 document.getElementById('myForm').submit()
</script>

 

- Referrer Check

요청의 Referrer를 확인 하면 해당 요청을 보낸 페이지를 알 수 있다. 이를 통해 특정 페이지에서 오는 요청만 받도록 개발한다.

단, 이 방식은 referrer를 변조 할 수 있다.

 

- 사용자만 알 수 있는 정보 요청

비밀번호 변경 등을 할 때 사용자의 현재 비밀번호를 받는 방법이 이와 같은 방법이다. token의 경우 사용자가 기입하는 것이 아닌 서버에서 전달 받기 때문에 iframe에 해당 정보가 포함되어 있지만. 사용자의 현재 비밀번호는 사용자가 직접 기입해야하기 때문에 알 수 없다.

'웹 해킹 코스 > 내용 정리' 카테고리의 다른 글

15주 File Upload, Download 취약점(LFI, RFI)  (0) 2024.02.15
14주차 (파일 업로드 취약점 & 웹 셸)  (0) 2024.02.13
12주차 CSRF  (0) 2024.02.01
12주차 CSRF  (1) 2024.01.21
11차 HTML의 DOM 접근  (0) 2024.01.15

CSRF

사이트 간 요청 위조(또는 크로스 사이트 요청 위조영어: Cross-site request forgery, CSRFXSRF)는 웹사이트 취약점 공격의 하나로, 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 하는 공격을 말한다.

유명 경매 사이트인 옥션에서 발생한 개인정보 유출 사건에서 사용된 공격 방식 중 하나다.

사이트 간 스크립팅(XSS)을 이용한 공격이 사용자가 특정 웹사이트를 신용하는 점을 노린 것이라면, 사이트간 요청 위조는 특정 웹사이트가 사용자의 웹 브라우저를 신용하는 상태를 노린 것이다. 일단 사용자가 웹사이트에 로그인한 상태에서 사이트간 요청 위조 공격 코드가 삽입된 페이지를 열면, 공격 대상이 되는 웹사이트는 위조된 공격 명령이 믿을 수 있는 사용자로부터 발송된 것으로 판단하게 되어 공격에 노출된다. - 위키 백과 -


즉 사용자의 동의 없이 특정 요청을 서버에 전송하게 하는 방법을 의미한다.

이는 사용자의 pc에서 동작하므로 사용자의 session 탈취 없이(요청자가 이미 사용자이기 때문에 session을 가지고 있다.)

 

Ex) 게시판 등록에 img 태그 삽입(src는 비밀번호를 변경하는 URL)

 

사용자가 게시물을 확인하면 img태그는 이미지를 가져오기 위해 해당 src를 조회하게 되며 비밀번호가 변경된다.

 


위의 예제와 같이 CSRF는 XSS와 함께 사용시 유용하게 사용될 수 있다.

+ Recent posts