LINE Developer Meetup in Fukuoka #16 http://connpass.com/event/38413/
࣮ྫʹֶͿXSS੬ऑੑͷൃݟͱमਖ਼ํ๏ma.la
View Slide
ςʔϚ• ͍͔ͭ͘ࠓ·ͰXSSʹؔͯ͠ൃද͖ͯͨ͠(AppSec,AVTokyo, etc)• ओʹൣͳαΠτʹӨڹ͢ΔϥΠϒϥϦͷ੬ऑੑͳͲΛղઆ• ࠓճൃݟํ๏ࣄྫʹ͍ͭͯத৺ʹղઆ• αΠτ։ൃऀଆͰͷରࡦ͕ඞཁͳՕॴ
Part0. XSSͬͯԿ• αʔϏεΛఏڙ͍ͯ͠ΔυϝΠϯ্ͰɺҙͷJavaScriptίʔυ͕࣮ߦͰ͖Δ੬ऑੑ• ͦͷυϝΠϯͰදࣔ͞ΕΔใΛ౪Έग़ͨ͠Γɺউखʹߋ৽͢Δ͜ͱ͕ग़དྷΔ• डಈత߈ܸ: ඃΛड͚Δͷ߈ܸϦϯΫΛ౿ΜͩϢʔβʔ
Part1. How to find XSS
ίʔυϨϏϡʔͷϙΠϯτ• ೖྗՕॴͱධՁ͢ΔՕॴʹ͢Δ• source ͱ sink ͱݺΕͨΓ͢Δ
୯७ͳXSSͷ߹• αʔόʔଆͷHTML templateͰͷग़ྗՕॴɺධՁՕॴಉ͡• ग़ྗՕॴͰhtml tagscriptΛදࣔ • ίʔυΛ͍͚ͬͯେମશ෦ݟ͔ͭΔ• Ұ෦Λআ͖ɺࣗಈΤεέʔϓͰશ෦Δ
୯७Ͱͳ͍XSSͷࣄྫ• JavaScriptίʔυͷಈతੜ• URLͷՕॴʹ javascript:xxx• HTML escapeͰ͛ͳ͍• DOM based XSSͱݺΕΔͷ
DOM based XSSͷ߹• ೖྗՕॴͱධՁ͢ΔՕॴ͕ҧ͏• JavaScriptͷίʔυΛΘͳ͍ͱ͔Βͳ͍• ൃݟ͕͍͠ݪҼ
ೖྗՕॴͷྫlocation.* (location.href, location.hash, etc)document.* (document.URL, document.cookie, etc)window.name
ධՁ͢ΔՕॴͷྫೖྗ͞Εͨύϥϝʔλ͕ग़ྗ͞ΕΔՕॴURLͱͯ͠ධՁɺJavaScriptίʔυͱͯ͠ධՁɺHTMLͱͯ͠ධՁ
URLͱͯ͠ධՁ• location.href = , iframe.src =• ajax, XMLHttpRequest ͰͷಡΈࠐΈ• etc
ίʔυͱͯ͠ධՁ• ίʔυΛಈతʹੜ͢ΔΑ͏ͳ͍ํ (͋·Γແ͍)• eval()• จࣈྻͰͷ setTimeout() setInterval() (͋·ΓΘΕͳ͍)• Function() (͋·ΓΘΕͳ͍)• etc
HTMLग़ྗ• innerHTML =• document.write()• jQuery() $() $(el).html()• ֤छςϯϓϨʔτΛͬͨग़ྗ• etc
ίʔυͷྲྀΕΛ͍ͬͯ͘• ag ͳͲͷίʔυݕࡧπʔϧΛ͏• ҆શͩͱ֬ೝ͕Ͱ͖ͨॴআ֎͍ͯ͘͠• ag innerHTML | ag -v "safe"• ೖྗՕॴɺग़ྗՕॴɺͲͪΒ͔ΒͰո͍͠Օॴݟ͔ͭΔ
ݟམͱ͕ͪ͠ͳϙΠϯτ
document.cookie / localStorage• ݕࡧΩʔϫʔυཤྺΛอ࣋͢Δػೳ• ҙͷΛอଘग़དྷΔ͜ͱ͕͋Δ• ೖྗ࣌ͱग़ྗ࣌Ͱ͕࣌ؒࠩൃੜ͢Δ͜ͱ͕͋Δ
Persistent DOM XSS• DOM based XSSͷӬଓԽ͕Մೳ• cookie / localStorageʹ߈ܸ༻ͷίʔυΛอଘ• දࣔ͢Δͨͼʹ࣮ߦ͞ΕΔΑ͏ͳέʔε• → ࣗࣾͰͷࣄྫɺࠂωοτϫʔΫͷiframeͰ࣮ྫ͋Γ
ಛʹ cookie ͷ߹• αϒυϝΠϯ͔ΒͰઃఆ͕Մೳ• vuln.example.com → .example.com• ੬ऑੑͷ͋ΔαϒυϝΠϯ͔ΒcookieΛset • ߈ܸରͷυϝΠϯͰ cookie ىҼͷ DOM based XSS • MITM attackͰcookieͷઃఆ͕Մೳ
CookieΛͬͨ߈ܸ (XSS or ServerSide)• ͦͷαʔϏεͰ৴༻Ͱ͖Δ͔͠ग़ྗ͠ͳ͍߹ͰXSSՄೳ• MITMͰͷcookieઃఆ → HSTS include subdomainΛΘͳ͍ͱ͛ͳ͍• JS ͰserverͰ৴པͰ͖ͳ͍͕ೖΔ͜ͱΛલఏʹઃܭ͢Δඞཁ͕͋Δ • ࡉͨ͠cookieΛͬͨremote code executionͷࣄྫ͍͔ͭ͋͘Γ
Part2. मਖ਼ํ๏
ग़ྗՕॴʹԠͯ҆͡શʹ͢Δ• ධՁ͞ΕΔίϯςΩετʹԠͯ͡ରࡦҧ͏• શͯʹରͯ͠༗ޮͳvalidationescape ruleଘࡏ͠ͳ͍• յΕͯྑ͍ͳΒҰϑΟϧλ͢ΔΑ͏ͳॲཧ࡞ΕΔ <> ͕ೖྗ͞Ε͍ͯΔͱແ༻ͰΤϥʔʂ
JavaScriptͷมग़ྗ• ͦͦආ͚Δ• data-xxx="html escaped value" ͰຒΊࠐΈΛਪ• ಉ͡escape ruleͰରԠՄೳɺίϯςΩετΛҙࣝ͠ͳ͍͍ͯ͘• Ͳ͏ͯ͠ඞཁͰ͋Εɺhtml escapeͰͳ͘js escape
URLΛग़ྗ͢Δ߹• javascript: xxx ͕ೖ͍͚ͬͯͳ͍• ̋ validation ruleΛ࡞ͬͯద༻͢Δ• HTML Escape / JS escape ͚ͩͰෆे• URLΛೖग़ྗ͢ΔΑ͏ͳՕॴɺͲͷΈͪvalidation͕͋Δͣ
ίʔυΛੜ͢Δ߹• eval() ͦͦΘͳ͍Α͏ʹ͢Δ• JSON.parseͷ༻ͱͯ͠ɺͨ·ʹݟΔ → ͏͍Βͳ͍ɺpolyfill༻͢ΕΑ͍
HTMLΛग़ྗ͢Δ߹• innerHTMLΛͳΔ͘Θͳ͍(࠷ऴతͳग़ྗ࣌ͷΈ)• ࣗಈescapeՄೳͳtemplate engine͏ → mustache ͳͲ• jQuery ͷ html() → ෆཁͰ͋Ε text() ʹஔ͖͑Δ• html() ͷଟ༻ϨϏϡʔͷෛ୲ʹͳΔ
XSSͷݟ͚ͭํͱ͠ํಉ͡• ag ͳͲͷίʔυݕࡧπʔϧΛ͏• ҆શͩͱ֬ೝ͕Ͱ͖ͨॴআ֎͍ͯ͘͠• ag innerHTML | ag -v "safe"• ೖྗՕॴɺग़ྗՕॴɺͲͪΒ͔ΒͰո͍͠Օॴݟ͔ͭΔ
मਖ਼ํ๏ͷϙΠϯτ• ։ൃऀ͔Βݟͯ҆શ != ϨϏϡΞʔ͔Βݟͯ҆શ • ։ൃऀةݥ͕ແ͍ύϥϝʔλͱ͍ͬͯͯύοͱݟͰΘ͔Βͳ͍ • ιʔείʔυݕࡧͰɺո͍͠Օॴ͕ݟ͔ͭΒͳ͍ঢ়ଶ• ϨϏϡʔ͍͢͠ίʔυʹ͢Δ → ࣗͰίʔυݕࡧͯ͠ΈΔͱྑ͍
Part3. ൃੜཁҼͷ• ͲͷλΠϛϯάͰԿʹҙ͢Εྑ͍ͷ͔͔Βͳ͍• ةݥͳ͜ͱΛ͍ͯ͠Δ͕֮ࣗͳ͍• ʮԿΛ͠Α͏ͱͯ͠ى͖ͨͷ͔ʯΛओ࣠ʹղઆ
ࣄྫ: ݕࡧΩʔϫʔυͷදࣔ• ϦϑΝϥ͔Βऔಘ• ݕࡧΫΤϦ͔Βͷऔಘ• ࠂ࠷దԽ༻ͷύϥϝʔλΩʔϫʔυϋΠϥΠτͰ͍ͬͯͨ• ऩӹ૿ՃͷͨΊʹ͋ΒΏΔαʔϏεʹXSS͕Ճ͞Ε͍ͯͨ
۩ମྫvar keyword = '[% param.keyword | html %]'; // ͜Ε͕ ↓ var keyword = ''; alert(1); ''; // ͜͏ͳΔ• ͍࣌ͬͯͨςϯϓϨʔτΤϯδϯ͕ɺγϯάϧΫΦʔτΛΤεέʔϓ͠ͳ͔ͬͨ• ࠓͰ͋·ΓΈͳ͍• ϦϑΝϥ͔Βऔಘ͢Δͷ → DOM based XSSʹ
ϦϑΝϥΛͬͨXSS• ϦϑΝϥ͔ΒΩʔϫʔυऔಘͯ͠Φεεϝهࣄදࣔ• ϦϑΝϥʹ ه߸HTMLλά͕ೖΔ͜ͱΛఆ͍ͯ͠ͳ͍
ֶͼ• ϓϥεΞϧϑΝͷػೳͰXSS͕ى͖͍ͯΔ• αʔϏεͷຊମͷػೳ͡Όͳ͍෦Ͱ͍ͭͷؒʹ͔XSS͕ग़དྷͯΔ• ։ൃऴΘͬͯΔΜ͚ͩͲɺ༉அͯ͠Δͱ͜ΖͰɻɻ• ιʔγϟϧϘλϯՃ → ݱࡏͷURLΛdocument.writeͰग़ྗɺ
ࣄྫ: HTML EntityͷղऍΛ͍ͨ͠• $(el).text() Λͬͯද͍ࣔͯͨ͠Β HTML࣮ମࢀরɺࢀরจࣈ͕දࣔ͞Εͳ͘ͳͬͨ• ͜͏͍͏ͷͶ B'z → B'z• → $(el).html() ʹมߋɺࣗಈΤεέʔϓ֎͢ॲཧΛೖΕͯ͠·͏• Ϣʔβʔೖྗ͕ೖΒͳ͍͔Ͳ͏͔֬ೝ͕ඞཁ• ҆શͳೖྗՕॴͰ͋ͬͯϨϏϡʔ͕େมʹͳΔ
Ͳ͏͢Εྑ͍ʁ• HTML entityͷղऍͷͨΊʹɺhtml() ΛΘͳ͍ɻ• html() Λ͏ͱɺ͋ΔಥવةݥʹͳΔ• ඞཁͳॲཧhtmlग़ྗͰͳ͘ɺdecode html entities• textarea hack $("").html(value).text()
ࣅͨࣄྫ: escapeํࣜͷมߋ• αʔόʔαΠυͰΤεέʔϓɺjsͰͷग़ྗͰΤεέʔϓ• ೋॏescapeʹͳͬͯ͠·ͬͨʂ & " ͳͲ͕ը໘ʹදࣔ͞ΕΔ• html escape → js escape ͷมߋ• ͜Εࣗମਖ਼͍͕͠ɺຊʹେৎʁ
escapeํࣜมߋʹ͏• A: ̋ js escapeͰมຒΊࠐΈ → js templateͰauto escapeͰදࣔ• B: ˚ html escapeͰมຒΊࠐΈ → js templateͰauto escapeͰදࣔ → ೋॏescape• C: ☓ js escapeͰมຒΊࠐΈ → innerHTML $() html() Ͱग़ྗՕॴ͕͋Δ
Կ͕͔ʁ• ೋॏΤεέʔϓόά͚ͩͲ XSS ੬ऑੑ• B → C ʹѱԽ͢ΔՄೳੑ͕͋Δ (όάΛͯ͠੬ऑੑ͕ൃݱ)• पลՕॴͷϨϏϡʔηοτͰߦΘͳ͍ͱμϝ
ֶͼ• ද͕ࣔόάͬͯ·͢ → ҰൠϢʔβʔQA͔Βͷใࠂ• ରॲྍ๏తʹ͢ɺ͔ͬͯΔਓ͕ϨϏϡʔ͠ͳ͍··ద༻• ೋॏΤεέʔϓόά͕XSSͱͯ͠ѱԽͯ͠͠·͏• ͨ͠ຊਓόάΛͨͭ͠Γ
ࣄྫ: ίϝϯτΞτׂͱ͍͠λΠϓ
JavaScriptதͷมग़ྗՕॴͷίϝϯτΞτ• /* */ Λ͏έʔε• */ ΛೖΕΔ͜ͱͰίϝϯτΛڧ੍ऴྃ͢Δ/* var keyword = '[% keyword %]' */↓/* var keyword = '*/ alert(1) /*' */
// Λ͏έʔε• վߦͰಥഁՄೳ// var keyword = ' alert(1)//‘• U+2028 / U+2029 ͰಥഁՄೳ• վߦΛϑΟϧλͳΜͯத్ͳ͜ͱ͠ͳ͍Α͏ʹɻ
ίϝϯτΞτ• jsͷಈతੜɺมຒΊࠐΈΛΊΔɺͱ͍͏ݪଇͰରԠՄೳ• มग़ྗՕॴͷจ຺Λҙࣝ͢Δ͜ͱͰ͙ → ͍͠ • JavaScriptத͔ͩΒjs escape!! ͱ͍͏ܒ͕ग़དྷ͍ͯͯൃੜ͢Δ• ͦͦίϝϯτΞτ͠ͳ͍Ͱؙ͝ͱফ͢ɺgitʹϩάΔ
ࣄྫ: ίϯςϯπͷಈతͳϩʔυ• HTMLஅยΛදࣔ͢ΔΑ͏ͳέʔε• Single page appͷྲྀߦͰଟ͘ͳͬͨ → router͕ͪΌΜͱॻ͔Ε͍ͯΕ੬ऑੑগͳ͍• ͪΐͬͱલʹ࡞ΒΕͨΑ͏ͳαΠτɺlocation.hash ͔Βऔಘ• ΞχϝެࣜαΠτϥϯσΟϯάϖʔδͳͲͰΑ͘ݟΔ
HTMLஅยϩʔυͷ• ಉҰυϝΠϯʹ੍ݶ͍ͯͯ҆͠શͰͳ͍έʔε͕͋Δ• ಉҰυϝΠϯʹΦʔϓϯϦμΠϨΫλ• ಉҰυϝΠϯͰ <> ΛؚΉίϯςϯπΛಈతੜՄೳ(JSONP API)• ඞཁͳ͜ͱ → ఆͨ͠path͔Ͳ͏͔ͷݫ֨ͳνΣοΫ
ϥΠϒϥϦͰͷ• ಉҰυϝΠϯͷίϯςϯπ҆શͰ͋Δɺͱ͍͏ࢥ͍ࠐΈ• jQuery mobile → ϋογϡࢦఆͰಉҰυϝΠϯϩʔυ• Rails ͷ turbolinks → ϦϯΫઌΛAjaxͰಡΈࠐΜͰߴԽ• ύονॻ͍ͨΓͨ͠ (ಈతϩʔυΛߦͳ͏content-typeͷ੍ݶ)
·ͱΊ• XSS͍ͯ͘͜͠͠• ҆શʹ͢ΔͨΊͷγϯϓϧͳݪଇ͋Δ• ಈతͳίʔυੜΛආ͚ΔɺࣗಈΤεέʔϓΛ͏• + ݪଇΛ֎Εͨ࣌ʹةݥͩͱ͢Δηϯε͕ඞཁ