💡 Key Takeaways
- Understanding the Attack Surface: What You're Really Protecting
- Input Validation: Your First Line of Defense
- Authentication and Authorization: Knowing Who and What
- SQL Injection: The Vulnerability That Won't Die
마커스 첸, 120억 달러 이상의 일일 거래를 처리하는 포춘 500 핀테크 회사의 선임 보안 엔지니어로 12년의 경력을 가진 전문가
💡 주요 요점
- 공격 표면 이해하기: 실제로 보호해야 할 것
- 입력 검증: 당신의 첫 번째 방어선
- 인증 및 권한 부여: 누가 무엇을 알고 있는가
- SQL 인젝션: 결코 사라지지 않는 취약성
3년 전, 나는 한 주니어 개발자가 금요일 오후 4시 47분에 프로덕션에 코드를 배포하는 모습을 보았다. 오후 6시 15분까지 우리의 보안 운영 센터는 크리스마스 트리처럼 빛나고 있었다. 겉보기에는 무해한 검색 기능의 SQL 인젝션 취약성이 340,000명의 고객 기록을 노출시켜버렸다. 이 위반은 우리에게 420만 달러의 복구, 규제 벌금 및 사업 손실을 초래했다. 그 개발자는? 웹 보안에 대해 몰랐던 뛰어난 엔지니어였다.
그 사건은 내가 보안 교육에 접근하는 방식을 변화시켰다. 대부분의 개발자는 무모하지 않다—단지 지식의 공백에서 운영되고 있을 뿐이다. 컴퓨터 과학 프로그램은 운이 좋으면 보안에 대해 2주 정도만 투자한다. 부트캠프는 종종 이 부분을 완전히 건너뛴다. 그럼에도 우리는 단지 벽돌 쌓는 방법만 알고 성채를 구축해야 한다는 기대를 받는다.
나는 지난 10년간 웹 보안의 최전선에서, 침투 테스트부터 200명 이상의 개발 팀이 사용하는 보안 프레임워크 구축까지의 경험을 쌓아왔다. 나는 공격이 조잡한 스크립트 공격에서 정교한 국가 차원의 작전으로 진화하는 것을 목격했다. 그리고 기본 원칙들—모든 개발자가 내면화해야 하는 기초는—생각보다 그리 많이 변하지 않았다는 것을 배웠다. 이 핵심 원칙들을 마스터하면, 나는 매일 생성 코드에서 보는 90%의 취약성을 방지할 수 있다.
공격 표면 이해하기: 실제로 보호해야 할 것
개발자에게 무엇을 보호하고 있는지 물으면, 보통 "사용자 데이터" 또는 "데이터베이스"라는 대답을 들는다. 맞긴 하지만 불완전하다. 당신의 공격 표면은 애플리케이션이 입력을 받고, 데이터를 처리하고, 외부 시스템과 상호작용하는 모든 단일 지점이다. 로그인 폼은 물론이요, 내부에서만 사용할 목적으로 작성한 API 엔드포인트, 관리 패널의 파일 업로드 기능, 심지어 사용자에게 표시하는 오류 메시지까지 포함한다.
내 경험에서 나오는 구체적인 예를 드리겠다. 우리는 대량 사용자 업데이트를 위한 JSON 페이로드를 수신하는 내부 API 엔드포인트를 가지고 있었다. 이것은 "내부 전용"이어서 인증이 필요 없었다. 왜냐하면 VPN을 통해서만 접근할 수 있었기 때문이다. 그런데 누군가 역방향 프록시를 잘못 구성했고, 갑자기 그 엔드포인트가 약 18시간 동안 인터넷에 노출되었다. 그 18시간 동안 자동 스캐너는 이미 그것을 발견하고 2,847가지의 다양한 공격 벡터를 시도했다.
공격 표면은 package.json 또는 requirements.txt의 모든 의존성을 포함한다. 2021년 12월 Log4Shell 취약점이 발생했을 때, 나는 72시간 연속으로 팀들이 영향을 받는 시스템을 식별하고 패치하도록 도와주었다. 취약점은 우리가 작성한 코드에 있는 것이 아니라, 의존성의 의존성의 의존성인 로그 라이브러리에서 발생했다. 당신의 공격 표면은 전체 의존성 트리를 통해 확장되며, 일반적인 Node.js 애플리케이션의 경우 800개 이상의 패키지를 포함할 수 있다.
애플리케이션의 신뢰 경계를 생각해보라. 신뢰할 수 없는 데이터가 시스템에 들어오는 지점은 어디인가? 모든 양식 필드, 모든 URL 매개변수, 모든 HTTP 헤더, 모든 쿠키, 모든 API 요청 본문. 서버의 메모리 외부에서 오는 것이라면 신뢰할 수 없는 것이다. 나는 개발자들이 양식 입력을 신중하게 검증하면서 URL 매개변수를 완전히 무시하거나 POST 데이터를 정리하는 반면, GET 매개변수를 방치하는 것을 보았다. 공격자는 "검증되어야 할 것"이라는 당신의 정신 모델에 신경 쓰지 않으며, 모든 것을 탐색한다.
당신의 공격 표면에는 시간 기반 취약성도 포함된다. 생성한 비밀번호 재설정 토큰? 그것이 예측 가능하거나 만료되지 않는다면 공격 벡터가 된다. 세션 식별자, API 키, 임시 파일 이름—공격자가 충분한 시간을 가지고 추측할 수 있는 모든 것. 나는 한 번 비밀번호 재설정 토큰이 단순한 순차적 정수로 이루어진 시스템을 발견했다. 공격자는 자신의 계정에 대해 재설정을 요청하고 토큰 45231을 보고, 다른 사용자의 비밀번호를 재설정하기 위해 45230, 45229, 45228을 시도할 수 있다.
입력 검증: 당신의 첫 번째 방어선
모든 개발자의 이마에 하나의 원칙을 문신할 수 있다면, 그것은 다음과 같다: 사용자 입력을 절대 신뢰하지 마라. 모바일 앱의 입력도, "신뢰할 수 있는" 파트너의 API의 입력도, 심지어 자신의 프론트엔드 JavaScript의 입력조차도 아니다. 신뢰 경계를 넘어서는 모든 것은 검증되어야 하며, 정리되고, 그렇지 않은 것으로 입증될 때까지 잠재적인 악성으로 취급되어야 한다.
가장 위험한 취약점은 해커가 찾는 것이 아니라—개발자가 자신의 코드에서 존재하는지 모르기까지 발견하지 못하는 것이다.
나는 개발자들이 같은 실수를 반복하는 것을 본다: 프론트엔드에서 입력을 검증하고 그것이 충분하다고 가정한다. 사실은 이렇다—브라우저 개발자 도구나 간단한 curl 명령을 사용하여 약 15초 만에 당신의 프론트엔드 검증을 우회할 수 있다. 프론트엔드 검증은 사용자 경험을 위한 것이지 보안을 위한 것이 아니다. 실제 검증은 서버에서 매번, 예외 없이 발생해야 한다.
효과적인 입력 검증에는 세 가지 요소가 있다: 유형 검사, 형식 검증 및 비즈니스 로직 검증. 유형 검사는 숫자를 기대하는 필드가 실제로 숫자를 수신하도록 보장하며, SQL 인젝션 시도를 포함하는 문자열이 아니라 실제 숫자가 수신된다. 형식 검증은 allowlist(금지 목록이 아닌)를 사용하여 데이터가 예상 패턴에 맞도록 보장한다. 이메일 주소를 기대하는 경우, 적절한 이메일 정규 표현식에 대해 검증한다. 미국 전화번호를 기대하는 경우, 형식을 명시적으로 검증한다.
비즈니스 로직 검증에서 대부분의 개발자들이 사고를 멈춘다. 기술적으로 유효하다고 해서 애플리케이션의 맥락에서 의미가 있다는 것을 의미하지는 않는다. 한 번은 내 카트를 검토했을 때 음수 수량을 허용하는 쇼핑 카트를 발견했다. 개발자는 입력이 정수임을 검증했지만, 그것이 양수인지 확인하지는 않았다. 공격자는 -100개의 아이템을 "구매"하고 요금을 청구받는 대신 크레딧을 받을 수 있었다. 수정은 사소했지만, 그 간과는 발견되기 전까지 회사에 23,000달러의 비용을 초래했다.
내 실용적인 접근 방식은 다음과 같다: 애플리케이션이 수용하는 모든 입력에 대한 엄격한 스키마를 정의하라. Node.js의 Joi, Python의 Pydantic 또는 Laravel이나 Django와 같은 프레임워크의 내장 검증 라이브러리를 사용하라. 이 라이브러리를 사용하면 유효한 입력이 어떤 모습인지 정확히 선언할 수 있으며, 기본적으로 다른 모든 것은 거부된다. 검증이 실패하면 이를 기록하라. 같은 IP 주소나 사용자 계정에서 반복적인 검증 실패가 발생하면 공격이 진행 중일 수 있다.
하나의 중요한 점: 모든 레이어에서 검증하라. 데이터가 API에서 백그라운드 작업을 통해 데이터베이스로 흐르면 각 단계에서 검증하라. API가 입력을 올바르게 검증했지만 백그라운드 작업이 데이터가 데이터베이스에서 온 것이라고 가정하는 경우 공격받을 수 있는 간극을 이용한 공격을 목격했다. 직접 데이터베이스에 쓸 수 있는 공격자는 모든 검증을 우회할 수 있었다.
인증 및 권한 부여: 누가 무엇을 알고 있는가
인증은 "당신이 누구인가?"에 대한 답변을 제공하고 권한 부여는 "당신이 무엇을 할 수 있는가?"에 대한 답을 제공한다. 이 두 개념을 혼동하거나 잘못 구현하면 내가 마주치는 가장 취약한 취약점 중 일부가 발생한다. 나는 강력한 인증 체계를 갖춘 시스템이 있지만, 인증된 사용자가 다른 사용자의 데이터에 접근할 수 있도록 허용하는 경우를 보았다. 권한 부여는 뒷전으로 미뤄졌다.
| 취약점 유형 | 공격 벡터 | 예방 방법 | 심각도 |
|---|---|---|---|
| SQL 인젝션 | 데이터베이스 쿼리에서 씻어지지 않은 사용자 입력 | 매개변수화된 쿼리, ORM 프레임워크, 입력 검증 | 심각 |
| 크로스 사이트 스크립팅 (XSS) | 웹 페이지에 주입된 악성 스크립트 | 출력 인코딩, 콘텐츠 보안 정책, 정리 라이브러리 | 높음 |
| CSRF (크로스 사이트 요청 위조) | 신뢰할 수 있는 사용자의 승인되지 않은 명령 | CSRF 토큰, SameSite 쿠키, 출처 검증 | 중간 |
| 인증 우회 | 약한 비밀번호, 세션 탈취, 잘못된 논리 | 다중 요소 인증, 안전한 세션 관리, 속도 제한 |