Blocking Vite.js Path Traversal Attacks with ModSecurity

Or: How Hackers Try to Steal Your Credentials Through a Dev Server Exploit

So you’re running ModSecurity with the OWASP Core Rule Set, feeling all cozy and protected, when suddenly you notice something peculiar in your logs: someone is hammering your server with requests like /@fs/etc/passwd and /@fs/.aws/credentials. What in the seven layers of the OSI model is going on here?

Welcome to CVE-2025-30208, a path traversal vulnerability in Vite.js development servers that attackers are now probing for on every server they can find. Because apparently scanning the entire internet for exposed dev servers is a thing people do.

Fear not, fellow security enthusiast. Let’s build some custom ModSecurity rules that actually work, including with HTTP/3.


The Attack Pattern

“/@fs/ called, it wants your secrets”

Vite.js is a popular frontend build tool that runs a development server. The /@fs/ endpoint was designed to serve files from the filesystem during development. The problem? Attackers discovered they could use it to read arbitrary files, and they’re scanning production servers hoping someone accidentally left Vite running or that the path somehow works.

Here’s what the attack looks like in your logs:

185.177.72.22 - - "GET /@fs/etc/passwd HTTP/1.1" 200 689
185.177.72.22 - - "GET /@fs/.aws/credentials HTTP/1.1" 200 689
185.177.72.22 - - "GET /@fs/.ssh/id_rsa HTTP/1.1" 200 689
185.177.72.22 - - "GET /@fs/.env HTTP/1.1" 200 689
185.177.72.22 - - "GET /@fs/.docker/config.json HTTP/1.1" 200 689

Those 200 responses? That’s your server happily responding (with an error page, hopefully). But the attacker doesn’t care. They’re spraying requests across thousands of servers hoping one of them is actually vulnerable.

Fun fact: The attackers also probe /@id/ which is another Vite.js endpoint with similar issues. Thorough, aren’t they?


Why CRS Doesn’t Catch Everything

When partial protection isn’t enough

You might expect the OWASP Core Rule Set (CRS) to block these attacks. It actually catches some of them. Rule 930130 checks requested filenames against restricted-files.data, which includes dotfiles like .env, .aws/, .ssh/, and .git/.

Attack CRS Detection Why
/@fs/.env ✅ 930130 .env in restricted-files.data
/@fs/.aws/credentials ✅ 930130 .aws/ in restricted-files.data
/@fs/etc/passwd ❌ Not detected etc/passwd only checked in ARGS
/@fs/proc/self/environ ❌ Not detected System paths not in REQUEST_URI check
/.git/config ✅ 930130 .git/ in restricted-files.data

The gap: Rule 930120 knows about system files like etc/passwd (via lfi-os-files.data), but it only checks query parameters (ARGS), not the URI path. So /@fs/etc/passwd slips through.

Pro tip: A dedicated /@fs/ rule provides complete coverage regardless of payload. Block the endpoint, not just specific files.


The Custom Rules

Teaching ModSecurity new tricks

Here’s a set of custom rules that will block these attacks. The key insight: we use REQUEST_URI instead of relying on any header that might be missing (looking at you, HTTP/3).

Rule 900001: Block Vite.js /@fs/ Path Traversal

SecRule REQUEST_URI "@beginsWith /@fs/" \
    "id:900001,\
    phase:1,\
    deny,\
    status:403,\
    log,\
    msg:'Vite.js Path Traversal Attack blocked (/@fs/)',\
    tag:'attack-lfi',\
    tag:'custom-rule',\
    severity:'CRITICAL'"

Rule 900002: Block Sensitive Directory Access

SecRule REQUEST_URI "@rx (?i)^/(\.aws|\.ssh|\.git|\.docker|\.env|\.npmrc|\.pypirc|\.netrc)/" \
    "id:900002,\
    phase:1,\
    deny,\
    status:403,\
    log,\
    msg:'Sensitive directory access attempt blocked',\
    tag:'attack-lfi',\
    tag:'custom-rule',\
    severity:'CRITICAL'"

Rule 900003: Block Sensitive File Access

SecRule REQUEST_URI "@rx (?i)/(credentials\.json|id_rsa|id_dsa|authorized_keys|config\.json)$" \
    "id:900003,\
    phase:1,\
    deny,\
    status:403,\
    log,\
    msg:'Sensitive file access attempt blocked',\
    tag:'attack-lfi',\
    tag:'custom-rule',\
    severity:'CRITICAL'"

What These Rules Block

The greatest hits of credential theft

Rule Blocks Example Requests
900001 Vite.js endpoints /@fs/anything, /@id/__vite_env
900002 Hidden directories /.aws/credentials, /.ssh/id_rsa, /.git/config
900003 Sensitive files /app/credentials.json, /home/user/.ssh/id_rsa

Here’s the full list of attack patterns we observed in the wild:

/@fs/etc/passwd
/@fs/.env
/@fs/.env.local
/@fs/.env.production
/@fs/.aws/credentials
/@fs/.ssh/id_rsa
/@fs/.docker/config.json
/@fs/config.php
/@fs/settings.py
/@fs/appsettings.json
/@fs/web.config
/.git/config
/.git/HEAD
/.env
/.env.local
/.env.production
/.env.development

Fun fact: We caught an entire /24 subnet (185.177.72.0/24) systematically scanning for these files. They hit over 1,300 requests in a single day. Block the subnet at your firewall if you see similar patterns.


Installation

Getting these rules into your setup

For nginx + ModSecurity

  1. Create or edit your custom rules file:
vim /etc/nginx/modsec/custom-rules.conf
  1. Add the rules from above

  2. Make sure it’s included in your ModSecurity config:

1. In modsecurity.conf
Include /etc/nginx/modsec/custom-rules.conf
  1. Reload nginx:
nginx -t && nginx -s reload

Verify It Works

1. This should return 403
curl -I "https://yourdomain.com/@fs/.env"

1. Check the audit log
tail -f /var/log/modsec/audit.log | grep 900001

Quick Reference Card

┌───────────────────────────────────────────────────────────────┐
│        VITE.JS & SENSITIVE FILE PROTECTION RULES              │
├───────────────────────────────────────────────────────────────┤
│                                                               │
│  RULES ADDED:                                                 │
│     900001 → Block /@fs/ and /@id/ paths                      │
│     900002 → Block /.aws/, /.ssh/, /.git/, etc.               │
│     900003 → Block credentials.json, id_rsa, etc.             │
│                                                               │
│  WHY REQUEST_URI:                                             │
│     • Works with HTTP/1.1, HTTP/2, AND HTTP/3                 │
│     • Doesn't depend on Host header                           │
│     • Catches the attack before backend processing            │
│                                                               │
│  INSTALLATION:                                                │
│     1. Add rules to custom-rules.conf                         │
│     2. Include in modsecurity.conf                            │
│     3. Reload nginx/apache                                    │
│     4. Test with: curl -I https://site/@fs/.env               │
│                                                               │
│  VERIFY IN LOGS:                                              │
│     grep "900001\|900002\|900003" /var/log/modsec/audit.log   │
│                                                               │
└───────────────────────────────────────────────────────────────┘

Appendix: Attack Indicators

Pattern Attack Type Rule ID Response
/@fs/* Vite.js Path Traversal 900001 🔴 Block
/@id/* Vite.js Module Exploit 900001 🔴 Block
/.aws/* AWS Credential Theft 900002 🔴 Block
/.ssh/* SSH Key Theft 900002 🔴 Block
/.git/* Git Repository Exposure 900002 🔴 Block
/.env* Environment Variable Theft 900002 🔴 Block
/id_rsa SSH Private Key 900003 🔴 Block
/credentials.json Various Credentials 900003 🔴 Block

Subnet Blocklist (observed attackers)

If you see these subnets in your logs, consider blocking at firewall level:

185.177.72.0/24  - Systematic scanner (France)
109.74.197.0/24  - /.git/config scanner (UK)
139.162.0.0/16   - Linode-based scanners

Conclusion

The Vite.js /@fs/ path traversal attack (CVE-2025-30208) is a perfect example of why you can’t rely solely on generic WAF rules. Framework-specific exploits require framework-specific protection.

Key takeaways:

  • 🎯 CRS catches some, not all. Dotfiles yes, system paths no.
  • 📝 Use REQUEST_URI. It works across all HTTP versions including HTTP/3.
  • 🔒 Layer your defenses. WAF rules + firewall blocks + monitoring.
  • 🚨 Watch for scanners. When you see one IP probing, expect more.

The attackers are automated and relentless. Your defenses should be too.


Stay patched, stay paranoid. 🛡️


Related Resources:

Leave a Reply

Your email address will not be published. Required fields are marked *