이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.
공부 › 보안 › 웹 공격과 방어
웹 공격 세 가지
입력값을 그대로 믿으면 생기는 일
SQL Injection, XSS, CSRF는 원리가 서로 다릅니다. 하나는 입력이 SQL의 일부가 되고, 하나는 입력이 다른 사용자 브라우저에서 스크립트로 실행되고, 하나는 로그인된 사용자의 브라우저가 대신 요청을 보내게 됩니다.
일부만 실행으로 확인 · 세 공격의 일부를 SQLite와 브라우저로 확인
이 실습에 나오는 개념
- SQL Injection
- 사용자 입력이 SQL 문의 일부로 해석되어 공격자가 의도한 쿼리가 실행되는 공격이다. 입력값을 문자열로 이어 붙여 SQL을 만들 때 발생한다. SQL 구조와 값을 분리하는 바인드 파라미터(PreparedStatement)를 쓰면 입력이 항상 값으로만 처리되어 막을 수 있다.
- XSS(크로스 사이트 스크립팅)
- 공격자가 웹 페이지에 악성 스크립트를 삽입해 다른 사용자의 브라우저에서 실행되게 하는 공격이다. 게시글이나 댓글에 넣은 스크립트가 그대로 출력될 때 발생하며, 사용자의 쿠키를 훔치거나 사용자 권한으로 요청을 보낼 수 있다. 사용자 입력을 화면에 출력할 때 특수 문자를 변환(이스케이프)하는 것이 기본 방어다.
- CSRF(사이트 간 요청 위조)
- 사용자가 로그인된 상태임을 이용해, 공격자가 만든 페이지에서 사용자의 브라우저가 의도하지 않은 요청을 대상 사이트로 보내게 하는 공격이다. 브라우저가 쿠키를 자동으로 붙여 보내기 때문에 서버는 정상 요청으로 판단한다. 서버가 발급한 예측 불가능한 토큰을 요청에 포함하게 하거나 쿠키의 SameSite 속성으로 막는다.
- HttpOnly, Secure, SameSite
- 쿠키를 보호하는 속성들이다. HttpOnly는 자바스크립트가 쿠키를 읽지 못하게 해 스크립트 삽입 공격으로 쿠키가 탈취되는 것을 막는다. Secure는 HTTPS 연결에서만 쿠키를 전송하게 한다. SameSite는 다른 사이트에서 시작된 요청에 쿠키가 함께 전송되는 것을 제한해 CSRF 공격을 줄인다.
- 쿠키
- 서버가 응답에 실어 보내면 브라우저가 저장해 두었다가 같은 사이트에 요청할 때마다 자동으로 함께 보내는 작은 데이터다. HTTP는 요청 사이의 상태를 기억하지 않으므로, 로그인 상태처럼 이어져야 하는 정보를 유지하는 데 쓰인다. 사용자의 브라우저에 저장되므로 내용을 보거나 바꿀 수 있다는 점을 전제로 사용해야 한다.
이렇게 바꿔 보세요
- 처음 설정으로 — SQL Injection, 방어 없음. 비밀번호 없이 로그인됩니다.
- 파라미터 바인딩 — 입력이 SQL 문법이 아닌 값으로만 다뤄집니다.
- XSS, 방어 없음 — 댓글에 넣은 스크립트가 다른 사용자 브라우저에서 실행됩니다.
- XSS, 출력 이스케이프 — 스크립트가 글자로만 보입니다.
- XSS, HttpOnly만 — 쿠키는 못 훔치지만 스크립트는 여전히 실행됩니다.
- CSRF, 방어 없음 — 다른 사이트를 열었을 뿐인데 내 계정으로 요청이 나갑니다.
- CSRF, SameSite=Lax — 다른 사이트에서 보낸 POST에는 쿠키가 붙지 않습니다.
처음 설정으로 실행하면
- 공격자가 비밀번호 칸에 ' OR '1'='1 을 입력합니다. 작은따옴표로 문자열을 일찍 닫고 그 뒤에 SQL 조건을 덧붙이려는 의도입니다.
- 서버가 입력을 SQL 문자열에 그대로 이어 붙였습니다. 입력의 작은따옴표가 SQL의 문자열을 닫아 버리고, 뒤의 OR '1'='1' 이 SQL 문법의 일부가 됐습니다.
- '1'='1'은 언제나 참이므로 WHERE 조건 전체가 참이 됩니다. 회원 행이 조회되어 서버는 로그인에 성공했다고 판단합니다.
실행 결과
- 입력값이 "값"이 아니라 "SQL 문법"으로 해석된 것이 원인입니다. 같은 방법으로 다른 테이블을 조회하거나 데이터를 지우는 SQL도 끼워 넣을 수 있습니다.
코드
// 입력을 문자열로 이어 붙인다
String sql = "SELECT * FROM member WHERE email = '" + email
+ "' AND password = '" + password + "'";
ResultSet rs = statement.executeQuery(sql);
이 모형의 전제
세 공격 모두 원리를 보이기 위한 교과서적인 예이며, OWASP의 설명과 스프링 시큐리티 문서의 방어 방식을 따랐습니다. 비밀번호를 SQL에서 직접 비교하는 로그인 코드는 SQL Injection을 설명하기 위한 것으로, 실제로는 비밀번호를 해시로 저장하고 애플리케이션에서 검증합니다. 방어는 한 가지만 켠 상태를 비교했지만 실무에서는 여러 방어를 겹쳐 씁니다. 세 공격 모두 일부를 실제로 실행해 확인했습니다. SQL Injection은 실제 SQL 엔진(SQLite)에서 문자열 연결은 1행이 조회되고 바인딩은 0행이었습니다. XSS는 실제 브라우저(크로미움 기반)에서 그대로 출력한 댓글의 스크립트가 실행되어 쿠키를 읽었고, 이스케이프하면 실행되지 않았으며, HttpOnly 쿠키는 스크립트가 실행돼도 보이지 않았습니다. CSRF는 다른 사이트에서 자동 제출한 POST에 SameSite=None 쿠키는 붙었고 SameSite=Lax 쿠키는 붙지 않았습니다. SameSite를 지정하지 않은 쿠키는 발급 직후에는 붙었지만 2분쯤 지난 뒤에는 붙지 않았습니다. CSRF 토큰 검증은 서버 쪽 로직이라 따로 실행하지 않았습니다.
앞 실습: 메서드 보안 · 다음 실습: CORS