테마 선택

📚 지식 노트

AI·하드웨어·주변기기·게임·컴퓨터 시스템·이론 컴퓨터 과학·수학·개발과 운영·보안·응용 분야에 대해 알아 두면 좋은 내용을 정리한 노트다. 위에서 분야를 고르고 아래에서 노트를 고른다.

웹 보안

브라우저와 서버 사이에서 자주 생기는 취약점과, 브라우저가 주는 방어 장치다.

목차 (8)
  1. 동일 출처 정책과 CORS: 브라우저가 그어 둔 기본 경계
  2. XSS: 남의 입력이 내 페이지의 코드가 될 때
  3. CSP: 실행해도 되는 스크립트를 미리 적어 두기
  4. CSRF: 브라우저가 쿠키를 알아서 붙이는 성질을 빌린다
  5. 쿠키 속성: 세션을 지키는 작은 깃발들
  6. SQL 인젝션: 데이터가 명령의 문법을 바꿀 때
  7. SSRF: 서버에게 대신 심부름을 시키는 공격
  8. OWASP Top 10: 웹 취약점을 한눈에

동일 출처 정책과 CORS: 브라우저가 그어 둔 기본 경계

브라우저는 한 번에 여러 사이트를 연다. 은행 탭과 아무 블로그 탭이 같은 브라우저에서 돌고, 두 탭 모두 같은 쿠키 저장소를 쓴다. 블로그의 스크립트가 은행 페이지를 마음대로 읽을 수 있다면 로그인은 아무 의미가 없다. 이를 막는 기본 규칙이 동일 출처 정책(same-origin policy)이다. 한 출처에서 온 스크립트는 다른 출처의 문서나 응답을 읽지 못한다.

MDN의 동일 출처 정책 문서가 정리하듯 출처(origin)는 스킴, 호스트, 포트 세 가지로 정한다. 셋이 모두 같아야 같은 출처이고, 경로나 쿼리는 상관없다. 주소를 비교하는 일은 사람이 눈으로 하면 틀리기 쉬워서, 서버 코드에서도 문자열을 직접 자르지 말고 URL 파서가 주는 origin 값을 비교하는 편이 안전하다.

기준 주소 https://app.example.com/page의 출처는 스킴, 호스트, 포트 셋이다. 경로만 다른 주소는 같은 출처이고, 스킴이나 호스트나 포트가 하나라도 다르면 다른 출처다스킴(scheme)https://호스트(host)app.example.com포트(port):443경로: 비교 안 함/page비교할 주소기준 주소와 비교한 결과https://app.example.com/other?q=1같은 출처http://app.example.com/page다른 출처: 스킴이 다름https://api.example.com/page다른 출처: 호스트가 다름https://app.example.com:8443/page다른 출처: 포트가 다름출처를 이루는 부분다른 출처: 스크립트가 응답을 읽지 못한다(CORS로 열 수 있다)
출처는 스킴·호스트·포트 셋이다. 경로만 다르면 같은 출처이고, http와 https, app과 api 같은 하위 도메인, 443과 8443 같은 포트 중 하나만 달라도 다른 출처다.

막는 것은 주로 읽기다. 다른 출처로 이미지를 불러오거나 폼을 제출하는 일, 곧 요청을 보내는 일은 웹이 처음부터 허용해 왔다. 이 틈이 뒤에 나올 CSRF의 바탕이 된다. 동일 출처 정책은 요청을 막는 장치가 아니라 응답을 읽지 못하게 하는 장치라는 점을 기억해 두면 여러 방어가 왜 따로 필요한지 이해가 쉽다.

앱과 API를 다른 도메인에 두면 이 경계가 걸림돌이 된다. CORS(Cross-Origin Resource Sharing)는 서버가 "이 출처에는 내 응답을 읽게 해도 된다"고 브라우저에 알리는 방법이다. 브라우저는 요청에 Origin 헤더를 붙이고, 서버가 Access-Control-Allow-Origin 헤더로 그 출처를 허락해야 응답을 스크립트에 넘긴다. JSON을 보내는 PUT처럼 단순하지 않은 요청은 실제 요청 전에 OPTIONS 메서드로 먼저 묻는 사전 요청(preflight)을 보낸다. 자세한 규칙은 MDN의 CORS 안내에 있다.

app.example.com 페이지의 스크립트가 api.example.com에 요청할 때, 브라우저가 먼저 OPTIONS 사전 요청으로 허락을 묻고 서버가 허용 출처를 답하면 실제 요청을 보낸다브라우저app.example.com 페이지API 서버api.example.com사전 요청(preflight): OPTIONSOrigin: https://app.example.com · 메서드·헤더허용 응답Access-Control-Allow-Origin: https://app.example.com실제 요청PUT /items · Content-Type: application/json허용 응답에 이 출처가 없으면 브라우저는 실제 요청을 보내지 않거나, 받은 응답을 스크립트에 넘기지 않는다
CORS 사전 요청. 브라우저가 먼저 OPTIONS로 허락을 묻고, 서버가 이 출처를 허용한다고 답하면 실제 요청을 보낸다. 판단과 차단은 브라우저가 한다.
  • CORS는 경계를 여는 장치다. 보안 기능을 켜는 것이 아니라 동일 출처 정책에 예외를 만드는 것이므로, 허용 출처는 필요한 곳만 적는다.
  • 요청에 담긴 Origin 값을 검사 없이 그대로 허용 출처로 되돌려 주면서 쿠키까지 허락하면, 어느 사이트든 로그인한 사용자의 응답을 읽을 수 있게 된다. 허용 출처는 정해 둔 목록과 비교한다.
  • CORS는 브라우저가 지키는 약속이라 서버 간 통신이나 curl 같은 도구에는 걸리지 않는다. 서버의 인증과 권한 검사를 대신하지 못한다.

XSS: 남의 입력이 내 페이지의 코드가 될 때

XSS(Cross-Site Scripting, 사이트 간 스크립팅)는 사용자가 넣은 글이 화면에 글자로 찍히지 않고 HTML이나 스크립트로 해석되는 취약점이다. 끼어든 스크립트는 그 사이트의 출처로 실행되므로 동일 출처 정책이 지켜 주지 못한다. 사용자의 화면을 바꾸고, 그 사용자 이름으로 요청을 보내고, 스크립트가 읽을 수 있는 쿠키나 저장소 값을 가져갈 수 있다. 경로에 따라 셋으로 나눈다.

XSS의 세 갈래. 저장형은 서버에 저장된 입력이 다른 사람의 페이지에 출력되고, 반사형은 링크에 담긴 입력을 서버가 응답에 되돌리며, DOM 기반은 페이지 스크립트가 주소의 값을 직접 문서에 넣는다. 셋 다 마지막에 피해자 브라우저에서 코드로 실행된다저장형(stored): 한 번 심어 두면 그 글을 여는 사람마다 당한다공격자가 글에스크립트를 저장서버 DB에그대로 남음다른 사람이 열면그대로 출력피해자브라우저에서 실행반사형(reflected): 조작된 링크를 누른 사람만 당한다조작된 링크를누름서버가 입력을응답에 되돌림검사 없이페이지에 섞임피해자브라우저에서 실행DOM 기반: 서버를 거치지 않고 브라우저 안에서 끝난다조작된 주소(#조각 등)페이지 스크립트가값을 읽음innerHTML 등으로문서에 넣음피해자브라우저에서 실행남의 입력이 데이터가 아니라 코드로 해석되는 지점
XSS의 세 갈래. 입력이 서버에 저장되었다 나오든(저장형), 요청에 실려 곧장 되돌아오든(반사형), 브라우저 안의 스크립트가 직접 문서에 넣든(DOM 기반), 끝은 같다. 입력이 코드로 해석되는 지점을 막으면 셋 다 막힌다.

근본 대책은 출력 인코딩(output encoding)이다. 데이터를 HTML에 넣을 때 <를 &lt;로 바꾸는 식으로, 그 자리의 문법에서 특별한 뜻을 갖는 글자를 무해한 표기로 바꾼다. 중요한 점은 넣는 자리(context)마다 규칙이 다르다는 것이다. HTML 본문, 속성 값, URL, 자바스크립트 문자열은 각각 위험한 글자가 다르다. OWASP XSS 방지 치트 시트는 자리별 인코딩을 기본으로 두고, 꼭 HTML을 받아야 할 때만 검증된 정화(sanitization) 라이브러리를 쓰라고 권한다.

// 위험: 문자열을 HTML로 해석해 문서에 넣는다
message.innerHTML = comment;

// 안전: 문자열을 글자로만 넣는다
message.textContent = comment;

DOM 기반 XSS는 서버를 거치지 않으므로 서버 쪽 인코딩으로는 잡히지 않는다. 주소, postMessage, 저장소처럼 바깥에서 오는 값을 innerHTML, document.write, eval 같은 위험한 입구(sink)에 넘기지 않는 것이 핵심이다. OWASP DOM 기반 XSS 방지 치트 시트가 이런 입구를 정리해 두었다.

방어하는 일한계
자리별 출력 인코딩데이터를 넣는 자리의 문법에 맞게 특수 글자를 바꾼다자리를 잘못 고르면 소용없다. 손으로 하지 말고 템플릿에 맡긴다
자동 이스케이프 템플릿React·Vue·Astro 같은 틀은 기본으로 값을 글자로 넣는다HTML을 그대로 넣는 탈출구를 쓰면 그 자리는 다시 무방비다
HTML 정화꼭 서식을 받아야 할 때 허용한 태그·속성만 남긴다직접 만든 정규식은 쉽게 뚫린다. 검증된 라이브러리를 쓴다
CSP끼어든 스크립트가 실행되지 못하게 한다두 번째 방어선이다. 인코딩 실수를 덮어 줄 뿐 대신하지 않는다
HttpOnly 쿠키세션 쿠키를 스크립트가 읽지 못하게 한다쿠키 도난만 막는다. 스크립트는 여전히 사용자 이름으로 요청을 보낼 수 있다

CSP: 실행해도 되는 스크립트를 미리 적어 두기

CSP(Content Security Policy, 콘텐츠 보안 정책)는 서버가 응답 헤더로 "이 페이지는 이런 곳의 스크립트·스타일·이미지만 쓴다"고 브라우저에 알리는 장치다. 브라우저는 목록 밖의 자원을 불러오지 않고, 특히 HTML 속에 끼워 넣은 인라인 스크립트와 eval을 기본으로 막는다. XSS 취약점이 남아 있어도 끼어든 코드가 실행되지 못하게 하는 두 번째 방어선이다.

CSP 헤더가 스크립트 출처를 자기 출처와 cdn.example.com으로 정하면, 브라우저는 그 두 곳의 스크립트만 실행하고 끼워 넣은 인라인 스크립트와 목록에 없는 곳의 스크립트는 막는다Content-Security-Policy: script-src 'self' https://cdn.example.com브라우저CSP 검사/app.js같은 출처실행cdn.example.com/lib.js목록에 있는 CDN실행<script>…</script>끼워 넣은 인라인 스크립트차단 (위반 보고 가능)evil.example/x.js목록에 없는 곳차단 (위반 보고 가능)CSP는 두 번째 방어선이다. 끼어들 틈은 출력 인코딩으로 먼저 막는다
CSP는 스크립트를 가져올 곳의 목록이다. 같은 출처와 정해 둔 CDN의 스크립트는 실행되고, 페이지에 끼워 넣은 인라인 스크립트와 목록 밖의 스크립트는 막힌다. 막은 일을 보고하게 할 수도 있다.

주소 목록 방식은 목록에 넣은 CDN에 누구나 올릴 수 있는 파일이 있으면 무너지기 쉽다. 그래서 MDN의 CSP 안내와 OWASP CSP 치트 시트는 요청마다 새로 만든 임의 값(nonce)이나 스크립트 내용의 해시를 붙인 스크립트만 실행하게 하는 엄격한 CSP(strict CSP)를 권한다. 공격자는 그 응답의 nonce를 미리 알 수 없으므로 끼워 넣은 스크립트가 실행되지 않는다.

  • 처음에는 Content-Security-Policy-Report-Only로 막지 않고 위반만 보고받아, 정상 기능이 깨지지 않는지 본 뒤 적용한다.
  • frame-ancestors로 내 페이지를 다른 사이트가 iframe으로 감싸지 못하게 해, 투명한 틀을 덮어 클릭을 가로채는 클릭재킹(clickjacking)도 함께 막는다.
  • 'unsafe-inline'을 넣으면 인라인 스크립트 차단이라는 가장 큰 이점이 사라진다.

CSRF: 브라우저가 쿠키를 알아서 붙이는 성질을 빌린다

CSRF(Cross-Site Request Forgery, 사이트 간 요청 위조)는 사용자가 로그인해 둔 사이트로, 다른 사이트가 사용자 모르게 요청을 보내게 만드는 공격이다. 앞에서 본 대로 동일 출처 정책은 다른 출처로 폼을 보내는 일은 막지 않고, 브라우저는 목적지 사이트의 쿠키를 요청에 자동으로 붙여 왔다. 서버 입장에서는 사용자가 직접 누른 송금 버튼과 구별할 수 없다. 공격자는 응답을 읽지 못하지만, 송금이나 비밀번호 변경처럼 상태를 바꾸는 요청이라면 보내는 것만으로 충분하다.

CSRF 단계 그림: 은행에 로그인해 둔 브라우저가 다른 사이트의 숨은 폼 때문에 은행으로 송금 요청을 보내고 쿠키가 함께 붙는다. SameSite 쿠키라면 쿠키가 빠져 요청이 거부된다evil.example다른 사이트브라우저사용자bank.example로그인해 둔 은행로그인세션 쿠키 발급다른 탭에서 방문숨긴 송금 폼 자동 제출송금 요청 + 세션 쿠키사용자 요청으로 알고 처리송금 요청 (쿠키 빠짐)로그인 안 됨: 거부위조된 요청SameSite 쿠키일 때
1 / 5

사용자가 bank.example에 로그인하자, 은행이 세션 쿠키를 브라우저에 넣어 둔다.

CSRF(사이트 간 요청 위조). 공격자는 쿠키를 훔치지 않는다. 브라우저가 쿠키를 자동으로 붙이는 성질을 빌려, 로그인한 사용자의 이름으로 요청을 보내게 만든다.

OWASP CSRF 방지 치트 시트는 여러 겹을 함께 쓰라고 권한다.

방어원리
CSRF 토큰서버가 폼마다 예측할 수 없는 값을 넣어 두고, 제출될 때 세션의 값과 맞는지 본다. 다른 사이트는 그 값을 읽을 수 없으니 맞출 수 없다
SameSite 쿠키다른 사이트에서 시작된 요청에는 쿠키를 붙이지 않게 한다. 아래 쿠키 절에서 자세히 본다
출처 확인Origin이나 Sec-Fetch-Site 요청 헤더로 요청이 어느 사이트에서 왔는지 보고 다른 사이트면 거부한다
GET은 읽기만상태를 바꾸는 일을 GET으로 받지 않는다. 링크 하나, 이미지 태그 하나로 일이 벌어지지 않게 한다
중요한 일은 다시 확인송금·비밀번호 변경은 비밀번호나 두 번째 인증을 다시 받는다

쿠키 대신 Authorization 헤더에 토큰을 실어 보내는 API는 브라우저가 그 헤더를 자동으로 붙이지 않으므로 CSRF에 덜 노출된다. 대신 토큰을 스크립트가 읽는 곳에 두면 XSS 한 번에 토큰이 새어 나간다. 세션 방식의 장단점은 인증과 인가 노트의 세션과 토큰 절에서 다룬다.

쿠키 속성: 세션을 지키는 작은 깃발들

로그인 상태는 대개 세션 쿠키 하나에 달려 있다. 서버는 Set-Cookie 헤더에 속성을 붙여 이 쿠키가 언제, 어디로, 누구에게 보이는지 정한다. MDN의 Set-Cookie 문서와 쿠키 표준인 RFC 6265를 바탕으로 정리하면 다음과 같다.

속성뜻막는 것
SecureHTTPS 연결에만 쿠키를 보낸다암호화되지 않은 연결에서 쿠키를 엿보는 일. 네트워크 보안 노트의 HTTPS·HSTS와 짝을 이룬다
HttpOnly자바스크립트(document.cookie)가 읽지 못한다XSS가 일어났을 때 세션 쿠키를 빼 가는 일
SameSite다른 사이트에서 시작된 요청에 쿠키를 붙일지 정한다(Strict·Lax·None)CSRF의 바탕인 쿠키 자동 첨부
Domain·Path쿠키를 보낼 호스트와 경로 범위Domain을 넓게 잡으면 하위 도메인 전부가 쿠키를 받는다. 필요 없으면 생략해 지금 호스트로 좁힌다
Max-Age·Expires쿠키의 수명세션 쿠키는 짧게 둔다. 서버 쪽 세션 만료와 함께 써야 한다
__Host- 접두사이름이 이렇게 시작하면 Secure 필수, Domain 금지, Path=/ 여야만 받아들인다하위 도메인이 같은 이름의 쿠키를 덮어쓰는 일

여기서 사이트(site)는 출처보다 넓은 단위다. 대략 스킴과 등록 가능한 도메인(example.com 같은)이 같으면 같은 사이트로 보므로, app.example.com과 api.example.com은 다른 출처지만 같은 사이트다. SameSite는 이 사이트 단위로 판단한다.

SameSite 값별 쿠키 전송. Strict는 같은 사이트 요청에만, Lax는 그에 더해 다른 사이트에서 링크로 이동할 때도 보내고, None은 모든 요청에 보낸다. 다른 사이트에서 온 폼 제출에는 Strict와 Lax 모두 쿠키를 보내지 않는다요청의 종류StrictLaxNone같은 사이트 안의 요청보냄보냄보냄다른 사이트에서 링크로 이동 (GET)안 보냄보냄보냄다른 사이트에서 폼 제출 (POST)안 보냄안 보냄보냄다른 사이트 안의 이미지·fetch안 보냄안 보냄보냄쿠키를 붙인다 · None은 Secure 필수, 생략 시 기본값은 브라우저마다 다르다
SameSite 값별 쿠키 전송. Lax는 다른 사이트에서 링크를 눌러 들어올 때는 로그인이 유지되게 쿠키를 보내지만, 다른 사이트가 몰래 보내는 POST와 하위 요청에는 보내지 않는다. 세션 쿠키에는 Lax나 Strict가 알맞다.

SameSite를 적지 않은 쿠키를 어떻게 다룰지는 브라우저마다 다르고 바뀌어 왔다. 일부 브라우저는 생략을 Lax로 취급하지만, 모든 사용자의 브라우저가 그렇다고 기댈 수는 없다. 값은 늘 명시하고, SameSite는 CSRF 토큰과 출처 확인을 대신하지 않는 한 겹으로 본다.

SQL 인젝션: 데이터가 명령의 문법을 바꿀 때

SQL 인젝션(SQL injection)은 사용자 입력을 SQL 문장 문자열에 그대로 이어 붙일 때 생긴다. 입력 속 따옴표나 기호가 문자열을 닫고 문장의 구조를 바꾸면, 데이터여야 할 글자가 명령으로 읽힌다. 악의가 없어도 드러난다. 이름이 O'Brien인 사용자 한 명이 검색을 망가뜨린다면, 같은 자리에 누군가 의도적으로 문법을 넣을 수도 있다는 뜻이다. 결과는 남의 데이터 열람부터 수정, 삭제까지 이어질 수 있다.

위: 입력을 쿼리 문자열에 이어 붙이면 이름 O'Brien의 따옴표가 문자열을 일찍 닫아 나머지가 SQL 문법으로 읽힌다. 아래: 매개변수 쿼리는 물음표 자리의 틀과 값을 따로 보내 값은 데이터로만 다뤄진다이어 붙이기: 입력이 쿼리 문장의 일부가 된다"SELECT * FROM users WHERE name = '" + name + "'"SELECT * FROM users WHERE name = 'O'Brien'이름 속 따옴표가 문자열을 일찍 닫아, 뒤의 글자가 SQL 문법으로 읽힌다(여기서는 문법 오류)매개변수 쿼리: 틀과 값을 따로 보낸다SELECT * FROM users WHERE name = ?값: O'BrienDB가 틀을 먼저 해석해 두므로, 값에 무엇이 들어 있어도 문법이 되지 못한다' 문법으로 잘못 읽힌 글자? 값만 들어가는 자리
이어 붙인 쿼리에서는 이름 속 따옴표가 문자열을 일찍 닫아 문장의 구조가 바뀐다. 매개변수 쿼리는 틀을 먼저 보내고 값을 따로 실어, 값이 무엇이든 데이터로만 다뤄진다.

해결책은 따옴표를 걸러 내는 것이 아니라 매개변수 쿼리(parameterized query, prepared statement)다. 문장의 틀과 값을 따로 보내면 DB는 값을 문법으로 해석할 기회가 없다. OWASP SQL 인젝션 방지 치트 시트도 이것을 첫째 방어로 둔다.

// 위험: 입력을 쿼리 문자열에 이어 붙인다
const rows = await db.query("SELECT * FROM users WHERE name = '" + name + "'");

// 안전: 틀과 값을 따로 보낸다($1은 node-postgres의 자리 표시자)
const rows = await db.query('SELECT * FROM users WHERE name = $1', [name]);
  • ORM과 쿼리 빌더는 대개 내부에서 매개변수를 쓰지만, 문자열을 그대로 넣는 탈출구(raw query)를 쓰는 순간 같은 문제가 돌아온다.
  • 테이블 이름이나 정렬 열처럼 자리 표시자로 넣을 수 없는 부분은 정해 둔 목록에서 고르게 한다(허용 목록).
  • 앱이 DB에 접속하는 계정에는 필요한 권한만 준다. 뚫려도 피해가 그 권한까지로 줄어든다(보안의 기초 노트의 최소 권한).
  • 같은 원리가 셸 명령, LDAP 질의, 템플릿 엔진 등 문자열로 명령을 만드는 모든 곳에 해당한다. 묶어서 인젝션(injection)이라 부른다.

SSRF: 서버에게 대신 심부름을 시키는 공격

링크 미리보기, 주소로 이미지 가져오기, 웹훅처럼 서버가 사용자가 준 URL로 직접 요청을 보내는 기능이 있다. SSRF(Server-Side Request Forgery, 서버 측 요청 위조)는 이 기능에 내부 주소를 넣어, 바깥에서는 닿지 않는 곳으로 서버가 대신 요청하게 만드는 취약점이다. 서버는 방화벽 안쪽에 있으므로 내부 관리 페이지나, 클라우드 서버가 자기 설정과 임시 자격 증명을 받아 가는 메타데이터 주소에 닿을 수 있다.

사용자가 준 URL을 웹 서버가 대신 가져올 때, 허용 목록에 있는 외부 사이트만 요청하고 내부 관리 서버와 클라우드 메타데이터 주소로 가는 요청은 거부한다사용자URL을 입력웹 서버링크 미리보기 생성허용 목록 검사서버는 내부망 안에 있다허용된 외부 사이트images.example.org✕내부 관리 서버10.0.0.5✕클라우드 메타데이터169.254.169.254허용 목록에 있는 곳만 요청그 밖의 주소는 서버가 보내기 전에 거부
서버가 사용자의 URL을 대신 가져올 때는 요청이 내부망 안에서 출발한다. 허용 목록에 있는 외부 주소만 가져오고, 내부 주소와 메타데이터 주소로 가는 요청은 보내기 전에 거부한다.

OWASP SSRF 방지 치트 시트와 MDN의 SSRF 설명이 공통으로 권하는 방어는 다음과 같다.

  • 허용 목록(allowlist): 가져올 수 있는 도메인과 스킴(https만)을 정해 두고 나머지는 거부한다. 막을 주소를 나열하는 차단 목록은 표기를 바꾼 주소에 쉽게 빠져나간다.
  • 이름이 아니라 실제로 연결할 IP를 확인한다. 도메인이 내부 IP로 풀리거나, 리다이렉트로 내부 주소로 넘어가는 경우도 같은 검사를 거친다.
  • 네트워크 단에서도 막는다. URL을 가져오는 일을 따로 떼어 내부망에 닿지 않는 곳에서 돌리고, 클라우드 메타데이터는 토큰을 요구하는 방식을 켠다.
  • 가져온 응답을 그대로 사용자에게 돌려주지 않는다. 필요한 것(제목, 이미지 크기)만 골라 돌려준다.

OWASP Top 10: 웹 취약점을 한눈에

OWASP Top 10은 실제 앱에서 모은 데이터와 업계 설문으로 가장 중요한 웹 앱 보안 위험 열 가지를 꼽은 문서다. 순위표라기보다 무엇부터 점검할지 정하는 공통 어휘로 쓰인다. 2025년판은 다음과 같고, 이 노트에서 다룬 XSS와 SQL 인젝션은 인젝션(A05)에, CSRF와 SSRF는 접근 통제 실패(A01)에 들어 있다.

번호이름뜻과 예
A01접근 통제 실패(Broken Access Control)권한 없는 데이터·기능에 닿는다. 주소의 번호만 바꿔 남의 주문을 보는 IDOR, CSRF, SSRF. 인증과 인가
A02보안 설정 오류(Security Misconfiguration)기본 계정·비밀번호, 너무 자세한 오류 화면, 공개로 열린 저장소, 빠진 보안 헤더
A03소프트웨어 공급망 실패(Software Supply Chain Failures)오래되거나 오염된 의존성, 빌드·배포 과정의 침해. 시스템·소프트웨어 보안
A04암호 실패(Cryptographic Failures)평문 전송, 약하거나 직접 만든 알고리즘, 잘못 관리한 키. 암호
A05인젝션(Injection)입력이 명령·쿼리·스크립트의 문법이 된다. SQL 인젝션, XSS
A06안전하지 않은 설계(Insecure Design)구현을 고쳐도 남는 설계의 빈틈. 위협 모델링이 없던 기능. 보안의 기초
A07인증 실패(Authentication Failures)무차별 대입을 막지 않는 로그인, 약한 비밀번호 저장, 허술한 세션 관리
A08소프트웨어·데이터 무결성 실패(Software or Data Integrity Failures)서명을 확인하지 않는 업데이트, 믿을 수 없는 데이터를 그대로 객체로 되살리기
A09보안 로깅·경보 실패(Security Logging and Alerting Failures)공격이 있어도 기록이 없거나 아무도 알림을 받지 못한다. 사람과 운영
A10예외 상황 처리 실패(Mishandling of Exceptional Conditions)오류가 나면 검사를 건너뛰고 통과시키는 식의 '열린 채 실패'(fail open), 오류 메시지로 내부 정보 노출

순위표 밖에서도 자주 묻는 취약점이 있다. 대부분 위 표의 인젝션이나 접근 통제 실패에 들어가지만, 이름이 따로 불리므로 대표 방어와 함께 적어 둔다.

이름무엇이 문제인가대표 방어
경로 조작(path traversal)파일 이름 입력에 ../ 같은 상위 경로를 넣어 정해진 폴더 밖의 파일을 읽거나 쓴다사용자가 준 이름을 경로로 쓰지 않고 번호로 찾는다. 써야 하면 정규화한 뒤 허용 폴더 안인지 확인한다
파일 업로드올린 파일이 서버에서 스크립트로 실행되거나, 다른 사용자에게 악성 파일로 내려간다OWASP 권고대로 확장자 허용 목록, Content-Type 불신, 서버가 지은 이름으로 바꾸기, 웹 루트 밖이나 다른 서버에 저장, 크기 제한, 악성코드 검사
열린 리다이렉트?next= 같은 값으로 아무 주소로나 보내 주면, 진짜 도메인으로 시작하는 피싱 링크가 된다돌아갈 주소를 허용 목록이나 사이트 안 상대 경로로만 받는다
XXE(XML 외부 개체)XML 파서가 문서 안의 외부 개체 선언을 따라가 서버 파일을 읽거나 내부 주소로 요청한다파서 설정에서 DTD와 외부 개체를 끈다

화면 없이 API만 내놓는 서비스라면 OWASP API Security Top 10(2023년판)이 따로 있다. 1위가 객체 단위 인가 실패(Broken Object Level Authorization), 곧 인증과 인가 노트의 IDOR다. 흔한 오해 하나는 "Top 10을 다 막았으니 안전하다"는 생각이다. Top 10은 인식을 위한 문서이고, 개발할 때 점검 기준으로 쓰라고 만든 것은 OWASP ASVS(애플리케이션 보안 검증 표준, 2025년 5.0판)다.

표의 위험 대부분은 하나의 습관으로 모인다. 바깥에서 온 값은 데이터로만 다루고, 권한은 서버에서 매번 확인하고, 한 겹이 뚫려도 다음 겹이 버티게 한다. 이 사이트의 정적 페이지들도 같은 원칙에 따라, 파일 처리 도구는 입력을 서버로 보내지 않고 브라우저 안에서만 다룬다.