PowerShell Unicode Quote Equivalence

PowerShell’s parser treats several non-ASCII quotation characters as fully interchangeable with their ASCII equivalents — by design, per the PowerShell language specification. The double-quote-character grammar production accepts:

CharCodepointName
"U+0022QUOTATION MARK (ASCII)
U+201CLEFT DOUBLE QUOTATION MARK
U+201DRIGHT DOUBLE QUOTATION MARK
U+201EDOUBLE LOW-9 QUOTATION MARK

…and the single-quote production likewise accepts U+2018/2019/201A/201B alongside ASCII '. The characters may even be mixed within one literal: "Hi!“ is a valid string. The open-source parser confirms it in SpecialChars/IsDoubleQuote (CharTraits.cs), and Microsoft classifies the behavior as by-design — changing it would break code written in word processors that auto-correct " to smart quotes (the presumed original rationale).

Security relevance: filter evasion and injection

Because the equivalence lives in the parser, an application that sanitizes only ASCII quotes before interpolating user input into a PowerShell command is injectable via U+201C/U+201D. This is a real CVE class: CVE-2021-28958 (ADSSP powerShellEscape) — the sanitizer escaped " but not the General Punctuation quotes, allowing string-escape and command injection. The research write-up also demonstrates the exotic variant: on a Japanese-locale (CP932) host, multi-byte sequences can be smuggled through an HTTP parameter so that, after code-page conversion, they become U+201D inside the PowerShell script — injection through an encoding layer.

Related pitfalls in the same family:

  • Smart quotes sneak into scripts copied from Word/Outlook/web pages, then behave as real quotes — a debugging hazard even with no attacker.
  • Sanitizers keyed on ASCII backslash/quote pairs miss doubled smart-quote escapes.
  • En dashes (U+2013) are accepted as parameter dashes in some contexts — another copy-paste boobytrap.

Defense

  • Normalize input to ASCII (or reject non-ASCII) before interpolation; validate against an allowlist rather than escaping a blocklist.
  • Never interpolate user input into command strings at all — use parameter binding / -EncodedCommand boundaries (see powershell-base64-encoding).
  • Enable Script Block Logging so executed code is captured post-parsing (the quotes appear decoded in logs).

Sources