
이 글은 인프런 강의 「백엔드 개발자를 위한 서버 보안 실무」 섹션 2를 입문자용으로 풀어 쓴 글이에요.
쇼핑몰 주문 API를 만들었다고 해 볼게요. 요청 본문에서 상품 번호와 수량을 꺼내 금액을 계산하고 저장합니다. 화면에서는 수량을 1 이상만 고를 수 있게 막아 두었어요.
그런데 누군가 화면을 거치지 않고 API를 직접 부르면서, 수량에 -3을 넣습니다. 금액이 음수가 되고, 그 주문이 그대로 저장돼요. 문자열이나 배열이 들어오면 어떻게 될지는 아무도 모릅니다.
이 코드는 "입력은 늘 착하다"고 가정하고 있었던 거예요. 하지만 서버에 도착한 입력은 누구든 마음대로 바꿔 보낼 수 있습니다. 화면의 제약은 사용자 편의일 뿐, 보안이 아니에요.
입력을 거르는 방법은 크게 두 가지예요.

차단 목록은 "작은따옴표나 스크립트 태그 같은 위험한 패턴"을 찾아서 막습니다. 직관적이지만, 인코딩을 바꾸는 새 우회 방법이 나오면 뚫려요. 공항 보안 검색에 비유하면 "금지 물품 목록에 없는 건 다 통과"인 셈입니다.
허용 목록은 반대예요. 받을 값의 타입, 길이, 형식, 범위를 먼저 정하고, 그 모양이 아니면 전부 거절합니다. "탑승권에 적힌 사람, 정해진 크기의 가방만 통과"죠. 기본은 언제나 허용 목록이고, 그 규칙은 코드 곳곳에 흩어 두지 말고 스키마 한 곳에 선언해 둡니다.
그리고 검사는 한 겹이 아니라 세 겹으로 생각하면 좋아요.

게시판 검색 API가 사용자가 보낸 검색어를 쿼리 문자열 사이에 그대로 이어 붙여 SQL을 만든다고 해 볼게요. 평소엔 잘 동작합니다. 그런데 검색어 자리에 [공격 입력 예시]처럼 SQL 조각을 섞어 보내면, DB는 그 일부를 데이터가 아니라 명령의 일부로 읽어요. 조건이 항상 참이 되면 전체 게시글이, 경우에 따라선 다른 테이블까지 나갑니다.

방어의 핵심은 쿼리의 모양을 먼저 고정하고, 입력은 값 자리에만 넣는 거예요. 이걸 파라미터 바인딩이라고 합니다. 빈칸이 뚫린 서식 종이를 먼저 인쇄해 두고, 사용자는 빈칸만 채울 수 있게 하는 것과 같아요. 빈칸에 무엇을 적든 서식 자체는 바뀌지 않습니다.
이 원리는 SQL에만 해당하지 않아요.
ORM을 쓴다고 안전한 것도 아니에요. ORM 위에서도 쿼리를 문자열로 이어 붙이면 똑같이 뚫립니다. 기억할 한 줄은 이것입니다. 쿼리와 명령은 문자열로 조립하지 않는다.
프로필 사진 업로드도 입력이에요. 그것도 아주 큰 입력이죠.

공격자는 이미지 헤더로 시작하지만 뒤에 서버에서 실행되는 코드가 붙은 파일을 만들어, 이미지라고 적어서 올립니다. 서버가 확장자와 Content-Type 헤더만 믿고, 원래 파일 이름 그대로 웹 루트에 저장한다면? 그 파일 주소를 여는 순간 서버에서 코드가 실행돼요. 이른바 웹셸입니다.
원리 수준의 방어는 네 가지예요. 내용으로 실제 형식을 판별하고, 저장 이름은 서버가 새로 정하고, 웹 루트 밖에 저장하고, 내려줄 때는 실행이 아니라 다운로드로 응답합니다.
강의에서는 이걸 직접 공격하고 고쳐 봅니다
섹션 2에서는 일부러 취약하게 만든 예제 서비스에 SQL·NoSQL 인젝션, 명령 인젝션, 경로 조작, 위장 파일 업로드를 직접 재현합니다.
- Spring Boot와 Express 양쪽에서 같은 취약점을 고치는 코드
- 고친 공격을 테스트 코드로 남겨, 리팩터링으로 되살아나지 않게 CI에서 막기
- 역직렬화처럼 눈에 덜 띄는 공격과 OWASP 목록 읽는 법
강의 보러 가기: https://inf.run/XvwyH