앵하니의 더 나은 보안

CSRF 취약점의 현태 본문

보안 기술/WEB

CSRF 취약점의 현태

앵한 2024. 5. 10. 15:27

서론

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)

  1. XMLHttpRequest 객체를 사용해 웹 서버의 board에 글을 작성하는 버튼
    단, 웹 서버의 쿠키에 접근 및 사용이 가능해야함
  2. GET 메소드를 사용해 웹 서버에 접근하는 버튼
  3. POST 메소드를 사용해 board에 글을 작성하는 버튼
  4. A태그를 사용해 웹 서버에 접근하는 버튼
  5. 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 설정 파일들도 동일한 경로에 둬야한다.

templates.zip
0.00MB
key.pem
0.00MB
cert.pem
0.00MB

빌드 때 물어보는 암호는 ‘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폴더

csrf templates.zip
0.00MB
 

Cross Site간 요청 위조가 가능한 조건 : SameSite

다른 사이트의 쿠키를 사용하려한다면 브라우져 간 쿠키 보안을 담당하는 SameSite 값이 관여한다.
이 SameSite 설정은 쿠키를 발급될때 확인할 수 있으며, 따로 언급이 없는 경우 디폴트 값인 ‘Lax’로 설정된다.

로그인 시 세션 값이 쿠키에 저장될 때, SameSite 확인 가능

이 SameSite은 3개의 빡빡함 레벨이 있다.

  1. Strict : 서드 파티 쿠키 사용 절대 불허
    서드 파티 쿠키란 다른 사이트의 쿠키를 뜻한다.
    어떠한 상황에서도 서드 파티 쿠키 사용을 허락하지 않는다.

  2. Lax : 예외를 두어 예외의 경우 사용 가능
    SameSite 설정을 건드리지 않았을때 디폴트 값으로 Lax가 설정된다.
    여기서 언급한 예외란
    1. top-level-navigation 접근
      사용자의 링크 직접 접근(주소창 입력)을 top-level-navigation이라 한다.
      사용자가 URL에 직접 접근하려하면 해당 URL의 쿠키에 접근 가능하다.
      top-level-navigation에 직접 입력

      top-level-navigation에 직접 입력 시 쿠키에 접근해 세션 값 사용 가능
      top-level-navigation 입력으로 세션 쿠키 값을 사용, user1로 게시글 등록된 모습
       
       
    2. A태그 사용
      A태그로 링크 된 상태면 외부 사이트의 쿠키를 사용해 접근할 수 있다.
      csrf.html에 작성해놓은 A태그 클릭

      마찬가지로 쿠키에 접근이 가능해 user1으로 글 등록
       
    3. Form태그의 GET 메소드 사용
      Form태그에 GET메소드를 사용해 submit하면 외부 사이트의 쿠키에 접근 가능하다.
      csrf.html에 작성해놓은 form태그의 GET 전송 버튼 클릭
      마찬가지로 쿠키에 접근이 가능해 user1로 글 등록

       

  3. None : SameSite 미설정
    미설정이라 돼있어서 그냥 전부 사용가능할 것 같지만 아니다. 크롬 브라우져 이놈이 또 막는다.
    SameSite : Lax와 달라지는건 form 태그의 POST 메소드를 사용해서 서드파티쿠키의 사용이 가능하다는 것.
    (2019년 이전에는 가능했지만 브라우저 보안이 강화되면서 None이라 설정돼있어도 함부로 서드파티쿠키를 사용할 수 없다.)
    ※서드파티쿠키란 제 3의 사이트에 존재하는 쿠키를 뜻 함
    csrf.html에 작성해놓은 form태그의 POST 전송 버튼 클릭

    SameSite로 None이 설정돼 쿠키에 접근이 가능해져 user1로 글 등록

그리고 SameSite의 빡빡함 레벨이 제일 낮은 None으로 설정을 해도, XMLHttpRequest 객체를 통한 쿠키 사용은 허용되지 않는다. ㅠ

 

 

찾아보니 2024년부터 서드파티 쿠키를 본격적으로 제한한다고 한다.

 

서드 파티 쿠키 종료 준비  |  Privacy Sandbox  |  Google for Developers

사이트에서 서드 파티 쿠키를 사용하는 경우 지원 중단이 임박한 시점에 조치를 취해야 합니다. 원활한 테스트를 위해 2024년 1월 4일부터 Chrome에서 사용자 1% 의 서드 파티 쿠키를 제한했습니다.

developers.google.com

 

 

기어코 XMLHttpRequest로 타 도메인의 쿠키를 날리고싶다면?

‘서드 파티 쿠키 허용’ 설정을 해주면 된다.

 

얘는 사용자가 직접 개입한 설정이라 XMLHttpRequest도 가능해진다.

만약 사용자한테 ‘서드 파티 쿠키 사용’을 받아 냈다면 그 다음부터 해당사이트에서는 다른 도메인의 쿠키가 SameSite='None'으로 설정된 경우 호출하고 사용하는것에 있어 자유롭다.

서드파티 쿠키 사용 가능케 설정 한 뒤 ‘send XHR’ 클릭

 

origin이 다름에도 쿠키 값을 태워 Request를 보낸 모습

 

사용자의 개입 없이 XMLHttpRequest로 타 도메인의 쿠키를 날리고싶다면?

그럼 최신 브라우저를 사용하지 않는 유저한테 해당 스크립트를 노출 시키면 된다.

실제로 Firefox 이전 버전 설치하기 | Firefox 도움말 링크에서 모질라 구버전(99.ob8) 다운로드 후 설치해보니
서드 파티 쿠키고 뭐고 바로 동작하는 걸 확인할 수 있다.

 

결론

2024년부로 CSRF는 더이상 XSS가 없으면 존재하기 꽤나 힘든 취약점

사용자에게 서드 파티 쿠키 사용을 허용토록 종용해야하는 취약점(자연빵으로는 매우매우 힘듦)

세션/토큰 값을 쿠키에 저장하지 않고 웹/로컬 스토리지에 저장해 사용하면 도리가 없는 취약점

 

번외긴 한데 쿠키 값 관련 헤더를 정리해놓은 좋은 사이트가 있어 공유한다.

 

HTTP 쿠키 (HTTP Cookie) 의 생성 옵션

쿠키 생성 옵션 쿠키는 생성할 때 몇가지 옵션을 줄 수 있다. HTTP 헤더에 들어간 쿠키 옵션 예제 Set-Cookie: =; Domain= Set-Cookie: =; Expires= Set-Cookie: =; HttpOnly Set-Cookie: =; Max-Age= Set-Cookie: =; Partitioned Set-Coo

jake-seo-dev.tistory.com

 

Comments