이력서에 내용 하나가 길어 지루해 보이길래 세부 내용은 다 삭제할까 고민이었다. 하지만 이력서 챌린지 대장님께서 "이 에피소드는 길게 풀어보는 게 어때요? 어떤 게 문제였고, 어떻게 해결했는지를 오히려 다 나열하면 좋겠어요. 혼자 책임감을 가지고 해결한 것이잖아요?"라고 말씀해 주셨다. 그때 당시 굉장히 힘들었던 기억이지만 그 누구보다 책임감을 가지고 임했던 프로젝트기에 이 에피소드에 대해 대장님 말대로 길게 작성해 보려고 한다. 하지만 전 회사와 관련된 내용이기에 자세히는 밝힐 수 없어 큰 그림으로 작성하려고 한다.
간단한 사이트라고 하셨는데...
처음 이 프로젝트를 시작할 때만 해도 고객이 올려놓은 첨부파일을 매핑해서 조건에 맞는 알맞은 자료를 보여주는 아주 간단한 사이트였고, 어려운 일은 없을 것이라며 혼자 진행하라고 하셨다. 이제 1년이 조금 넘은 주니어 개발자였기에 처음 몇 주만 팀장님이 함께해 주셨고 이후에는 혼자 회의에 참석하며 매주 1시간씩 고객과 회의를 하며 요구사항을 정리하였다. 개발이 완료된 기능들은 바로바로 고객의 확인을 받으며 수월하게 진행되었다. 전체적인 개발이 완료된 후 UAT를 진행했고 그에 대한 피드백을 받아 하나씩 개선해 나가며 꼼꼼히 개발을 할 수 있었다. 이렇게 개발서버에서의 모든 작업이 끝난 후 고객사에서 최종적으로 보안 검사를 진행하겠다는 연락을 받았다. 개발 서버에서 완료까지에도 테스트가 많았기에 문제 될 게 없을 거라 생각했고 별생각 없이 '와 프로젝트 끝났다! 이제 운영 서버로 소스 옮기고 잘 동작만 하면 끝이다~~'라고 생각했었다.
그리고 시작된 취약점의 늪😈
개발한 사이트는 의료분야 관련 사이트이다. 의료 분야이다 보니 환자의 안전과 관련되어 엄격한 규제를 받아 정보가 변조되면 법적 문제가 발생할 수 있어 보안 취약점은 아주 치명적이었다. 그렇기 때문에 고객사에서 보안 전문 업체에 검사를 의뢰한 것인데 개발서버에서 취약점이 총 25개가 나왔다.
파트, 팀 그리고 다른 개발팀에게 보안검사로 취약점이 나왔는데 해결했던 경험이 있냐고 여기저기 여쭤보았지만 그때 당시 마땅한 해결책이 없었다. 내가 개발한 사이트의 특성이 의료분야라는 점, 그리고 사이트 이용객이 로그인을 안 한 사람도 모두 이용 가능하다는 점이 특수한 케이스였는지 나에게서만 존재한 문제였고, 도와주다가도 '모르겠다.. 혹시 해결되면 나도 알려줘'라는 얘기만 남게 되었다.
오픈까지 남은 시간은 얼마 남지 않았고 25개의 취약점을 해결해야 했기에 막막했지만 이 문제들을 반드시 해결하고 말겠다는 책임감을 가지고 도전하기 시작했다. Contrast Security, OWASP(The Open Worldwide Application Security Project)와 같은 보안 전문 사이트에서 각 취약점의 원인과 해결 방법을 찾아 적용했고 시행착오를 거치며 하나씩 해결해 나갔다.
아래 문제들은 겪었던 모든 취약점 내용들이다.
1. 경로
취약점 | 경로 | 원인 | 해결 방법 |
Routes Exercised | 모든 경로 | - 실제 고객에게 공개된 페이지는 아니지만 빠르게 개발하기 위해 미리 넣어 둔 소스가 문제된 것 - 모든 페이지 경로를 접속할 수 있어야 함 |
필요한 소스 외 모두 삭제 |
2. 라이브러리
라이브러리 명 | 취약점 | 원인 | 해결 방법 |
Microsoft.Owin.Security.Cookies | CVE-2022-29117 | - 회사 기존 셋팅 환경으로 진행했었기에 라이브러리 업데이트가 되어 있지 않은 상태가 문제 | 라이브러리 업그레이드 (종속성 때문에 업그레이드가 불가능하여 기존 존재하는 모든 라이브러리 업그레이드) |
Microsoft.Owin |
1번 경로와 2번 라이브러리와 같은 경우 회사 기존 셋팅 환경을 따와서 진행한 것이라 그 환경들이 문제가 된 경우였다.
1번의 경우 원인에서 언급한 것과 동일하게 실제 고객에게 공개된 페이지는 아니지만 혹시 모를 상황에 대비하여 빠르게 개발하기 위해 미리 넣어둔 소스들이 문제가 되었고 모든 페이지가 접속 확인이 되어야 한다는 것을 알게 된 후 필요한 소스 외 모두 삭제하였다.
2번 라이브러리도 기존 셋팅 환경을 사용하였기에 모든 라이브러리들이 오래되었다는 것이 문제였다. 취약점으로 발견된 2가지만 업그레이드 진행하고 싶었지만 종속성 때문에 단순한 업그레이드조차 오류가 많이 발생하게 되었고, 모든 라이브러리들이 얽혀 있어 안된다는 점을 인지하여 기존 존재하는 모든 라이브러리들도 다 업그레이드를 진행하여 해결했다.
3. 개발 서버에서 발견된 취약점
취약점 | 경로 | 원인 | 해결 방법 |
Path Traversal | FileDownload | 비정상적인 상위 디렉토리 접근을 통한 파일이 다운로드가 가능 - 정상 경로: /FileDownload/example.pdf - 비정상 경로: /FileDownload/../../passwd |
(1)파일명 퍼센트 인코딩 및 (2)가상 디렉토리 설정으로 허용된 디렉토리 내에서만 접근 가능하도록 제한하여 해결 (1) 파일명 퍼센트 인코딩 - `../`와 같은 특수문자를 인코딩 처리 - `../` → `%2E%2E%2F`로 변환 - 경로 조작 문자가 실제 경로 이동 명령으로 해석되지 않게 함 (2)가상 디렉토리 설정 - 설정된 폴더 외부로는 절대 접근할 수 없도록 제한 |
SQL Injection | Login | 사용자 입력값이 SQL 쿼리에 직접 문자열 결합되어 악의적인 SQL 구문 실행 가능 | ASP.NET의 `SqlParameter` 사용으로 사용자 입력을 안전하게 전달하여 해결 (아래 예시 코드 참고) |
UserList | |||
SResultList | |||
WView | |||
UserSave | |||
UserView | |||
UserEdit | |||
ItemView | |||
CodeView | |||
CodeList | |||
ItemSave | |||
ModelSave | |||
ItemList | |||
ItemEdit | |||
MResultList | |||
CResultList | |||
CSResultList | |||
ItemListExcel | |||
CMResultList | |||
ModelDelete | 테이블 명과 여러 SN값을 결합하여 악의적인 SQL 구문 실행 가능 | 프로시저에서 테이블명과 SN 배열을 매개변수로 받아 동적 쿼리를 생성하여 안전하게 처리 | |
CodeModelDelete |
SQL Injection 예시 코드는 아래와 같다.
// 위험한 코드 - 악의적인 입력
string userId = "' OR '1'='1";
string query = "SELECT * FROM Users WHERE UserId = '" + userId + "'";
// 실제 실행되는 쿼리: SELECT * FROM Users WHERE UserId = '' OR '1'='1'
// 모든 유저 정보가 노출됨
- 매개변수화 하지 않은 방식
- 사용자 입력값이 그대로 쿼리문으로 들어가면 문제가 생길 수 있는데 특히 사용자가 악의적인 입력을 하게 된다면 모든 유저의 정보가 노출되는 문제가 있다.
// 해결한 안전한 코드
string query = "SELECT * FROM Users WHERE UserId = @UserId";
SqlCommand command = new SqlCommand(query, connection);
command.Parameters.AddWithValue("@UserId", userId);
- ASP.NET의 `SqlParameter`를 사용
- 사용자 입력값을 `@변수` 형태로 따로 보관하기 때문에 SQL 실행 엔진이 이 값을 안전하게 처리할 수 있다.
이렇게 개발 서버에서 발견된 취약점이었고 25개의 취약점을 모두 해결하였다. 고객사는 바로 개발서버 보안 검사를 재진행하였고 통과가 되었다.
여기서 눈치 채신 분들도 있을 것이다.
개발 서버에서 발견된 취약점? 취약점이 취약점이지. 왜 자꾸 개발 서버에서 발견된 취약점이라고 더 덧붙이는 걸까?
그렇다... 총 25개의 취약점을 모두 해결하고 '와 이제 진짜 끝! 바로 운영서버 가보자!!'라고 생각했지만...
운영서버에서 업체는 또 한 번의 보안 검사를 진행했고 5개의 취약점이 추가로 발견되었다 😇😇😇😇😇
하지만 이번 취약점은 정말 이해할 수 없었다.
개발 서버에서는 발견되지 않았던 취약점이 운영서버에서 나온다..? 왜...?
심지어 개발 서버에서 재검사했을 때도 나오지 않았는데...?
이때는 정말 오픈까지 시간이 남지 않아서 더욱더 여기저기 개발자 친구, 다른 팀 팀장님, 이사님 다 여쭤봤지만 다들 모르겠다는 답변 밖에 들을 수 없었고 매일 구글과 함께 야근을 했다.
(이 프로젝트를 진행할 때 매일 같이 야근을 했는데 그 모습을 본 동료가 회사에 있는 시간이 훨씬 더 긴데 퇴근은 왜 하고 월세는 왜 내냐고.. 자취는 사치 아니냐고 해서 울컥하기도🥺... 너어는 진짜 나빴다)
구글링으로도 해결책을 찾지 못하는 도중 '이 경로들에 공통점이 있지 않을까?'라는 생각이 들게 되어 소스 5개를 화면에 다 펼쳐 놓았다. 모두 저장이나 업로드하는 과정에서 발생한 것으로 SQL의 `insert`문이 문제가 된 것으로 추측하게 되었다. 편의를 위해 MVC 패턴으로 통일되었던 공통 `Insert`문을 직접 SQL 소스로 기입하니 해당 문제 5개를 모두 해결할 수 있었다.
(되자마자 팀장님께 "팀장님!!!! 해결 됐어요!!!!" 하고 달려갔었던...🏃🏻♀️➡️)
4. 운영 서버에서 발견된 취약점
취약점 | 경로 | 원인 | 해결 방법 |
SQL Injection | UserSave | 편의를 위해 작성되었던 MVC 패턴의 공통 `insert`문이 문제 | 통일된 `insert`문이 아닌 각각 해당하는 `insert`문으로 직접 수정 및 ASP.NET의 `SqlParameter` 사용으로 사용자 입력을 안전하게 전달하여 해결 |
ModelUpload | |||
ItemUpload | |||
ItemSave | |||
ModelSave |
총 30개의 취약점 100% 해결✨
모든 취약점을 100% 해결한 후 운영서버에서 다시 재검사를 진행하였고 모든 테스트가 드디어 통과되었다.
이 프로젝트 오픈 날 정말 눈물 흘릴 뻔....
오픈 후 취약점의 늪에서 헤매던 시절 여기저기 문의드렸던 분들이 '그거 결국 어떻게 해결했니?'라고 물어보셔서 엑셀로 취약점, 원인, 해결 방법 등을 정리하여 전달드리곤 하였다. 이사님께서도 직접 자리로 오셔서 해결 방법을 물어보셨고 '이야.. 우리 편하려고 MVC패턴을 쓴 거고 공통 로직을 만든 건데 그게 취약점으로 걸려버릴 줄은 몰랐다'라고 웃으면서 자리로 돌아가셨는데 이 부분이 여전히 기억에 남는다...🥳
아주 기본적인 사항이었지만 지키지 않았던 필요한 소스 외 다른 소스 모두 삭제, 오래된 라이브러리 업데이트...
또 우리 소스에 얼마나 많은 SQL Injection 문제를 가지고 있었는지에 대해 인지하게 되었고 팀 전체의 보안 의식에 대해 높일 수 있는 좋은 경험이 되었다.
지금은 ChatGPT가 굉장히 잘되어 있지만 그 당시는 아니었기에 많이 헤매었고 그래서 더더욱 기억에 많이 남는다. 오픈까지 시간이 얼마 안 남았는데 해결해야 할 사람은 나뿐이고, 관련 지식을 가지고 있는 사람은 아무도 없어 너무나 막막했는데... 어려운 문제일수록 차분하고 끈기 있게 노력하면 해결할 수 있다는 것을 다시 한번 깨닫게 되었다.
구글링을 하면서 검색 결과 페이지가 끝에 다다를 때까지 찾아보고,
해결 방법을 찾아 모조리 적용해 보고 시행착오를 거치며 결국에는 100% 해결!!!
힘들었던 나의 첫 단독 프로젝트가 이젠 나만의 멋진 스토리가 되었다🍀
'코딩 > 트러블슈팅' 카테고리의 다른 글
Tanstack Query - prefetchQuery로 공휴일 데이터 패칭 최적화하기 (0) | 2024.11.19 |
---|---|
React-Quill 게시물 저장 방식 개선하기: HTML에서 Delta로 (0) | 2024.11.10 |
[Oracle] ORA-28001 : the password has expired. 오라클 패스워드 만료 (0) | 2021.06.20 |
[Oracle] IO 오류: The Network Adapter could not establish the connection업체 코드 17002 (0) | 2021.02.12 |
[Java]The local variable 변수명 may not have been initialized (0) | 2021.02.11 |