Skip to content

[CRITICAL] CSP Policy Terlalu Permissive - Security Risk #987

@vickyrolanda

Description

@vickyrolanda

📋 Description

Current CSP policy is disabled in debug mode and uses hardcoded hashes, making the application vulnerable to XSS attacks in production.

🐛 Current Behavior

  • CSP is completely disabled when APP_DEBUG=true
  • 10+ hardcoded script hashes in policy
  • unsafe-eval is globally allowed
  • No nonce implementation for inline scripts/styles

✅ Expected Behavior

  • CSP always enabled (including debug mode)
  • Dynamic nonce generation for inline scripts/styles
  • Remove unsafe-eval directive
  • Remove all hardcoded hashes
  • Proper CSP directives for security

🔒 Security Impact

  • Severity: Critical
  • CVSS Score: 7.5 (High)
  • Attack Vector: XSS injection
  • Exploitability: High
  • Impact: Data theft, session hijacking, malware injection

🛠️ Solution

1. Update CustomCSPPolicy.php

// filepath: app/Policies/CustomCSPPolicy.php

<?php

namespace App\Policies;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Spatie\Csp\Directive;
use Spatie\Csp\Keyword;
use Spatie\Csp\Policies\Basic;
use Symfony\Component\HttpFoundation\Response;

class CustomCSPPolicy extends Basic
{
    private $excludeRoute = [
        'fm.tinymce5', 
        'fm.initialize', 
        'fm.content', 
        'fm.tree', 
        'cms.statistic.summary', 
        'presisi.index', 
        'presisi.kependudukan', 
        'laporan-bulanan.index', 
        'laporan-bulanan.filter', 
        'laporan-bulanan.export-excel', 
        'laporan-bulanan.detail-penduduk', 
        'laporan-bulanan.export-excel-detail'
    ];

    private $hasTinyMCE = ['articles.create', 'articles.edit'];

    public function configure()
    {
        parent::configure();
        
        $currentRoute = Route::getCurrentRoute()?->getName() ?? '';
        
        // TinyMCE routes - special handling
        if (in_array($currentRoute, $this->hasTinyMCE)) {
            $this->addDirective(Directive::IMG, ['blob:'])
                ->addDirective(Directive::STYLE, ['unsafe-inline']);
        }
        
        // Base directives
        $this->addDirective(Directive::IMG, [
            'data:', 
            'https://tile.openstreetmap.org/',
            'https://*.tile.openstreetmap.org/'
        ])
        ->addDirective(Directive::STYLE, [
            Keyword::SELF,
            'https://fonts.googleapis.com/',
            'https://fonts.bunny.net/',
            'https://cdn.jsdelivr.net/',
            'https://unpkg.com/',
            'https://cdn.datatables.net/',
            'https://code.ionicframework.com/',
        ])
        ->addDirective(Directive::SCRIPT, [
            Keyword::SELF,
            // REMOVED: 'unsafe-eval'
            'https://cdn.datatables.net/',
            'https://unpkg.com/',
            'https://cdn.jsdelivr.net/',
            'https://cdn.jsdelivr.net/npm/sweetalert2@11',
        ])
        ->addDirective(Directive::FONT, [
            Keyword::SELF,
            'data:',
            'https://fonts.bunny.net/',
            'https://fonts.gstatic.com/',
            'https://code.ionicframework.com/',
        ])
        ->addDirective(Directive::CONNECT, [
            Keyword::SELF,
            config('app.serverPantau'),
            config('app.databaseGabunganUrl'),
            'https://unpkg.com',
            'https://*.openstreetmap.org',
        ])
        ->addDirective(Directive::OBJECT, [Keyword::NONE])
        ->addDirective(Directive::BASE, [Keyword::SELF])
        ->addDirective(Directive::FORM_ACTION, [Keyword::SELF])
        ->addDirective(Directive::FRAME_ANCESTORS, [Keyword::NONE]); // Prevent clickjacking
    }

    public function shouldBeApplied(Request $request, Response $response): bool
    {
        // ALWAYS apply CSP, even in debug mode
        $currentRoute = Route::getCurrentRoute()?->getName() ?? '';
        
        if (in_array($currentRoute, $this->excludeRoute)) {
            return false;
        }
        
        // CSP enabled for all environments
        return config('csp.enabled', true);
    }
}

2. Update Blade Templates

Add @cspNonce to inline scripts and styles:

<!-- Before -->
<script>
    // inline code
</script>

<!-- After -->
<script nonce="{{ csp_nonce() }}">
    // inline code
</script>

✅ Action Items

  • Remove APP_DEBUG check from shouldBeApplied()
  • Remove all hardcoded hashes from SCRIPT directive
  • Remove unsafe-eval directive
  • Add frame-ancestors 'none' directive
  • Add form-action 'self' directive
  • Update blade templates to use @cspNonce directive
  • Test CSP violations in browser console
  • Security audit & penetration testing

📚 References

🧪 Testing

# Test CSP in browser
# Open DevTools > Console
# Look for CSP violations

# Test with CSP evaluator
curl -I https://yourdomain.com | grep -i content-security-policy

Estimated Time: 4-6 hours
Risk Level: High if not fixed
Blocking: Production deployment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions