https://dreamhack.io/wargame/challenges/269
csrf-2
여러 기능과 입력받은 URL을 확인하는 봇이 구현된 서비스입니다. CSRF 취약점을 이용해 플래그를 획득하세요. 문제 수정 내역 2023.07.18 css, html 제공 및 read_url() 코드 일부가 변경되었습니다. Referen
dreamhack.io
문제 설명
여러 기능과 입력받은 URL을 확인하는 봇이 구현된 서비스입니다.
CSRF 취약점을 이용해 플래그를 획득하세요.
문제 풀이
vuln, flag, login 페이지로 구성되어 있다. 들어가자마자 로그인하라는 문구가 눈에 띈다.
1. vuln page
'script' 문자열이 취약성을 갖고 있으므로 '*'로 자동 치환되는 것을 확인할 수 있다.
script 말고 다른 방법을 이용해야 한다는 점을 기억하자.
코드를 보면 script 외에도 frame, on 등의 태그들도 필터링되고 있다.
@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
2. flag
입력한 param값이 전달되는 공간이다.
csrf-1 문제와 달리, 로그인한 사용자의 세션 정보를 이용해 어드민 여부를 확인하고 있다.
def read_url(url, cookie={"name": "name", "value": "value"}):
cookie.update({"domain": "127.0.0.1"})
try:
service = Service(executable_path="/chromedriver")
options = webdriver.ChromeOptions()
for _ in [
"headless",
"window-size=1920x1080",
"disable-gpu",
"no-sandbox",
"disable-dev-shm-usage",
]:
options.add_argument(_)
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:
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("/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>'
3. login
로그인 요청에 대하여 비밀번호 확인 후 세션을 저장한다.
users = {
'guest': 'guest',
'admin': FLAG
}
@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>'
4. change_password
화면상에는 보이지 않지만, 코드를 보면 /change_password API가 열려있음을 확인할 수 있다.
param으로 받은 pw 값으로 비밀번호를 변경하는 메소드이다.
@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'
Sol.
- script를 사용할 수 없으므로 img나 div와 같은 다른 태그를 이용하자
- memo는 결과가 출력되는 공간이므로 찾고자 하는 flag를 memo 페이지에 출력하게 하자
- flag의 입력값이 vuln을 거쳐 실행되므로 flag > vuln > memo에 FLAG 출력 플로우를 구성해보자
- 어드민 세션을 확보해야 하므로 change_password 메서드로 어드민의 비밀번호를 내가 원하는 값으로 바꿔버리자
결과
아래의 스크립트를 flag의 param에 넣어주면, 비밀번호가 1로 변경된다.
<img src="/change_password?pw=1">
로그인 페이지로 돌아와, 변경한 비밀번호 값 '1'을 넣어보자.
로그인이 되며 flag 얻기 성공!
'Security > War Games' 카테고리의 다른 글
[Dreamhack Lv.1] XSS-1 (0) | 2024.12.20 |
---|---|
[Dreamhack Lv.1] csrf-1 (0) | 2024.12.20 |
[Dreamhack Lv.1] simple_sqli (0) | 2024.12.20 |