Skip to content

Commit 27c8708

Browse files
Escape U+007F and C1 controls in the JavaScript encoders (#163)
In non-ASCII mode, all four JavaScript encoder modes passed U+007F (DEL) and the C1 control characters U+0080 to U+009F through raw. This is not a string-literal breakout (ECMAScript line terminators are only LF, CR, U+2028 and U+2029), but every other encoder already refuses to emit these characters raw, and encoded output often reaches logs, templates and other consumers that treat U+0085 as a line break or U+009B as a terminal control. Hex encode them as \xNN (lowercase, like the C0 controls) in every mode: DEL is no longer marked valid in the mask, and firstEncodedOffset and encodeArrays send U+0080 to U+009F to the existing \xNN writer. U+00A0 and above still pass through except U+2028/U+2029, and the ASCII-only mode is unchanged. JavaScriptEncoderTest now starts the valid range at U+00A0, marks U+007F to U+009F as encoded, and has exact cases for DEL, U+0080, U+0085 (NEL), U+009B and U+009F in all four modes. Document the new row in the forJavaScript table, the other three methods and the JSP and Jakarta TLDs, and add a README News entry.
1 parent c4b379a commit 27c8708

8 files changed

Lines changed: 57 additions & 5 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ News
156156
### Unreleased - 1.4.2
157157
* fix: `forHtmlUnquotedAttribute` now replaces U+0085 (NEL) with a hyphen like the other C1 control characters, instead of emitting `…`, which HTML5 parsers decode as U+2026 [#136](https://github.com/OWASP/owasp-java-encoder/issues/136).
158158
* fix: the XML 1.1 encoders (`forXml11`, `forXml11Content`, `forXml11Attribute`) now encode U+0085 (NEL) as `…` and U+2028 (line separator) as `
`, so they are not normalized to a line feed [#136](https://github.com/OWASP/owasp-java-encoder/issues/136).
159+
* change: the JavaScript encoders (`forJavaScript`, `forJavaScriptAttribute`, `forJavaScriptBlock`, `forJavaScriptSource`) now hex encode U+007F (DEL) and the C1 control characters U+0080 to U+009F as `\xNN` instead of passing them through, for consistency with the other encoders. This changes output for inputs containing these characters but not the decoded string value; it is not a security fix, since JavaScript does not treat them as line terminators [#163](https://github.com/OWASP/owasp-java-encoder/issues/163).
159160

160161
### 1.4.1 Security Release
161162
Upgrade all OWASP Java Encoder dependencies to 1.4.1. This release fixes three

‎core/src/main/java/org/owasp/encoder/Encode.java‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,13 @@ public static void forJava(Writer out, String input)
11741174
* <td class="colLast">Hyphen character</td>
11751175
* </tr>
11761176
* <tr class="altColor">
1177+
* <td class="colFirst" colspan="2">U+007F&nbsp;to&nbsp;U+009F</td>
1178+
* <td class="colLast"><code>\x##</code></td>
1179+
* <td class="colLast">DEL and the C1 control characters, including
1180+
* U+0085 (next line), are hexadecimal encoded so they never appear
1181+
* raw in the output.</td>
1182+
* </tr>
1183+
* <tr class="rowColor">
11771184
* <td class="colFirst" colspan="2">U+2028, U+2029</td>
11781185
* <td class="colLast"><code>&#92;u2028</code>, <code>&#92;u2029</code></td>
11791186
* <td class="colLast">Line and paragraph separators</td>
@@ -1218,6 +1225,10 @@ public static void forJavaScript(Writer out, String input)
12181225
* same encode as {@link #forJavaScript(String)} with the
12191226
* exception that <code>/</code> and <code>-</code> are not escaped.</p>
12201227
*
1228+
* <p>Like {@link #forJavaScript(String)}, this method hex encodes
1229+
* U+007F and the C1 control characters U+0080 to U+009F, for example
1230+
* U+0085 as <code>\x85</code>.</p>
1231+
*
12211232
* <p><strong>Unless you are interested in saving a few bytes of
12221233
* output or are writing a framework on top of this library, it is
12231234
* recommend that you use {@link #forJavaScript(String)} over this
@@ -1267,6 +1278,10 @@ public static void forJavaScriptAttribute(Writer out, String input)
12671278
* encoded as <code>\"</code> and <code>\'</code>
12681279
* respectively.</p>
12691280
*
1281+
* <p>Like {@link #forJavaScript(String)}, this method hex encodes
1282+
* U+007F and the C1 control characters U+0080 to U+009F, for example
1283+
* U+0085 as <code>\x85</code>.</p>
1284+
*
12701285
* <p><strong>Unless you are interested in saving a few bytes of
12711286
* output or are writing a framework on top of this library, it is
12721287
* recommend that you use {@link #forJavaScript(String)} over this
@@ -1318,6 +1333,10 @@ public static void forJavaScriptBlock(Writer out, String input)
13181333
* escaped and <code>"</code> and <code>'</code> are encoded as
13191334
* <code>\"</code> and <code>\'</code> respectively.</p>
13201335
*
1336+
* <p>Like {@link #forJavaScript(String)}, this method hex encodes
1337+
* U+007F and the C1 control characters U+0080 to U+009F, for example
1338+
* U+0085 as <code>\x85</code>.</p>
1339+
*
13211340
* <p><strong>Unless you are interested in saving a few bytes of
13221341
* output or are writing a framework on top of this library, it is
13231342
* recommend that you use {@link #forJavaScript(String)} over this

‎core/src/main/java/org/owasp/encoder/JavaScriptEncoder.java‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ enum Mode {
123123
0,
124124
-1 & ~((1 << '\'') | (1 << '\"')),
125125
-1 & ~((1 << '\\')),
126-
asciiOnly ? ~(1 << Unicode.DEL) : -1,};
126+
// DEL is hex encoded in every mode, like the C1 controls
127+
~(1 << Unicode.DEL),};
127128

128129
if (mode == Mode.BLOCK || mode == Mode.HTML) {
129130
// in <script> blocks, we need to prevent the browser from seeing
@@ -160,7 +161,8 @@ int firstEncodedOffset(String input, int off, int len) {
160161
if ((validMasks[ch >>> 5] & (1 << ch)) == 0) {
161162
return i;
162163
}
163-
} else if (_asciiOnly || ch == Unicode.LINE_SEPARATOR || ch == Unicode.PARAGRAPH_SEPARATOR) {
164+
} else if (_asciiOnly || ch <= Unicode.MAX_C1_CTRL_CHAR
165+
|| ch == Unicode.LINE_SEPARATOR || ch == Unicode.PARAGRAPH_SEPARATOR) {
164166
return i;
165167
}
166168
}
@@ -189,7 +191,8 @@ protected CoderResult encodeArrays(CharBuffer input, CharBuffer output, boolean
189191
if ((validMasks[ch >>> 5] & (1 << ch)) == 0) {
190192
break encoded;
191193
}
192-
} else if (_asciiOnly || ch == Unicode.LINE_SEPARATOR || ch == Unicode.PARAGRAPH_SEPARATOR) {
194+
} else if (_asciiOnly || ch <= Unicode.MAX_C1_CTRL_CHAR
195+
|| ch == Unicode.LINE_SEPARATOR || ch == Unicode.PARAGRAPH_SEPARATOR) {
193196
if (ch <= 0xff) {
194197
break hexEncoded;
195198
}

‎core/src/test/java/org/owasp/encoder/JavaScriptEncoderTest.java‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,22 @@ public static Test suite() {
100100
.encode("Line Separator", "\\u2028", "\u2028")
101101
.encode("Paragraph Separator", "\\u2029", "\u2029")
102102
.encode("abc", "abc")
103-
.encode("ABC", "ABC");
103+
.encode("ABC", "ABC")
104+
// DEL and the C1 controls are hex encoded in every mode
105+
.encode("DEL", "\\x7f", "\u007f")
106+
.encode("U+0080", "\\x80", "\u0080")
107+
.encode("NEL", "\\x85", "\u0085")
108+
.encode("CSI", "\\x9b", "\u009b")
109+
.encode("U+009F", "\\x9f", "\u009f")
110+
.encode("NEL in text", "a\\x85b", "a\u0085b");
104111

105112
if (asciiOnly == 0) {
106113
builder
107114
.encode("unicode", "\u1234", "\u1234")
108115
.encode("high-ascii", "\u00ff", "\u00ff")
109-
.valid(0x7f, Character.MAX_CODE_POINT)
116+
.encode("NBSP", "\u00a0", "\u00a0")
117+
.valid(0xa0, Character.MAX_CODE_POINT)
118+
.encoded(0x7f, Unicode.MAX_C1_CTRL_CHAR)
110119
.encoded("\u2028\u2029");
111120
} else {
112121
builder

‎jakarta/src/main/resources/META-INF/java-encoder-advanced.tld‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
quotation marks around the JavaScript string. Template literals (backticks) and JSON are
7878
unsupported; use a JSON serializer for JSON. Do not use in javascript: or data: URLs,
7979
where URL decoding introduces a separate parsing context.
80+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
8081
</description>
8182
<display-name>forJavaScript</display-name>
8283
<name>forJavaScript</name>
@@ -98,6 +99,7 @@
9899
JavaScript string. Template literals (backticks) and JSON are unsupported; use a JSON
99100
serializer for JSON. Do not use in javascript: or data: URLs, where URL decoding
100101
introduces a separate parsing context.
102+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
101103
</description>
102104
<display-name>forJavaScriptAttribute</display-name>
103105
<name>forJavaScriptAttribute</name>
@@ -119,6 +121,7 @@
119121
the JavaScript string. Template literals (backticks) and JSON are unsupported; use a
120122
JSON serializer for JSON. Do not use in javascript: or data: URLs, where URL decoding
121123
introduces a separate parsing context.
124+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
122125
</description>
123126
<display-name>forJavaScriptBlock</display-name>
124127
<name>forJavaScriptBlock</name>
@@ -141,6 +144,7 @@
141144
Template literals (backticks) and JSON are unsupported; use a JSON serializer for JSON.
142145
Do not use in javascript: or data: URLs, where URL decoding introduces a separate
143146
parsing context.
147+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
144148
</description>
145149
<display-name>forJavaScriptSource</display-name>
146150
<name>forJavaScriptSource</name>
@@ -489,6 +493,7 @@
489493
quotation marks around the JavaScript string. Template literals (backticks) and JSON are
490494
unsupported; use a JSON serializer for JSON. Do not use in javascript: or data: URLs,
491495
where URL decoding introduces a separate parsing context.
496+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
492497
</description>
493498
<display-name>forJavaScript</display-name>
494499
<name>forJavaScript</name>
@@ -504,6 +509,7 @@
504509
JavaScript string. Template literals (backticks) and JSON are unsupported; use a JSON
505510
serializer for JSON. Do not use in javascript: or data: URLs, where URL decoding
506511
introduces a separate parsing context.
512+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
507513
</description>
508514
<display-name>forJavaScriptAttribute</display-name>
509515
<name>forJavaScriptAttribute</name>
@@ -519,6 +525,7 @@
519525
the JavaScript string. Template literals (backticks) and JSON are unsupported; use a
520526
JSON serializer for JSON. Do not use in javascript: or data: URLs, where URL decoding
521527
introduces a separate parsing context.
528+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
522529
</description>
523530
<display-name>forJavaScriptBlock</display-name>
524531
<name>forJavaScriptBlock</name>
@@ -535,6 +542,7 @@
535542
Template literals (backticks) and JSON are unsupported; use a JSON serializer for JSON.
536543
Do not use in javascript: or data: URLs, where URL decoding introduces a separate
537544
parsing context.
545+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
538546
</description>
539547
<display-name>forJavaScriptSource</display-name>
540548
<name>forJavaScriptSource</name>

‎jakarta/src/main/resources/META-INF/java-encoder.tld‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
quotation marks around the JavaScript string. Template literals (backticks) and JSON are
8181
unsupported; use a JSON serializer for JSON. Do not use in javascript: or data: URLs,
8282
where URL decoding introduces a separate parsing context.
83+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
8384
</description>
8485
<display-name>forJavaScript</display-name>
8586
<name>forJavaScript</name>
@@ -392,6 +393,7 @@
392393
quotation marks around the JavaScript string. Template literals (backticks) and JSON are
393394
unsupported; use a JSON serializer for JSON. Do not use in javascript: or data: URLs,
394395
where URL decoding introduces a separate parsing context.
396+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
395397
</description>
396398
<display-name>forJavaScript</display-name>
397399
<name>forJavaScript</name>

‎jsp/src/main/resources/META-INF/java-encoder-advanced.tld‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
quotation marks around the JavaScript string. Template literals (backticks) and JSON are
7878
unsupported; use a JSON serializer for JSON. Do not use in javascript: or data: URLs,
7979
where URL decoding introduces a separate parsing context.
80+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
8081
</description>
8182
<display-name>forJavaScript</display-name>
8283
<name>forJavaScript</name>
@@ -98,6 +99,7 @@
9899
JavaScript string. Template literals (backticks) and JSON are unsupported; use a JSON
99100
serializer for JSON. Do not use in javascript: or data: URLs, where URL decoding
100101
introduces a separate parsing context.
102+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
101103
</description>
102104
<display-name>forJavaScriptAttribute</display-name>
103105
<name>forJavaScriptAttribute</name>
@@ -119,6 +121,7 @@
119121
the JavaScript string. Template literals (backticks) and JSON are unsupported; use a
120122
JSON serializer for JSON. Do not use in javascript: or data: URLs, where URL decoding
121123
introduces a separate parsing context.
124+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
122125
</description>
123126
<display-name>forJavaScriptBlock</display-name>
124127
<name>forJavaScriptBlock</name>
@@ -141,6 +144,7 @@
141144
Template literals (backticks) and JSON are unsupported; use a JSON serializer for JSON.
142145
Do not use in javascript: or data: URLs, where URL decoding introduces a separate
143146
parsing context.
147+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
144148
</description>
145149
<display-name>forJavaScriptSource</display-name>
146150
<name>forJavaScriptSource</name>
@@ -489,6 +493,7 @@
489493
quotation marks around the JavaScript string. Template literals (backticks) and JSON are
490494
unsupported; use a JSON serializer for JSON. Do not use in javascript: or data: URLs,
491495
where URL decoding introduces a separate parsing context.
496+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
492497
</description>
493498
<display-name>forJavaScript</display-name>
494499
<name>forJavaScript</name>
@@ -504,6 +509,7 @@
504509
JavaScript string. Template literals (backticks) and JSON are unsupported; use a JSON
505510
serializer for JSON. Do not use in javascript: or data: URLs, where URL decoding
506511
introduces a separate parsing context.
512+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
507513
</description>
508514
<display-name>forJavaScriptAttribute</display-name>
509515
<name>forJavaScriptAttribute</name>
@@ -519,6 +525,7 @@
519525
the JavaScript string. Template literals (backticks) and JSON are unsupported; use a
520526
JSON serializer for JSON. Do not use in javascript: or data: URLs, where URL decoding
521527
introduces a separate parsing context.
528+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
522529
</description>
523530
<display-name>forJavaScriptBlock</display-name>
524531
<name>forJavaScriptBlock</name>
@@ -535,6 +542,7 @@
535542
Template literals (backticks) and JSON are unsupported; use a JSON serializer for JSON.
536543
Do not use in javascript: or data: URLs, where URL decoding introduces a separate
537544
parsing context.
545+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
538546
</description>
539547
<display-name>forJavaScriptSource</display-name>
540548
<name>forJavaScriptSource</name>

‎jsp/src/main/resources/META-INF/java-encoder.tld‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
quotation marks around the JavaScript string. Template literals (backticks) and JSON are
7878
unsupported; use a JSON serializer for JSON. Do not use in javascript: or data: URLs,
7979
where URL decoding introduces a separate parsing context.
80+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
8081
</description>
8182
<display-name>forJavaScript</display-name>
8283
<name>forJavaScript</name>
@@ -389,6 +390,7 @@
389390
quotation marks around the JavaScript string. Template literals (backticks) and JSON are
390391
unsupported; use a JSON serializer for JSON. Do not use in javascript: or data: URLs,
391392
where URL decoding introduces a separate parsing context.
393+
U+007F and the C1 controls U+0080 to U+009F are hex encoded, e.g. U+0085 as \x85.
392394
</description>
393395
<display-name>forJavaScript</display-name>
394396
<name>forJavaScript</name>

0 commit comments

Comments
 (0)