개념정리
○ CSRF
CSRF(Cross Site Request Forgery)은 웹 어플리케이션 취약점 중 하나로 인터넷 사용자가 자신의 의지와는 상관없이 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 만드는 공격이다.
즉, CSRF는 클라이언트 사이드에서 취약점이 발생한다.
예를 들어보자.
만약, SNS를 사용하는 일반 유저가 현재 로그인 상태라고 가정하자. 다음으로 공격자가 흥미로운 글과 함께 CSRF를 수행하는 악성 링크를 같이 게시글로 올렸다고 해보자. 일반 유저는 해당 게시글의 내용에 흥미를 느끼고 링크를 클릭하게 될 것이다.
이때, 일반 유저는 CSRF를 수행하는 악성 링크에 의해 클라이언트 사이드의 권한(일반 유저 권한)으로 공격자가 의도한 행위(계정 정보 변경, 악성 게시글 등록 등)을 수행한다.
이처럼 일반 유저가 자기도 모르는 사이에 공격자가 심어놓은 악성 스크립트에 의해 본인권한으로 서버에 특정 행위를 요청하는 공격을 CSRF라고한다.
위와 같은 예제에서 알 수 있듯이 공격자가 CSRF를 수행하기 위해서는 일반 유저의 상태가 특정 행위를 위한 권한을 가진 상태여야한다. (예를 들면 계정 로그인 상태)
예제 문제를 풀어보면서 이해해보자.
문제풀이
4가지 페이지가 존재한다.
@app.route("/vuln")
def vuln():
param = request.args.get("param", "").lower()
xss_filter = ["frame", "script", "on"]
for _ in xss_filter:
param = param.replace(_, "*")
return param
/vuln
코드를 보면 "frame", "script", "on" 키워드를 필터링하고 있다.
키워드를 우회하면서 CSRF를 수행할 수 있는 스크립트를 작성해야한다.
@app.route("/admin/notice_flag")
def admin_notice_flag():
global memo_text
if request.remote_addr != "127.0.0.1":
return "Access Denied"
if request.args.get("userid", "") != "admin":
return "Access Denied 2"
memo_text += f"[Notice] flag is {FLAG}\n"
return "Ok"
/admin/notice_flag
는 remote_addr이 127.0.0.1이면서 userid가 admin인 경우에 flag를 memo에 저장한다.
remote_addr이 127.0.0.1이면서 userid가 admin인 조건을 만족하기위해서는 공격 대상이 /admin/notice_flag
에 접근하도록 스크립트를 실행시켜야한다.
즉, bot이 /admin/notice_flag
에 접근하도록 스크립트를 작성해서 /flag
에서 전달하면 된다.
<img src='/admin/notice_flag?userid=admin'></img>
payload는 <img>
태그를 이용해서 작성했다.
코드가 실행되면 bot이 /admin/notice_flag
에 접근하면서 flag를 /memo
에 저장할 것이다.
/flag
에서 payload를 입력하고 전송해보자.
/memo
에 접속하면 flag를 획득할 수 있다.
대응방안
1) Referer 검증
백엔드에서 request 패킷의 referer를 확인하고 도메인이 일치하는지 검증한다.
2) CSRF token 사용
origin에서만 접근 가능한 형태로 특정 Token을 저장해두고, HTTP 요청을 전송할 때 함께 전송하는 것을 말한다. 즉 사용자의 세션에 임의의 난수값을 저장하고 사용자의 요청마다 해당 난수값을 포함시켜 전송한다. 이후 백엔드에서 요청을 받을 때마다 세션에 저장된 토큰값과 요청 파라미터에서 전달되는 토큰값이 같은지 검증하는 방법이다. CSRF token을 사용할 때 주의할 점은 공격자가 유추할 수 있는 형태로 token을 제작하면 안된다. 만약 CSRF token의 길이나 너무 짧거나 공격자가 추측 가능한 데이터, 암호학적으로 안전하지 않은 의사 난수 생성기 (Pseudorandom Number Generator, PRNG) 등으로 생성한다면 공격자는 충분히 유추하여 우회할 수 있음으로 주의해야한다. 또한 URL 쿼리 파라미터로 넘겨 CSRF 토큰이 Referer를 통해 유출되는 등의 상황을 만들지 않는 것도 중요하다.
3) Double submit cookie 검증
웹브라우저의 Same Origin 정책으로 인해 자바스크립트에서 타 도메인의 쿠키값을 확인, 수정하지 못한다는 것을 이용한 방어 기법이다. <script> 단에서 요청시 난수를 생성하여 쿠키에 저장하고 동일한 난수 값을 요청 파라미터에 저장하여 서버에 전송한다. 서버 단에서 쿠키의 토큰 값과 파라미터의 토큰 값이 일치하는지만 검사하여 인증한다. 이 방법은 서버에 토큰값을 저장할 필요가 없어 세션 검증보다 방식보다 가볍다는 장점이 있다.