แนวทางปฏิบัติที่ดีที่สุดสำหรับการฝัง JSON โดยพลการใน DOM?


110

ฉันกำลังคิดเกี่ยวกับการฝัง JSON โดยพลการใน DOM ดังนี้:

<script type="application/json" id="stuff">
    {
        "unicorns": "awesome",
        "abc": [1, 2, 3]
    }
</script>

ซึ่งคล้ายกับวิธีที่เราอาจจัดเก็บเทมเพลต HTML ที่กำหนดเองใน DOM เพื่อใช้กับเอ็นจินเทมเพลต JavaScript ในภายหลัง ในกรณีนี้เราสามารถดึง JSON และแยกวิเคราะห์ได้ในภายหลังด้วย:

var stuff = JSON.parse(document.getElementById('stuff').innerHTML);

วิธีนี้ได้ผลแต่เป็นวิธีที่ดีที่สุดหรือไม่? สิ่งนี้ละเมิดแนวทางปฏิบัติหรือมาตรฐานที่ดีที่สุดหรือไม่

หมายเหตุ: ฉันไม่ได้มองหาทางเลือกอื่นในการจัดเก็บ JSON ใน DOM ฉันตัดสินใจแล้วว่าเป็นทางออกที่ดีที่สุดสำหรับปัญหาเฉพาะที่ฉันมี ฉันแค่มองหาวิธีที่ดีที่สุดที่จะทำ


1
ทำไมคุณถึงไม่มีมันเป็นvarในจาวาสคริปต์?
Krizz

@Krizz ต้องเป็นส่วนหนึ่งของเอกสารแบบคงที่ซึ่งจะถูกประมวลผลในภายหลังโดยจาวาสคริปต์แบบห่อหุ้มที่ซับซ้อน การจัดเก็บไว้ใน DOM คือสิ่งที่ฉันต้องการทำ
Ben Lee

@Krizz ฉันถูกวางด้วยปัญหาที่คล้ายกัน ฉันต้องการใส่ข้อมูลในไซต์ที่แตกต่างกันสำหรับผู้ใช้แต่ละคนโดยไม่ต้องร้องขอ AJAX ดังนั้นฉันฝัง PHP ในคอนเทนเนอร์ทำสิ่งที่คล้ายกับสิ่งที่คุณมีด้านบนเพื่อรับข้อมูลในจาวาสคริปต์
Patrick Lorio

2
ฉันคิดว่าวิธีการดั้งเดิมของคุณดีที่สุดจริงๆ HTML5 ใช้ได้ 100% เป็นการแสดงออกโดยไม่สร้างองค์ประกอบ "ปลอม" ที่คุณจะลบหรือซ่อนด้วย CSS และไม่ต้องการการเข้ารหัสอักขระใด ๆ ข้อเสียคืออะไร?
Jamie Treworgy

22
หากคุณมีสตริงที่มีค่า</script><script>alert()</script><script>ภายในออบเจ็กต์ JSON คุณจะต้องประหลาดใจ สิ่งนี้ไม่ปลอดภัยเว้นแต่คุณจะล้างข้อมูลให้สะอาดก่อน
silviot

คำตอบ:


77

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

"เมื่อใช้เพื่อรวมบล็อกข้อมูล (ซึ่งตรงข้ามกับสคริปต์) ข้อมูลจะต้องฝังอยู่ในบรรทัดรูปแบบของข้อมูลต้องกำหนดโดยใช้แอตทริบิวต์ type ไม่ต้องระบุแอตทริบิวต์ src และเนื้อหาขององค์ประกอบสคริปต์ต้อง เป็นไปตามข้อกำหนดที่กำหนดไว้สำหรับรูปแบบที่ใช้ "

อ่านที่นี่: http://dev.w3.org/html5/spec/Overview.html#the-script-element

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


3
ขอบคุณ. คำพูดจากข้อมูลจำเพาะทำให้ฉันเชื่อมั่น
Ben Lee

17
จะใช้ได้อย่างสมบูรณ์ก็ต่อเมื่อคุณตรวจสอบและทำความสะอาดออบเจ็กต์ JSON ก่อน: คุณไม่สามารถฝังข้อมูลต้นทางของผู้ใช้ได้ ดูความคิดเห็นของฉันในคำถาม
silviot

1
สงสัยเป็นพิเศษ: จะใส่อะไรดี? ศีรษะหรือลำตัวด้านบนหรือด้านล่าง?
ชาลเลต

1
น่าเสียดายที่ดูเหมือนว่านโยบาย CSP อาจ / หยุดscriptแท็กทั้งหมด
Larry K

2
คุณจะป้องกันอย่างมีประสิทธิภาพจากการฝัง JSON ที่มี </script> และทำให้การแทรก HTML ได้อย่างไร มีอะไรที่มั่นคง / ง่ายหรือใช้แอตทริบิวต์ข้อมูลดีกว่าไหม
jonasfj

23

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

<div id="mydiv" data-unicorns='{"unicorns":"awesome", "abc":[1,2,3]}' class="hidden"></div>

หากคุณใช้ jQuery การดึงข้อมูลจะทำได้ง่ายพอ ๆ กับ:

var stuff = JSON.parse($('#mydiv').attr('data-unicorns'));

1
มีเหตุผล. แม้ว่าจะมีเครื่องหมายคำพูดเดียวสำหรับชื่อคีย์ แต่JSON.parseจะใช้ไม่ได้ (อย่างน้อย Google Chrome JSON.parse ดั้งเดิมจะใช้ไม่ได้) ข้อมูลจำเพาะ JSON ต้องการเครื่องหมายคำพูดคู่ แต่นั่นง่ายพอที่จะแก้ไขโดยใช้เอนทิตีเช่น...&lt;unicorns&gt;:....
Ben Lee

4
คำถามหนึ่งข้อ: มีการจำกัดความยาวของแอตทริบิวต์ใน HTML 5 หรือไม่?
Ben Lee

ใช่มันจะได้ผล คุณยังสามารถสลับไปมาเพื่อให้ HTML ของคุณใช้เครื่องหมายคำพูดเดี่ยวและข้อมูล JSON ใช้สองครั้ง
Horatio Alderaan

1
โอเคพบคำตอบสำหรับคำถามของฉัน: stackoverflow.com/questions/1496096/… - นี่เพียงพอสำหรับวัตถุประสงค์ของฉัน
Ben Lee

2
สิ่งนี้ใช้ไม่ได้กับสตริงเดี่ยวเช่น"I am valid JSON"และใช้เครื่องหมายคำพูดคู่สำหรับแท็กหรืออัญประกาศเดี่ยวที่มีเครื่องหมายคำพูดเดี่ยวในสตริงเช่นdata-unicorns='"My JSON's string"'เครื่องหมายคำพูดเดี่ยวจะไม่ได้รับการยกเว้นด้วยการเข้ารหัสเป็น JSON
Robbie Averill

13

วิธีการฝัง json ในแท็กสคริปต์นี้อาจมีปัญหาด้านความปลอดภัย สมมติว่าข้อมูล json มาจากการป้อนข้อมูลของผู้ใช้เป็นไปได้ที่จะสร้างสมาชิกข้อมูลที่จะมีผลแยกออกจากแท็กสคริปต์และอนุญาตให้มีการแทรกเข้าไปในโดมโดยตรง ดูที่นี่:

http://jsfiddle.net/YmhZv/1/

นี่คือการฉีดยา

<script type="application/json" id="stuff">
{
    "unicorns": "awesome",
    "abc": [1, 2, 3],
    "badentry": "blah </script><div id='baddiv'>I should not exist.</div><script type="application/json" id='stuff'> ",
}
</script>

ไม่มีทางหลีกเลี่ยง / เข้ารหัสได้


7
นี่เป็นเรื่องจริง แต่ไม่ใช่ข้อบกพร่องด้านความปลอดภัยของวิธีการนี้ หากคุณเคยใส่สิ่งที่เกิดจากการป้อนข้อมูลของผู้ใช้ลงในหน้าเว็บของคุณคุณจะต้องพยายามอย่างยิ่งที่จะหลีกหนี วิธีนี้ยังคงใช้งานได้ตราบเท่าที่คุณใช้มาตรการป้องกันตามปกติเกี่ยวกับการป้อนข้อมูลของผู้ใช้
Ben Lee

JSON ไม่ได้เป็นส่วนหนึ่งของ HTML โปรแกรมแยกวิเคราะห์ HTML จะทำงานต่อไป เช่นเดียวกับเวลาที่ JSON จะเป็นส่วนหนึ่งของย่อหน้าข้อความหรือ div-element HTML - หลีกเลี่ยงเนื้อหาในโปรแกรมของคุณ นอกจากนี้คุณยังอาจหนีจากเครื่องหมายทับ แม้ว่า JSON ไม่ต้องการสิ่งนี้ แต่ก็ทนต่อเครื่องหมายทับที่ไม่จำเป็นได้ ซึ่งสามารถใช้เธอเพื่อจุดประสงค์ในการทำให้ปลอดภัยในการฝัง json_encode ของ PHP ทำสิ่งนี้ตามค่าเริ่มต้น
Timo Tijhof

7

ดูกฎ # 3.1ในเอกสารโกงการป้องกัน XSS ของ OWASP

สมมติว่าคุณต้องการรวม JSON นี้ใน HTML:

{
    "html": "<script>alert(\"XSS!\");</script>"
}

สร้างที่ซ่อนอยู่<div>ใน HTML จากนั้นหลีกเลี่ยง JSON ของคุณโดยการเข้ารหัสเอนทิตีที่ไม่ปลอดภัย (เช่น &, <,>, ", 'และ /) แล้วใส่ไว้ในองค์ประกอบ

<div id="init_data" style="display:none">
        {&#34;html&#34;:&#34;&lt;script&gt;alert(\&#34;XSS!\&#34;);&lt;/script&gt;&#34;}
</div>

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

var text = document.querySelector('#init_data').textContent;
var json = JSON.parse(text);
console.log(json); // {html: "<script>alert("XSS!");</script>"}

ฉันเชื่อว่านี่เป็นคำตอบที่ดีและปลอดภัยที่สุด ขอให้สังเกตว่าจำนวนมากของตัวละคร JSON {name: 'Dwayne "The Rock" Johnson'}ทั่วไปได้หนีออกมาและบางตัวละครที่ได้รับการหนีคู่เช่นคำพูดที่ด้านในวัตถุ แต่ก็ยังดีที่สุดที่จะใช้แนวทางนี้เนื่องจากเฟรมเวิร์ก / ไลบรารีเทมเพลตของคุณน่าจะมีวิธีเข้ารหัส HTML ที่ปลอดภัยอยู่แล้ว อีกทางเลือกหนึ่งคือใช้ base64 ซึ่งทั้ง HTML ปลอดภัยและปลอดภัยในการใส่สตริง JS ง่ายต่อการเข้ารหัส / ถอดรหัสใน JS โดยใช้ btoa () / atob () และอาจเป็นเรื่องง่ายสำหรับคุณที่จะทำฝั่งเซิร์ฟเวอร์
sstur

วิธีที่ปลอดภัยยิ่งขึ้นคือการใช้<data>องค์ประกอบที่ถูกต้องตามความหมายและรวมข้อมูล JSON ไว้ในvalueแอตทริบิวต์ จากนั้นคุณจะต้องหลีกเลี่ยงเครื่องหมายคำพูด&quotหากคุณใช้เครื่องหมายคำพูดคู่เพื่อใส่ข้อมูลหรือ&#39;ถ้าคุณใช้เครื่องหมายคำพูดเดี่ยว (ซึ่งน่าจะดีกว่า)
Rúnar Berg

5

ฉันขอแนะนำให้ใส่ JSON ลงในสคริปต์แบบอินไลน์ด้วยฟังก์ชันเรียกกลับ (ชนิดของJSONP ):

<script>
someCallback({
    "unicorns": "awesome",
    "abc": [1, 2, 3]
});
</script>

หากสคริปต์การเรียกใช้งานถูกโหลดหลังเอกสารคุณสามารถจัดเก็บสิ่งนี้ไว้ที่ใดที่หนึ่งอาจมีอาร์กิวเมนต์ตัวระบุเพิ่มเติม: someCallback("stuff", { ... });


@ BenLee มันควรจะทำงานได้ดีโดยมีข้อเสียเพียงอย่างเดียวคือต้องกำหนดฟังก์ชันการโทรกลับ โซลูชันที่แนะนำอื่น ๆ จะแบ่งอักขระ HTML พิเศษ (เช่น &) และเครื่องหมายคำพูดหากคุณมีสิ่งเหล่านี้ใน JSON
คัดลอก

สิ่งนี้ให้ความรู้สึกดีขึ้นเพราะคุณไม่จำเป็นต้องใช้แบบสอบถาม Dom เพื่อค้นหาข้อมูล
Jaseem

@copy การแก้ปัญหานี้ยังคงต้องการการหลบหนี (เพียงแค่ประเภทอื่น) ดูคำตอบของ MadCoder เพียงแค่ทิ้งไว้ที่นี่เพื่อความสมบูรณ์
pvgoran

2

คำแนะนำของฉันคือให้เก็บข้อมูล JSON ไว้ใน.jsonไฟล์ภายนอกจากนั้นดึงไฟล์เหล่านั้นผ่าน Ajax คุณไม่ได้ใส่โค้ด CSS และ JavaScript ลงในหน้าเว็บ (แบบอินไลน์) แล้วทำไมคุณถึงทำกับ JSON?


12
คุณไม่ใส่ CSS และ Javascript แบบอินไลน์ในหน้าเว็บเพราะโดยปกติจะใช้ร่วมกันระหว่างหน้าอื่น ๆ หากข้อมูลที่เป็นปัญหาถูกสร้างขึ้นโดยเซิร์ฟเวอร์อย่างชัดเจนสำหรับบริบทนี้การฝังข้อมูลจะมีประสิทธิภาพมากกว่าการเริ่มต้นคำขออื่นสำหรับบางสิ่งที่ไม่สามารถแคชได้
Jamie Treworgy

เป็นเพราะฉันกำลังอัปเดตระบบเดิมที่ออกแบบมาไม่ดีและแทนที่จะออกแบบระบบใหม่ทั้งหมดฉันต้องแก้ไขเพียงส่วนเดียว การจัดเก็บ JSON ใน DOM เป็นวิธีที่ดีที่สุดในการแก้ไขส่วนนี้ นอกจากนี้ฉันเห็นด้วยกับสิ่งที่ @jamietre พูด
Ben Lee

@jamietre โปรดทราบว่า OP ระบุว่าสตริง JSON นี้จำเป็นในภายหลังเท่านั้น คำถามคือว่าจำเป็นเสมอหรือเฉพาะในบางกรณี หากจำเป็นในบางกรณีก็ควรมีในไฟล์ภายนอกและโหลดตามเงื่อนไขเท่านั้น
Šime Vidas

2
ฉันยอมรับว่ามี "what ifs" มากมายที่สามารถลดระดับได้ไม่ทางใดก็ทางหนึ่ง แต่โดยทั่วไปแล้วถ้าคุณรู้ว่าเมื่อใดที่หน้าเว็บแสดงสิ่งที่คุณต้องการ - แม้ว่าอาจจะเป็นไปได้ก็ตาม - มักจะดีกว่าที่จะส่งขึ้นทันที เช่นถ้าฉันมีกล่องข้อมูลบางส่วนที่เริ่มยุบลงฉันมักจะต้องการรวมเนื้อหาไว้ในบรรทัดเพื่อให้ขยายได้ทันที คำขอใหม่มีค่าใช้จ่ายมากเมื่อเทียบกับค่าใช้จ่ายของข้อมูลเพิ่มเติมเล็กน้อยในข้อมูลที่มีอยู่และสร้างประสบการณ์ของผู้ใช้ที่ตอบสนองได้ดีขึ้น ฉันมั่นใจว่ามีจุดพัก
Jamie Treworgy

2

HTML5 มี<data>องค์ประกอบสำหรับเก็บข้อมูลที่เครื่องอ่านได้ ในฐานะที่เป็นทางเลือกที่ปลอดภัยกว่า<script type="application/json">คุณสามารถรวมข้อมูล JSON ของคุณไว้ในvalueแอตทริบิวต์ขององค์ประกอบนั้นได้

const jsonData = document.querySelector('.json-data');
const data = JSON.parse(jsonData.value);

console.log(data)
<data class="json-data" value='
  {
    "unicorns": "awesome",
    "abc": [1, 2, 3],
    "careful": "to escape &#39; quotes"
  }
'></data>

ในกรณีนี้คุณต้องแทนที่เครื่องหมายคำพูดเดี่ยวทั้งหมดด้วย&#39;หรือด้วย&quot;ถ้าคุณเลือกที่จะใส่เครื่องหมายคำพูดคู่ค่า มิฉะนั้นความเสี่ยงของคุณจะถูกโจมตีด้วยXSSเหมือนคำตอบอื่น ๆ ที่แนะนำ

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