Step 176. CTF First Taste 1: Five Web Challenges — Entering the Real Arena
Level 3 — CTF in the Field and Attack Skills Deepened | Difficulty ★★★☆☆ | Estimated time: 5 hours
Prerequisites: Level 2 complete (Step 175). You’ve practiced the web vulnerability basics (SQLi, XSS, upload, cookies).
⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime. The platforms appearing in this chapter — Dreamhack, pwnable.kr, and others — are legal learning platforms built to be solved.
- What you need: Python (Flask) and
curl. If you’ll use an external platform, a Dreamhack account. - Caution: in this environment we don’t connect to external CTF servers. Platform screens appear as "screen examples," and the five backbone challenges are measured on a local mini CTF you launch yourself.
This is the start of Level 3. If everything through Level 2 was basic conditioning, CTF is the real match. Each problem hands you a server or a file, and finding the vulnerability and submitting the flag becomes points. Today’s goal is not points but feel — the day you measure with your body "how are real problems different from class."
1. Learning Objectives
By the end of this chapter, you will be able to:
- Explain CTF’s format (flags, categories, submission)
- Know the approach order for web problems’ representative types (information exposure, headers, cookies, access control)
- Approach problems with the habit of writing "a guess at this feature’s inner workings" first, then solving
- Record solving results by type, time spent, and stuck point
- Classify unsolved problems as "concept gap / tooling / research"
2. Background Knowledge — Today’s Tools and Concepts
Today’s Tools at a Glance
| Category | Details |
|---|---|
| Language/environment | Python (Flask mini CTF server) + curl (measured: Python 3.12, Flask 3.1) |
| Today’s commands | curl -s (body), curl -sI (headers), curl --cookie (cookie manipulation) |
| Concepts needed | flag format, CTF categories, information exposure, cookie manipulation (Step 134), IDOR (Step 149) |
| Today’s artifact | a 5-row solving record sheet + one paragraph of self-assessment |
2-1. CTF — Capture The Flag
CTF (Capture The Flag) is a competition format where you find a hidden string (the flag) in a vulnerable program and submit it. The format beginners meet is mostly Jeopardy style — picking problems to solve from a per-field problem list.
Flags have an agreed format. Like DH{...} (Dreamhack), FLAG{...}, flag{...} — the "prefix before the braces" is fixed per problem, and finding a string of that format and putting it in the submission box marks it correct. So CTF play is essentially "finding a string of the agreed shape" — the eye that spots suspicious strings is the first tool.
2-2. Categories — Five Events
CTF problems divide by field. Across early Level 3 we taste five in order:
| Category | What kind of event it is |
|---|---|
| Web | obtaining flags via web service vulnerabilities — the field version of Level 2’s web part |
| Pwn | attacking binaries’ memory vulnerabilities (Step 177) |
| Reversing | dissecting executables to grasp their logic (Step 178) |
| Crypto | finding mathematical weaknesses in cipher implementations (Step 179) |
| Forensics/Misc | recovering evidence from files, packets, images (Step 180) |
Today is Web among them. It’s the field you practiced most in Level 2 — perfectly suited as your first match event.
2-3. The Difference Between Class and the Field — No Hints
The biggest difference between class (wargames, DVWA) and CTF is the absence of hints. DVWA has the vulnerability’s name written on the menu, but a CTF problem is just "here’s a service" and nothing more. Finding where the hole is — that itself is the problem.
So the approach order is fixed: ① make a feature list (what can this service do) → ② write a guess at each feature’s inner workings → ③ the point where a guess misfires = a vulnerability candidate. "Finding the misfire" is CTF’s engine.
2-4. Records Are the Score — Today’s Real Task
Today’s report card for the five problems is not "solved or not" but the record sheet. For each problem you fill four cells: solved? | vulnerability type | time spent | stuck point. Even unsolved, if there’s a record, today is a success — those records become the raw material for Step 181 (choosing your main field).
3. Follow Along
3-1. Opening the Arena — A Local Mini CTF
Since we can’t see the servers behind external-platform problems, we launch and solve the five backbone types ourselves. Below is a mini CTF packing web problems’ five classic types into one server. Save it as step176_ctf.py. (For now, I recommend solving first without reading the code — reading it now is like taking an exam with the answer key. After you’ve solved everything, read the code and compare.)
Input (step176_ctf.py)
from flask import Flask, request, make_response
app = Flask(__name__)
F1 = "FLAG{v1ew_s0urce_1s_th3_f1rst_t00l}"
F2 = "FLAG{r0b0ts_txt_1s_n0t_a_d00r}"
F3 = "FLAG{r34d_th3_h34d3rs_c4r3fully}"
F4 = "FLAG{c00k13s_c4n_b3_c00k3d}"
F5 = "FLAG{1d0r_ch4ng3_th3_numb3r}"
@app.route("/")
def index():
return f"""<h1>Mini CTF Arena</h1>
<p>Find the five flags. There are no hints.</p>
<ul><li><a href="/robots.txt">robots.txt</a></li>
<li><a href="/notice">Notice</a></li>
<li><a href="/profile?uid=2">My Profile</a></li></ul>
<!-- dev note: first flag is here. {F1} -->"""
@app.route("/robots.txt")
def robots():
return "User-agent: *\nDisallow: /secret_backup\n", 200, {"Content-Type": "text/plain"}
@app.route("/secret_backup")
def backup():
return f"This is the backup folder. {F2}"
@app.route("/notice")
def notice():
resp = make_response("<h1>Notice</h1><p>What's visible is not all there is on this page.</p>")
resp.headers["X-Flag"] = F3
return resp
@app.route("/enter")
def enter():
if request.cookies.get("admin") == "true":
return f"Welcome, admin. {F4}"
resp = make_response("<p>You are a regular user. Only admins can see the flag.</p>")
resp.set_cookie("admin", "false")
return resp
@app.route("/profile")
def profile():
uid = request.args.get("uid", "2")
if uid == "1":
return f"<h1>Admin Profile</h1><p>Name: admin / rank: highest / {F5}</p>"
return f"<h1>User {uid}'s Profile</h1><p>Name: user{uid} / rank: regular</p>"
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8176)
Run
python step176_ctf.py
Now you are a contestant. http://127.0.0.1:8176/ is today’s arena. Sections 3-2~3-6 below are the solution walkthroughs for each problem — try solving on your own first, and when stuck, unfold only that problem’s section.
Predict: before solving, write one line of guess per problem — "where would they have hidden it?" This book’s repeating rule — guess → confirm — applies as-is in CTF.
3-2. Solving Problem 1 — The View-Source Classic
Look at the main page’s entire HTML. The same job as the browser’s "view source," done with curl:
curl -s http://127.0.0.1:8176/
<li><a href="/profile?uid=2">My Profile</a></li></ul>
<!-- dev note: first flag is here. FLAG{v1ew_s0urce_1s_th3_f1rst_t00l} -->
(Measured 2026-09-09. Front part omitted.)
How to read the output: the flag sits inside an HTML comment <!-- -->. Information invisible on the browser screen but present in the source — memos a developer forgot to delete get stolen in real incidents about as often as flags. The first approach is always reading the full source.
3-3. Solving Problem 2 — robots.txt Is Not a Door
Open the robots.txt that was in the main page’s link list:
curl -s http://127.0.0.1:8176/robots.txt
User-agent: *
Disallow: /secret_backup
(Measured 2026-09-09.)
Disallow is merely a request — "search robots, please don’t come here" — not a lock. To an attacker, it’s rather a "list of paths they want hidden." Let’s go there as-is:
curl -s http://127.0.0.1:8176/secret_backup
This is the backup folder. FLAG{r0b0ts_txt_1s_n0t_a_d00r}
(Measured 2026-09-09.)
How to read the output: this is Step 145 (information exposure) in field form. In real CTFs and pentests, robots.txt, .git/, .DS_Store, and backup files (index.php.bak) are first-priority check items.
3-4. Solving Problem 3 — What’s Visible Isn’t Everything
The notice page has nothing in its body. The place to look at times like this is the response headers:
curl -sI http://127.0.0.1:8176/notice
HTTP/1.1 200 OK
Server: Werkzeug/3.1.8 Python/3.12.14
Date: Wed, 09 Sep 2026 08:08:51 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 88
X-Flag: FLAG{r34d_th3_h34d3rs_c4r3fully}
Connection: close
(Measured 2026-09-09.)
How to read the output: -I is the option that receives only headers. A header starting with X- is a non-standard extension header — something a developer put in arbitrarily. The habit of checking headers when the body looks clean is the very command you used in Step 175 for defense verification. Attack and defense use the same tools.
3-5. Solving Problem 4 — Cookies Can Be Cooked
Accessing /enter, the server hands down a cookie:
curl -si http://127.0.0.1:8176/enter
Set-Cookie: admin=false; Path=/
...
<p>You are a regular user. Only admins can see the flag.</p>
(Measured 2026-09-09.)
You can see the cookie admin=false. A cookie is a value the client keeps — that is, I can change it (Step 134). Change false to true and request again:
curl -s --cookie "admin=true" http://127.0.0.1:8176/enter
Welcome, admin. FLAG{c00k13s_c4n_b3_c00k3d}
(Measured 2026-09-09.)
How to read the output: it worked because the server judged authority trusting only the "cookie value." Remember both: why a real service must never do this (which is why sessions are managed and signed by the server — Steps 131, 134), and that in CTF you meet naive servers like this.
3-6. Solving Problem 5 — Let’s Change the Number
The main page’s last link was /profile?uid=2. It’s "my profile," yet my number is written in the address bar. What if I change the number?
curl -s "http://127.0.0.1:8176/profile?uid=1"
<h1>Admin Profile</h1><p>Name: admin / rank: highest / FLAG{1d0r_ch4ng3_th3_numb3r}</p>
(Measured 2026-09-09.)
How to read the output: this is IDOR (Insecure Direct Object Reference, Step 149) — the server didn’t check "who asked" and only looked at "which number was asked for." Changing the numbers, names, and filenames in the address is the basic calisthenics of web problems.
3-7. Filling the Record Sheet — Today’s Report Card
Organize the five problems’ results into a table:
| Problem | Solved | Vulnerability type | Time spent | Stuck point |
|---|---|---|---|---|
| 1 | O/X | info exposure (HTML comment) | min | e.g., thought of checking comments late |
| 2 | O/X | info exposure (robots.txt) | min | |
| 3 | O/X | info exposure (response headers) | min | |
| 4 | O/X | cookie manipulation (authz bypass) | min | |
| 5 | O/X | IDOR | min |
And one paragraph of self-assessment: "of the five types, which solved fastest, and which blocked me? Is web a viable candidate for my main field?" This paragraph is the evidence material for choosing a field in Step 181.
If you’ll challenge an external platform (Dreamhack), pick five difficulty 2~3 problems here and solve them with the same record sheet. A 2-hour cap per problem; past that, read the write-up, understand it, and fill in the "stuck point" (screen example: a single screen with the problem title, difficulty, server connection address, and flag submission box).
4. Missions & Exercises
Mission — Completing the Five-Problem Record Sheet
Solve the local mini CTF (or five Dreamhack web difficulty 2~3 problems) and complete 3-7’s record sheet. Note: in each problem’s "stuck point" cell, write not only for unsolved cases but also for solved ones — whether your first guess was right. Your guess accuracy rate is your growth speedometer.
Exercises
Exercise 1. Why does it matter that CTF flags have an agreed format (e.g., FLAG{...})? Explain the advantages this format gives during solving.
Exercise 2. Using today’s solution as grounds, explain why robots.txt’s Disallow is not a security device.
Exercise 3. For Problem 4 (cookie manipulation) to be blocked in a real service, what must the server do? Answer with the two devices learned in Steps 131/134.
Exercise 4. Problem 5 (IDOR) and Problem 4 (cookie manipulation) are both "gaining someone else’s authority." Compare which checks the server skipped in each.
5. Model Answers & Completion Criteria
Mission Model Answer
An example record sheet (times differ per person — what matters is that the cells are filled):
1. O | HTML comment info exposure | 5 min | full-source reading habit kicked in right away
2. O | robots.txt path exposure | 2 min | already knew Disallow = list of hidden paths
3. O | response header exposure | 15 min | looked only at the body, thought of headers late → need a curl -I habit
4. O | cookie manipulation | 3 min | saw admin=false and immediately tried true
5. O | IDOR | 4 min | changed uid=2 → 1, first guess hit
Self-assessment: among the info-exposure types (1~3), headers came latest.
Glue a checklist to my hand that goes "body → source → headers."
Web worked with Level 2 skills as-is — a strong main-field candidate.
How to verify: ① are all five rows’ types filled? ② are stuck points concrete actions ("didn’t check headers") rather than "dunno"? ③ does the self-assessment include a next action (a checklist, etc.)?
Exercise Answers
Answer 1. Because it tells you the shape of the answer. Knowing the format means ① when scanning suspicious strings, what you’re looking for is clear (searchable with FLAG{), ② you can confirm whether what you found is the answer, and ③ the submission system can auto-grade. Field tip: for any problem, check the flag format first and start by searching for that prefix — it saves time.
Answer 2. Because Disallow is a "request" sent to robots, not access control. In today’s measurement, /secret_backup opened as-is with no authentication at all. Security comes not from "not telling" but from "refusing even when asked (authentication & authorization)" — robots.txt effectively publishes the very list of paths you want hidden.
Answer 3. ① Keep authority information out of client cookies and manage it with server-side sessions (the cookie holds only a meaningless session ID, a signed value). ② Even when using cookies, attach a signature (HMAC) that prevents forgery — this is what Flask’s session does (Step 131). The core is designing from the premise that "every value the client sends can be manipulated."
Answer 4. Problem 4 skipped where authentication state is stored — it entrusted "whether admin" to a client cookie. Problem 5 skipped the authorization check itself — it never asked whether the logged-in user was entitled to view uid=1’s resource. Their shared root is "the server doesn’t verify the subject who sent the request," and this is the theme running through half of all web vulnerabilities.
Completion Criteria Checklist
- [ ] I can explain CTF’s format (flags, categories, submission)
- [ ] I know the purpose of the flag format (prefix + braces)
- [ ] I solved the local mini CTF’s five problems (or challenged 5 platform problems)
- [ ] I internalized the approach order: "source → hidden paths like robots.txt → headers → cookies → parameters"
- [ ] I filled the record sheet (solved/type/time/stuck point) with five rows
- [ ] I wrote one paragraph of self-assessment (main-field candidate judgment)
- [ ] For unsolved problems, I understood the write-up and replaced it with a record
6. Common Pitfalls & Fixes
Wall 1. I don’t know where to start
Symptom: you open the page and just stare blankly.
Cause: CTF doesn’t give you a menu (the vulnerability’s name). Finding it is itself the problem.
Fix: follow today’s approach order exactly — ① read the page’s full source ② try robots.txt and common filenames ③ view headers (curl -sI) ④ check cookies ⑤ change parameters in the address. These five moves open most beginner web problems.
Wall 2. I can’t solve three or four of the five
Symptom: you finished Level 2, yet you’re on a losing streak.
Cause: that’s normal. Between class and the field there’s one more wall called "no hints," and that wall lowers only with experience.
Fix: don’t score — classify: is it "didn’t know the concept / didn’t know the tool / knew but didn’t try"? Today’s completion criterion is not the solve count but the record sheet. For problems past 2 hours, read the write-up, understand it, and fill in only the "stuck point" accurately — that problem becomes yours next time.
Wall 3. curl output with Korean looks garbled
Symptom: responses containing Korean are garbled in Git Bash.
Cause: a terminal encoding difference (UTF-8 vs CP949).
Fix: flags are ASCII, so solving is unaffected. If you need to check, convert with curl -s URL | iconv -f utf-8 or open it in a browser.
Wall 4. I started the server but can’t connect
Symptom: curl gets connection refused.
Cause: the server process didn’t start, or a previous lab process is holding the port.
Fix: check the port state with netstat -ano | findstr 8176, and if it’s taken, clean up that PID with taskkill /PID number /F and restart. Same treatment as Step 174’s Wall 2.
Wall 5. External-platform problems are far harder than the local ones
Symptom: you solved the whole mini CTF, but Dreamhack difficulty 2 won’t budge.
Cause: a natural difference. The mini CTF holds only today’s five types, while field problems mix combinations of vulnerabilities, filters, and traps.
Fix: apply the approach order you trained locally as-is, but at each stage suspect "combinations." And spread Step 154’s weakness list beside you — the types that come up often are organized there.
7. Summary
Today’s Concepts
| Concept | One-line explanation |
|---|---|
| CTF / flag | A competition of finding and submitting a hidden agreed string — the format (prefix) is a hint |
| Jeopardy style | The beginner-standard format of picking problems from a per-field list |
| Approach order | source → hidden paths → headers → cookies → parameters |
| Information exposure | comments, robots.txt, headers — an ambush of "forgot to delete" information |
| Record sheet | more than solved-or-not, the type and stuck point are the assets |
Today’s Commands
| Command | What it does |
|---|---|
curl -s URL |
Fetching and reading a page’s full source (the CLI version of view-source) |
curl -sI URL |
Viewing only response headers — checking X- extension headers |
curl -s --cookie "k=v" URL |
Requesting with a swapped cookie |
curl -s "URL?p=1" |
Trying changed parameter values (IDOR calisthenics) |
An Instinct More Important Than Commands
Today’s five problems had not one new technique. Reading comments, robots.txt, headers, cookies, parameters — all were Level 2 materials. What changed is the stage. When you meet a problem with no menu, whether the order of where to lay hands first has stuck to your body — that is today’s real score.
And the record habit — "writing the stuck point concretely" — is a study method for this whole field beyond CTF. Today’s five record rows are Level 3’s first page. Whether web suits you or not, these records will tell you.
Once every box is checked, Step 176 is complete. Click the checkbox in the sidebar to save your progress.