크롤링

CSRF 토큰과 DOM

필만이 2025. 4. 22. 13:55

배경

  1. CSRF 토큰, dom 은 뭐고 왜필요할까?
  2. 둘은 어떤관계를 가지고 있을까?

본문

1. 한 줄 요약

CSRF 토큰은 로그인이나 폼 전송 시 서버가 발급한 보안 코드이고, DOM은 그 토큰이 담긴 HTML 구조 안에서 우리가 그 값을 추출해내는 장소입니다.


2. 🧠 용어 먼저 정리

✅ CSRF 토큰이란?

  • CSRF = Cross-Site Request Forgery (사이트 간 요청 위조)
  • 사용자가 로그인한 상태를 악용해, 의도치 않은 요청을 자동으로 보내는 공격을 막기 위해 사용됨
  • 서버는 폼 안에 무작위 토큰을 삽입해서, 이 토큰이 없거나 틀리면 요청을 거부함
<!-- 예: 로그인 폼 HTML -->
<form method="POST" action="/login">
  <input type="text" name="username">
  <input type="password" name="password">
  <input type="hidden" name="csrf_token" value="abc123xyz"> <!-- ← 이게 CSRF 토큰 -->
</form>

CSRF 토큰은 HTML 안에 숨겨진 input 필드로 들어가며,
서버는 이 값을 확인해 정상 요청인지 판단합니다.


✅ DOM이란?

  • DOM (Document Object Model) = 웹페이지의 HTML 구조를 트리 형태로 표현한 것
  • 웹페이지의 모든 요소(div, form, input 등)는 DOM의 노드(Node) 로 표현됨
  • JavaScript, Selenium, BeautifulSoup 등을 통해 접근하거나 조작할 수 있음

예시 DOM 구조:

<html>
  <body>
    <form id="login-form">
      <input type="text" name="username">
      <input type="password" name="password">
      <input type="hidden" name="csrf_token" value="abc123xyz"> <!-- ← 여기서 추출 -->
    </form>
  </body>
</html>

3. 🔗 CSRF 토큰과 DOM의 관계

개념 역할 관계
CSRF 토큰 폼 요청이 안전한지 판단하는 보안 코드 DOM 안에 숨겨진 필드로 포함됨
DOM 웹페이지의 HTML 요소를 구조적으로 표현 CSRF 토큰이 들어 있는 위치를 탐색 가능

즉, 크롤러 입장에서는 "DOM을 파싱해서 CSRF 토큰을 추출해야" 로그인을 정상적으로 요청할 수 있습니다.


4. 🛠 실전 예시 (requests + BeautifulSoup)

import requests
from bs4 import BeautifulSoup

# 로그인 페이지에 먼저 접속
session = requests.Session()
r = session.get("https://example.com/login")

# DOM 파싱을 통해 CSRF 토큰 추출
soup = BeautifulSoup(r.text, 'html.parser')
csrf_token = soup.find("input", {"name": "csrf_token"})["value"]

# 토큰 포함해서 로그인 요청
session.post("https://example.com/login", data={
    "username": "test",
    "password": "1234",
    "csrf_token": csrf_token
})

5. ❗왜 이걸 모르면 로그인 실패할까?

  • requests.post()로 바로 로그인하면 서버는 CSRF 토큰이 없다고 판단하고 거부
  • 브라우저(Selenium)는 HTML을 실제 렌더링하고 DOM에서 자동으로 이 값을 읽어 전달하기 때문에 성공

✅ 결론

항목 설명
CSRF 토큰 서버가 발급한 1회용 보안 코드
DOM HTML 문서의 구조 표현 (브라우저나 BeautifulSoup이 접근하는 대상)
관계 CSRF 토큰은 DOM 내부 <input>에 숨겨져 있으며, 이를 파싱해 추출해야 로그인 성공 가능