[정보보호관리] 웹해킹 5문제 풀이
Dreamhack / Suninatas / Webhacking.kr 풀이 기록입니다.
혹시 이런 경험 있으신가요?
워게임 문제를 열었는데 화면에 아무것도 없는 경우요. 버튼 하나, 입력창 하나가 전부인데 도대체 어디서 시작해야 할지 모르겠는 그 느낌.
사실 그게 힌트입니다. UI가 단순할수록 숨겨놓은 게 있다는 뜻이거든요.
이번 포스팅에서는 그 감각을 익히는 데 딱 좋은 웹 워게임 5문제를 정리해봤습니다.
1. Dreamhack — devtools-sources
🔗 https://dreamhack.io/wargame/challenges/267 유형: WEB | 핵심: 소스맵 분석
"다운로드 버튼 하나가 전부입니다"
문제 페이지에 접속하면 zip 파일을 받을 수 있는 버튼 하나만 있습니다. 서버와 직접 상호작용하는 구조가 아니라는 걸 바로 파악할 수 있었습니다.
압축을 풀고 IntelliJ로 열었습니다. 실행해봤자 단순한 정적 페이지만 나옵니다. 입력창도, 특별한 기능도 없습니다.
이 시점에서 판단했습니다.
"화면에 아무것도 없으면, 파일 내부를 뒤져야 한다."
⌘ + Shift + F로 전체 파일에서 DH 키워드를 검색했습니다.
결과
main.map 파일 안에서 플래그 형태의 문자열이 바로 나왔습니다. 제출하니 정답이었습니다.
핵심 포인트 — 소스맵이 왜 위험한가요?
.map 파일, 즉 소스맵은 번들링·난독화 이전의 원본 코드 구조를 그대로 담고 있습니다.
파일 경로, 변수명, 함수명이 전부 들어있어서 공격자 입장에서는 압축된 JS를 굳이 해석할 필요도 없이 내부 구현을 바로 파악할 수 있습니다.
프로덕션 배포 시에는 소스맵을 반드시 제외하거나, 인증된 내부 도구에서만 접근 가능하도록 제한해야 합니다. 배포 파일에 .map을 그냥 포함하는 건 설계도를 현관문 앞에 붙여놓는 것과 같습니다.
2. Dreamhack — simple-web-request
🔗 https://dreamhack.io/wargame/challenges/830 유형: WEB | 핵심: Flask 라우팅 분석, GET/POST 구분
"단계별 입력 폼, 어디서 실마리를 찾을까요?"
VM URL에 접속하면 두 개의 입력값을 요구하는 step1 폼이 나타납니다. 올바른 값을 입력해야 다음 단계로 넘어가는 구조입니다.
문제는 UI에 아무런 힌트가 없다는 겁니다. 당연히 app.py를 먼저 열었습니다.
Step 1 분석
@app.route("/step1", methods=["GET", "POST"]) def step1(): if request.method == "GET": prm1 = request.args.get("param", "") prm2 = request.args.get("param2", "") if prm1 == "getget" and prm2 == "rerequest": return redirect(url_for("step2", prev_step_num=step1_num)) return render_template("step1.html", text=step1_text)
조건이 그대로 드러나 있습니다. GET 요청으로 ?param=getget¶m2=rerequest를 보내면 step2로 넘어갑니다.
Step 2 분석
step2 페이지 소스코드를 확인하면 숨겨진 hidden_num 값이 있습니다. 이 값을 활용해서 param=pooost, param2=requeeest를 POST 요청으로 보내면 플래그가 나옵니다.
한 가지 중요한 점이 있습니다. /flag를 직접 접근하면 "not yet"만 출력됩니다. 서버가 이전 단계의 상태값을 검증하기 때문에, step1 → step2 흐름을 정상적으로 통과해야만 플래그가 나옵니다.
핵심 포인트 — GET과 POST, 뭐가 다른가요?
방식 데이터 위치 노출 여부 GET URL 쿼리스트링 URL에 그대로 노출 POST 요청 본문(body) URL에 노출되지 않음
GET으로 민감한 데이터를 보내면 브라우저 히스토리, 서버 로그, 프록시 캐시에 전부 기록됩니다. 인증 정보나 중요한 파라미터는 반드시 POST로 처리해야 합니다.
3. Suninatas — Prob 08
🔗 http://suninatas.com/challenge/web08/web08.asp 유형: WEB | 핵심: 소스코드 힌트 분석, 브루트포스
"범위가 보이면 스크립트를 짜면 됩니다"
ID / PW 입력 폼이 있는 로그인 문제입니다. 개발자 도구로 소스코드를 확인하니 힌트가 있었습니다.
-
ID:
admin -
PW: 0 ~ 9999 범위의 숫자
범위가 특정되어 있으면 직접 손으로 입력하는 게 아니라 스크립트를 짜서 돌리는 게 맞습니다.
풀이 스크립트
import requests url = "http://suninatas.com/challenge/web08/web08.asp" baseline_len = None for password in range(0, 10000): params = {"id": "admin", "pw": password} r = requests.post(url, params=params) if password == 0: baseline_len = len(r.text) else: if baseline_len != len(r.text): print("[+] Password Found:", password) break print("[-] Try:", password)
응답 본문의 길이 변화로 성공 여부를 판별하는 방식입니다. 로그인 실패 시 응답 길이는 일정하고, 성공하면 달라진다는 전제를 활용했습니다.
결과
PW는 7707이었습니다. admin / 7707로 로그인하면 Authkey를 얻을 수 있습니다.
핵심 포인트 — 응답 길이 비교, 왜 동작하나요?
상태 코드만으로는 성공/실패를 구분하기 어려울 때 응답 본문 길이 비교가 유효합니다. 실제 서비스라면 계정 잠금(Account Lockout), CAPTCHA, Rate Limiting 중 하나라도 없으면 이런 단순한 브루트포스에 그대로 뚫립니다.
4. Webhacking.kr — old-15
🔗 https://webhacking.kr/challenge/js-2/ 유형: WEB | 핵심: Burp Suite 인터셉트
"개발자 도구를 열 시간도 안 줍니다"
접속하자마자 "접근이 거부되었습니다" Alert이 뜨고 페이지가 되돌아갑니다. JS가 즉시 실행되어 개발자 도구를 열 여유 자체가 없습니다.
이런 상황에서 선택지는 하나입니다. Burp Suite로 응답을 직접 가로채는 것.
풀이 과정
Burp Suite의 Intercept를 켜고 요청을 보내면 서버 응답을 중간에 잡을 수 있습니다. HTML을 확인하니 소스코드 내부에 ?getFlag로 이동하는 <a> 태그가 있었습니다.
해당 링크로 직접 접근하면 문제가 해결됩니다.
핵심 포인트 — JS 리다이렉트는 "막는" 게 아닙니다
JS로 즉시 리다이렉트하는 방어는 서버가 HTML을 내려주는 것 자체를 막지는 않습니다. 응답이 오는 이상 그 내용을 인터셉트해서 읽을 수 있습니다.
클라이언트 측 로직만으로 접근을 막는 건 보안적으로 아무 의미가 없습니다. 접근 제어는 반드시 서버에서 이루어져야 합니다.
5. Webhacking.kr — old-20
🔗 https://webhacking.kr/challenge/code-4/ 유형: WEB | 핵심: 콘솔 JS 주입, 클라이언트 검증 우회
"2초 안에 captcha까지 입력하라고요?"
접속하면 nickname, comment, captcha를 2초 안에 입력해서 제출해야 합니다. 당연히 사람 손으로는 불가능합니다. 시간을 초과하면 "Too Slow…" 페이지로 이동합니다.
소스코드를 확인하니 Submit 버튼 클릭 시 ck() 함수가 호출되고, captcha 검증 로직이 전부 클라이언트 사이드에 있었습니다. captcha_ 숨김 필드의 값을 그대로 captcha 필드에 복사하는 구조입니다.
풀이 코드
function a() { lv5frm.reset(); lv5frm.id.value = "a"; lv5frm.cmt.value = "a"; lv5frm.captcha.value = lv5frm.captcha_.value; lv5frm.submit(); } a();
접속 직후 콘솔에 붙여넣으면 2초 안에 폼이 제출되며 문제가 해결됩니다.
핵심 포인트 — 클라이언트 검증은 검증이 아닙니다
captcha를 포함한 모든 폼 검증이 JS로만 이루어진다면, 콘솔 한 줄로 전부 우회할 수 있습니다. 시간 제한, 입력값 검증, captcha 모두 서버에서 한 번 더 검증해야만 실제로 의미가 있습니다.
클라이언트 측 검증은 사용자 경험을 위한 것이지, 보안을 위한 것이 아닙니다.
마치며
이번 문제들을 풀면서 반복적으로 느낀 게 하나 있습니다.
UI가 단순할수록, 소스를 먼저 봐야 합니다.
-
입력창이 없으면 → 파일 내부를 뒤진다
-
제한 시간이 있으면 → 클라이언트 로직을 우회한다
-
로그인 폼이 있으면 → 서버 코드에서 힌트를 찾는다
결국 "이게 어디에 숨겨져 있을까?"를 먼저 생각하는 습관이 워게임의 핵심입니다.
그리고 한 가지 더. 웹 보안에서 "클라이언트에서만 막는다"는 표현은 없는 말입니다. 막는 건 항상 서버에서 해야 합니다.
VM 코인은 너무 아까웠지만… 그래도 얻은 게 많은 문제들이었습니다. 🙂
저자 소개
DevOps Engineer. 금융과 여행에 관심이 많습니다.