ZINO
HomeResearchBlogTagsPlaygroundStackPortfolio
⌘K

레드팀 · AI 자동화 · 시스템 엔지니어링

직접 만들고, 실험하고, 기록한다

개인정보처리방침·이용약관

© 2026 ZINO LAB

← 태그 목록

Tag Archive

#flask

이 태그가 포함된 글을 최신순으로 모아봤어요.

31
posts
BLOG2026-07-29
[🥈 Silver 2] 검사기는 문자열을 보고 curl 은 URL 을 파싱한다 — DreamHack curling 풀이
blog

[🥈 Silver 2] 검사기는 문자열을 보고 curl 은 URL 을 파싱한다 — DreamHack curling 풀이

"simple api server" 라는 한 줄 설명이 전부인 문제. 46줄짜리 Flask 앱이 url 파라미터를 받아 curl 로 대신 요청해 주는데, 호스트 화이트리스트가 접두사 비교라서 URL 의 userinfo 를 끼워 넣으면 통과하고, 엔드포인트 차단은 문자열 끝만 봐서 물음표 하나에 무너진다. 두 구멍을 한 줄에 겹쳐 놓으면 서버가 자기 자신에게 요청을 보내고 플래그를 뱉는다.
#dreamhack#ctf#web+6
 
BLOG2026-07-21
[🥉 Bronze 4] 화면의 "10초 뒤 공개"는 클라이언트만의 약속이었다 — DreamHack Test Your Luck 풀이
blog

[🥉 Bronze 4] 화면의 "10초 뒤 공개"는 클라이언트만의 약속이었다 — DreamHack Test Your Luck 풀이

0부터 10000 사이의 숫자를 맞히면 flag를 준다는 게임이다. 정답은 서버 프로세스가 뜰 때 딱 한 번 뽑혀 전역변수에 박히고, 그 뒤로는 절대 바뀌지 않는다. 화면에는 "결과는 10초 뒤에 공개됩니다"라고 적혀 있지만, 그 10초는 서버가 강제하는 규칙이 아니라 브라우저 자바스크립트의 setTimeout 한 줄일 뿐이다. 직접 요청을 찔러보면 서버는 밀리초 단위로 즉시 대답한다 — 그러면 10001가지 뿐인 정답을 그냥 처음부터 끝까지 다 물어보면 된다.
#dreamhack#ctf#web+5
BLOG2026-07-20
[🥈 Silver 2] 검사한 곳과 실제로 간 곳이 다를 수 있다 — DreamHack crawling 풀이
blog

[🥈 Silver 2] 검사한 곳과 실제로 간 곳이 다를 수 있다 — DreamHack crawling 풀이

URL을 넣으면 그 페이지를 대신 가져와 보여주는 미니 크롤러다. 127.0.0.1 같은 내부 주소를 직접 넣으면 "전역 IP인지" 검사하는 필터가 바로 막는다. 그런데 이 필터는 딱 하나만 확인한다 — 우리가 처음 준 URL의 호스트가 가리키는 IP. requests 라이브러리는 기본적으로 HTTP 리다이렉트를 자동으로 따라간다는 사실은 전혀 고려되지 않았다. 공인 IP를 가진 내 서버가 302로 127.0.0.1의 관리자 페이지를 가리키게만 하면, 검사는 내 공인 서버를 보고 통과시키고 실제 응답은 내부 페이지에서 그대로 새어나온다.
#dreamhack#ctf#web+4
BLOG2026-07-20
[🥇 Gold 4] 텍스트는 못 읽어도 속성은 읽힌다 — DreamHack CSS Injection 풀이
blog

[🥇 Gold 4] 텍스트는 못 읽어도 속성은 읽힌다 — DreamHack CSS Injection 풀이

모든 페이지의 <style> 블록에 쿼리스트링 하나가 이스케이프 없이 그대로 꽂힌다. 플래그는 관리자 메모 텍스트 안에 있는데, 처음 세운 계획 — @font-face의 unicode-range로 글자 존재 여부를 외부 요청으로 알아내고 text-indent로 위치를 잘라내는 방법 — 은 실제 브라우저로 검증해보니 완전히 틀렸다. 화면에 안 보이게 잘라낸 글자의 폰트도 브라우저는 똑같이 요청해버린다. 결국 플래그 텍스트가 아니라 마이페이지의 API 토큰이 <input value="..."> 속성으로 노출된다는 걸 발견하고서야 고전적인 속성 선택자 유출로 방향을 틀었다. 관리자 봇을 8번만 왕복시켜 8자리 토큰을 통째로 복원했다.
#dreamhack#ctf#web+4
BLOG2026-07-20
[🥈 Silver 2] 연결은 미리 다 맺어두고 마지막 1바이트만 아꼈다 — DreamHack chocoshop 레이스 컨디션 풀이
blog

[🥈 Silver 2] 연결은 미리 다 맺어두고 마지막 1바이트만 아꼈다 — DreamHack chocoshop 레이스 컨디션 풀이

쿠폰 하나로 1000원, 플래그는 2000원. 세션당 쿠폰은 딱 한 번만 발급된다는 제약이 있었지만, "이미 받았는지 확인"과 "받았다고 기록"하는 두 Redis 명령 사이에는 짧은 틈이 있었다. requests + ThreadPoolExecutor로 30개를 동시에 쏴봤지만 결과는 1장뿐이었다 — 스레드마다 새로 여는 connect() 오버헤드가 도착 시각을 흩어놓은 탓이었다. 소켓을 전부 미리 열어두고, 헤더의 마지막 1바이트만 아껴뒀다가 한 번에 흘려보내는 single-packet 기법까지 가서야 100건 중 90건 넘는 쿠폰을 한 번에 받아냈다.
#dreamhack#ctf#web+5
BLOG2026-07-20
[🥇 Gold 4] 범인은 서버가 아니라 curl이었다 — DreamHack Flask-Dev 풀이
blog

[🥇 Gold 4] 범인은 서버가 아니라 curl이었다 — DreamHack Flask-Dev 풀이

16줄짜리 Flask 앱. 임의 파일을 읽어주는 라우트가 있고, debug=True로 Werkzeug 인터랙티브 콘솔까지 열려 있다. 그런데 정작 "../"로 상위 디렉터리를 탈출하려 하면 매번 서버가 파일명을 그대로 못 찾는다. 범인은 서버의 방어 로직이 아니라, 요청을 보내는 curl 자신이 URL을 미리 손질해 보내고 있었다는 사실이었다.
#dreamhack#ctf#web+4
BLOG2026-07-13
[🥉 Bronze 3] 메뉴판이 곧 정답 — DreamHack amocafe 풀이
blog

[🥉 Bronze 3] 메뉴판이 곧 정답 — DreamHack amocafe 풀이

카페 사이트가 좋아하는 메뉴라며 이상한 문자열을 보여주고, 그 "메뉴 번호"를 맞히면 flag를 준다. 알고 보면 그 문자열은 flag에서 잘라낸 19자리 숫자(org)의 16진수를 살짝 가린 것이다. nibble a는 "10", b는 "_"로만 바뀌어 있어 그대로 되짚으면 풀린다.
#dreamhack#ctf#web+4
 
BLOG2026-07-13
[🥉 Bronze 2] 세션 쿠키가 pickle이면 그건 코드다 — DreamHack web-deserialize-python 풀이
blog

[🥉 Bronze 2] 세션 쿠키가 pickle이면 그건 코드다 — DreamHack web-deserialize-python 풀이

세션을 base64(pickle)로 주고받는 Flask 앱이다. check_session이 사용자가 준 값을 그대로 pickle.loads 한다. pickle은 역직렬화 중 객체의 __reduce__를 실행하므로, flag를 읽어 반환하는 객체를 dict에 심어 응답 화면에 flag를 그대로 띄웠다.
#dreamhack#ctf#web+5
BLOG2026-07-12
[🥉 Bronze 2] 회수 가능한 입금으로 코인 값을 흔들다 — DreamHack out of money 풀이
blog

[🥉 Bronze 2] 회수 가능한 입금으로 코인 값을 흔들다 — DreamHack out of money 풀이

DHD 코인 가격이 풀 잔고 비율(dhc_balance/dhd_balance)로만 정해지는데, 그 잔고를 바꾸는 입금이 음수 value로 전액 회수된다. 싸게 사둔 DHD를 DHC 대량 입금으로 값을 9배 띄운 뒤 되팔아 공짜 차익을 만들고, 대출을 상환해도 남는 코인으로 flag를 받았다.
#dreamhack#ctf#web+5
BLOG2026-07-07
[🥉 Bronze 3] X-Forwarded-For 헤더로 커맨드 인젝션 — DreamHack what-is-my-ip 풀이
blog

[🥉 Bronze 3] X-Forwarded-For 헤더로 커맨드 인젝션 — DreamHack what-is-my-ip 풀이

"당신의 IP를 알려주는" 단순한 Flask 앱. 그런데 IP를 구할 때 X-Forwarded-For 헤더값을 bash -c "echo {ip}" 에 그대로 넣는다. 헤더에 ;cat /flag 를 실으면 명령이 그대로 실행되어 플래그가 나온다.
#dreamhack#ctf#web+5
 
BLOG2026-07-06
[🥉 Bronze 3] cat 대신 실행 — DreamHack File Vulnerability Advanced 풀이
blog

[🥉 Bronze 3] cat 대신 실행 — DreamHack File Vulnerability Advanced 풀이

경로검증 없는 /file 엔드포인트로 path traversal, /admin의 API_KEY는 Dockerfile 더미값이라 무의미해 보이지만 /proc/self/environ을 읽으면 런타임 실제 키가 그대로 나온다. 그 키로 연 /admin RCE로 flag를 찾아도 cat은 500만 뜬다 — flag가 ---x--x--x(실행 전용) ELF라 읽지 말고 실행해야 한다.
#dreamhack#ctf#web+5
BLOG2026-07-05
[🥈 Silver 4] 이미지 태그 하나면 충분했다 — DreamHack CSRF Advanced 풀이
blog

[🥈 Silver 4] 이미지 태그 하나면 충분했다 — DreamHack CSRF Advanced 풀이

reflected 페이지의 XSS 필터가 frame/script/on 세 단어를 '*'로 지우지만, 정작 CSRF에 필요한 건 <img src=...> 하나뿐이다. 게다가 비밀번호 변경 API의 CSRF 토큰이 md5(username+접속IP)라서, 봇이 항상 127.0.0.1로 접속한다는 사실만으로 토큰을 미리 계산해버릴 수 있었다.
#dreamhack#ctf#web+4
BLOG2026-06-30
[🥉 Bronze 3] raw socket 으로 만든 SSRF — localhost 전용 /admin 우회 — DreamHack proxy-1 풀이
blog

[🥉 Bronze 3] raw socket 으로 만든 SSRF — localhost 전용 /admin 우회 — DreamHack proxy-1 풀이

임의 host:port 로 raw 바이트를 보내고 응답을 돌려주는 /socket 이 그대로 SSRF 발판이 된다. flag 를 주는 /admin 은 remote_addr 가 127.0.0.1 일 때만 동작하는데, /socket 으로 서버 자신의 8000 포트에 생 HTTP 요청을 흘려보내면 이 검사를 통과한다. 요청에 필요한 헤더·쿠키·폼까지 직접 적어 보내 flag 를 회수했다.
#dreamhack#ctf#web+5
BLOG2026-06-28
[🥉 Bronze 2] GET만 열어둔 Flask 라우트를 HEAD로 뚫기 — DreamHack blind-command 풀이
blog

[🥉 Bronze 2] GET만 열어둔 Flask 라우트를 HEAD로 뚫기 — DreamHack blind-command 풀이

Flask 라우트에 methods=['GET']만 걸어도 HEAD와 OPTIONS는 자동으로 따라붙는다. HEAD 요청이면 request.method가 'HEAD'가 되어 else 분기의 os.system(cmd)이 실행되지만, HEAD 응답엔 본문이 없어 출력이 보이지 않는다. sleep 시간차로 flag를 한 글자씩 빼냈다.
#dreamhack#ctf#web+4
BLOG2026-06-28
[🥈 Silver 3] 404 페이지에 식을 넣으면 답을 한다 — DreamHack simple-ssti 풀이
blog

[🥈 Silver 3] 404 페이지에 식을 넣으면 답을 한다 — DreamHack simple-ssti 풀이

Flask 404 핸들러가 사용자 경로(request.path)를 템플릿 문자열에 그대로 박은 뒤 render_template_string 으로 렌더한다. 없는 경로에 Jinja2 식을 실으면 서버가 평가하는 SSTI 가 터지고, FLAG 가 app.secret_key 로 들어가 있어 config 한 줄로 그대로 새어 나온다.
#dreamhack#ctf#web+5
BLOG2026-06-27
[🥉 Bronze 2] 칸 하나짜리 로그인 폼으로 admin 행만 콕 — DreamHack simple_sqli_chatgpt 풀이
blog

[🥉 Bronze 2] 칸 하나짜리 로그인 폼으로 admin 행만 콕 — DreamHack simple_sqli_chatgpt 풀이

로그인 폼에 입력칸이 userlevel 하나뿐인데, 그 값이 따옴표째 쿼리에 박힌다. guest와 admin 둘 다 userlevel이 0이라 흔한 or 1=1은 첫 행 guest를 데려온다. userid를 직접 지정해 admin 행만 남긴 과정을 정리했다.
#dreamhack#ctf#web+5
BLOG2026-06-26
[🥉 Bronze 2] 대소문자 한 끗으로 뚫는 XSS 필터 — DreamHack XSS Filtering Bypass 풀이
blog

[🥉 Bronze 2] 대소문자 한 끗으로 뚫는 XSS 필터 — DreamHack XSS Filtering Bypass 풀이

script·on·javascript: 를 지우는 필터가 있는데도, 검사는 소문자로 하고 치환은 원본 그대로 한다는 한 줄 차이 때문에 <sCrIpt> 가 그대로 살아남는다. 봇이 들고 방문하는 flag 쿠키를 동기 XHR로 /memo에 흘려 회수한 과정을 정리했다. location.href 비동기 전송이 driver.quit 레이스에 걸려 실패했던 삽질까지 담았다.
#dreamhack#ctf#web+6
BLOG2026-06-25
[🌱 새싹] admin 세션 ID가 단 1바이트였다 — DreamHack session 풀이
blog

[🌱 새싹] admin 세션 ID가 단 1바이트였다 — DreamHack session 풀이

로그인 세션을 쿠키 하나로 식별하는 Flask 앱. 일반 사용자 세션은 4바이트 난수로 만들지만, 서버가 시작할 때 미리 깔아 두는 admin 세션은 os.urandom(1) — 단 1바이트(00~ff, 256가지)로 만든다. sessionid 쿠키를 256번 갈아 끼우면 admin 세션을 그대로 주워 flag를 읽는다.
#dreamhack#ctf#web+5
 
BLOG2026-06-24
[🥈 Silver 2] SSRF로 내부 포트 스캔 — netloc 문자열 필터를 0.0.0.0으로 우회 — DreamHack web-ssrf 풀이
blog

[🥈 Silver 2] SSRF로 내부 포트 스캔 — netloc 문자열 필터를 0.0.0.0으로 우회 — DreamHack web-ssrf 풀이

이미지 뷰어가 입력한 URL을 서버가 대신 가져와 보여준다. 같은 프로세스 안에 flag.txt를 서빙하는 내부 SimpleHTTPRequestHandler가 1500~1800 중 한 포트에 떠 있다. 필터는 netloc에 `127.0.0.1`/`localhost` 문자열이 있는지만 보므로, 같은 loopback을 가리키는 `0.0.0.0`으로 우회하고 포트를 전수 스캔해 flag.txt를 읽었다.
#dreamhack#ctf#web+5
BLOG2026-06-21
[🥉 Bronze 3] img 한 줄로 봇을 시켜 admin 비밀번호를 바꾸기 — DreamHack csrf-2 풀이
blog

[🥉 Bronze 3] img 한 줄로 봇을 시켜 admin 비밀번호를 바꾸기 — DreamHack csrf-2 풀이

admin의 비밀번호가 곧 FLAG인데, 직접 알아낼 방법은 없다. 대신 127.0.0.1에서 admin 세션으로 도는 봇에게 /change_password를 대신 호출하게 만들면 admin 비번을 내가 정한 값으로 덮어쓸 수 있다. script·frame·on을 막는 필터는 img 태그 한 줄로 통과하고, 바뀐 비번으로 admin 로그인해 flag를 회수한 과정을 정리했다.
#dreamhack#ctf#web+6
BLOG2026-06-20
[🥉 Bronze 1] img 한 줄로 봇을 빌려 내부 전용 /admin 호출하기 — DreamHack csrf-1 풀이
blog

[🥉 Bronze 1] img 한 줄로 봇을 빌려 내부 전용 /admin 호출하기 — DreamHack csrf-1 풀이

외부에서 직접 부르면 Access Denied인 /admin/notice_flag를, 127.0.0.1에서 도는 봇의 브라우저로 대신 호출하게 만드는 전형적인 CSRF 문제. script·frame·on을 막는 XSS 필터는 img 태그 한 줄이면 그냥 통과하고, 봇이 그 이미지를 로드하며 flag를 memo에 적어준다. 그 memo를 읽어 플래그를 회수한 과정을 정리했다.
#dreamhack#ctf#web+5
BLOG2026-06-20
[🥉 Bronze 2] 따옴표 하나로 admin 행만 콕 집기 — DreamHack simple_sqli 풀이
blog

[🥉 Bronze 2] 따옴표 하나로 admin 행만 콕 집기 — DreamHack simple_sqli 풀이

로그인 쿼리에 userid가 따옴표째 그대로 박힌다. admin 행만 정확히 반환시켜야 플래그가 나오는데, 흔한 ` or 1=1`은 첫 행 guest를 데려온다. 주석으로 패스워드 조건을 지워 admin 행만 집어낸 과정을 정리했다.
#dreamhack#ctf#web+5
 
BLOG2026-06-20
[🌱 새싹] 필터는 업로드에만 걸려 있었다 — DreamHack file-download-1 풀이
blog

[🌱 새싹] 필터는 업로드에만 걸려 있었다 — DreamHack file-download-1 풀이

메모 저장소 Flask 앱. 업로드 경로는 파일명에 '..'가 들어가면 막지만, 읽기 경로(/read)에는 같은 검사가 빠져 있다. uploads 디렉토리를 기준으로 경로를 그대로 이어붙이는 탓에 /read?name=../flag.py 한 줄로 상위 디렉토리의 flag.py를 그대로 읽어 플래그를 가져온다.
#dreamhack#ctf#web+4
BLOG2026-06-04
[🥉 Bronze 1] 봇의 admin 헤더를 빌려 /whoami 털기 — DreamHack Are you admin? 풀이
blog

[🥉 Bronze 1] 봇의 admin 헤더를 빌려 /whoami 털기 — DreamHack Are you admin? 풀이

intro 페이지의 name이 `| safe`로 이스케이프 없이 렌더되는 reflected XSS. 봇은 모든 요청에 admin Basic-Auth 헤더를 자동으로 붙이고 다니므로, 봇 컨텍스트에서 /whoami를 fetch하면 admin으로 인증돼 FLAG가 나온다. 이를 webhook으로 빼낸 과정.
#dreamhack#ctf#web+5
BLOG2026-06-04
[🥉 Bronze 2] 봇의 쿠키를 /memo로 흘려 회수하기 — DreamHack xss-1 풀이
blog

[🥉 Bronze 2] 봇의 쿠키를 /memo로 흘려 회수하기 — DreamHack xss-1 풀이

/vuln이 입력을 이스케이프 없이 그대로 뱉는 reflected XSS. flag를 쿠키로 들고 페이지를 방문하는 봇에게 스크립트를 주입해 document.cookie를 같은 서버의 /memo로 흘려보내고, 그 memo를 직접 읽어 플래그를 회수한 과정을 정리했다.
#dreamhack#ctf#web+5
 
BLOG2026-06-04
[🥉 Bronze 3] sleep 한 줄로 KEY를 흘리게 만들기 — DreamHack BypassIF 풀이
blog

[🥉 Bronze 3] sleep 한 줄로 KEY를 흘리게 만들기 — DreamHack BypassIF 풀이

Admin KEY를 모르는데 KEY는 md5(flag)다. 명령 필터를 통과하는 sleep으로 일부러 타임아웃을 내면 except 분기가 KEY를 응답에 그대로 흘린다. 유출한 KEY를 다시 제출해 플래그를 받아낸 과정을 정리했다.
#dreamhack#ctf#web+4
 
BLOG2026-06-04
[🥉 Bronze 3] X-User·Referer 헤더 위조로 비밀 문서 읽기 — DreamHack DreamDocs 풀이
blog

[🥉 Bronze 3] X-User·Referer 헤더 위조로 비밀 문서 읽기 — DreamHack DreamDocs 풀이

문서 열람 권한을 Referer와 X-User 헤더로만 판단하는 게시판이다. 둘 다 요청자가 마음대로 보내는 값이라, X-User: admin 과 Referer: /share 를 위조하면 confidential 문서의 본문(FLAG)이 그대로 열린다.
#dreamhack#ctf#web+6
 
BLOG2026-06-04
[🥉 Bronze 4] 부분 일치 누출로 4자리 사물함 번호 복원 — DreamHack random-test 풀이
blog

[🥉 Bronze 4] 부분 일치 누출로 4자리 사물함 번호 복원 — DreamHack random-test 풀이

로그인 폼이 전체 값이 아니라 입력 길이만큼의 prefix만 비교하면, 한 글자씩 정답을 떠보는 oracle이 된다. 2.1억 조합짜리 사물함 번호+비밀번호를 176번의 요청으로 복원해 플래그를 얻는 과정을 정리했다.
#dreamhack#ctf#web+5
 
BLOG2026-05-29
[🌱 새싹] 내부 전용 API를 ../ 한 방으로 뚫기 — DreamHack Path Traversal 풀이
blog

[🌱 새싹] 내부 전용 API를 ../ 한 방으로 뚫기 — DreamHack Path Traversal 풀이

127.0.0.1 에서만 호출 가능한 internal API 서버. 사용자 입력이 그대로 내부 요청 URL에 들어가는 SSRF + Path Traversal 취약점을 이용해, `../flag` 한 줄로 접근 제한된 /api/flag 엔드포인트의 플래그를 빼낸다.
#dreamhack#ctf#web+4
 
BLOG2026-05-23
[⭐⭐⭐⭐] 디버그 엔드포인트가 세션 전체를 토해낸다 — DreamHack session-basic 풀이
blog

[⭐⭐⭐⭐] 디버그 엔드포인트가 세션 전체를 토해낸다 — DreamHack session-basic 풀이

Flask 세션 인증 서비스에서 개발자가 "나중에 고칠게" 주석으로 남겨둔 /admin 엔드포인트가 session_storage 딕셔너리 전체를 JSON으로 반환한다. 서버 부팅 시 미리 생성된 admin sessionid를 그대로 가져와 쿠키에 박으면 끝.
#dreamhack#ctf#web+4
 
BLOG2026-03-31
[🥈 Silver 4] 참/거짓만 캐물어 admin 비밀번호를 한 글자씩 — DreamHack Secret Data 풀이
blog

[🥈 Silver 4] 참/거짓만 캐물어 admin 비밀번호를 한 글자씩 — DreamHack Secret Data 풀이

/search 엔드포인트의 SQL 문자열 직접 결합 취약점을 이용해 블라인드 boolean 방식으로 admin 비밀번호를 한 글자씩 추출하고, /admin에서 최종 플래그를 획득한 전 과정을 정리합니다.
#dreamhack#ctf#sqli+3