Step 321. CVE Reproduction and Publishing the Analysis Report — The Proof of an Analysis Is Reproduction
Level 4 — Reporting, CVE Analysis & Open-Source Contribution | Difficulty ★★★★☆ | Estimated time: 3 hours
Prerequisites: Step 320’s patch diff analysis and analysis notes complete. You have the
oneday-labpractice repository.
- What you need: Step 320’s practice repository (
oneday-lab) and analysis notes, Git Bash, Python 3.10+. - Caution: ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime. Every reproduction today runs only against local files you created yourself. Real CVE reproduction must also happen strictly inside a VM or container you installed — running a PoC against a production system is illegal regardless of its patch status.
In Step 320 you read a diff and could claim a vulnerability’s mechanism. Today you prove that claim. The proof has exactly one form — showing both that the attack succeeds on the vulnerable version and that it’s blocked on the patched version. Once this contrast experiment succeeds, your analysis notes become an analysis report, and publishing that report on a blog becomes your portfolio as a researcher. At the end of today, you’ll also learn the rules of "responsible disclosure" to think through before publishing.
1. Learning Objectives
By the end of this chapter, you will be able to:
- Explain the structure of reproduction proof: "success on the vulnerable version + blocked on the patched version"
- Write a minimal trigger (PoC script) based on your analysis notes
- Read reproduction results and judge "was my analysis correct?"
- Write a vulnerability analysis report (overview / root cause / impact / reproduction / fix)
- Apply the pre-publication checklist (patch released?, vendor notified?, legal scope)
2. Background Knowledge — Today’s Tools and Concepts
Today’s Tools at a Glance
| Category | Details |
|---|---|
| Language/environment | Git Bash + Python 3.10+ (standard library only) |
| Today’s commands | python poc.py target-file, git show hash:file (Step 320 review) |
| Concepts needed | PoC, the contrast structure of reproduction proof, responsible disclosure |
| Today’s deliverable | One PoC script + one CVE analysis report |
2-1. The Structure of Reproduction Proof — Why "Both"?
"The attack worked on the vulnerable version" is not enough. The program might simply have behaved oddly for some other reason. To count as proof, you need a control group.
vulnerable version + attack input → success (vulnerable!)
patched version + same input → blocked
Only when both results hold at once can you conclude "the very thing the patch blocks is the very vulnerability I analyzed." If it doesn’t work even on the vulnerable version, your trigger is wrong; if it still works on the patched version, your patch analysis is wrong. Either way, go back to the diff and read it again.
2-2. PoC — The Minimal Unit of Proof
A PoC (Proof of Concept) is what you met as "attack code" back in Step 115. Today you write one yourself. A good PoC satisfies three conditions.
- Minimality — contains only what’s needed to trigger the vulnerability (no side effects)
- Reproducibility — anyone who runs it gets the same result
- Safety — operates only against targets inside your lab
The PoC you’ll build today checks all three boxes — a script that works against two local files and reports results only through output.
2-3. The Five Sections of an Analysis Report
Step 320’s analysis notes (type / trigger / impact / patch principle) were "a memo for yourself." A report is "a document for others." Expand it to five sections.
| Section | Contents |
|---|---|
| Overview | CVE number (or target), affected versions, one-line summary |
| Root cause analysis | "Why it was vulnerable," alongside the patch diff |
| Impact | What’s possible on success, who is at risk |
| Reproduction | Environment, PoC, vulnerable/patched contrast results |
| Fix & mitigation | Patch principle, actions users should take |
2-4. Responsible Disclosure — The Ethics of Publishing
The heart of responsible disclosure is order. ① Notify the vendor first → ② give the vendor time to develop a patch → ③ publish your analysis after the patch is public. Keep this order, and publishing analysis becomes an act that helps defenders.
Today’s practice target is a fictional vulnerability you built yourself, so there’s no legal issue. But learn the checks for publishing real CVE analysis as a form now — the checklist in 3-6 is that form.
3. Follow Along
3-1. Preparing the Materials — Extracting the Two Version Files
Move into Step 320’s oneday-lab repository and extract both versions to files (skip if you already did this).
Input:
cd oneday-lab
git show HEAD~1:app.py > app_v1.py
git show HEAD:app.py > app_v2.py
ls
Output (measured 2026-09-09):
app.py app_v1.py app_v2.py
How to read it: app_v1.py is the vulnerable version, app_v2.py is the patched version. For a real CVE reproduction, this spot is occupied by "a VM/container with the vulnerable version installed." Obtaining the old version often accounts for more than half of reproduction work — use vendor archives or vulnerable-environment collections like vulhub.
3-2. Writing the PoC — Turning Analysis Notes into Code
Look at the trigger section of your Step 320 analysis notes: "admin' -- in the username input." Build the minimal script that verifies it. Save as poc.py.
"""poc.py — SQL injection reproduction script (lab use only)
usage: python poc.py <app_v1.py|app_v2.py>
"""
import importlib.util
import sys
path = sys.argv[1]
spec = importlib.util.spec_from_file_location("target_app", path)
app = importlib.util.module_from_spec(spec)
spec.loader.exec_module(app)
conn = app.init_db()
print("=== [1] Normal login attempt ===")
ok1 = app.login(conn, "guest", "guest123")
print()
print("=== [2] Injection attack attempt: admin login without password ===")
ok2 = app.login(conn, "admin' --", "anything")
print()
print("=== Results ===")
print(f"Normal login: {'succeeded' if ok1 else 'failed'}")
print(f"Injection attack: {'succeeded (vulnerable!)' if ok2 else 'blocked'}")
How to read it: the script takes a filename as its argument and loads that file as a module (importlib). Thanks to this, the same PoC can test the vulnerable and patched versions in turn — the conditions of a controlled experiment (same input, same procedure) are kept automatically. Since what matters is whether any password at all succeeds, the password field gets 'anything'.
3-3. Reproduction Act 1 — The Vulnerable Version
Input:
python poc.py app_v1.py
Output (measured 2026-09-09):
=== [1] Normal login attempt ===
[DEBUG] executed query: SELECT * FROM users WHERE username = 'guest' AND password = 'guest123'
[OK] login succeeded: guest
=== [2] Injection attack attempt: admin login without password ===
[DEBUG] executed query: SELECT * FROM users WHERE username = 'admin' --' AND password = 'anything'
[OK] login succeeded: admin
=== Results ===
Normal login: succeeded
Injection attack: succeeded (vulnerable!)
How to read it: watch the [DEBUG] line. Because of the admin' -- you entered, the query mutated into username = 'admin' --' AND .... Everything after -- is a SQL comment, so the entire password check vanished. That’s why you’re logged in as admin even though the password was "anything." Vulnerability triggered — confirmed.
3-4. Reproduction Act 2 — The Patched Version (Control Group)
Input:
python poc.py app_v2.py
Output (measured 2026-09-09):
=== [1] Normal login attempt ===
[OK] login succeeded: guest
=== [2] Injection attack attempt: admin login without password ===
[FAIL] login failed
=== Results ===
Normal login: succeeded
Injection attack: blocked
How to read it: same PoC, same input — but blocked on the patched version. Notice the [DEBUG] line is gone too: with parameter binding, the query never mutates based on input, so there’s no "polluted query" to show. The input admin' -- is treated not as syntax but as just a weird username string, and since no such user exists, login failure is the correct behavior.
Proof complete: vulnerable version succeeds + patched version blocks. Step 320’s diff analysis was right. These two outputs are the heart of your analysis report.
3-5. Writing the Analysis Report — Filling the Five Sections
Now write the report in the format from 2-3. Below is a model example filled in with today’s measured results.
[Title] guestbook v1.0 SQL Injection Analysis (fictional CVE-2026-EX01)
[Overview]
- Target: guestbook v1.0 (practice mini-login)
- Type: CWE-89 SQL Injection
- One-line summary: the login query is built by string assembly,
so the username input can neutralize the password check
[Root cause analysis]
- In the v1.0 → v1.1 patch diff, the f-string query was replaced with ? binding
- Vulnerable location: the query-assembly line in login()
- Trigger: username containing ' and the SQL comment -- (e.g., admin' --)
[Impact]
- Login to arbitrary accounts without a password (authentication bypass)
- If the same pattern exists in other queries, it can expand to data exfiltration
[Reproduction]
- Environment: local Python 3.12, both versions extracted to files (git show)
- Method: contrast runs of python poc.py app_v1.py / app_v2.py
- Result: injection succeeds on v1.0 / blocked on v1.1 — matches the analysis
[Fix & mitigation]
- Patch principle: separating query syntax from data (parameter binding)
- User action: update to v1.1 or later
(In an actual report file, each [section] becomes a Markdown subheading like ## Overview.)
How to read it: the four sections of your Step 320 analysis notes go straight into "root cause analysis" and "impact," and today’s reproduction results fill the "reproduction" section. A report is analysis notes plus reproduction evidence.
3-6. Before Publishing — The Responsible Disclosure Checklist
Before posting the report to your blog, check five things. Today’s practice report covers a fictional vulnerability, but use this exact format when you write up a real CVE analysis.
[ ] Is the vendor patch already public? (Never publish details of an unpatched vulnerability)
[ ] Did you go through vendor notification? (If you discovered a new vulnerability, reporting comes before publishing)
[ ] Is the PoC minimized for lab reproduction? (It's not a real attack payload or exfiltration tool, is it?)
[ ] Is it stated that the reproduction environment is your own lab? (No traces of experiments on production systems)
[ ] Does the report read as "analysis for defense"? (Does it include a fix/mitigation section?)
How to read it: the fifth item matters most. A post with reproduction but no mitigation reads as an attack manual. Only with a "fix & mitigation" section is it an analysis report. Publishing analysis of an already-patched vulnerability is — provided you keep these five checks — recognized as model responsible disclosure.
Why do this: a security researcher’s career is the product of "how deeply you dig" and "how responsibly you disclose." If either side is zero, the product is zero.
4. Missions & Exercises
Mission — Reproduce and Report the Search-Feature Vulnerability
- Extract the two versions of
search.py(the search feature, with vulnerable/patched commits) from your Step 320 mission usinggit show - Following today’s
poc.pystructure, write a PoC for the search feature — one that contrasts a normal search against an injection input (%' OR '1'='1) - Confirm injection succeeds on the vulnerable version and is blocked on the patched version
- Complete the analysis report in the five-section format from 3-5
- Answer the five items of the 3-6 checklist, then publish to your blog (or a practice Markdown file)
Exercises
Exercise 1. Why is "the attack succeeded on the vulnerable version" alone insufficient? Explain why a control group is needed.
Exercise 2. Explain each of a good PoC’s three conditions (minimality / reproducibility / safety) in one sentence, and say how today’s poc.py satisfies each.
Exercise 3. If the reproduction result were "doesn’t work even on the vulnerable version," what would be wrong? And if it were "still works on the patched version"?
Exercise 4. You discovered and analyzed a vulnerability whose patch hasn’t been released yet. List what you must do, in order, before publishing it on your blog.
5. Model Answers & Completion Criteria
Mission Model Answer
The PoC’s core structure is the same as today’s poc.py — take the target file as an argument, feed normal and attack inputs in turn, and contrast the results. An attack-input example for the search-feature version:
rows = app.search(conn, "%' OR '1'='1")
print(f"rows returned: {len(rows)}") # vulnerable version: total user count, patched version: 0
If the vulnerable version returns all users (2 of them) and the patched version returns 0 rows, the proof succeeds. For the report, reuse the 3-5 example format with the target changed to "search.py search feature."
How to verify: ① does the PoC produce different results on the two versions? ② does the report have all five sections (overview/root cause/impact/reproduction/fix)? ③ does the "reproduction" section contain environment, method, and results — and do the results match actual output? ④ did you answer the 3-6 checklist?
Exercise Answers
Answer 1. Success on the vulnerable version alone can’t distinguish whether it happened because of "the vulnerability you analyzed" or because of another bug or an environment difference. Only when the same input is blocked on the patched version does the causal chain "what the patch blocks = what I analyzed" hold. The contrast experiment turns reproduction from a claim into a proof.
Answer 2. Minimality means including only what triggers the vulnerability; reproducibility means anyone running it gets the same result; safety means it only touches targets inside your lab. Today’s PoC performs only two login attempts (minimality), uses one script with swappable files (reproducibility), and calls only functions inside local files (safety).
Answer 3. If it doesn’t work even on the vulnerable version, the trigger (attack input or trigger conditions) is wrong — go back to the analysis notes and re-check the conditions. If it still works on the patched version, the patch analysis is wrong — either the fix wasn’t at the vulnerable spot or the attack entered through a different path, so re-read the diff.
Answer 4. ① Report to the vendor first (the reporting procedure from Steps 318–319). ② Give the vendor time to develop and ship a patch (there’s a customary disclosure embargo, typically around 90 days). ③ Publish your analysis only after the patch is public. Break the order and you’ve effectively distributed an attack manual to unpatched systems.
Completion Criteria Checklist
- [ ] I can explain the contrast structure of "vulnerable version succeeds + patched version blocks"
- [ ] I can turn an analysis note’s trigger into a PoC script
- [ ] I ran
poc.pyagainst both versions and confirmed the contrast - [ ] When reproduction fails, I can distinguish the cause candidates (trigger error / analysis error)
- [ ] I can write a five-section analysis report (overview/root cause/impact/reproduction/fix)
- [ ] I can explain the five items of the responsible disclosure checklist
- [ ] Mission: I completed the search-feature reproduction and report
6. Common Pitfalls & Fixes
Wall 1. poc.py says "module not found"
Symptom (similar error measured 2026-09-09):
FileNotFoundError: [Errno 2] No such file or directory: 'app_v1.py'
Cause: you didn’t extract the files with git show, or you ran the script from a different folder.
Fix: check with ls that app_v1.py, app_v2.py, and poc.py are in the same folder. If not, start over from 3-1’s git show HEAD~1:app.py > app_v1.py.
Wall 2. Injection doesn’t work on the vulnerable version
Symptom: even app_v1.py prints "login failed."
Cause: nine times out of ten the extraction direction is reversed — you pulled HEAD (the patched version) instead of HEAD~1. Since the input lives inside poc.py, shell quoting isn’t the issue, but the files can get swapped.
Fix: run git log --oneline and confirm the lower (older) commit is the vulnerable version, and check that the first line of head app_v1.py says v1.0.
Wall 3. It seems to work on the patched version too
Symptom: login succeeds on app_v2.py as well.
Two likely causes: ① you mistook the normal login (guest/guest123) succeeding for an attack success — distinguish [1] and [2] in the output. ② if the attack truly succeeded, that file is not the patched version.
Fix: the judging criterion is the last line of output, "Injection attack: blocked." Normal login succeeding is the result that should appear on both versions (a sanity check for the experiment).
Wall 4. The report’s "reproduction" section is empty
Symptom: you wrote the root-cause analysis but don’t know what to put in the reproduction section.
Cause: the reproduction section is where you write down, as-is, "what you ran and what you saw." There’s nothing new to analyze.
Fix: three lines are enough — environment (Python version, extraction method), method (the two commands you ran), results (the last output line for each version). Copy in the output you measured today. A screenshot makes it even better.
Wall 5. Fear of publishing shelves the report forever
Symptom: you let the post sit, wondering "is this analysis good enough to publish?"
Cause: perfectionism. Every world-class researcher’s first analysis post was rough.
Fix: passing the 3-6 checklist is enough to publish — patched vulnerability, lab reproduction, mitigation section included. If errors are found later, correct them with an edit history. Publishing is something you do during growth, not after completion (this theme continues in Step 323 with publishing code).
7. Summary
Today’s Concepts
| Concept | One-line explanation |
|---|---|
| Reproduction proof | A contrast experiment: vulnerable version succeeds + patched version blocks |
| Control group | The patched-version run that proves "the analysis is right" |
| PoC | A minimal script proving the trigger — minimal, reproducible, safe |
| Analysis report | A public document with five sections: overview/root cause/impact/reproduction/fix |
| Responsible disclosure | The ethics of keeping the order: vendor notification → patch → publication |
Today’s Commands & Formats
| Tool | What it does |
|---|---|
git show hash:file > extracted-file |
Extract the two version files for the experiment |
python poc.py app_v1.py |
Reproduce on the vulnerable version |
python poc.py app_v2.py |
Control run on the patched version |
| Report’s five sections | Overview/root cause/impact/reproduction/fix |
| Publication checklist | Patch public · vendor notified · minimal PoC · lab stated · mitigation included |
The Core Instinct
The proof of an analysis is reproduction, and the proof of reproduction is contrast. The answer is not "it worked" but "it worked only on the vulnerable version." Today’s hand motion — running the PoC back and forth between two versions — will become the closing ritual of every 1-day analysis you ever do.
And publication is not a matter of fear but of order. Vendor first, patch next, publication after. As long as you keep that order, your analysis report is an alarm to defenders, evidence of skill to recruiters, and a nameplate that says "researcher" to you.
Once every box is checked, Step 321 is complete.