dreamhack | webhacking
01. csrf-1
CSRF (cross-site-request forgery) 이란?
웹 어플리케이션에서 발생 가능한 보안 취약점 중 하나로 인증된 사용자의 권한을 이용하여, 사용자의 의지와 상관없이 공격자가 의도한 요청을 서버로 보내는 것.
xss를 이용한 공격이 사용자가 특정 웹사이트를 신용하는 점을 노리는 공격이라면, csrf는 특정 웹사이트가 사용자의 웹 브라우저를 신용하는 상태를 노리는 공격.
+ 막기 위해서 사용되는 대중적인 보안 메커니즘은 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"}): # url과 쿠키를 인자로 받는 함수
cookie.update({"domain": "127.0.0.1"}) # cookie의 도메인을 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/") # 접속url : 127.0.0.1의 8000포트
driver.add_cookie(cookie) # 인자로 받은 쿠키값으로 브라우저 쿠키 업로드
driver.get(url) # 브라우저가 접속할 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"}):
# 인자로 받은 param을 urlib.parse.quote함수를 통해 인코딩
url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}"
return read_url(url, cookie) # url값을 매개인자로 전달하며 read_url호출
@app.route("/")
def index():
return render_template("index.html")
@app.route("/vuln")
def vuln():
# request.args.get함수를 통해 GET요청으로 전달된 param값을 가져오고, lower함수로 parma값을 모두 소문자로 변경
param = request.args.get("param", "").lower()
xss_filter = ["frame", "script", "on"] # xss공격을 방지하기 위해 걸러낼 문자열
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): # check_csrf가 false를 반환시에
# 공격자한테 wrong반환
return '<script>alert("wrong??");history.go(-1);</script>'
return '<script>alert("good");history.go(-1);</script>' # good
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"
# addr가 127.0.0.1이면서 userid == "admin"인 경우
memo_text += f"[Notice] flag is {FLAG}\n" # 전역변수에 flag추가되서 memo에서 확인가능
return "Ok"
app.run(host="0.0.0.0", port=8000)
각 페이지를 정리하면
/vul : param을 받아 xss_filtering을 해서 param을 반환
/flag : post로 데이터를 보내면 check_scrf () -> read_url()을 통해 domain:127.0.0.1이 추가된다.
/memo : 전역변수에 차곡차곡 덧붙여줘버리기
/admin/notice_flag: addr가 127.0.0.1이면서 userid == "admin"인 경우에 전역변수를 통해 flag 추가
즉, flag를 찾기 위해 위의 두 조건을 만족해야한다. 이때, /flag를 통해 /admin뭐시기 페이지로 연결한 후, 파라미터로 userid == "admin"조건을 추가해서 exploit해야한다. 이때 vul에서 필터링되지않는 img태그를 통해 공격을 실행한다.
02. file-download-1
사이트를 열어보면 다음과 같다.
메모내용을 입력하고 업로드하면 바로 업로드된 메모를 볼 수 있다.
이제 코드를 보자
#!/usr/bin/env python3
import os
import shutil
from flask import Flask, request, render_template, redirect
from flag import FLAG
APP = Flask(__name__)
UPLOAD_DIR = 'uploads'
@APP.route('/')
def index():
files = os.listdir(UPLOAD_DIR)
return render_template('index.html', files=files)
@APP.route('/upload', methods=['GET', 'POST'])
def upload_memo():
if request.method == 'POST': # post요청시
filename = request.form.get('filename') # 파일이름이랑
content = request.form.get('content').encode('utf-8') # 파일내용 겟
if filename.find('..') != -1: # filename에 '..'이 있을 경우
return render_template('upload_result.html', data='bad characters,,')
with open(f'{UPLOAD_DIR}/{filename}', 'wb') as f: # 업로드된 파일을 바이너리모드wb로 open
f.write(content) # 내용저장
return redirect('/')
return render_template('upload.html')
@APP.route('/read')
def read_memo():
error = False
data = b''
filename = request.args.get('name', '') # name매개변수를 가져와 filename에 저장
try:
with open(f'{UPLOAD_DIR}/{filename}', 'rb') as f:
data = f.read() # 파일을 read
except (IsADirectoryError, FileNotFoundError):
error = True # excepion발생시 error설정
return render_template('read.html',
filename=filename,
content=data.decode('utf-8'),
error=error) # read.html 템플릿을 렌더링하고 내용 전달
if __name__ == '__main__':
if os.path.exists(UPLOAD_DIR): # upload dir경로에 dir가 있는지 확인
shutil.rmtree(UPLOAD_DIR) # remove
os.mkdir(UPLOAD_DIR) # upload_dir경로에 새로운 dir생성
APP.run(host='0.0.0.0', port=8000)
생성한 파일의 주소를 보면 filename을 읽어서 반환하는 것을 알 수 있다.
문제에서 flag.py를 열어서 flag를 찾으라고 하는 거보니 filename을 통해 exploit하면 될 것 같다.
상대적 디렉토리를 이용해서 공격하면 /upload에서 bad response가 나와서 /read에서 exploit을 해주어야한다 !
위의 주소를 넣어주면
dreamhack | forensic
03. snowing!
파일을 다운로드받아보면 snow.jpeg파일과 flag.txt가 있다. txt파일을 hxd에 넣어보니
분명 txt파일에서 공백으로 보였던 부분이 요상한 거로 가득 차 있다.
whitespace steganography를 통해 공백에 은닉된 데이터를 찾아낼 수 있다. 여러 툴 중에 snow를 unix기반의 os에서 이용해보았다. C옵션을 사용하여 은닉된 데이터를 추출가능했다 !
'wargame' 카테고리의 다른 글
write-up 07 (0) | 2023.05.25 |
---|---|
write-up 06 (0) | 2023.05.18 |
write-up 04 (0) | 2023.05.04 |
write-up 03 (0) | 2023.04.05 |
write-up 02 (0) | 2023.03.30 |