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:
| Char | Codepoint | Name |
|---|---|---|
" | U+0022 | QUOTATION MARK (ASCII) |
“ | U+201C | LEFT DOUBLE QUOTATION MARK |
” | U+201D | RIGHT DOUBLE QUOTATION MARK |
„ | U+201E | DOUBLE 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 /
-EncodedCommandboundaries (see powershell-base64-encoding). - Enable Script Block Logging so executed code is captured post-parsing (the quotes appear decoded in logs).
Related
- powershell-execution-policy-bypass — neighboring command-line/parser manipulation
- amsi-bypass — another layer where parser quirks matter
- powershell-base64-encoding — opaque-blob alternative to quote tricks