<현재 환경 세팅>
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 데이터를 포함한 에러문이 출력되는 듯.
끗.
'웹 개발(초보)' 카테고리의 다른 글
웹 개발: 게시글 읽기 페이지 (1) | 2025.01.03 |
---|---|
웹 개발: 게시판 - 글 목록 (1) | 2025.01.01 |
웹 개발: 마이페이지 (2) | 2024.11.25 |
웹 개발: 게시판 - 글 작성 (2) | 2024.11.13 |
내가 만든 사이트에 세션, 로그아웃, DB 연동 파일 분리 적용하기 (1) | 2024.11.12 |