วิธีที่แนะนำสำหรับการหลีกเลี่ยง HTML ใน Java


262

มีวิธีที่แนะนำที่จะหลบหนี<, >, "และ&ตัวอักษรเมื่อ outputting HTML ในรหัส Java ธรรมดา? (นอกเหนือจากการทำสิ่งเหล่านี้ด้วยตนเองนั่นคือ)

String source = "The less than sign (<) and ampersand (&) must be escaped before using them in HTML";
String escaped = source.replace("<", "&lt;").replace("&", "&amp;"); // ...

2
โปรดทราบว่าหากคุณแสดงผลเป็นแอตทริบิวต์ HTML ที่ไม่มีเครื่องหมายอัญประกาศอักขระอื่น ๆ เช่นช่องว่างแท็บแบ็คสเปซ ฯลฯ ... อาจทำให้ผู้โจมตีสามารถเปิดใช้งานคุณลักษณะจาวาสคริปต์ได้โดยไม่ต้องแสดงอักขระใด ๆ ดูเอกสารโกงการป้องกัน OWASP XSS เพิ่มเติม
Jeff Williams

BTW ในรหัสนี้คุณควรหลีกเลี่ยง "&" ก่อน "<" เพื่อให้มันทำงานได้อย่างถูกต้อง ("& lt;" ถูกแทนที่ด้วย "& amp; lt;" มิฉะนั้นจะแสดงเป็น "& lt;" ไม่ใช่ "< "):source.replace("&", "&amp;").replace("<", "&lt;");
Tey '23

คำตอบ:


261

StringEscapeUtilsจากApache Commons Lang :

import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
// ...
String source = "The less than sign (<) and ampersand (&) must be escaped before using them in HTML";
String escaped = escapeHtml(source);

สำหรับเวอร์ชัน 3 :

import static org.apache.commons.lang3.StringEscapeUtils.escapeHtml4;
// ...
String escaped = escapeHtml4(source);

2
แม้ว่าStringEscapeUtilsจะดี แต่ก็ไม่สามารถหลบหนีช่องว่างอย่างถูกต้องสำหรับแอตทริบิวต์หากคุณต้องการหลีกเลี่ยงการทำให้เป็นปกติของช่องว่าง HTML / XML ดูคำตอบของฉันสำหรับรายละเอียดเพิ่มเติม
Adam Gent

21
ตัวอย่างด้านบนใช้งานไม่ได้ ใช้วิธี escapeHtml4 () ทันที
stackoverflowuser2010

3
สำหรับแฟน Guava เห็น คำตอบของ okranzด้านล่าง
George Hawkins

2
หากหน้าเว็บมีการเข้ารหัส UTF-8 สิ่งที่เราต้องการก็คือ htmlEscaper ของ Guava ที่หนีออกมาเพียงห้าตัวอักษร ASCII ต่อไปนี้: '"& <> Apache's escapeHtml () ยังแทนที่ตัวอักษรที่ไม่ใช่ ASCII รวมถึงการเน้นเสียงที่ไม่จำเป็นกับเว็บ UTF-8 หน้า?
zdenekca

4
ตอนนี้เลิกใช้แล้วใน Commons-lang3 มันถูกย้ายไปที่ commons.apache.org/proper/commons-text
Danny

137

ทางเลือกอื่นสำหรับ Apache Commons: ใช้วิธีของSpringHtmlUtils.htmlEscape(String input)


9
ขอบคุณ ฉันใช้มัน (แทนStringEscapeUtils.escapeHtml()จากapache-commons2.6) เพราะมันปล่อยให้ตัวละครรัสเซียเหมือนเดิม
Slava Semushin

6
เป็นเรื่องดีที่รู้ TBH ฉันให้อาปาเช่แบบกว้าง ๆ ในทุกวันนี้
Adamski

1
ฉันใช้มันเหมือนกันมันจะทิ้งตัวอักษรจีนไว้เหมือนกัน
smartwjw

มันเปรียบเทียบกับฝรั่งทางเลือกที่กล่าวถึงด้านล่างได้อย่างไร?
vishvAs vAsuki

2
และมันยังเข้ารหัสเครื่องหมายอะโพสโทรฟีดังนั้นมันจึงมีประโยชน์จริง ๆ ไม่เหมือน
อะแพชี

57

วิธีสั้น ๆ ที่ดี:

public static String escapeHTML(String s) {
    StringBuilder out = new StringBuilder(Math.max(16, s.length()));
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
        if (c > 127 || c == '"' || c == '\'' || c == '<' || c == '>' || c == '&') {
            out.append("&#");
            out.append((int) c);
            out.append(';');
        } else {
            out.append(c);
        }
    }
    return out.toString();
}

อ้างอิงจากhttps://stackoverflow.com/a/8838023/1199155 (แอมป์ขาดหายไป) อักขระสี่ตัวที่ตรวจสอบในประโยคถ้าเป็นเพียงตัวอักษรที่ต่ำกว่า 128 ตามhttp://www.w3.org/TR/html4/sgml/entities.html


ดี ไม่ใช้การเข้ารหัส "html version" (ตัวอย่าง: "á" จะเป็น "& aacute;" แทนที่จะเป็น "& # 225;") แต่เนื่องจากตัวเลขทำงานได้แม้ใน IE7 ฉันคิดว่าฉันไม่ ต้องกังวล ขอบคุณ
nonzaprej

ทำไมคุณเข้ารหัสอักขระทั้งหมดเมื่อ OP ขอให้หนีอักขระที่เกี่ยวข้องทั้ง 4 ตัว คุณกำลังสูญเสีย CPU และหน่วยความจำ
David Balažic

1
คุณลืมเครื่องหมายอะโพสโทรฟี เพื่อให้ผู้คนสามารถฉีดคุณลักษณะที่ไม่มีเครื่องหมายอัญประกาศได้ทุกที่ที่ใช้รหัสนี้เพื่อหลีกเลี่ยงค่าคุณลักษณะ
David Balažic

45

มีไลบรารี Apache Commons Langเวอร์ชันใหม่กว่าและใช้ชื่อแพ็กเกจอื่น (org.apache.commons.lang3) StringEscapeUtilsตอนนี้มีวิธีการคงแตกต่างกันสำหรับการหลบหนีชนิดของเอกสาร ( http://commons.apache.org/proper/commons-lang/javadocs/api-3.0/index.html ) ดังนั้นเพื่อหลีกเลี่ยงสตริง HTML เวอร์ชัน 4.0:

import static org.apache.commons.lang3.StringEscapeUtils.escapeHtml4;

String output = escapeHtml4("The less than sign (<) and ampersand (&) must be escaped before using them in HTML");

3
น่าเสียดายที่ไม่มีสิ่งใดสำหรับ HTML 5 และเอกสาร Apache ไม่ระบุว่าเหมาะสมหรือไม่ที่จะใช้ escapeHtml4 สำหรับ HTML 5
Paul Vincent Craven

43

สำหรับผู้ที่ใช้ Google Guava:

import com.google.common.html.HtmlEscapers;
[...]
String source = "The less than sign (<) and ampersand (&) must be escaped before using them in HTML";
String escaped = HtmlEscapers.htmlEscaper().escape(source);

40

บน android (API 16 หรือสูงกว่า) คุณสามารถ:

Html.escapeHtml(textToScape);

หรือสำหรับ API ที่ต่ำกว่า:

TextUtils.htmlEncode(textToScape);

มีเหตุผลอะไรที่จะใช้escapeHtmlแทนhtmlEncodeหรือไม่?
Muz

2
ดูคำถามของฉันเกี่ยวกับความแตกต่างระหว่างสองสิ่งนี้ด้วย (@Muz)
JonasCz - Reinstate Monica

37

ระวังด้วยนะ มี 'บริบท' ที่แตกต่างกันจำนวนหนึ่งภายในเอกสาร HTML: ภายในองค์ประกอบ, ค่าแอตทริบิวต์ที่ยกมา, ค่าแอตทริบิวต์ที่ไม่มีเครื่องหมายอัญประกาศ, แอตทริบิวต์ URL, javascript, CSS, ฯลฯ ... คุณจะต้องใช้วิธีการเข้ารหัสที่แตกต่างกันสำหรับแต่ละ สิ่งเหล่านี้เพื่อป้องกัน Cross-Site Scripting (XSS) ตรวจสอบเอกสารการป้องกัน OWASP XSSสำหรับรายละเอียดเกี่ยวกับบริบทเหล่านี้ คุณสามารถหาวิธีการหลบหนีสำหรับแต่ละบริบทเหล่านี้ในห้องสมุด OWASP ESAPI - https://github.com/ESAPI/esapi-java-legacy


6
ขอบคุณสำหรับการชี้ให้เห็นว่าบริบทที่คุณต้องการเข้ารหัสเรื่องสำคัญมาก คำว่า "encode" เป็นคำกริยาที่เหมาะสมมากกว่า "escape" เช่นกัน Escape หมายถึงการแฮ็กพิเศษบางประเภทซึ่งตรงข้ามกับ "ฉันจะเข้ารหัสสตริงนี้ได้อย่างไร: แอตทริบิวต์ XHTML / พารามิเตอร์การสืบค้น SQL / สตริงการพิมพ์ PostScript / เขตข้อมูลเอาต์พุต CSV?
Roboprog

5
'Encode' และ 'escape' มีการใช้อย่างกว้างขวางเพื่ออธิบายเรื่องนี้ โดยทั่วไปคำว่า "การหลบหนี" จะใช้เมื่อกระบวนการคือการเพิ่ม "ตัวละครการหลบหนี" ก่อนที่ตัวละครที่เกี่ยวข้องกับวากยสัมพันธ์เช่นการหลีกเลี่ยงตัวละครที่มีเครื่องหมายคำพูดด้วยเครื่องหมายแบ็กสแลช \ "คำว่า" การเข้ารหัส " อักขระในรูปแบบอื่นเช่น URL ที่เข้ารหัสอักขระคำพูด% 22 หรือการเข้ารหัสเอนทิตี HTML เป็น & # x22 หรือ @quot
Jeff Williams

owasp-esapi-java.googlecode.com/svn/trunk_doc/latest/index.html ลิงค์เสียตอนนี้
andrew pate

1
หากต้องการบันทึก googling ให้ค้นหา Encoder class static.javadoc.io/org.owasp.esapi/esapi/2.0.1/org/owasp/esapi/ ......
Jakub Bochenski

14

สำหรับวัตถุประสงค์บางอย่างHtmlUtils :

import org.springframework.web.util.HtmlUtils;
[...]
HtmlUtils.htmlEscapeDecimal("&"); //gives &#38;
HtmlUtils.htmlEscape("&"); //gives &amp;

1
จากความคิดเห็น HtmlUtils ฤดูใบไม้ผลิ: * <p> สำหรับชุดโปรแกรมอรรถประโยชน์การหลบหนีของสตริงที่ครอบคลุม * ให้พิจารณา Apache Commons Lang และคลาส StringEscapeUtils * เราไม่ได้ใช้คลาสนั้นที่นี่เพื่อหลีกเลี่ยงการพึ่งพารันไทม์ * ในคอมมอนส์ Lang สำหรับการหลบหนี HTML นอกจากนี้การหลบหนี * HTML ของ Spring นั้นมีความยืดหยุ่นมากกว่าและสอดคล้องกับ HTML 4.0 100% หากคุณใช้ Apache คอมมอนส์ในโครงการของคุณอยู่แล้วคุณควรใช้ StringEscapeUtils จาก apache
andreyro

10

ในขณะที่คำตอบ @dfa org.apache.commons.lang.StringEscapeUtils.escapeHtmlเป็นสิ่งที่ดีและฉันเคยใช้มาแล้วในอดีตไม่ควรใช้เพื่อหลบหนีแอตทริบิวต์ HTML (หรือ XML) มิฉะนั้นช่องว่างจะถูกทำให้เป็นมาตรฐาน (หมายถึงอักขระช่องว่างที่อยู่ติดกันทั้งหมดกลายเป็นช่องว่างเดียว)

ฉันรู้สิ่งนี้เพราะฉันมีข้อบกพร่องยื่นต่อห้องสมุดของฉัน (JATL) สำหรับคุณลักษณะที่ช่องว่างไม่ได้ถูกเก็บรักษาไว้ ดังนั้นฉันมีการลดลงใน (คัดลอกวาง ' คลาส ) (ซึ่งฉันขโมยบางส่วนจาก JDOM) ที่แตกต่างการหลบหนีของคุณลักษณะและเนื้อหาองค์ประกอบที่แตกต่างหนีของคุณลักษณะและเนื้อหาองค์ประกอบ

แม้ว่าสิ่งนี้อาจไม่ได้มีความสำคัญเท่าในอดีต (การหลบหนีของคุณสมบัติที่เหมาะสม) แต่ก็เป็นที่สนใจมากขึ้นเนื่องจากการใช้การใช้data-คุณลักษณะของ HTML5


9

org.apache.commons.lang3.StringEscapeUtils เลิกใช้แล้ว ตอนนี้คุณต้องใช้ org.apache.commons.text.StringEscapeUtils โดย

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-text</artifactId>
        <version>${commons.text.version}</version>
    </dependency>

1

ห้องสมุดส่วนใหญ่เสนอการหลบหนีทุกอย่างที่ทำได้รวมถึงสัญลักษณ์หลายร้อยตัวและอักขระที่ไม่ใช่ ASCII หลายพันตัวซึ่งไม่ใช่สิ่งที่คุณต้องการในโลก UTF-8

นอกจากนี้ดังที่ Jeff Williams ตั้งข้อสังเกตไม่มีตัวเลือก“ escape HTML” เดียวมีหลายบริบท

สมมติว่าคุณไม่เคยใช้แอททริบิวที่ยังไม่ได้อ้างถึงและระลึกไว้เสมอว่ามีบริบทที่แตกต่างกันมันได้เขียนเวอร์ชั่นของฉันเอง:

private static final long BODY_ESCAPE =
        1L << '&' | 1L << '<' | 1L << '>';
private static final long DOUBLE_QUOTED_ATTR_ESCAPE =
        1L << '"' | 1L << '&' | 1L << '<' | 1L << '>';
private static final long SINGLE_QUOTED_ATTR_ESCAPE =
        1L << '"' | 1L << '&' | 1L << '\'' | 1L << '<' | 1L << '>';

// 'quot' and 'apos' are 1 char longer than '#34' and '#39' which I've decided to use
private static final String REPLACEMENTS = "&#34;&amp;&#39;&lt;&gt;";
private static final int REPL_SLICES = /*  |0,   5,   10,  15, 19, 23*/
        5<<5 | 10<<10 | 15<<15 | 19<<20 | 23<<25;
// These 5-bit numbers packed into a single int
// are indices within REPLACEMENTS which is a 'flat' String[]

private static void appendEscaped(
        StringBuilder builder,
        CharSequence content,
        long escapes // pass BODY_ESCAPE or *_QUOTED_ATTR_ESCAPE here
) {
    int startIdx = 0, len = content.length();
    for (int i = 0; i < len; i++) {
        char c = content.charAt(i);
        long one;
        if (((c & 63) == c) && ((one = 1L << c) & escapes) != 0) {
        // -^^^^^^^^^^^^^^^   -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        // |                  | take only dangerous characters
        // | java shifts longs by 6 least significant bits,
        // | e. g. << 0b110111111 is same as >> 0b111111.
        // | Filter out bigger characters

            int index = Long.bitCount(SINGLE_QUOTED_ATTR_ESCAPE & (one - 1));
            builder.append(content, startIdx, i /* exclusive */)
                    .append(REPLACEMENTS,
                            REPL_SLICES >>> 5*index & 31,
                            REPL_SLICES >>> 5*(index+1) & 31);
            startIdx = i + 1;
        }
    }
    builder.append(content, startIdx, len);
}

พิจารณาการคัดลอกจากGist โดยไม่จำกัดความยาวของบรรทัดสรุปสาระสำคัญได้โดยไม่ต้องเส้นขีดจำกัดความยาว

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.