Tag: csp

  • WordPress Content Security Policy

    WordPress Content Security Policy

    1. CSP สำหรับ WordPress ค่อนข้างยุ่งยาก ปรับโน่นนิดปิดนี่หน่อย เว็บเพี้ยน
    2. เมื่อลองค้นไป ก็เจอปลักอินอยู่ตัวนึง ที่ช่วยสร้าง CSP ให้อัตโนมัติ นั่นคือ CSP-ANTS&STP แต่เนื่องจากปลักอินตัวนี้ไม่ได้รับการปรับปรุงมาตั้งแต่ 2022 ซึ่งนานเกินไป แล้ว แต่จริงๆ มันยังใช้ได้อยู่
    3. แต่ปลั๊กอินก็ยังมีบักเมื่อไปตรวจกับเว็บ https://securityheaders.com แล้วจะมีข้อความเตือนดังภาพ
    1. เพื่อแก้ปัญหานี้เลยต้องเขียนปลักอินตัวนี้ลงในปลักอินอีกตัวที่ชื่อ Woody snippets
    2. คลิก Add ใน Woody snippets เพิ่มข้อความต่อไปนี้ ส่วนนี้ได้แก้ไข Error บน https://securityheaders.com แล้วด้วย
    /**
    * Plugin Name:       CSP-ANTS&ST
    * Description:       Add a nonce to each script and style tags, and set those nonces in CSP header
    * Version:           1.1.1
    * Requires at least: 5.9
    * Requires PHP:      7.3
    * Author:            Pascal CESCATO
    * Author URI:        https://pascalcescato.gdn/
    * License:           GPL v2 or later
    * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
    */
    
    add_action('send_headers', function () {
    if (is_admin()){
        header("Content-Security-Policy: upgrade-insecure-requests");
        return;
    } 
    //เลือก page ที่ไม่ต้องการให้ CSP ทำงาน
    if (is_page(['cd-key'])){
        header("Content-Security-Policy: upgrade-insecure-requests");
        return;
    } 
    
    if (function_exists('litespeed_autoload')):
    // lscache version
    function cspantsst_cspantsst_lscwp_check ( $content ) {
    
    $uris = implode ( ' ', cspantsst_search_for_sources ( $content ) );
    
    $sha256_csp = cspantsst_search_for_events ( $content );
    
    $nonces = [];
    
    $content = preg_replace_callback ( '#<script.*?\>#', function ( $matches ) use ( &$nonces ) {
    $nonce = wp_create_nonce ( $matches[ 0 ] );
    $nonces[] = $nonce;
    
    return str_replace ( '<script', "<script nonce='{$nonce}'", $matches[ 0 ] );
    }, $content );
    
    $content = preg_replace_callback ( '#<style.*?\>#', function ( $matches ) use ( &$nonces ) {
    $nonce = wp_create_nonce ( $matches[ 0 ] );
    $nonces[] = $nonce;
    
    return str_replace ( '<style', "<style nonce='{$nonce}'", $matches[ 0 ] );
    }, $content );
    
    $nonces_csp = array_reduce ( $nonces, function ( $header, $nonce ) {
    return "{$header} 'nonce-{$nonce}'";
    }, '' );
    
    header ( sprintf ( "Content-Security-Policy:  base-uri 'self' %1s data:; object-src 'none'; script-src https:%2s %3s 'strict-dynamic'; frame-ancestors 'none'; ", $uris, $nonces_csp, $sha256_csp ));
    return $content;
    }
    
    add_filter ( 'litespeed_buffer_after', 'cspantsst_cspantsst_lscwp_check', 0 );
    
    else:
    // otherwise
    add_action ( 'template_redirect', function () {
    
    ob_start ( function ( $output ) {
    
    $uris = implode ( ' ', cspantsst_search_for_sources ( $output ) );
    
    $sha256_csp = cspantsst_search_for_events ( $output );
    
    $nonces = [];
    
    $output = preg_replace_callback ( '#<script.*?\>#', function ( $matches ) use ( &$nonces ) {
    $nonce = wp_create_nonce ( $matches[ 0 ] );
    $nonces[] = $nonce;
    return str_replace ( '<script', "<script nonce='{$nonce}'", $matches[ 0 ] );
    }, $output );
    
    $output = preg_replace_callback ( '#<style.*?\>#', function ( $matches ) use ( &$nonces ) {
    $nonce = wp_create_nonce ( $matches[ 0 ] );
    $nonces[] = $nonce;
    return str_replace ( '<style', "<style nonce='{$nonce}'", $matches[ 0 ] );
    }, $output );
    
    $header = '';
    $nonces_csp = array_reduce ( $nonces, function ( $header, $nonce ) {
    return "{$header} 'nonce-{$nonce}'";
    }, '' );
    
    header ( sprintf ( "Content-Security-Policy: base-uri 'self' %1s data:; object-src 'none'; script-src https:%2s %3s 'strict-dynamic'; frame-ancestors 'none';", $uris, $nonces_csp, $sha256_csp ) );
    return $output;
    } );
    } );
    endif;
    
    function cspantsst_search_for_events ( $output ) {
    
    $sha256 = array ();
    
    preg_match_all ( '/onload="(?<onload>[^"]+)"|onclick="(?<onclick>[^"]+)"/', $output, $matches );
    foreach ( $matches[ 'onload' ] as $match ):
    if ( !empty ( $match ) )
    $sha256[] = base64_encode ( hash ( 'sha256', $match, true ) );
    endforeach;
    foreach ( $matches[ 'onclick' ] as $match ):
    if ( !empty ( $match ) )
    $sha256[] = base64_encode ( hash ( 'sha256', $match, true ) );
    endforeach;
    
    if ( class_exists ( 'autoptimizeConfig' ) ):
    $sha256[] = base64_encode ( hash ( 'sha256', "this.onload=null;this.media='all';", true ) );
    endif;
    
    
    $header_sha256 = "'unsafe-hashes'";
    $sha256_csp = array_reduce ( $sha256, function ( $header, $sha256_item ) {
    return "{$header} 'sha256-{$sha256_item}'";
    }, '' );
    
    if ( !empty ( $sha256_csp ) )
    $sha256_csp = $header_sha256 . $sha256_csp;
    
    return $sha256_csp;
    }
    
    function cspantsst_search_for_sources ( $string ) {
    
    $result = array ();
    if ( strpos ( $string, 'https://secure.gravatar.com/avatar/' ) ):
    $result[] = 'https://secure.gravatar.com/avatar/';
    endif;
    if ( strpos ( $string, 'https://fonts.googleapis.com/' ) ):
    $result[] = 'https://fonts.googleapis.com/';
    endif;
    if ( strpos ( $string, 'https://maxcdn.bootstrapcdn.com/' ) ):
    $result[] = 'https://maxcdn.bootstrapcdn.com/';
    endif;
    return $result;
    
    }
    });
    1. และใน code นี้มีส่วนของโค้ดที่ใช้ระบุหน้าที่ไม่ต้องการให้ CSP-ANTS&STP ทำงานด้วย เนื่องจากการกำหนด CSP บางครั้งอาจทำให้เว็บไม่สามารถทำงานได้อย่างที่ต้องการ ต้องปรับลดความปลอดภัยลงให้เหลือ upgrade-insecure-requests แทนโค้ดดังกล่าวคือ
    if (is_page(['cd-key'])){
        header("Content-Security-Policy: upgrade-insecure-requests");
        return;
    } 
    
    1. แล้วเลื่อนลงมาในส่วนของ Base options เลือกเป็น Run everywhere
    1. กด Save
    2. และมาตรวจสอบในหน้า Woody snippets ว่าเปิดใช้งานดังภาพแล้วหรือยัง
    1. ทดสอบด้วยการไปเช็คด้วย https://securityheaders.com/ ก็จะได้ A+ เย่..
    1. แถม https://developer.mozilla.org/en-US/observatory/ อีกเว็บนึง เมื่อติดตั้งโค้ดของ CSP ก็สามารถทำคะแนนที่ดีขึ้นทันตาเห็น
    1. สุดท้ายก็สามารถ ถอนปลักอิน CSP-ANTS&STP ออกจากเว็บไซต์ได้เลย ต้องขอบคุณคนเขียนปลักอินตัวนี้จากใจจริง
    2. จบ.
    3. ขอให้สนุก..
  • Hardening your HTTP response headers

    Introduction

    HTTP Response headers คือ ค่าของสตริงที่ส่งกลับมาจากเซิร์ฟเวอร์ที่มีเนื้อหาตามที่ถูกร้องขอ โดยปกติจะใช้บอกข้อมูลทางเทคนิค เช่น เบราเซอร์ควรแคชเนื้อหา, ประเภทของเนื้อหาคืออะไร, ซอฟต์แวร์ที่ทำงานบนเซิร์ฟเวอร์ และอื่น ๆ   HTTP Response headers ถูกใช้เพื่อส่งต่อนโยบายความปลอดภัยไปยังเบราเซอร์  ทำให้การเปิดเว็บไซต์ของเรามีความปลอดภัยเพิ่มมากขึ้น

    Header ของ Apache2 ที่ควรต้องใส่เพื่อเพิ่มความปลอดภัยมีดังนี้

    Content Security Policy

    Header เรื่อง Content Security Policy (CSP) ช่วยให้กำหนดต้นทางของเนื้อหาที่อนุญาตสำหรับเว็บไซต์ โดยการจำกัดเนื้อหาที่เบราเซอร์สามารถโหลดได้ ได้แก่ js และ css

    สามารถสร้าง CSP ได้จาก https://report-uri.com/home/generate ทั้งนี้ต้องทดสอบการทำงานทุกครั้งเนื่องจาก การกำหนดค่าบางอย่างอาจทำให้เว็บไซต์ ทำงานไม่ถูกต้อง ดูรายละเอียดเพิ่มเติมได้ที่ https://scotthelme.co.uk/content-security-policy-an-introduction/ สำหรับ Apache2 เพิ่ม Header ต่อไปนี้ ในแฟ้มของไซต์ที่ต้องการ เช่น /etc/apache2/site-enabled/lsc-ssl.conf  หรือแฟ้ม .htaccess ในไซต์ที่ต้องการ (ซึ่งแนะนำว่าใช้ .htaccess จะได้ไม่ต้องรีสตาร์ทเซิร์ฟเวอร์) เพื่อเปิดการใช้งาน CSP


    Header always set Content-Security-Policy "default-src https: data: 'unsafe-inline' 'unsafe-eval'"

    HTTP Strict Transport Security (HSTS)

    เว็บไซต์ ต้องมีการตั้งค่าให้ redirect จาก HTTP ไปยัง HTTPS เสมอ และ HSTS จะเป็น Header ที่กำหนดให้เบราเซอร์จำสถานะของ HTTPS เอาไว้แม้ว่าจะเป็นการเปิดจาก bookmark ที่เป็น HTTP ก็ตาม ก็จะถูกบังคับให้เป็น HTTPS รายละเอียดเพิ่มเติม https://scotthelme.co.uk/hsts-the-missing-link-in-tls/ เช่น เมื่อเปิด http://licensing.psu.ac.th ก็จะถูก redirect ไป https://licensing.psu.ac.th

    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"

    X-Frame-Options

    X-Frame-Options หรือ XFO header จะช่วยป้องกันผู้ใช้จากการโจมตีแบบ clickjacking ที่ผู้บุกรุกสามารถโหลด iframe จากไซต์ของเขาบนไซต์ของเราได้ ซึ่งทำให้ผู้ใช้งานเว็บไซต์ของเราเชื่อว่าไม่อันตราย!! รายละเอียดเพิ่มเติม https://www.troyhunt.com/clickjack-attack-hidden-threat-right-in/

    Header always set X-Frame-Options "SAMEORIGIN"

    X-Xss-Protection

    Header นี้ใช้กำหนดค่าการป้องกัน XSS ที่มีอยู่บนเบราเซอร์ต่างๆ โดยการตั้งค่าจะมี 0 คือ ปิดการทำงาน 1 คือเปิดการทำงาน และ 1; mode=block ซึ่งจะกำหนดให้เบราเซอร์ทำการบล็อคการกระทำใดๆ ก็ตามที่มากกว่าการล้างข้อมูลสคริปต์

    Header always set X-Xss-Protection "1; mode=block"

    X-Content-Type-Options

    X-Content-Type-Options ใช้ในการป้องกันการโจมตีผ่านทางช่องโหว่ MIME sniffing ซึ่งจะเกิดเมื่อ เว็บไซต์อนุญาตให้ผู้ใช้อัพโหลดเนื้อหาไปยังเซิร์ฟเวอร์ ซึ่งผู้ใช้อาจเปลี่ยนหรือซ่อนไฟล์อันตราย แล้วอัพโหลดขึ้นเซิร์ฟเวอร์ รายละเอียดเพิ่มเติม https://www.keycdn.com/support/x-content-type-options/

    Header always set X-Content-Type-Options "nosniff"

    เบื้องต้นแนะนำเท่านี้ก่อนครับ พิเศษ!! สำหรับผู้ใช้งาน wordpress มีปลั๊กอินชื่อ HTTP Headers ใช่ตั้งค่า Header ต่างๆ ที่เล่ามาข้างต้นได้อย่างสบายใจหายห่วง!!! ไม่ต้องแก้ .htaccess ไม่ต้องแก้ config ใด ๆ  เมื่อติดตั้งเสร็จแล้วจะพบว่ามี Header อีกมากที่สามารถตั้งค่าเพิ่มเติมได้ ซึ่งจะกล่าวอีกในครั้งต่อ ๆ ไป

    ต้นฉบับ

    https://scotthelme.co.uk/hardening-your-http-response-headers/

    มีวิธีการเซ็ตสำหรับ Nginx และ IIS สามารถดูเพิ่มเติมได้ครับ
    อย่าลืม!!

    ตรวจ HTTP Response ได้ที่ https://securityheaders.com

    จบขอให้สนุก