프로그래밍 언어: PHP, JavaScript
개발환경: Visual Studio Code, Vim
서버 및 관리: Apache, AWS EC2, AMAZON LINUX 2023, AMAZON LINUX 2, ANSIBLE
기타: Git Hub, Notion, Slack
현대오토에버 SW 모빌리티 스쿨의 프로젝트 주제로 앤서블을 활용한클라우드 보안 자동화 진단 프로그램을 개발하게 되었다.KISA의 클라우드 취약점 점검 가이드 2024를 참고하여 진단항목을 선택하고 만드는 방식이었다.
우리 팀은 과거 많이 사용하였고 아직까지도 많은 기업에서 쓰고 있는 LAMP 스택에 대해 진단을 하기로 결정했다.개발기간: 2024.10.15~2024.10.25
LAMP 스택을 활용한 웹 페이지 제작 및 인프라 구축
- 자동점검: 앤서블을 이용한
LAMP 스택의 취약점 자동 점검- 진단결과: csv 형식으로 진단 결과 출력 및 웹에서 가시화
- 웹 페이지:
로그인,회원가입,게시판,**진단결과,히스토리**의 기능 존재- 가시화: 표 및 그래프로 진단결과를 보기 쉽게 가시화
- 보고서: 진단결과인 csv을 기반으로 자동으로
WORD 보고서작성- 히스토리: 진단결과를 데이터베이스에 저장하여 나의 진단결과 히스토리 확인 가능
AWS EC2를 사용해 구성한 네트워크 토폴로지
Web서버는NAT를 이용하여 80, 443 포트에서 접근이 가능하도록 설정했다.
DB서버는Private로 하여 Web 서버에서만 접근이 가능하도록 보안그룹과 라우팅 테이블을 설정했다.
members(회원 정보)CREATE TABLE members ( num INT NOT NULL AUTO_INCREMENT, id VARCHAR(20) NOT NULL, pass VARCHAR(255) NOT NULL, name VARCHAR(20) NOT NULL, email VARCHAR(80), regist_day DATE, PRIMARY KEY (num) );
memberboard(게시판)
CREATE TABLE memberboard ( num INT NOT NULL AUTO_INCREMENT, member_num INT NOT NULL, name VARCHAR(20) NOT NULL, subject VARCHAR(200) NOT NULL, content TEXT NOT NULL, regist_day DATE, file_name VARCHAR(40), file_type VARCHAR(40), file_copied VARCHAR(40), PRIMARY KEY (num), FOREIGN KEY (member_num) REFERENCES members(num) ON DELETE CASCADE );
security_results(진단결과)
CREATE TABLE security_results ( user_id INT AUTO_INCREMENT PRIMARY KEY, diagnostic_item VARCHAR(255) NOT NULL, `system` VARCHAR(255) NOT NULL, vulnerability_status VARCHAR(20) NOT NULL, solution TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, user_id INT NOT NULL, FOREIGN KEY (user_id) REFERENCES members(num) ON DELETE CASCADE );
Ansible의 경우 나는 관여를 하지 않아 자세한 설명 없이 프로젝트의 중요한 실행 부분 및 결과 사진만 첨부했다.
Prepared statement
이 프로젝트의
로그인,회원가입,게시판은 예전에 php로 개발했던 코드에서 html, css의 약간의 변형을 주고, 시큐어코딩을 했다.
Injection공격 자체를 막기 위해Prepared statement를 사용했다.$sql = "UPDATE memberboard SET subject = ?, content = ?, regist_day = ? WHERE num = ?"; $stmt = mysqli_prepare($con, $sql); mysqli_stmt_bind_param($stmt, "sssi", $subject, $content, $regist_day, $num); $execute_result = mysqli_stmt_execute($stmt);위 방식으로 sql문과 데이터를 분리하고 입력받는 데이터는 문자열로 처리하여 `Injection`을 방어했다.
password_hash()
$hashed_pass = password_hash($pass, PASSWORD_DEFAULT);php에서 제공하는 해쉬암호 함수,
password_hash()함수는 기본적으로bcrypt해시 알고리즘을 사용했다.
bcrypt는 해시를 생성할 때salt를 자동으로 포함하는 특징이 있다. 따라서password_hash()함수는 별도로salt를 추가할 필요 없이, 내부적으로 고유한salt를 생성하여 적용한다. 이salt는 고유한 값으로 각 해시마다 다르게 설정되므로, 동일한 비밀번호라도 다른 해시 값이 생성된다.
ID 및 PASSWORD 설정
function check_id() { var id = document.member.id.value; var idPattern = /^(?=.*[a-zA-Z])[a-zA-Z0-9_!@#\$%\^&\*]{5,15}$/;ID는 영문자를 포함하고, 숫자, 밑줄(_), 특수기호(!@#$%^&*)를 포함한 5~15자리만 가능
if (!preg_match("/^(?=.*[a-zA-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$/", $pass)) { echo "<script>alert('비밀번호는 최소 8자리 이상이며, 영어(대문자 또는 소문자), 숫자, 특수기호를 포함해야 합니다.'); history.back();</script>"; exit(); }$stmt->bind_param("sssss", $diagnosis, $system, $vulnerabilityStatus, $solution, $userId); $stmt->execute();비밀번호는 최소 8자리 이상이며, 영문자, 숫자, 특수기호를 포함해야 한다. 비밀번호도
Prepared statement로 값을 안전하게 전송한다.
파일업로드 취약점
// 확장자 및 MIME 타입 리스트 $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf']; $allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf']; // 파일 확장자와 MIME 타입 검사 $file_ext = strtolower(pathinfo($upfile_name, PATHINFO_EXTENSION)); if (!in_array($file_ext, $allowed_extensions) || !in_array($upfile_type, $allowed_mime_types)) { echo "<script>alert('허용되지 않은 파일 형식입니다.'); history.go(-1);</script>"; exit; } // 업로드 파일 이름 난수화 $copied_file_name = uniqid('file_', true) . "." . $file_ext; $uploaded_file = $upload_dir . $copied_file_name;허용되지 않은 파일 형식을 필터링하고, 업로드된 파일의 경로와 이름을 무작위화했다.
문제점: Apache서버에서
MYSQL이 접속이 되지 않았다.해결방법: ping을 보냈는데 잘 되었고
MYSQL서버 자체도 정상적으로 돌아가고 있었다. 하지만TELNET을 이용해 원격 접속을 시도하니 접속이 불가능했다.sudo systemctl status mysql이 명령어를 통해
MYSQL을 확인했는데 포트가 3306이 아닌 다른 곳이 열려있었다. 초기화를 한 후 다시 설정했다.기본적으로 MySQL 포트를 차단했다.
sudo iptables -A INPUT -p tcp --dport 3306 -j DROP그 후, 특정 IP(예: 10.10.10.10)에서만 MySQL 접근을 허용했다.
sudo iptables -A INPUT -p tcp -s 10.10.10.10 --dport 3306 -j ACCEPT
문제점:
/etc/my.cnf에서MYSQL로의 접근이 특정 ip만 가능하도록bind-address를 설정했다. 하지만 모든 IP인 0.0.0.0에서는 접속이 잘 되지만 특정 IP를 해 놓으면 접근이 되지 않았다.해결방법: 내 DB의 데이터를 확인해보니 데이터 자체가 저장이 되지 않았다. 그래서 DB계정이나 권한 등의 문제라고 생각 후 방법을 찾아보니 알게되었다. DB의 계정을 만드는 과정에서 특정 IP의 권한 설정을 부여해서 해결했다.
CREATE USER 'username'@'10.10.10.10' IDENTIFIED BY 'password'; GRANT ALL PRIVILEGES ON database_name.* TO 'username'@'10.10.10.10'; FLUSH PRIVILEGES;이런 식으로 DB 계정에 나의 서버 IP에 대한 권한을 부여하지 않아서 생긴 문제였다.
문제점: 회원가입 후 로그인을 하는 하는 코드를 기존의 2022년에 했던 코드에서 수정 및 Hash를 적용했다. 하지만 로그인 과정에서 자꾸 비밀번호가 틀렸다는 오류 메시지가 떴다.
원인: 데이터베이스에 저장된 값을 확인하니 바로 알 수 있었다. 원인은 크게 2가지였다.
데이터베이스에서 비밀번호를
varchar(20)으로 저장을 했는데, Hash값이 데이터베이스에 저장이 되면서 20보다 더 큰 값이 들어가고 그로 인해 Hash값에 손실이 발생한다.로그인 시 입력 받은 값을 Hash값으로 바꾸어 비밀번호로 비교를 해야하는데 그것을 구현하지 않았다.
해결방법:
varchar(20)을varchar(255)로 변경하고 DB에 저장된 해쉬값과 입력 받은 값을 Hash화해서 비교했다.
문제점: aws ec2의 아마존리눅스2와 2023에서 호환성 문제인지 mysql이 설치가 안 됐다.
해결방법:
1. 아마존 리눅스 2
아마존 리눅스2에서는도커를 사용하여MYSQL을 설치했다.2. 아마존 리눅스 2023
sudo dnf install mysql-community-server위 명령어를 실행하면 설치가 되지 않고
The GPG keys listed for the "MySQL 8.0 Community Server" repository are already installed but they are not correct for this package.이런 에러 메시지가 떴다. 알고 보니 KEY를 받아오고 그것을 업데이트를 해줘야 하는데 하지 않아서 난 오류같다.
sudo rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 sudo yum update위 명령어로 키를 받아오고 yum update를 해줘야한다. 나는 yum update를 하지 않아서 에러가 났다.
문제점: 서버와 데이터베이스가 분명 설치를 잘 했고 동작이 개별로는 잘 되지만 서로 호환이 되지 않는 문제점이 있었다.
해결방법: 서로의 호환성을 해결하기 위한 명령어를 실행했다
sudo apt install apache2 libapache2-mod-php php-mysql
문제점: 아마존리눅스 2023에서도 가장 처음에는 도커를 사용해서 MYSQL을 설치했다. 하지만 도중에 계속 MYSQL의 접속이 끊겼다.
원인: 이유는 정확히 모르겠지만 도커가 계속 중단되었다. 아마 프리티어를 사용하며 메모리를 적게 부여한 인스턴스에 도커 실행 및 여러 명이 동시에 앤서블 코드를 돌리며 서버 메모리에 과부화가 오며 도커가 멈춘 것으로 예상된다.
해결방법: 인스턴스를 새로 만들어서 더 많은 메모리를 주었고, 위에 나온 것처럼
도커를 사용하지 않고MYSQL을 설치했다.
문제점: 게시판 파일업로드가 윈도우 로컬에서는 잘 동작했지만 리눅스 서버에서는 동작을 하지 않았다.
해결방법: 알고보니 권한 설정을 하지 않아서 생긴 문제였다.
var/www/html/디렉토리 내에서 파일업로드 부분만 권한을 부여하여 해결했다.
문제점: 게시판 리스트에서 줄 간격이 맞지 않았다. 첨부파일이 있으면 조그만 사진으로 표시하고 없으면 아무것도 없었는데 그로 인해 줄 간격에 차이 발생했다.
**해결방법**: css에서 열 너비를 고정하여 해결했다. .board_list .col3 { width: 15%; /* 글쓴이 열의 고정 너비 */ white-space: nowrap; /* 한 줄로 표시 */ overflow: hidden; /* 넘치는 내용 숨기기 */ text-overflow: ellipsis; /* 넘칠 경우 ... 표시 */ letter-spacing: 1px; margin-right: 20px; } /* 첨부 파일 열 */ .board_list .col4 { width: 10%; min-height: 15px; display: flex; justify-content: center; align-items: center; background-color: #f9f9f9; }
문제점: 위에 파란 헤더 부분과 해당 페이지에서 적용하는 css가 서로 겹치는 부분이 생기며 css가 이상하게 적용이 됨
해결방법: css에서 겹치는 부분을 제거 및 css를 클래스로 설정 및
!important로 우선순위를 설정했다
문제점:
CSV 파일을 읽어와서 디비에 저장하고,json으로 데이터를 전송하여 데이터를 그래프를 그리는 함수에 넣어 그래프를 그린다. 그래프가 출력이 안 되는 문제점이 있었다.원인: DB를 확인해보니 csv를 읽고 데이터가 잘 저장이 되었다. 즉,
read_csv.php파일에서 csv를 읽는 부분은 문제가 없었고 그것을 json으로 전송하는 부분에서 문제가 생긴 것으로 추정했다.해결방법: 코드에 에러 문구를 출력해주는 부분을 추가해 디버깅을 해봤더니 ajax 요청이 실패를 하고 있었다.
CORS설정을 하여 서버가 프론트엔드 애플리케이션의 출처에서의 요청을 허용하도록 구성되어 있는지 확인했다.header("Access-Control-Allow-Origin: *");위의 코드를 추가하여 해결했다.
문제점:
CSV읽어온 것을 WORD문서로 쓰려고 하는데 작성이 되지 않았다.<br /> <b>Fatal error</b>: Uncaught Error: Class "ZipArchive" not found in C:\xampp\htdocs\project\test\vendor\phpoffice\phpword\src\PhpWord\Shared\ZipArchive.php:139 Stack trace: #0 C:\xampp\htdocs\project\test\vendor\phpoffice\phpword\src\PhpWord\Writer\AbstractWriter.php(284): PhpOffice\PhpWord\Shared\ZipArchive->open('C:\\Users\\user\\A...', 8) #1 C:\xampp\htdocs\project\test\vendor\phpoffice\phpword\src\PhpWord\Writer\Word2007.php(98): PhpOffice\PhpWord\Writer\AbstractWriter->getZipArchive('C:\\Users\\user\\A...') #2 C:\xampp\htdocs\project\test\word.php(181): PhpOffice\PhpWord\Writer\Word2007->save('C:\\Users\\user\\A...') #3 {main} thrown in <b>C:\xampp\htdocs\project\test\vendor\phpoffice\phpword\src\PhpWord\Shared\ZipArchive.php</b> on line <b>139</b><br />
해결방법:
ZipArchive 클래스가 제대로 설치가 되지 않아 생긴 문제였다. 그리고 WORD파일을 생성할시에는 리눅스 권한도 적절히 부여해야 한다.
문제점: AWS에서
보안 그룹을 설정하지 않아 접속이 안 됐다.해결방법: 웹서버 측 인스턴스는 80, 443포트를
0.0.0.0으로 허용했고, DB측 서버는 3306포트만 웹서버의 프라이빗 IP만 접근이 가능하도록 허용했다.