T09 · Insecure Skill Coding Practices
- Location
scripts/batch_roster.py:160- Finding
CSV Formula Injection in Batch Roster Summary
- Content
View full analysis
Vulnerability Details
File Location:
scripts/batch_roster.py:160-163
Vulnerability Type: CSV formula injection and improper CSV serialization
Risk Level: MediumVulnerable Code
python (out_dir / "_summary.csv").write_text( "\n".join(["index,name,status,html"] + [ f"{x['index']},{x['name']},{x['status']},{x['html']}" for x in summary["students"] ]), encoding="utf-8-sig")Technical Analysis
Student names are read from a user-supplied CSV or JSON roster and copied directly into
_summary.csv. The output is assembled through string interpolation rather than a CSV serializer.If a name starts with a spreadsheet formula prefix such as
=,+,-, or@, spreadsheet software may interpret it as a formula when the generated summary is opened. For example, a roster could contain:text =HYPERLINK("https://example.invalid","Open")The implementation also fails to quote commas, double quotes, carriage returns, and line breaks. These characters can corrupt the CSV structure or inject additional rows and columns.
Attack Path
- An attacker creates or modifies a roster accepted by
--roster. - A student name is set to a formula-prefixed value or a value containing CSV control characters.
- A teacher runs
scripts/batch_roster.pywith the crafted roster. - The untrusted value is written verbatim to
_summary.csv. - The teacher opens the generated file in spreadsheet software.
- Depending on the spreadsheet application's security configuration, the value is evaluated as a formula or manipulates the apparent CSV structure.
Impact Assessment
Exploitation does not grant privileges to the Python process itself. The impact occurs in the context of the user opening the generated CSV with spreadsheet software.
Possible consequences include:
- Display of deceptive links or misleading spreadsheet content.
- Corruption or manipulation of rows ...[truncated 396 chars]
- An attacker creates or modifies a roster accepted by
- Remediation
View remediation
Remediation Suggestions
-
Generate
_summary.csvwith Python'scsv.writerrather than manually concatenating fields. This correctly handles commas, double quotes, carriage returns, and line breaks. -
Neutralize spreadsheet formula prefixes in every untrusted textual field. If a value begins with
=,+,-, or@, prefix it with an apostrophe or apply another mitigation appropriate for supported spreadsheet applications. -
Apply formula neutralization after trimming relevant leading whitespace, since some spreadsheet applications may interpret formulas preceded by whitespace or control characters.
-
Treat all roster-derived fields as untrusted, including names and student identifiers. Apply the same protection if additional fields are later added to the summary.
-
Add regression tests covering:
- Formula-prefixed names.
- Names containing commas and double quotes.
- Names containing CR/LF characters.
- Unicode names.
- Empty names.
A hardened implementation could use:
python import csv def safe_csv_cell(value): value = str(value) check = value.lstrip() if check.startswith(("=", "+", "-", "@")): return "'" + value return value with (out_dir / "_summary.csv").open( "w", encoding="utf-8-sig", newline="" ) as f: writer = csv.writer(f) writer.writerow(["index", "name", "status", "html"]) for item in summary["students"]: writer.writerow([ item["index"], safe_csv_cell(item["name"]), safe_csv_cell(item["status"]), safe_csv_cell(item["html"]), ])-
