웹 개발(초보)

웹 개발: 게시글 수정 및 삭제

whydontyoushovel 2025. 1. 23. 04:23

 <현재 환경 세팅>

1. Virtual Box 7.1.4 + 우분투 리눅스 20.04

2. 우분투 APM (Apache2 + PHP + MySQL)

3. 기본 웹 루트 경로 /var/www/html

4. VScode SSH

5. 사진 등은 termius

6. 디자인은 부트스트랩 ...

 

 

 

<사이트 컨셉>

그림판으로 그린 그림 자랑하는 사이트

 

 

<게시글 수정 및 삭제 드롭다운 버튼>

 

조건             

  • 게시글 작성자가 해당 페이지 읽을 때만 보임

 

관련 코드          

㉮ DB 데이터 추출

<?php
    $db_conn = mysqli_connection();
    $select_sql = "SELECT * FROM upload_table WHERE idx = $idx";
    $result = mysqli_query($db_conn,$select_sql);
    $row = mysqli_fetch_array($result);

    $id_select= "SELECT * FROM registration_list WHERE id = '{$_SESSION['id']}'";
    $id_result = mysqli_query($db_conn,$id_select);
    $id_row = mysqli_fetch_array($id_result);
?>

 

 

㉯ 조건에 따라 드롭다운 버튼 출력

  • 드롭다운 버튼 클릭 시 해당 게시물의 idx 파라미터를 함수에 전달
  • 이때 드롭다운 버튼은 현재 사용자의 id와 게시물 업로드 시 저장한 작성자의 id가 같을 때만 출력
<?php if($row['id'] == $id_row['id']):?>
	<div class="btn-group dropend">
		<button type="button" class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown" data-bs-display="static" aria-expanded="false">
			수정 및 삭제
		</button>
		<ul class="dropdown-menu dropdown-menu-lg-end">
			<?php
			echo '<li><button class="dropdown-item" type="button" onclick ="updatePost('.$row['idx'].')">수정하기</button></li>
			<li><button class="dropdown-item" type="button" onclick="deletePost('.$row['idx'].')'.'">삭제하기</button></li>';
			?>
		</ul>
	</div>  
<?php endif; ?>

 

 

㉰ 버튼 선택 후 자바스크립트로 파라미터 전달 및 요청 결과 안내

  • "수정하기" 버튼 클릭 -> updatePost 함수에 게시물 idx 값을 인자로 보내 실행
  • "삭제하기" 버튼 클릭 -> deletePost 함수에 게시물 idx 값을인자로 보내 실행
<script>
    function updatePost(idx) {
        if(confirm("수정하시겠습니까?")) {
            window.location.href = `updatePost.php?id=${idx}`;
        }
    }

    function deletePost(idx) {
        if(confirm("정말 삭제하시겠습니까?")) {
            fetch(`delete_proc.php?id=${idx}`, { method: 'GET'})
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    alert("삭제되었습니다.");
                    location.href = "index.php";
                } else {
                    alert("삭제 실패: " + data.message);
                }
            });
        }
    }
</script>

 

 

 

 

 

<게시글 수정>

시연               

수정 요청

수정 페이지에서 본문 내용 수정

 

수정이 완료되었다는 alert 확인

리다이렉션 된 페이지에서 수정된 내용 확인

 

 

관련 코드                   

㉮ 수정하기 버튼 클릭 후 updatePost 함수 실행 (게시글 읽기 페이지)

  • confirm 창에서 "확인" 버튼을 누르면 게시물의 idx를 인자로 하여 updatePost.php 페이지로 이동
<script>
    function updatePost(idx) {
        if(confirm("수정하시겠습니까?")) {
            window.location.href = `updatePost.php?id=${idx}`;
        }
    }
</script>

 

 

㉯ DB에서 게시물 정보 불러오기 (게시글 수정 페이지)

  • updatePost.php 페이지에 표시할 데이터 추출
  • 게시물의 idx값을 인자로 하여 해당 데이터 행 추출
  • prepared statement 적용
<?php
    $idx = $_GET['id'];

    $sql = "SELECT * FROM upload_table WHERE idx = ?";
    $stmt = $db_conn->prepare($sql);
    $stmt -> bind_param("i",$idx);

    if($stmt -> execute()) {
        $result = $stmt -> get_result();
        $row = $result -> fetch_assoc();
    } else {
        echo "셀렉트 안됨.";
    }
?>

 

 

㉰ 불러온 데이터 value에 표시. (게시글 수정 페이지)

  • 추출한 데이터를 제목, 내용 태그에 value 값으로 출력 (수정하지 않아도 입력값을 유지하기 위함)
  • required 속성을 사용해 입력값이 없을 경우를 방지
  • 그림은 미리보기 형식으로 출력,  수정 불가
<form class="container mt-5" method="POST" action="" enctype="multipart/form-data">
    <div class="row mb-3">
        <label for="inputEmail3" class="col-sm-2 col-form-label">제목</label>
        <div class="col-sm-10">
            <input type="text" class="form-control" id="inputTitle" name="title" maxlength="20" value="<?=$row['title']?>"required>
            <p>최대 20자까지 입력 가능합니다.</p>
        </div>
    </div>
    <div class="row mb-3">
        <label for="inputPassword3" class="col-sm-2 col-form-label">그림 파일</label>
        <div class="col-sm-10">
            <img src="<?=$row['image_url']?>" alt="현재 이미지" style="width: 200px;">
        </div>
    </div>
    <div class="row mb-3">
        <label for="inputPassword3" class="col-sm-2 col-form-label" style="white-space:pre-wrap">내용</label>
        <div class="col-sm-10">
        <textarea class="form-control" name="content" required><?=$row['description']?></textarea>
        </div>
    </div>
    <div class="d-flex">
    <button type="submit" class="btn btn-warning">올리기</button>
    </div>
</form>

 

 

 

㉱ 폼 submit 시 수정한 데이터 DB에 반영 (게시글 수정 페이지)

  • prepared statement 적용
  • 받은 인자를 변수에 저장하여 준비된 쿼리문에 바인딩, 쿼리 실행
  • 변경되었으면 alert 출력 후 변경된 게시글 읽기 페이지로 리다이렉션
  • 변경되지 않았으면 실패 alert 출력 
<?php
if($_SERVER['REQUEST_METHOD'] === 'POST') {

    $title = isset($_POST['title'])?$_POST['title']:"";   //게시물 제목
    $description = isset($_POST['content'])?$_POST['content']:"";   //게시물 글

    $update_sql = "UPDATE upload_table SET title = ?, description = ? WHERE idx = ?";
    $stmt = $db_conn -> prepare($update_sql);

    $stmt -> bind_param("ssi",$title,$description,$idx);
    $stmt -> execute();

    if ($stmt -> affected_rows > 0) {
        echo "<script>
        alert('게시물이 정상적으로 수정되었습니다.');
        location.href='post.php?id=$idx';
        </script>"; //수정한 게시물로 이동
    } else {
        echo"<script>alert('게시글 수정에 실패했습니다.');</script>";
    }

    $db_conn -> close();

}
?>

 

 

 

 

<게시글 삭제>

시연        

삭제할 게시물 선택

읽기 페이지에서 삭제 요청

 

삭제 확인 알림

 

게시물 리스트 페이지에서 사라짐

DB에도 삭제됨

 

 

관련 코드         

㉮ 삭제하기 버튼 클릭 후 deletePost 함수 실행 (게시글 읽기 페이지)

  • 백엔드 응답을 받기 위해 fetch로 파라미터 전달
<script>
    function deletePost(idx) {
        if(confirm("정말 삭제하시겠습니까?")) {
            fetch(`delete_proc.php?id=${idx}`, { method: 'GET'})
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    alert("삭제되었습니다.");
                    location.href = "index.php";
                } else {
                    alert("삭제 실패: " + data.message);
                }
            });
        }
    }
</script>
  • 서버의 응답 본문을 json 형식으로 변환
  • 서버 응답의 success가 true일 경우 alert 출력 후 index 페이지로 리다이렉션
  • success가 false일 경우 삭제 실패와 응답의 message에 할당된 내용을 alert로 출력

 

 

㉯ 전달받은 파라미터에 해당하는 DB 데이터 추출 후 데이터 행 삭제 (게시글 읽기 페이지)

  • prepared statement 적용
<?php
    include 'db_connection.php'; //db 연결
    require 'session_init.php'; //세션 설정

    ini_set('display_errors', 1);
    ini_set('display_startup_errors', 1);
    error_reporting(E_ALL);  //php 에러메시지 출력

    $db_conn = mysqli_connection(); //db 권한 저장

    if (isset($_GET['id'])) { //게시물 idx파라미터가 들어오면

        $idx = intval($_GET['id']); //입력값 검증 후 저장

        $sql = "DELETE FROM upload_table WHERE idx = ?";
        $stmt = $db_conn->prepare($sql);
        $stmt -> bind_param("i",$idx); //쿼리문 준비

        if($stmt->execute()) { //쿼리 실행이 성공하면
            echo json_encode(["success" => true]); //응답 본문에 json 형식으로 "success":true 포함
        } else {
            echo json_encode(["success" => false, "message" => $db_conn->error]);
        }   //실행 성공 못하면 응답 본문에 success는 false, message는 에러문 할당한 뒤 전송

        $stmt -> close();

    } else {
        echo json_encode(["success" => false, "message" => "게시글 id가 누락되었습니다."]);
        //파라미터 못 받으면 이 데이터를 응답으로 보냄
    }

    $db_conn -> close();
?>

 

 

 

 

 

[+] prepared statement와 stored xss              

 

게시글 본문에 <script>alert(1)</script> 같은 거 입력해서 저장한 뒤 게시글 읽는 페이지에서 xss 일어나게 하려면 prepared statement가 설정되어 있어야 할 거 같다. prepared가 아니면 스크립트가 쿼리문의 일부로 인식될 것 같기 때문이다.

 

▷ 게시글 업로드 페이지

  • prepared 적용 코드
<?php
	if(move_uploaded_file($_FILES['image']['tmp_name'],$targetPath)) {
            
            $uploadFile = $targetPath;

            $insert_sql = "INSERT INTO upload_table(id,title,image_url,description,nick) VALUES(? ,? , ? , ? , ?)";
            $stmt = $db_conn->prepare($insert_sql);
            $stmt->bind_param("sssss",$id,$title,$uploadFile,$description,$nick);

            if ($stmt->execute()) {
                $idx = $stmt ->insert_id; 

                echo "<script>
                alert('게시물이 정상적으로 업로드되었습니다.');
                location.href='post.php?id=$idx';
                </script>"; //여기다 올린 게시물로 이동하는 거 구현
    
                exit;    
            } else {
                echo "<script>alert('게시물이 저장되지 않았습니다." . mysqli_error($db_conn)."');</script>";
            }
            
         } else {
            echo "<script>
            alert('파일이 서버에 저장되지 않았습니다');
            </script>";
        }
?>

 

prepared 적용했을 때 stored xss가 되는 건 예상함

 

 

  • prepare 적용 안된 코드
<?php
	if(move_uploaded_file($_FILES['image']['tmp_name'],$targetPath)) {
              
            $uploadFile = $targetPath;
        
            $insert_sql = "INSERT INTO upload_table(id,title,image_url,description,nick) VALUES('$id','$title','$uploadFile','$description','$nick')";
        
            $insert_result = mysqli_query($db_conn,$insert_sql);

            if ($insert_result) {
                $idx = mysqli_insert_id($db_conn); 
                
                echo "<script>
                alert('지금은 prepared statement가 적용되지 않았습니다. 게시물이 정상적으로 업로드되었습니다.');
                location.href='post.php?id=$idx';
                </script>"; 
                
                exit;
            } else {
                echo "저장 안됨.";
            }
            
        } else {
            echo "<script>
            alert('파일이 서버에 저장되지 않았습니다');
            </script>";
        }
?>

세미콜론은 상관 없나봄...ㄷㄷ

prepared statement 적용 안해도 작은 따옴표 없으면 잘 실행됨.

 

 

 

작은 따옴표가 포함되면 저장이 안됨

 

> prepared statement를 적용했을 때 stored xss에 작은 따옴표가 포함되어있을 경우 db 저장이 어려움!

 

 

 

[+] error-based sql injection 만들기           

error-based sql injection이 성립하려면 어떤 코드가 필요할까?

<?php
    echo "query failed: " . mysqli_error($db_conn);
?>

 

이런 mysqli_error 사용하면 아래와 같이 서버 DB 데이터를 포함한 에러문이 출력되는 듯.

 

 

끗.