사이트 간 요청 위조(Cross-site request forgery, CSRF)는 웹사이트 취약점 공격의 하나로, 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 하는 공격을 말합니다
XSS 와 차이
-> XSS는 특정 사이트를 신뢰하는 정책을 이용한 코드 삽입하여 사용자가 script 실행
-> CSRF는 특정 인증된 사용자의 요청을 신뢰하는 정책을 이용해 script 요청을 서버에서 실행시킴.
접속 정보 보기, 문제 파일 다운로드를 눌러준다.
접속 정보 보기를 통해 사이트를 들어가면
다음과 같이 나올 것이다.
vuln(csrf) 페이지에 접속하면 csrf 파라미터로 alert(1)를 실행하는 스크립트 구문을 전달한다.
응답 화면에서는 script 구문이 필터링된 것을 볼 수 있다.
<script>alert(1)</script> -> <*>alert(1)
다음은 memo페이지를 보자.
memo 페이지에 접속하면 memo 파라미터로 hello를 전달하고,
응답 화면에서는 hello를 출력하는 것을 볼 수 있다.
notice flag 페이지는...
그냥 거부 한다.
flag 페이지는..
나 공격해주세요~ 하면서 소리치고 있다.
그리고 코드를 보자.
#!/usr/bin/python3
from flask import Flask, request, render_template
from selenium import webdriver
import urllib
import os
app = Flask(__name__)
app.secret_key = os.urandom(32)
try:
FLAG = open("./flag.txt", "r").read()
except:
FLAG = "[**FLAG**]"
def read_url(url, cookie={"name": "name", "value": "value"}):
cookie.update({"domain": "127.0.0.1"})
try:
options = webdriver.ChromeOptions()
for _ in [
"headless",
"window-size=1920x1080",
"disable-gpu",
"no-sandbox",
"disable-dev-shm-usage",
]:
options.add_argument(_)
driver = webdriver.Chrome("/chromedriver", options=options)
driver.implicitly_wait(3)
driver.set_page_load_timeout(3)
driver.get("http://127.0.0.1:8000/")
driver.add_cookie(cookie)
driver.get(url)
except Exception as e:
driver.quit()
print(str(e))
# return str(e)
return False
driver.quit()
return True
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)
@app.route("/")
def index():
return render_template("index.html")
@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
@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>'
memo_text = ""
@app.route("/memo")
def memo():
global memo_text
text = request.args.get("memo", None)
if text:
memo_text += text
return render_template("memo.html", memo=memo_text)
@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"
app.run(host="0.0.0.0", port=8000)
코드를 살펴보면,
csrf 페이지는 스크립트 구문(script, iframe, on) 필터링이 있다.
memo 페이지는 flag가 출력되는 페이지이다.
notice flag 페이지는 userid 값이 admin 이면 웹 서버 local 환경에서 flag를 memo로 추가하는 페이지다.
flag 페이지는 csrf 페이지를 웹 서버 local IP로 접속하는 페이지이다.
라고 할 수 있다.
한마디로 flag페이지를 이용해 notice flag에 접속해 memo로 flag를 출력하면 될 것 같다.
notice flag페이지 접속을 유도해야 하므로,
<img src=/admin/notice_flag?userid=admin>
다음과 같이 접속시도가 가능한 구문인 img태그를 활용한다.
제출 후 memo페이지로 가보면?
다음과 같이 flag가 나오는 것을 볼 수 있다. 제출하면 끝이다.