Najlepsza praktyka dotycząca osadzania dowolnego formatu JSON w DOM?

110

Myślę o osadzeniu dowolnego JSON w DOM w ten sposób:

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

Jest to podobne do sposobu, w jaki można przechowywać dowolny szablon HTML w DOM do późniejszego wykorzystania z silnikiem szablonów JavaScript. W takim przypadku moglibyśmy później pobrać JSON i przeanalizować go za pomocą:

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

To działa , ale czy to najlepszy sposób? Czy narusza to jakiekolwiek najlepsze praktyki lub standardy?

Uwaga: nie szukam alternatywy dla przechowywania JSON w DOM, już zdecydowałem, że to najlepsze rozwiązanie dla konkretnego problemu, który mam. Po prostu szukam najlepszego sposobu, aby to zrobić.

Ben Lee
źródło
1
dlaczego nie masz go jako varw javascript?
Krizz
@Krizz, musi być częścią statycznego dokumentu, który jest później przetwarzany przez złożony łańcuch zhermetyzowanego kodu JavaScript. Przechowywanie go w DOM jest tym, co chcę zrobić.
Ben Lee,
@Krizz miałem podobny problem. Chciałem umieścić dane w witrynie innej dla każdego użytkownika bez wykonywania żądania AJAX. Więc osadziłem trochę PHP w kontenerze, zrobiłem coś podobnego do tego, co masz powyżej, aby uzyskać dane w javascript.
Patrick Lorio,
2
Myślę, że twoja oryginalna metoda jest właściwie najlepsza. Jest w 100% poprawny w HTML5, jest wyrazisty, nie tworzy „fałszywych” elementów, które po prostu usuniesz lub ukryjesz za pomocą CSS; i nie wymaga żadnego kodowania znaków. Jakie są wady?
Jamie Treworgy
22
Jeśli masz ciąg znaków z wartością </script><script>alert()</script><script>wewnątrz obiektu JSON, otrzymasz niespodzianki. Nie jest to bezpieczne, chyba że najpierw wyczyścisz dane.
silviot,

Odpowiedzi:

77

Myślę, że twoja oryginalna metoda jest najlepsza. Specyfikacja HTML5 odnosi się nawet do tego użycia:

„W przypadku użycia do dołączania bloków danych (w przeciwieństwie do skryptów) dane muszą być osadzone w tekście, format danych należy podać za pomocą atrybutu type, atrybutu src nie można określać, a zawartość elementu skryptu musi spełniają wymagania określone dla używanego formatu. "

Przeczytaj tutaj: http://dev.w3.org/html5/spec/Overview.html#the-script-element

Zrobiłeś dokładnie to. Czego nie kochać? Brak wymaganego kodowania znaków z danymi atrybutów. Możesz go sformatować, jeśli chcesz. Jest wyrazisty, a przeznaczenie jest jasne. Nie wygląda to na włamanie (np. Użycie CSS do ukrycia elementu „carrier”). To jest całkowicie poprawne.

Jamie Treworgy
źródło
3
Dziękuję Ci. Przekonał mnie cytat ze specyfikacji.
Ben Lee,
17
Jest to całkowicie poprawne tylko wtedy, gdy najpierw sprawdzisz i wyczyścisz obiekt JSON: nie możesz po prostu osadzić danych pochodzących od użytkownika. Zobacz mój komentarz do pytania.
silviot
1
dodatkowo zastanawiam się: jakie jest dobre miejsce, aby to umieścić? głowa czy tułów, góra czy dół?
Challet
1
Niestety wydaje się, że zasady CSP mogą / zatrzymają wszystkie scripttagi.
Larry K
2
Jak skutecznie chronić się przed osadzaniem JSON, który zawiera </script>, a tym samym umożliwia wstrzykiwanie HTML? Czy jest coś solidnego / łatwego, czy lepiej jest użyć atrybutów danych?
jonasfj
23

Jako ogólny kierunek, spróbuję zamiast tego użyć atrybutów danych HTML5 . Nic nie stoi na przeszkodzie, aby wprowadzić prawidłowy kod JSON. na przykład:

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

Jeśli używasz jQuery, odzyskanie go jest tak proste, jak:

var stuff = JSON.parse($('#mydiv').attr('data-unicorns'));
Horatio Alderaan
źródło
1
Ma sens. Należy jednak pamiętać, że pojedyncze cudzysłowy nazwy klucza JSON.parsenie będą działać (przynajmniej natywny Google Chrome JSON.parse nie zadziała). Specyfikacja JSON wymaga podwójnych cudzysłowów. Ale to dość łatwe do naprawienia przy użyciu jednostek takich jak ...&lt;unicorns&gt;:....
Ben Lee,
4
Jedno pytanie: czy istnieje ograniczenie długości atrybutów w HTML 5?
Ben Lee,
Tak, to zadziała. Możesz też to zmienić, aby kod HTML używał pojedynczych cudzysłowów, a dane JSON - double.
Horatio Alderaan
1
Ok, znalazłem odpowiedź na moje pytanie: stackoverflow.com/questions/1496096/… - to wystarczy do moich celów.
Ben Lee,
2
To nie zadziała dla pojedynczego ciągu znaków, np. Z "I am valid JSON"użyciem podwójnych cudzysłowów dla tagu lub pojedynczych cudzysłowów z pojedynczymi cudzysłowami w ciągu, np. data-unicorns='"My JSON's string"'Ponieważ pojedyncze cudzysłowy nie są chronione przed kodowaniem jako JSON.
Robbie Averill
13

Ta metoda osadzania json w tagu skryptu ma potencjalny problem z bezpieczeństwem. Zakładając, że dane json pochodzą z danych wejściowych użytkownika, możliwe jest utworzenie elementu członkowskiego danych, który w efekcie wyłamie się ze znacznika skryptu i umożliwi bezpośrednie wstrzyknięcie do domeny. Spójrz tutaj:

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

Oto zastrzyk

<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>

Po prostu nie ma sposobu na ucieczkę / kodowanie.

MadCoder
źródło
7
To prawda, ale tak naprawdę nie jest to luka w zabezpieczeniach metody. Jeśli kiedykolwiek umieszczasz na swoich stronach coś, co pochodzi z danych wejściowych użytkownika, musisz uważnie unikać tego. Ta metoda jest nadal skuteczna, o ile podejmiesz zwykłe środki ostrożności dotyczące danych wejściowych użytkownika.
Ben Lee
JSON nie jest częścią HTML, parser HTML po prostu działa. Działa tak samo, jak wtedy, gdy JSON byłby częścią akapitu tekstu lub elementu div. Zmień znaczenie kodu HTML w swoim programie. Dodatkowo możesz też uniknąć ukośników. Chociaż JSON tego nie wymaga, toleruje niepotrzebne ukośniki. Które można jej użyć, aby zapewnić bezpieczeństwo osadzania. PHP json_encode robi to domyślnie.
Timo Tijhof
7

Patrz zasada nr 3.1 w ściągawce OWASP dotyczącej zapobiegania XSS.

Załóżmy, że chcesz dołączyć ten JSON do HTML:

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

Utwórz ukryty <div>w HTML. Następnie wymknij kod JSON, kodując niebezpieczne elementy (np. &, <,>, „, 'I, /) i umieść go wewnątrz elementu.

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

Teraz możesz uzyskać do niego dostęp, czytając textContentelement za pomocą JavaScript i analizując go:

var text = document.querySelector('#init_data').textContent;
var json = JSON.parse(text);
console.log(json); // {html: "<script>alert("XSS!");</script>"}
Mateusz
źródło
Uważam, że to najlepsza i najbezpieczniejsza odpowiedź. Zwróć uwagę, że wiele typowych znaków JSON jest zmienianych, a niektóre znaki są podwójnie znakowane, takie jak wewnętrzne cudzysłowy w obiekcie {name: 'Dwayne "The Rock" Johnson'}. Ale prawdopodobnie nadal najlepiej jest używać tego podejścia, ponieważ Twoja biblioteka framework / szablonów prawdopodobnie zawiera już bezpieczny sposób kodowania HTML. Alternatywą byłoby użycie base64, który jest zarówno bezpieczny dla HTML, jak i bezpieczny do umieszczenia w ciągu JS. Kodowanie / dekodowanie w JS jest łatwe przy użyciu btoa () / atob () i prawdopodobnie łatwo jest to zrobić po stronie serwera.
sstur
Jeszcze bezpieczniejszą metodą byłoby użycie poprawnego semantycznie <data>elementu i dołączenie danych JSON do valueatrybutu. Następnie musisz tylko uciec przed cudzysłowami, &quotjeśli używasz podwójnych cudzysłowów do ujęcia danych lub &#39;jeśli używasz apostrofów (co jest prawdopodobnie lepsze).
Rúnar Berg
5

Sugerowałbym umieszczenie JSON w skrypcie wbudowanym z wywołaniem zwrotnym funkcji (rodzaj JSONP ):

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

Jeśli skrypt wykonawczy jest ładowany po dokumencie, możesz go gdzieś przechowywać, prawdopodobnie z dodatkowym argumentem identyfikatora: someCallback("stuff", { ... });

Kopiuj
źródło
@BenLee powinno działać bardzo dobrze, z jedyną wadą polegającą na konieczności zdefiniowania funkcji wywołania zwrotnego. Inne sugerowane rozwiązanie przerywa działanie na specjalnych znakach HTML (na przykład &) i cudzysłowach, jeśli masz je w swoim JSON.
skopiuj
To jest lepsze, ponieważ nie potrzebujesz zapytania dom, aby znaleźć dane
Jaseem
@copy To rozwiązanie nadal wymaga ucieczki (tylko innego rodzaju), zobacz odpowiedź MadCodera. Po prostu zostawiam to tutaj dla kompletności.
pvgoran
2

Moim zaleceniem byłoby przechowywanie danych JSON w .jsonplikach zewnętrznych , a następnie pobieranie tych plików przez Ajax. Nie umieszczasz kodu CSS i JavaScript na stronie internetowej (w tekście), więc dlaczego miałbyś to robić z JSON?

Šime Vidas
źródło
12
Nie umieszczasz CSS i Javascript w treści strony internetowej, ponieważ zwykle są one współdzielone między innymi stronami. Jeśli dane, o których mowa, są generowane przez serwer specjalnie dla tego kontekstu, osadzenie ich jest znacznie bardziej wydajne niż inicjowanie kolejnego żądania dotyczącego czegoś, czego nie można buforować.
Jamie Treworgy
Dzieje się tak, ponieważ dokonuję aktualizacji starszego systemu, który został źle zaprojektowany i zamiast przeprojektowywać cały system, muszę tylko naprawić jedną część. Przechowywanie JSON w DOM to najlepszy sposób na naprawienie tej jednej części. Zgadzam się też z tym, co powiedział @jamietre.
Ben Lee,
@jamietre Zauważ, że OP stwierdził, że ten ciąg JSON jest potrzebny dopiero później . Pytanie brzmi, czy jest to potrzebne zawsze, czy tylko w niektórych przypadkach. Jeśli jest to potrzebne tylko w niektórych przypadkach, warto umieścić go w zewnętrznym pliku i ładować tylko warunkowo.
Šime Vidas
2
Zgadzam się, że istnieje wiele „a co, jeśli”, które mogą przechylić szalę w jedną lub drugą stronę. Ale ogólnie rzecz biorąc, jeśli wiesz, kiedy strona zostanie wyrenderowana, czego będziesz potrzebować - nawet jeśli tylko możliwe - często lepiej jest wysłać ją od razu. Na przykład, gdybym miał jakieś pola informacyjne, które zaczynają się zwijać, zwykle chciałbym dołączyć ich zawartość w tekście, aby natychmiast się rozwijały. Narzut nowego żądania jest dużo w porównaniu z narzutem trochę dodatkowych danych na istniejącym i tworzy bardziej responsywne środowisko użytkownika. Jestem pewien, że jest punkt krytyczny.
Jamie Treworgy
2

HTML5 zawiera <data>element do przechowywania danych do odczytu maszynowego. Jako - być może bezpieczniejsza - alternatywa dla <script type="application/json">Ciebie, możesz dołączyć dane JSON do valueatrybutu tego elementu.

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>

W takim przypadku musisz zamienić wszystkie pojedyncze cudzysłowy na &#39;lub na, &quot;jeśli zdecydujesz się ująć wartość w cudzysłów. W przeciwnym razie ryzyko ataków XSS, jak sugerują inne odpowiedzi.

Rúnar Berg
źródło