앵하니의 더 나은 보안
CSRF 취약점의 현태 본문
서론
XSS 취약점이 존재하는 사이트에 CSRF는 발생할 가능성이 아주 높다. 그럼 반대로 XSS가 없는 사이트는 CSRF 또한 없는 걸까? 물론 아니다. CSRF의 그 이름처럼 Cross SIte. 즉 제2, 제3의 다른 사이트에서 대상사이트로의 변조된 요청이 들어올 수 있다. 그래서 XSS가 없다해도, 제2 제3의 사이트에서 요청 위조 공격이 들어올 수 있으니 XSS 취약점이 없다해도 CSRF의 발생 가능성은 존재한다.
그럼 다른사이트에서의 CSRF 공격은 어떤식으로 수행해볼 수 있을까?
말만 들었을때는 XMLHttpRequest 객체나 뭐 form 객체, A태그 등등 여러가지 써서 가능해보이는데 실제로 가능한지 시연해보겠다.
본론
CSRF 공격 대상 웹 서버(127.0.0.1)
구축한 웹서버는 기본적으로 로그인해서 글을 작성하는 기능만 존재
ID/PW는 각각 user1/password1 그리고 user2/password2로 2개 사용 가능
로그인 하면 세션이 발급돼 쿠키에 저장
이후 쿠키의 세션 값을 사용해서 board에 글 작성 가능
글 작성을 하게되면 세션 값을 통해 로그인 한 사용자의 ID와 작성한 글이 게시
CSRF 공격이 존재하는 웹 페이지(172.x.x.x)
- XMLHttpRequest 객체를 사용해 웹 서버의 board에 글을 작성하는 버튼
단, 웹 서버의 쿠키에 접근 및 사용이 가능해야함 - GET 메소드를 사용해 웹 서버에 접근하는 버튼
- POST 메소드를 사용해 board에 글을 작성하는 버튼
- A태그를 사용해 웹 서버에 접근하는 버튼
- iframe내에 웹 서버를 포함시켜 쿠키에 접근을 시도하는 용도의 iframe
각 버튼, 기능의 쓰임새를 왜 이렇게 구성했는지는 차차 알아보겠다.
시연환경 구성
우선 CORS 및 CSRF를 테스트하기 위해 구성한 시연환경으로 피해서버로는 python의 flask를 사용했고, CSRF 스크립트는 그냥 html 파일을 작성해 테스트를 진행했다.
아래는 로그인/보드 기능이 있는 웹 서버 소스코드
from flask import session, Flask, render_template, request, redirect, url_for
import ssl
from flask_cors import CORS
app = Flask(__name__)
app.secret_key = 'csrf test'
app.config['SESSION_COOKIE_SAMESITE'] = 'None'
app.config['SESSION_COOKIE_SECURE'] = True
#app.config['SESSION_COOKIE_PARTITIONED'] = True
CORS(app, supports_credentials=True)
users = {
'user1': 'password1',
'user2': 'password2'
}
posts = []
@app.route('/')
def index():
return render_template('index.html')
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
if username in users and users[username] == password:
session['username'] = username
# 로그인 성공 시 게시판으로 리다이렉트
return redirect(url_for('board'))
else:
# 로그인 실패 시 다시 로그인 페이지로 리다이렉트
return redirect(url_for('index'))
@app.route('/board')
def board():
return render_template('board.html', posts=posts)
@app.route('/write', methods=['POST','GET'])
def write():
try:
if 'content' in request.form or 'content' in request.args:
username = session['username']
if request.method=='POST':
content = request.form['content']
elif request.method=='GET':
content = request.args.get('content')
posts.append({'username': username, 'content': content})
# 글 작성 후 게시판으로 리다이렉트
return redirect(url_for('board'))
except KeyError:
pass
return redirect(url_for('board'))
if __name__ == '__main__':
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain('cert.pem', 'key.pem')
#app.run(debug=True, host='0.0.0.0', port=80)
app.run(debug=True, host='0.0.0.0', port=443, ssl_context=context)
flask를 쓰려면 동일한 경로에 template이라는 폴더를 두어야하고, 그 안에 페이지 구현에 사용할 html 파일들을 넣어줘야하며, 쿠키의 secure는 https 통신에서만 유효하므로 secure가 설정된 세션값 사용을 위해 ssl 설정 파일들도 동일한 경로에 둬야한다.
빌드 때 물어보는 암호는 ‘shinyh’
그리고 아래는 CSRF 공격을 구현해놓은 서버의 app.py이고, ssl 설정에 사용되는 키 값은 위에 첨부한 파일을 똑같이 사용하면 된다.
from flask import Flask, render_template
import ssl
app = Flask(__name__)
app.secret_key = 'csrf test'
@app.route('/')
def index():
return render_template('csrf.html')
if __name__ == '__main__':
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain('cert.pem', 'key.pem')
#app.run(debug=True, host='0.0.0.0', port=80)
app.run(debug=True, host='0.0.0.0', port=443, ssl_context=context)
그리고 아래는 csrf 스크립트가 있는 templates폴더
Cross Site간 요청 위조가 가능한 조건 : SameSite
다른 사이트의 쿠키를 사용하려한다면 브라우져 간 쿠키 보안을 담당하는 SameSite 값이 관여한다.
이 SameSite 설정은 쿠키를 발급될때 확인할 수 있으며, 따로 언급이 없는 경우 디폴트 값인 ‘Lax’로 설정된다.
이 SameSite은 3개의 빡빡함 레벨이 있다.
- Strict : 서드 파티 쿠키 사용 절대 불허
서드 파티 쿠키란 다른 사이트의 쿠키를 뜻한다.
어떠한 상황에서도 서드 파티 쿠키 사용을 허락하지 않는다. - Lax : 예외를 두어 예외의 경우 사용 가능
SameSite 설정을 건드리지 않았을때 디폴트 값으로 Lax가 설정된다.
여기서 언급한 예외란- top-level-navigation 접근
사용자의 링크 직접 접근(주소창 입력)을 top-level-navigation이라 한다.
사용자가 URL에 직접 접근하려하면 해당 URL의 쿠키에 접근 가능하다.
- A태그 사용
A태그로 링크 된 상태면 외부 사이트의 쿠키를 사용해 접근할 수 있다.
- Form태그의 GET 메소드 사용
Form태그에 GET메소드를 사용해 submit하면 외부 사이트의 쿠키에 접근 가능하다.
- top-level-navigation 접근
- None : SameSite 미설정
미설정이라 돼있어서 그냥 전부 사용가능할 것 같지만 아니다. 크롬 브라우져 이놈이 또 막는다.
SameSite : Lax와 달라지는건 form 태그의 POST 메소드를 사용해서 서드파티쿠키의 사용이 가능하다는 것.
(2019년 이전에는 가능했지만 브라우저 보안이 강화되면서 None이라 설정돼있어도 함부로 서드파티쿠키를 사용할 수 없다.)
※서드파티쿠키란 제 3의 사이트에 존재하는 쿠키를 뜻 함
그리고 SameSite의 빡빡함 레벨이 제일 낮은 None으로 설정을 해도, XMLHttpRequest 객체를 통한 쿠키 사용은 허용되지 않는다. ㅠ
찾아보니 2024년부터 서드파티 쿠키를 본격적으로 제한한다고 한다.
기어코 XMLHttpRequest로 타 도메인의 쿠키를 날리고싶다면?
‘서드 파티 쿠키 허용’ 설정을 해주면 된다.
얘는 사용자가 직접 개입한 설정이라 XMLHttpRequest도 가능해진다.
만약 사용자한테 ‘서드 파티 쿠키 사용’을 받아 냈다면 그 다음부터 해당사이트에서는 다른 도메인의 쿠키가 SameSite='None'으로 설정된 경우 호출하고 사용하는것에 있어 자유롭다.
사용자의 개입 없이 XMLHttpRequest로 타 도메인의 쿠키를 날리고싶다면?
그럼 최신 브라우저를 사용하지 않는 유저한테 해당 스크립트를 노출 시키면 된다.
실제로 Firefox 이전 버전 설치하기 | Firefox 도움말 링크에서 모질라 구버전(99.ob8) 다운로드 후 설치해보니
서드 파티 쿠키고 뭐고 바로 동작하는 걸 확인할 수 있다.
결론
2024년부로 CSRF는 더이상 XSS가 없으면 존재하기 꽤나 힘든 취약점
사용자에게 서드 파티 쿠키 사용을 허용토록 종용해야하는 취약점(자연빵으로는 매우매우 힘듦)
세션/토큰 값을 쿠키에 저장하지 않고 웹/로컬 스토리지에 저장해 사용하면 도리가 없는 취약점
번외긴 한데 쿠키 값 관련 헤더를 정리해놓은 좋은 사이트가 있어 공유한다.
'보안 기술 > WEB' 카테고리의 다른 글
Google reCAPTCHA selenium으로 무력화 시켜보기 (5) | 2024.05.10 |
---|---|
HttpOnly와 XST(Cross-Site Tracing) (0) | 2024.05.10 |
REST API 환경 웹 서버에서의 기존 취약점 탐구 (1) | 2024.02.06 |
TLS 1.3 (0) | 2023.11.05 |
restAPI 환경에서 XSS는 왜 동작하지 않는걸까? (0) | 2023.11.05 |