Developers Summit 2023 10-A-4 「フロントエンド開発のためのセキュリティ入門」の発表資料です。 https://event.shoeisha.jp/devsumi/20230209/session/4176/
「HTTPS化」「CORS」「XSS」「脆弱なライブラリのチェック」について説明しています。
ϑϩϯτΤϯυ։ൃͷͨΊͷηΩϡϦςΟೖฏ ণ࢜ (@shisama_)
View Slide
ฏ ণ࢜ / Masashi HiranoFrontend Developer@shisama_shisamaNode.js Core Collaboratorࣾձਓେֶੜ@UoPeople
ʲએʳ2݄13ʹॻ੶Λग़൛͠·͢ʂ੬ऑੑͷΈͱରࡦΛίʔυΛॻ͖ͳ͕ΒֶΔϋϯζΦϯܝࡌ͍ͯ͠·͢ʂࠓॻ੶͔Βൈਮͨ͠༰Λൃද͠·͢ɻϑϩϯτΤϯυ։ൃͷͨΊͷηΩϡϦςΟೖ
WebΞϓϦέʔγϣϯʹ ͲΜͳηΩϡϦςΟϦεΫ͕͋Δͷ͔
WebΞϓϦέʔγϣϯͰൃੜ͢ΔηΩϡϦςΟϦεΫ08"415PQ" ΞΫηε੍ޚͷෆඋ" ҉߸Խͷࣦഊ" ΠϯδΣΫγϣϯ" ҆શ͕֬ೝ͞Εͳ͍ෆ҆ͳઃܭ" ηΩϡϦςΟͷઃఆϛε" ੬ऑͰݹ͘ͳͬͨίϯϙʔωϯτ" ࣝผͱೝূͷࣦഊ" ιϑτΣΞͱσʔλͷ߹ੑͷෆ۩߹" ηΩϡϦςΟϩάͱϞχλϦϯάͷࣦഊ" αʔόʔαΠυϦΫΤετϑΥʔδΣϦ
• ҉߸Խͷࣦഊ• ΞΫηε੍ޚͷෆඋ• ΠϯδΣΫγϣϯ• ੬ऑͰݹ͘ͳͬͨίϯϙʔωϯτࠓճ͓͢ΔηΩϡϦςΟϦεΫ
• ҉߸Խͷࣦഊ ➡ ௨৴σʔλͷ҉߸Խ• ΞΫηε੍ޚͷෆඋ ➡ CORSΛͬͨΞΫηε੍ޚ• ΠϯδΣΫγϣϯ ➡ XSSରࡦ• ੬ऑͰݹ͘ͳͬͨίϯϙʔωϯτ ➡ ੬ऑͳϥΠϒϥϦͷνΣοΫࠓճ͓͢ΔηΩϡϦςΟϦεΫ
Webϖʔδͷදࣔϒϥβͱαʔόͷ௨৴͔Β͡·ΔHTMLJSWebαΠτͷαʔό site.exampleଞͷWebαΠτCDNͷαʔόJSONϢʔβ͕WebαΠτΞΫηεHTMLͳͲͷϦιʔεΛૹ৴WebαΠτͱผͷαʔόϦΫΤετϦιʔεΛૹ৴
Webϖʔδͷදࣔϒϥβͱαʔόͷ௨৴͔Β͡·ΔHTMLJSWebαΠτͷαʔό site.exampleଞͷWebαΠτCDNͷαʔόJSONϢʔβ͕WebαΠτΞΫηεHTMLͳͲͷϦιʔεΛૹ৴WebαΠτͱผͷαʔόϦΫΤετϦιʔεΛૹ৴௨৴ʹ)551ͱ͍͏ϓϩτίϧ͕ΘΕ͍ͯΔ௨৴్தͰ߈ܸΛ͞ΕΔͱɺϒϥβαʔόͰରࡦ͍͍ͯͯ͠ແҙຯ
HTTPͷऑ• ௨৴σʔλͷ౪ௌ͕Մೳ• ௨৴૬ख͕ຊ͔Θ͔Βͳ͍• ௨৴σʔλͷվ͟Μ
௨৴σʔλͷ౪ௌ• HTTPͷσʔλ҉߸Խ͞Ε͍ͯͳ͍ฏจͷ··ૹ৴͞ΕΔ• ௨৴్தͰ߈ܸऀ͕౪ௌ͢Δͱ σʔλ͕ݟ͑ͯ͠·͏
௨৴૬ख͕ຊ͔Θ͔Βͳ͍• ௨৴ઌͷαʔό͕ຊ͔Ͳ͏͔ূ໌Ͱ͖ͳ͍• ِͷαʔό͕ຊʹͳΓ͢·͢͜ͱ͕Ͱ͖Δ• URLͰͷΈ௨৴૬खΛ ಛఆ͢ΔͨΊɺِ͔ Ͳ͏͔Θ͔Βͳ͍
௨৴σʔλͷվ͟Μ• ௨৴σʔλͷ༰͕ਖ਼͍͔͠ݕূ͢ΔΈ͕ͳ͍• ͨͱ͑ɺϒϥβ͕ૹ৴ͨ͠σʔλͱαʔό͕ड৴ͨ͠σʔλ͕Ұக͢Δ͔ݕূͰ͖ͳ͍• ௨৴్தͰվ͟Μʢॻ͖͑ʣ ͕͋ͬͯݕͰ͖ͳ͍
HTTPSԽͰHTTPͷऑͷࠀ• ௨৴σʔλͷ҉߸Խ• ௨৴૬खͷݕূ• վ͟ΜνΣοΫ
HTTP௨৴ͷલʹ҉߸Խ௨৴Λ ཱ֬͢Δ
௨৴σʔλͷ҉߸Խ• TLSͱ͍͏ϓϩτίϧΛར༻ͯ͠௨৴Λ҉߸Խ͢Δ• ҉߸Խʹʮެ։伴҉߸ํࣜʯͱʮڞ௨伴҉߸ํࣜʯΛ༻͍Δ ެ։伴҉߸ํࣜɿ҉߸伴ͷड͚͠؆୯͕ͩॲཧ͕͍ ڞ௨伴҉߸ํࣜɿ҉߸伴ͷड͚͍͕͠͠ॲཧ͍
௨৴૬खͷݕূ• ిࢠূ໌ॻΛͬͯ௨৴૬ख͕ຊ͔ݕূ͢Δ• ిࢠূ໌ॻೝূہʢCAʣ͕ൃߦͨ͠ͷΛ͏• ϒϥβʹ͋Β͔͡Ίϧʔτূ໌ॻͱݺΕΔిࢠূ໌ॻ͕Έࠐ·Ε͓ͯΓɺαʔό͔Βड͚औͬͨిࢠূ໌ॻͱরΒ͠߹Θͤͯূ໌ॻ͕ຊ͔ݕূ͢Δ• ূ໌ॻِ͕ͩͬͨ߹ɺϒϥβܯࠂը໘Λදࣔ͢Δ
վ͟ΜνΣοΫ• ҉߸௨৴ͷͨΊʹૹ৴ͨ͠҉߸伴ిࢠূ໌ॻʹվ͟Μ͕ͳ͍͔νΣοΫ͢Δ• ͠҉߸伴ిࢠূ໌ॻʹվ͟Μ͕͋ΕɺͦͷޙͷHTTP௨৴҆શͰͳ͍ͨΊ௨৴Λऴྃ͢Δ
WebαΠτશମͷHTTPSԽʢৗ࣌SSLԽʣ͕ඞਢʹͳ͖͍ͬͯͯΔ• IABʢΠϯλʔωοτΞʔΩςΫνϟҕһձʣ͕ʮ৽͘͠ϓϩτίϧΛઃܭ͢Δͱ͖҉߸ػೳΛඞਢʹ͖͢ʯͱओு• HTTPଓͷ߹ɺϒϥβͷURLόʔʹܯࠂ͕ දࣔ͞ΕΔ• HTTPSଓͷϖʔδͰ͔͑͠ͳ͍API͋Δ ྫɿPayment Request API
WebαΠτΛHTTPSԽͷͨΊʹ͢Δ͜ͱ• Mixed ContentΛमਖ਼͢Δ• HTTP→HTTPSͷϦμΠϨΫτઃఆ• HSTSΛઃఆ͢Δ
Mixed Contentͷةݥੑ• 1ͭͷϖʔδ্ͰHTTPSͰ৴͞ΕͨϦιʔεͱHTTPͰ৴͞ΕͨϦιʔε͕ࠞࡏ͢Δ͜ͱΛMixed ContentͱݺͿ• HTML͕HTTPSʹ৴͞Ε͍ͯͯαϒϦιʔεͷJavaScriptͳͲ͕HTTPͰ৴͞Ε͍ͯΔͱɺ͔ͦ͜Β߈ܸ͞ΕΔՄೳੑ͕͋Δ• Chrome DevToolsͳͲΛ͍ͳ͕ΒMixed ContentΛमਖ਼͠ͳ͚ΕͳΒͳ͍
HTTP→HTTPSͷϦμΠϨΫτઃఆ• http://͔Β͡·ΔURLͷϦϯΫ͕ଞͷWebαΠτʹషΒΕ͍ͯΔͳͲͷࣄ͔ΒHTTP৴ΛΊΔ͜ͱ͕Ͱ͖ͳ͍͜ͱ͋Δ• HTMLʹαϒϦιʔεͷURLΛϕλॻ͖͍ͯ͠ΔͳͲͷ߹ɺҰʹमਖ਼͍͠ͷͰHTTP৴Λ͓ͯ͘͠έʔε͋Δ• αʔόଆͰHTTPͷϦΫΤετΛͯ͢HTTPSϦμΠϨΫτ͢ΔઃఆΛೖΕΔ
HSTSΛઃఆ͢Δ• HSTSʢHTTP Strict Transport Securityʣϒϥβ͔Βૹ৴͢ΔϦΫΤετΛHTTPSʹڧ੍͢Δ͜ͱ͕Ͱ͖Δػೳ• Strict-Transport-SecurityϔομΛड͚औͬͨϒϥβͦͷWebαΠτͷ࣍ճҎ߱ͷϦΫΤετHTTPSͰߦ͏• HSTSͷظݶΛઃఆ͢Δ max-age͕ඞਢ
αϒυϝΠϯʹHSTSΛઃఆ͢Δ• Strict-Transport-Security: includeSubdomainsͰ αϒυϝΠϯʹHSTSΛ ద༻ͤ͞Δ͜ͱ͕Մೳ
PreloadΛ͑ॳճ͔ΒHTTPSΛڧ੍Ͱ͖Δ
֎෦͔ΒͷΞΫηεʹΑΔ• ෳͷWebαΠτͷίϯςϯπΛ1ͭͷWebϖʔδͰར༻͢Δ͜ͱ͕Մೳ• APIͷݺͼग़͠ɺiframeΛͬͨίϯςϯπͷຒΊࠐΈɺը૾ͷऔಘ etc…• ͋ΒΏΔWebαΠτ͔Β੍ݶͳ͠ʹΞΫηεͰ͖Δঢ়ଶʹͳ͍ͬͯΔͱػີใͷ࿙͍͑վ͟ΜͳͲͷ߈ܸͷՄೳੑ͕͋Δ
֎෦͔ΒͷΞΫηεʹΑΔ
Originͱ͍͏ڥք• ϒϥβʹ֎෦ͷΞΫηεΛ੍ݶ͢ΔOriginͱ͍͏ڥքઢ͕ઃ͚ΒΕ͍ͯΔ• OriginεΩʔϜ+ϗετ໊+ϙʔτ൪߸ͷΈ߹ΘͤΛࢦ͢
Originͱ͍͏ڥք• 2ͭͷURLͷOrigin͕ಉ͡߹ɺಉҰΦϦδϯʢSame OriginʣͱݺͿ• 2ͭͷURLͷOrigin͕ҟͳΔ߹ɺΫϩεΦϦδϯʢCross Origin)ͱݺͿ
Same Origin Policy(ಉҰΦϦδϯϙϦγʔɺSOP)• ϒϥβҰ෦ͷσʔλͷΓऔΓΛSame Origin͚ͩʹ੍ݶ͍ͯ͠Δɻͬ͘͟Γݴ͏ͱ͜Ε͕Same Origin Policy
Same Origin PolicyʹΑΔ੍ݶର• JavaScriptʢfetchɺXHRʣΛͬͨΫϩεΦϦδϯͷϦΫΤετ• JavaScriptΛͬͨiframeͷΫϩεΦϦδϯͷϖʔδͷΞΫηε• ΫϩεΦϦδϯͷը૾ΛಡΈࠐΜͩཁૉͷσʔλͷΞΫηε• Web StorageIndexedDBʹอଘ͞ΕͨΫϩεΦϦδϯͷσʔλͷΞΫηε
• ͨͱ͑ɺfetchؔΛͬͯΫϩεΦϦδϯϦΫΤετΛૹ৴͢Ε͜ͷΑ͏ͳΤϥʔ͕ൃੜ͢ΔJavaScriptʢfetchɺXHRʣΛͬͨ ΫϩεΦϦδϯͷϦΫΤετ
Cross Origin Resource SharingʢCORSʣ• CORSΛ͑ɺΫϩεΦϦδϯؒͷϦΫΤετΛڐՄ͢Δ͜ͱͰ͖Δ• JavaScriptʢfetchɺXHRʣΛͬͨΫϩεΦϦδϯͱͷσʔλͷΓͱΓ͕ՄೳʹͳΔ
CORSͷΈ• ΫϩεΦϦδϯͷαʔόϦΫΤετࣗମૹ৴͞ΕͯɺϨεϙϯεฦͬͯ͘Δ• ϨεϙϯεͷCORSϔομʹΑͬͯαʔό͕ڐՄͨ͠ϦΫΤετͱஅͨ͠ΒɺϒϥβϨεϙϯεͷσʔλͷऔΓग़͠ΛڐՄ͢Δ
CORSͷΈ• αʔό͕ڐՄͨ͠Origin͔Ͳ͏͔ఆ͢ΔͨΊʹ Access-Control-Allow-Originͱ͍͏ϨεϙϯεϔομΛར༻͢Δ ྫ `Access-Control-Allow-Origin: https://site.example`
CORSͷ՝• σʔλΛऔͬͯ͘Δ͚ͩͳΒ͜͜·Ͱͷઆ໌Ͱͳ͍• σʔλΛߋ৽ɾআͳͲ෭࡞༻Λى͜͢ϦΫΤετͷ߹ɺϦΫΤετࣗମΛ͙ඞཁ͕͋Δ• ͨͱ͑σʔλΛআ͢ΔϦΫΤετ͕ૹ৴͞Εͯαʔόಧ͘ͱαʔόϨεϙϯεΛฦ͢લʹσʔλΛআͯ͠͠·͏͔͠Εͳ͍• Access-Control-Allow-OriginϔομͰɺϦΫΤετΛड͚औͬͨαʔόͷಈ࡞·Ͱ੍ޚͰ͖ͳ͍
CORSͷ՝ᶃ σʔλΛআ͢ΔϦΫΤετΛૹ৴ᶅ Access-Control-Allow-OriginΛؚΉ ϨεϙϯεΛૹ৴ᶄ σʔλͷআॲཧΛ࣮ߦᶆ ϨεϙϯεΛഁغϨεϙϯεσʔλഁغͰ͖ͯɺσʔλͷআࢭΊΕ͍ͯͳ͍
CORSͷΈʢϓϦϑϥΠτϦΫΤετʣ• ෭࡞༻Λى͜͢Α͏ͳϦΫΤετࣄલʹͳ͍͔֬ೝ͢Δ ϓϦϑϥΠτϦΫΤετͱݺΕΔϦΫΤετ͕ૹ৴͞ΕΔ• ϓϦϑϥΠτϦΫΤετʹOPTIONSͱ͍͏HTTPϝιου͕ར༻͞Εɺ࣮ࡍͷσʔλͷߋ৽ɾআॲཧ͠ͳ͍• ϨεϙϯεຊจʹσʔλΛؚΊͳ͍
CORSͷΈʢϓϦϑϥΠτϦΫΤετʣ
ϓϦϑϥΠτϦΫΤετ͕ૹ৴͞ΕΔ݅• GETɺHEADɺPOSTҎ֎ͷHTTPϝιου͕ར༻͞ΕΔͱ͖• AcceptɺAccept-LanguageɺContent-LanguageɺContent-TypeҎ֎ͷHTTPϔομ͕ૹ৴͞ΕΔͱ͖
ϓϦϑϥΠτϦΫΤετͰૹ৴͞ΕΔϔομ
• ҉߸Խͷࣦഊ ➡ ௨৴σʔλͷ҉߸Խ• ΞΫηε੍ޚͷෆඋ ➡ CORSΛͬͨΞΫηε੍ޚ• ΠϯδΣΫγϣϯ ➡ XSSରࡦ• ੬ऑͰݹ͘ͳͬͨίϯϙʔωϯτ ➡ ੬ऑͳϥΠϒϥϦͷνΣοΫࠓճ͓͢ΔηΩϡϦςΟϦεΫଞʹ42-ΠϯδΣΫγϣϯͳͲ͕͋Δ͕ࠓ944͚ͩઆ໌͠·͢
XSSͱ• ΫϩεαΠτεΫϦϓςΟϯάʢCross-Site Scriptingʣ• Webϖʔδ্ͷJavaScriptͷܽؕΛૂ͏߈ܸख๏• ෆਖ਼ͳεΫϦϓτΛૠೖʢΠϯδΣΫγϣϯʣ࣮ͯ͠ߦ͢Δ͜ͱͰػີใͷ౪ΜͩΓɺϖʔδͷվ͟ΜΛߦͬͨΓ͢Δ• ଞͷ੬ऑੑʹൺͯใࠂ͕݅ଟ͍
XSSͷڴҖ• ػີใͷ࿙͍͑• ِใͷදࣔ• ҙਤ͠ͳ͍ૢ࡞ͷ࣮ߦ• ηογϣϯใୣऔʹΑΔɺͳΓ͢·͠• ِͷೖྗϑΥʔϜදࣔʹΑΔϑΟογϯά
XSSͷڴҖ• YouTubeTwitterͳͲେ͖ͳαʔϏεඃΛड͚ͨ͜ͱ͕͋Δ
3छྨͷXSS• ࣹܕXSSʢReflected XSS)• ੵܕXSSʢStored XSSʣ• DOM-based XSS
ࣹܕXSS (Reflected XSS)ᶃ ߈ܸऀ͕༻ҙͨ͠ϖʔδʹΞΫηεᶄ εΫϦϓτΛύϥϝʔλʹͯ͠ରαΠτʹભҠalert("XSS") ?token=“”/>alert(“XSS")ᶅ εΫϦϓτ͕ ຒΊࠐ·ΕͨHTML
ࣹܕXSSͷಛ• αʔόͰϨεϙϯεͷHTMLΛΈཱͯΔͱ͖ʹϦΫΤετʹؚ·ΕͨXSSͷίʔυΛͦͷ··HTMLөͯ͠͠·͏• ϦΫΤετʹෆਖ਼εΫϦϓτؚ͕·Ε͍ͯΔͱ͖ͷΈʹൃੜ͢Δ• ෆਖ਼ͳεΫϦϓτΛؚΉϦΫΤετΛૹ৴ͨ͠Ϣʔβʔ͚͕ͩඃΛड͚Δ
ੵܕXSS (Stored XSS)ᶃ ϑΥʔϜ͔ΒPOSTᶄ POST͞ΕͨΛͦͷ··อଘalert(“xss")alert(1)ᶅ ଞͷϢʔβʔ͕αΠτʹΞΫηεᶆ DBͷΛHTMLʹө
ੵܕXSSͷಛ• αʔόૹ৴͞Εͨෆਖ਼εΫϦϓτΛσʔλϕʔεͳͲʹอଘͯ͠͠·͍ӬଓతʹXSSͷةݥ͕͋Δ• σʔλϕʔεͷ༰͕ө͞ΕΔશͯͷϢʔβʔ͕ඃΛड͚Δ• σʔλϕʔε͔Βෆਖ਼εΫϦϓτΛؚΉσʔλΛআ͢ΔͳͲରॲ͠ͳ͍ݶΓɺඃࢭ·Βͳ͍
DOM-based XSSconst hash = decodeURIComponent(location.hash.slice(1));document.querySelector('#result').innerHTML = hash;ϩέʔγϣϯϋογϡͷΛJavaScriptͰૠೖ
DOM-based XSSͷಛ• ϒϥβ্ͷJavaScriptΛͬͨDOMૢ࡞͕ݪҼͰൃੜ͢Δ• ଞͷ2ͭͱҧ͍ϑϩϯτΤϯυͷίʔυͷෆඋʹΑͬͯൃੜ͢Δ• αʔόΛհͣ͞ʹ߈ཱܸ͕͢ΔͨΊɺ߈ܸͷݕϩάऔಘ͕͍͠• ϑϩϯτΤϯυͷJavaScriptDevTools͔ΒݟΔ͜ͱ͕ग़དྷΔͨΊɺ߈ܸऀ੬ऑੑΛൃݟ͍͢͠
DOM-based XSSͷݪҼconst hash = decodeURIComponent(location.hash.slice(1));document.querySelector('#result').innerHTML = hash;ιʔε (Source)γϯΫ (Sink)දతͳSink - innerHTML - location.href - document.write - jQuery() ͳͲදతͳSource - location.hash - location.href - document.referrer - IndexedDB ͳͲ
දతͳXSSରࡦ• จࣈྻͷΤεέʔϓ• ReactɺVueɺAngularͳͲͷϑϨʔϜϫʔΫΛ͏• javascript:εΩʔϜͷURLΛຒΊࠐ·ͳ͍• DOMPurifyͳͲͷϥΠϒϥϦSanitizer APIΛͬͯةݥͳจࣈྻΛআڈ͢Δ• Content Security Policy
จࣈྻͷΤεέʔϓ<br/><br/><br/>cookieHijack()<br/><br/><br/><script>cookieHijack()</script>&<มલ มޙ>“‘&<>"'Τεέʔϓॲཧ
ReactͳͲͷϑϨʔϜϫʔΫΛ͏• ReactɺVueɺAngularɺSvelteͳͲͷ༗໊ͳϥΠϒϥϦ/ϑϨʔϜϫʔΫΤεέʔϓॲཧΛࣗಈͰͬͯ͘ΕΔ• ։ൃऀΤεέʔϓॲཧΛ͢Δඞཁͳ͍• ͨͩ͠ɺҰ෦ͷAPIΛ͏͜ͱͰXSSൃੜ͢Δ͜ͱ͕͋Γ·͢• dangerouslySetInnerHTMLinnerHTMLͱಉ͘͡จࣈྻΛͦͷ··HTMLͱͯ͠ೝࣝ͢ΔʢReactʣ• ޙड़ͷjavascript:εΩʔϜʹΑΔXSSʹରԠ͍ͯ͠ͳ͍ʢReactʣ• ίϯιʔϧʹܯࠂग़Δ
javascript:εΩʔϜͷURLΛຒΊࠐ·ͳ͍• ͨͱ͑ɺϦϯΫͷURLΛೖྗͰ͖ΔϑΥʔϜ͕͋ͬͨͱ͢Δ• ϑΥʔϜʹhttps://example.comͱೖྗͨ͠Βhttps://example.comͷϦϯΫ͕ੜ͞ΕΔͱ͢Δ{title}ҙͷ63-ΛઃఆՄೳͱ͢Δ
javascript:εΩʔϜͷURLΛຒΊࠐ·ͳ͍• ϑΥʔϜʹjavascript:alert(“xss”)ͱೖྗ͞ΕΔͱɺΫϦοΫͨ͠ͱ͖ʹalert(“xss”)͕࣮ߦ͞ΕΔϦϯΫ͕Ͱ͖ͯ͠·͏const App = (props) => (click me!KBWBTDSJQUεΩʔϜΛ͑ҙͷεΫϦϓτΛૠೖͰ͖Δ
javascript:εΩʔϜͷURLΛຒΊࠐ·ͳ͍const Link = props => {const protocol = new URL(props.url).protocol;const safeUrl = /^https?:/.test(protocol) ? props.url : "";return {props.children};};const App = (props) => {return ({title});};IUUQ·ͨIUUQT͔Β͡·Δ63-͚ͩΛઃఆ͢ΔΑ͏ʹϑΟϧλϦϯά͢Δ
DOMPurifyΛͬͨةݥͳจࣈྻͷআڈ• DOMPurifyOSSͷϥΠϒϥϦ• npmCDN͔Βμϯϩʔυͯ͠͏͜ͱ͕Ͱ͖Δ• จࣈྻ͔ΒXSSΛى͜͢ةݥͳจࣈྻΛআͯ͘͠ΕΔconst untrustedStr = location.hash;// const trustedStr = DOMPurify.sanitize(untrustedStr);$('#foo').innerHTML = trustedStr;//
Sanitizer APIΛͬͨةݥͳจࣈྻͷআڈ• ϒϥβʹΈࠐ·Ε͍ͯΔAPI• Ͱ͖Δ͜ͱDOMPurifyͱࣅ͍ͯͯةݥͳจࣈྻΛআڈ͢Δconst untrustedStr = location.hash;// const sanitizer = new Sanitizer(untrustedStr);const target = document.getElementById(untrustedStr);target.setHTML(untrustedStr, { sanitizer });//
Content Security Policy (CSP)• ڐՄ͞Ε͍ͯͳ͍JavaScriptͷ࣮ߦϦιʔεͷಡΈࠐΈͳͲΛϒϩοΫ͢Δϒϥβͷػೳ• Content-Security-Policyͱ͍͏ϨεϙϯεϔομʹΑͬͯԿΛͲ͏ϒϩοΫ͢Δͷ͔ઃఆ͢Δ͜ͱ͕Մೳ
Content Security Policyͷ༻ํ๏• HTTPϨεϙϯεϔομʔʹՃ͢Δ• ϦιʔεͷऔಘઌεΫϦϓτͷ࣮ߦΛࢦఆͨ͠ιʔεͷΈʹ੍ݶͰ͖Δ• script-src ‘self’; img-src *.trusted.comͷ ͷΑ͏ʹෳઃఆՄೳ▼ Response Headerscontent-security-policy: script-src ‘self’ *.trusted.comσΟϨΫςΟϒ ιʔεscript-src ‘self’; img-src *.trusted.com
Content Security Policyͷʹ͍ͭͯcontent-security-policy: script-src ‘self’ *.trusted.com: εΫϦϓτͷ࣮ߦ੍ݶ: ڐՄ͢ΔιʔεΛࢦఆscript-src‘self’ *.trusted.comΩʔϫʔυ ϗετ
දతͳσΟϨΫςΟϒσΟϨΫςΟϒDPOOFDUTSD 9)3ɺ8FC4PDLFUͳͲͷ੍ݶEFGBVMUTSD ଞͷϑΣονσΟϨΫςΟϒͷϑΥʔϧόοΫJNHTSD ը૾ͷಡΈࠐΈઌΛ੍ݶNFEJBTSD BVEJPWJEFPͷಡΈࠐΈઌΛ੍ݶTDSJQUTSD εΫϦϓτͷ࣮ߦॴΛ੍ݶTUZMFTSD ελΠϧͷద༻ॴΛ੍ݶ
͔ΒCSPΛ༗ޮʹͰ͖Δ• HTMLͷλάͰࢦఆͰ͖ΔͷͰϑϩϯτΤϯυ͚ͩͰ࣮Մೳ• ΑΓઌʹઃఆ͞Εͨͷ্ॻ͖ෆՄ• ΑΓઌʹಡΈࠐ·ΕͨϦιʔεεΫϦϓτʹద༻Ͱ͖ͳ͍• ͰઃఆͰ͖ͳ͍σΟϨΫςΟϒ͕͋Δ e.g. report-only
content-security-policy: script-src ‘self’alert("XSS")ϗετͱϙʔτ͕ಉ͡εΫϦϓτͷΈڐՄCSPͷྫ
CSPͷྫcontent-security-policy: script-src ‘self’alert("XSS")ϗετͱϙʔτ͕ಉ͡εΫϦϓτͷΈڐՄࢦఆ͞Εͨϗετͱҧ͏ͷͰېࢭ❌
CSPͷྫcontent-security-policy: script-src ‘self’alert("XSS")ϗετͱϙʔτ͕ಉ͡εΫϦϓτͷΈڐՄ❌❌ΠϯϥΠϯεΫϦϓτېࢭ
CSPͷྫcontent-security-policy: script-src ‘self’alert("XSS")ϗετͱϙʔτ͕ಉ͡εΫϦϓτͷΈڐՄ❌❌❌ΠϕϯτଐੑKBWBTDSJQUͳͲېࢭ
CSPͷ՝• CSP͕ద༻͞Ε͍ͯΔϖʔδͰΠϯϥΠϯεΫϦϓτېࢭ͞ΕΔ• unsafe-inlineΩʔϫʔυΛ͑ΠϯϥΠϯεΫϦϓτΛڐՄͰ͖Δ͕ɺXSSʹΑΓຒΊࠐ·ΕͨΠϯϥΠϯεΫϦϓτ·Ͱ࣮ߦͯ͠͠·͏Մೳੑ͕͋Γ໊લͷͱ͓Γ҆શͰͳ͍ʢunsafe-inlineʣ• ϗετ໊ͷࢦఆᷖճ͕ՄೳͰ͋Δ※ ※ https://research.google/pubs/pub45542/
Strict CSP• ҆શʹΠϯϥΠϯεΫϦϓτΛ࣮ߦͰ͖Δnonce·ͨhashΛϕʔεͱͨ͠Strict CSPͱ͍͏ઃఆ͕GoogleʹΑͬͯਪ͞Ε͍ͯΔContent-Security-Policy:script-src 'nonce-tXCHNF14TxHbBvCj3G0WmQ==' 'strict-dynamic'https: 'unsafe-inline';object-src 'none';base-uri 'none';
content-security-policy: script-src ‘nonce-EDNnf03nceIOfn39fn3e9h3sdfa'alert("OK")alert(“NG")alert("NG")OPODFCBTFΤϯίʔυ
content-security-policy: script-src ‘nonce-EDNnf03nceIOfn39fn3e9h3sdfa'alert("OK")alert(“NG")alert("NG")OPODFͷ͕Ұக͍ͯ͠ΔͷͰ࣮ߦΛڐՄOPODFCBTFΤϯίʔυ
content-security-policy: script-src ‘nonce-EDNnf03nceIOfn39fn3e9h3sdfa'alert("OK")alert(“NG")alert("NG")OPODF͕ෆҰக·ͨະࢦఆͳͷͰ࣮ߦΛېࢭOPODFCBTFΤϯίʔυ
nonceͷҙ• nonceͷαʔόʔଆͰϦΫΤετ͝ͱʹมߋ͢Δ͜ͱ• ਪଌ͞Εͳ͍ϥϯμϜͳʹ͢Δ͜ͱ• scriptλάΛಈతʹੜͰ͖ͳ͍content-security-policy: script-src ‘nonce-EDNnf03nceIOfn39fn3e9h3sdfa'alert("OK")
strict-dynamiccontent-security-policy: script-src ‘nonce-EDNnf03nceIOfn39fn3e9h3sdfa’ ‘strict-dynamic’<br/><br/><br/>const script = document.createElement('script');<br/><br/><br/>script.src = ‘/static/js/main.js’;<br/><br/><br/>document.head.appendChild(script);<br/><br/><br/>QBSTFSJOTFSUFEͰͳ͍TDSJQUͷಈతੜ͕Մೳ JOOFS)5.-ͳͲγϯΫʹͳΔؔېࢭ͞Ε͍ͯΔ※parser-inserted = HTMLύʔαʔXMLύʔαʔʹΑͬͯૠೖ͞ΕΔ͜ͱ
CSPͷReport-OnlyϞʔυ• ࣮ࡍͷCSPΛద༻ͤͣʹϨϙʔτ͚ͩૹΔ͜ͱ͕Մೳ• CSPͰڐՄ͞Ε͍ͯͳ͍߹ɺϨϙʔτΛPOST͢Δ• CSPͷຊ൪ಋೖલʹӨڹΛ֬ೝ͢Δ͜ͱ͕Մೳ• λάͰͷઃఆෆՄ▼ Response Headerscontent-security-policy-report-only: script-src ‘self’ *.trusted.comreport-uri /csp-report
Ϩϙʔτͷྫ{"csp-report": {"document-uri": "http://example.com/index.html","referrer": "","blocked-uri": "http://invalid-cdn.com/js/react.js","violated-directive": "script-src self *.trusted.com*","original-policy": "script-src 'self' *.trusted.com; report-uri /csp-report/","disposition": "report"}}
XSSରࡦͰॏཁͳ͜ͱ• XSSରࡦʹ༗໊ͳϥΠϒϥϦϑϨʔϜϫʔΫΛ͍·͠ΐ͏• CSPXSSରࡦʹڧྗͳखஈ͚ͩͲɺ్தͰಋೖ͢Δͷ͍͠• αʔϏεͷϦϦʔεલʹಋೖ͠ɺेʹݕূ͠·͠ΐ͏• ӡ༻தͷWebΞϓϦέʔγϣϯReport-OnlyϞʔυͰӨڹൣғΛेʹ֬ೝ͔ͯ͠ΒCSPΛ༗ޮʹ͍͖ͯ͠·͠ΐ͏
JavaScriptͷϥΠϒϥϦࣄ• Node.js / npmͷొʹΑΓJavaScriptͷϥΠϒϥϦ͕രൃతʹ૿͑ͨ• Node.jsϑϩϯτΤϯυͳͲJSͷ։ൃͰଟ͘ͷϥΠϒϥϦ͕ΘΕ͍ͯΔ
http://www.modulecounts.com/npmʹొ͞Ε͍ͯΔϥΠϒϥϦ͕ѹతͳʹͳ͍ͬͯΔ
http://www.modulecounts.com/npmʹొ͞Ε͍ͯΔϥΠϒϥϦ͕ѹతͳʹͳ͍ͬͯΔ͕ଟ͍ɺ੬ऑੑͷ͋ΔϥΠϒϥϦଟ͍
JavaScriptϥΠϒϥϦͷ੬ऑੑࣄ• ਓؾϥΠϒϥϦʹѱҙͷ͋Δίʔυ͕ૠೖ͞ΕΔࣄ݅• ESLint (JavaScriptͷ࠷ϙϐϡϥʔͳ੩తղੳπʔϧ)ʹѱҙͷ͋Δίʔυ͕ૠೖ͞Ε͍ͯͨ(20187݄)• 1िؒʹ200ສDLͷϥΠϒϥϦevent-streamʹԾ௨՟ΥϨοτ͔Β҉߸伴Λ౪Έग़͢ػೳ͕ૠೖ͞ΕΔ(201811݄)
JavaScriptϥΠϒϥϦͷ੬ऑੑࣄ• ੈքͷτοϓ75,000ͷWebαΠτ(Alexaௐ)ͷ্Ґ37%͕Կ͔͠Βͷ੬ऑੑͷ͋ΔJSϥΠϒϥϦΛ༻• ϊʔεΠʔελϯେֶͷݚڀνʔϜʹΑΔௐࠪ݁Ռ(2017)• ݹ͍ϥΠϒϥϦΛ༻͍ͯ͠Δ• ࠂτϥοΩϯάɺιʔγϟϧϝσΟΞͷΟδΣοτʹຒΊࠐ·Ε͍ͯΔ͜ͱ͕ଟ͍
੬ऑͳϥΠϒϥϦͷ༻Λ͙ରࡦ• npm auditίϚϯυΛͬͨ੬ऑੑஅ• DependabotɺSnykɺyamoryͳͲΛͬͨ੬ऑੑஅ
npm auditΛ͏• npmʹඪ४ͰΈࠐ·Ε͍ͯΔίϚϯυπʔϧ• ར༻͍ͯ͠Δnpmύοέʔδͷ੬ऑੑΛஅͯ͘͠ΕΔ• npm installΛ͢Δͱ͖ʹࣗಈͰ࣮ߦ͞ΕΔ• CIͰ࣮ߦ͢ΕࣗಈͰ੬ऑੑͷ͋ΔϥΠϒϥϦͷར༻Λ͙͜ͱ͕Ͱ͖Δ• npm auditfixίϚϯυͰ੬ऑͳϥΠϒϥϦͷΞοϓσʔτΛҰׅͰߦ͏͜ͱ͕Ͱ͖Δ
੬ऑੑͷ͋ΔϥΠϒϥϦπʔϧΛݕ͢Δ• ར༻͍ͯ͠ΔϥΠϒϥϦπʔϧͷ੬ऑੑΛݕ͢ΔΈΛ͑·͠ΐ͏• GitHubͷDependabotɺSnykɺyamoryͳͲ༷ʑͳαʔϏε͕ଘࡏ͢Δ• ͲͷαʔϏεಠࣗͷػೳ੬ऑੑσʔλϕʔεͳͲΛ͍࣋ͬͯΔ
DependabotΛ͏• GitHubʹඪ४ͰඋΘ͍ͬͯΔ੬ऑੑஅαʔϏε• ੬ऑੑͷ͋ΔϥΠϒϥϦΛΞοϓσʔτ͢ΔϓϧϦΫΤετΛࣗಈͰ࡞• ੬ऑੑͷ͋ΔϥΠϒϥϦπʔϧΛҰཡͰ֬ೝͰ͖Δ• ϦϙδτϦͷઃఆը໘͔Β؆୯ʹ༗ޮʹͰ͖Δ
੬ऑͳϥΠϒϥϦΛݟ͚ͭΔͱɺDependabot͕ڭ͑ͯ͘ΕΔ
Dependabot͕ϓϧϦΫΤετΛ࡞ͯ͘͠ΕΔ
·ͱΊ• ͍·͙͢HTTPSԽ͠Α͏• CORSΛཧղͯ͠దʹΫϩεΦϦδϯͷΞΫηεΛ੍ޚ͠Α͏• XSSରࡦϥΠϒϥϦϑϨʔϜϫʔΫɺCSPΛ͓͏• ϥΠϒϥϦʹ੬ऑੑ͕ͳ͍͔֬ೝ͢ΔͨΊͷΈΛ͑Α͏
࠷ޙ·Ͱ͝ࢹௌ͍͖ͨͩ͋Γ͕ͱ͏͍͟͝·ͨ͠