웹 어플리케이션 취약점 중 하나로 희생자가가 자신의 의지와는 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 만드는 공격이다.
악성 코드나, 악성 링크가 포함된 이메일이나 사이트를 희생자가 접속하도록 유도해서 공격을 한다. 즉, 희생자가 이메일을 열거나 악성 웹 페이지에 접속하면 공격이 실행되는 것이다. 예를 들어, 한 취약한 웹사이트에서 패스워드를 바꾸는 페이지가 /chpw?new_pw=pw 형식이라고 가정하자. 그렇다면, 공격자는 /chpw?new_pw=1234와 같은 링크로 희생자가 접속하게 해, 희생자의 계정 비번을 강제로 바꾸게 할 수 있다.
이런 공격을 방지하기 위해서는 CSRF 토큰을 사용하거나 Referer 헤더를 검사하는 방법과 같이, 여러 방식이 존재하는데, 주로 CSRF 토큰을 많이 사용하는것 같다. 또한, 이런 공격을 방지하기 위해서, 모르는 이메일로 온 링크를 누르거나, 모르는 사람에게서 받은 링크를 누르지 않는 것이 가장 바람직한 방법이다.
CSRF Token은 CSRF 공격을 방지하기 위해 사용된다. CSRF 공격을 방지하고, 요청의 유효성을 검증하기 위해서 사용된다. CSRF Token은 서버가 클라이언트에게 제공하는 랜덤한 문자열이다. 랜덤한 문자열이지만, 사람이 해석할 수 없는 문자열이다. 이 토큰은 일반적으로 세션에 저장되거나, form 등의 숨겨진 필드(hidden field)에 포함된다.
그니까 사이트는 사용자가 로그인하거나 인증을 받을 때마다 CSRF Token을 생성하고, 클라이언트에게 전달한다(주로 쿠키로). 그리고 클라이언트는 서버로부터 받은 CSRF Token을 폼 제출 시 숨겨진 필드에 포함하거나, 요청 헤더에 포함해 전달한다. 그러면 서버는 받은 CSRF Token과 세션에 저장된 CSRF Token을 비교하고, 일치하지 않는다면, 요청을 거부한다. (간단)
즉, 공격자가 공격을 시도해도, 공격자가 유효한 토큰을 요청해 포함해서 서버에게 전달하지 않는 이상, 서버는 그 요청을 처리하지 않을 것이라는 소리다. 또한, 아까도 말했듯이 토큰은 길고 랜덤한 문자열이므로, 해커가 추정할 수 없다. (123e4567-e89b-12d3-a456-426614174000 와 같은 값을 추정할 수 있는가?)
Server-Side Request Forgery는 말 그대로 서버사이드 요청 위조라는 의미를 가지고 있는 단어다. 이 공격은 일반적으로 서버가 수행하지 않아야 할 작업을 수행하도록 유도하는 공격으로. 약 외부에서 특정 민감한 페이지에 접근할 수 있다면, 이러한 공격에 당할 수 있다.
악성 사용자는 웹 요청 URL을 조작하여 공격을 수행한다. 공격자는 서버에 있는 민감한 데이터에 접근하거나, 서버 내부 네트워크에 접속하기 위하여 SSRF를 주로 사용한다. 요청 URL을 조작해, 내부 리소스나 외부 리소스에 대한 요청을 조작한다.
이 공격으로 인해, 내부에 있는 민감한 데이터(민감하지 않은 데이터라도)가 유출될 수 있고, 외부 리소스에 대한 요청을 조작해서 대량의 트래픽을 전송하거나, 메모리를 많이 차지하는 작업을 수행하도록 할 수 있다.
이러한 공격을 방지하기 위해, 신뢰할 수 있는 주소에서 보낸 요청만 허용하거나, 내부 시스템과 외부 시스템을 분리해서, 외부에서 내부로 직접적인 접근이 불가능하도록 설정하는 것도 좋은 방법이다.
from flask import Flask, request
import requests
app = Flask(__name__)
FLAG = "FLAG{**SECRET**}"
@app.route("/", methods=["GET"])
def index():
return "Hello"
@app.route("/req", methods=["GET"])
def req():
if request.method == "GET":
url = request.args.get('url')
if url:
return requests.get(url).text
else:
return "<h1>example : /req?url=https://google.com</h1>"
@app.route("/admin", methods=["GET"])
def admin():
if request.remote_addr == "127.0.0.1":
return FLAG
else:
return "You're not admin."
app.run(host="0.0.0.0", port=10050)
코드를 보자. admin 함수에를 자세히 보자
함수를 자세히 보면, remote_addr이 127.0.0.1인 경우에만 FLAG를 리턴하도록 되어있다. 즉, req?url=http://158.247.215.127:10050/admin로 요청을 보내면, You're not admin.이 뜨지만, /req?url=http://127.0.0.1:10050/admin로 요청을 보내면 플래그가 뜨는 아주 간단한 문제였던 것이다