T09 · Insecure Skill Coding Practices
- Location
scripts/generate_ics.py:43- Finding
ICS Property Injection Through Unvalidated Timezone and Incomplete Control-Character Escaping
- Content
View full analysis
Vulnerability Details
File Location:
scripts/generate_ics.py, lines 43–49, 145–147, and 193
Vulnerability Type: Calendar content injection caused by incomplete escaping and unsafe property-parameter interpolation
Risk Level: MediumVulnerable Code
python def esc(text: str) -> str: return ( text.replace("\\", "\\\\") .replace(";", r"\;") .replace(",", r"\,") .replace("\n", r"\n") )python fields = [ "BEGIN:VEVENT", f"UID:{uid_for(course, start_dt)}", f"DTSTAMP:{stamp}", f"DTSTART;TZID={timezone}:{start_dt.strftime('%Y%m%dT%H%M%S')}", f"DTEND;TZID={timezone}:{end_dt.strftime('%Y%m%dT%H%M%S')}", f"SUMMARY:{esc(course['name'])}", f"LOCATION:{esc(course['location'])}", f"DESCRIPTION:{esc(chr(10).join(description_lines))}", "END:VEVENT", ]python lines = [ "BEGIN:VCALENDAR", "VERSION:2.0", "CALSCALE:GREGORIAN", "METHOD:PUBLISH", f"X-WR-CALNAME:{esc(calendar_name)}", f"X-WR-TIMEZONE:{timezone}", *events, "END:VCALENDAR", "", ]Technical Analysis
The ICS serializer accepts manifest-controlled text and inserts it into calendar properties. The
esc()function escapes backslashes, semicolons, commas, and line-feed characters, but it does not reject or escape carriage returns or other control characters.The
timezonefield is more directly exposed: it is inserted intoTZIDparameter positions and theX-WR-TIMEZONEproperty without escaping or validation. The documented schema describes this field as an IANA timezone, but the implementation does not enforce that requirement.A JSON string can represent carriage-return and line-feed characters using escape sequences such as
\r\n. After JSON parsing, these become control characters. A crafted timezone can therefore terminate the current ICS line and introduce attacker-se ...[truncated 1766 chars]- Remediation
View remediation
Remediation Suggestions
-
Validate
timezoneas an IANA timezone before serialization:python from zoneinfo import ZoneInfo, ZoneInfoNotFoundError def validate_timezone(value: str) -> str: if any(ord(char) < 0x20 or ord(char) == 0x7F for char in value): raise ValueError("Timezone contains control characters") try: ZoneInfo(value) except ZoneInfoNotFoundError as exc: raise ValueError(f"Invalid IANA timezone: {value}") from exc return value -
Reject carriage returns, null bytes, and other unsupported control characters in all manifest strings before generating output.
-
Normalize line endings before text escaping. Convert
\r\nand lone\rto\n, and then encode permitted newlines as the ICS literal\n. -
Apply stricter validation to values used in ICS property-parameter positions. Do not rely on text-property escaping for parameters such as
TZID. -
Validate field types explicitly so that
calendar_name,timezone, course names, locations, teachers, and notes must be strings. -
Add regression tests covering:
- Lone
\r \r\n- Null bytes and other C0 controls
- Semicolons, colons, commas, backslashes, and quotes
- Attempted
BEGIN:VEVENT,END:VEVENT, andBEGIN:VALARMinjection - Invalid and valid IANA timezone identifiers
- Lone
-
