“Backup Files Disclosure” means a scanner retrieved a leftover copy of a web file (such as index.php.bak, config.php~ or site.zip) that the server returns as raw content instead of running it. Fix it by deleting those copies from the web root, blocking backup and archive extensions in nginx, Apache or IIS, and rotating any credentials they contained.
What the scanner is actually detecting
Every tool below guesses file names derived from pages it has already found and reports the ones the server actually returns. The titles and ratings differ:
| Scanner | Finding title | ID | Published rating |
|---|---|---|---|
| Nessus | Backup Files Disclosure | Plugin 11411 | Medium (CVSS v2 5.0), family CGI abuses |
| Tenable Web App Scanning | Backup File | Plugin 98074 | Medium (CVSS v3 5.3), CWE-530 |
| Tenable Web App Scanning | Backup Directory | Plugin 98073 | Medium (CVSS v3 5.3), CWE-530 |
| ZAP | Backup File Disclosure | Alert 10095 (active rule) | Medium, CWE-530 |
| Acunetix | [Possible] Backup Source Code Detected | Not published as a numeric ID | High, CWE-538 |
Tenable describes plugin 11411 as appending suffixes such as .old, .bak and ~ to the names of files on the host and fetching the result. It depends on the Nessus web mirroring plugin (webmirror.nasl), so its candidates are built from files the crawler discovered. Tenable WAS does the same for crawled files and directories, with extensions such as .bak, .orig and .backup.
ZAP 10095 is open source. For each crawled file that did not return 404, it first requests a random nonexistent name in the same directory to learn how the server answers misses. It then tries appended extensions (profile.asp.old), replaced extensions (profile.old), name prefixes and suffixes such as Copy of and – Copy, and renamed parent folders. At the default Medium attack strength it tries about a dozen extensions, including .bak, .backup, .zip, .tar, .swp, ~ and .old; High and Insane try many more. At the default alert threshold it raises the alert only when a candidate returns a non-empty 2xx response that does not match the miss response, and it does not follow redirects.
Real-world risk
Web servers choose a handler by file extension. index.php runs through PHP, but index.php.bak matches no script handler, so the server sends the source code as a static file. The OWASP Web Security Testing Guide (WSTG-CONF-04) gives the classic example of login.asp.old being served as plain text. Source code often contains database passwords, API keys and internal host names, and it shows attackers exactly how the application validates input.
Archives and dumps are usually worse than single-file copies. A site.zip or backup.tar.gz can hold the whole application with its configuration, and a .sql dump can hold password hashes and personal data. On the other hand, a stale copy of a static HTML page reveals nothing new. Severity depends entirely on the content, which is why Nessus rates this Medium while Acunetix, assuming source code, rates it High.
IIS is less exposed by default: Microsoft documents that IIS will not return file types that have no MIME map or handler, so a plain .bak copy is often refused already. Archives with a MIME mapping are still served. These names are predictable, so any internet-facing web root belongs in your external attack surface management scope.
How to confirm it on the host
Request the flagged URL and a random name next to it. If both return the same status and size, you are probably looking at a soft 404 rather than a real file:
curl -s -o /dev/null -w '%{http_code} %{content_type} %{size_download}n' https://www.example.com/index.php.bak
curl -s -o /dev/null -w '%{http_code} %{content_type} %{size_download}n' "https://www.example.com/zz$RANDOM.php.bak"
Find every candidate under the document root (Linux, GNU find):
sudo find /var/www -type f ( -iname '*.bak' -o -iname '*.backup' -o -iname '*.old' -o -iname '*.orig'
-o -iname '*.save' -o -iname '*.swp' -o -name '*~' -o -iname '*.sql' -o -iname '*.tar' -o -iname '*.tgz'
-o -iname '*.tar.gz' -o -iname '*.zip' -o -iname '*.7z' -o -iname '*.rar' -o -name '* - Copy*' -o -name 'Copy of *' )
-printf '%TY-%Tm-%Td %10s %pn'
On Windows and IIS:
Get-ChildItem -Path C:inetpubwwwroot -Recurse -File -Include *.bak,*.backup,*.old,*.orig,*.sql,*.zip,*.7z,*.rar,*~,'* - Copy*','Copy of *' |
Select-Object LastWriteTime, Length, FullName
List which files contain secrets without printing them, then check whether anyone downloaded them. In the default combined log format, field 9 is the status code:
sudo grep -liE 'password|passwd|secret|api[_-]?key' /var/www/example.com/public/index.php.bak
sudo zgrep -hiE '(.(bak|backup|old|orig|swp|sql|tar|tgz|tar.gz|zip|7z|rar)|~) HTTP/' /var/log/nginx/access.log* | awk '$9 == 200'
How to fix it
The Nessus 11411 remediation is the same on every platform: deleting the files closes the finding, and the server rules are a safety net for the next stray copy.
1. Remove the files from the web root
Confirm nothing references a file (grep -rn ‘index.php.bak’ /var/www/example.com), then move it to a quarantine folder outside the document root instead of deleting it straight away:
sudo install -d -m 700 /root/webroot-quarantine
sudo mv /var/www/example.com/public/index.php.bak /root/webroot-quarantine/
If a file held credentials and your logs show a 200 for it (or your logs do not cover the whole period), rotate those credentials. Tenable’s WAS guidance says the same.
2a. nginx: block .bak, .old and other backup files
server {
# place above other regex locations
location ~* (.(bak|backup|old|orig|save|swp|sql|tar|tgz|tar.gz|zip|7z|rar)|~)$ {
return 404;
}
}
nginx checks regex locations in the order they appear and stops at the first match, and a prefix location marked ^~ skips regex checks entirely, so put this block first and copy it into any ^~ location. Use deny all; if you prefer a 403. Apply with sudo nginx -t && sudo systemctl reload nginx.
2b. Apache: FilesMatch deny for backup files
<FilesMatch "(?i)(.(bak|backup|old|orig|save|swp|sql|tar|tgz|tar.gz|zip|7z|rar)|~)$">
Require all denied
</FilesMatch>
Put it in the virtual host or main config. FilesMatch tests only the basename, so it covers every directory. In .htaccess, Require needs AllowOverride AuthConfig or All. For a 404 instead of 403, mod_alias accepts RedirectMatch 404 “(?i)(.(bak|old|orig)|~)$” with no target URL. Apply with sudo apachectl configtest && sudo systemctl reload apache2 (httpd on RHEL-family).
2c. IIS: request filtering
%windir%system32inetsrvappcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /+"fileExtensions.[fileExtension='.bak',allowed='False']"
Repeat for .old, .orig and .sql. Blocked requests get a 404, logged as substatus 404.7. Names ending in ~ are not an extension, so delete those.
3. Stop creating them
- Deploy, do not edit in place. ZAP’s own solution text is not to edit files in situ on the web server. Ship from version control or a CI artifact.
- rsync. –delete removes files on the server that are not in the build; files matched by –exclude (for example an uploads folder) are kept unless you add –delete-excluded. Do not use –backup against a web root without –backup-dir: its default suffix is ~.
- Editors. On Unix, Vim writes .name.swp next to the file unless the directory option points elsewhere, and nano writes name.save emergency files when killed.
- Dumps and archives. Write them to /var/backups or another path no virtual host serves, and schedule the find command above with an alert on any output.
How to verify the fix and rescan
for p in /index.php.bak /index.php~ /index.php.old /backup.zip /db.sql; do
printf '%s ' "$p"; curl -s -o /dev/null -w '%{http_code}n' "https://www.example.com$p"
done
Use the exact paths from your finding. Each should return 403 or 404, the site should still load, and the find command should print nothing. Purge any CDN cache for those paths. Then rescan with the same policy: Nessus needs its crawler to reach the same pages, ZAP needs a spider run first and the same attack strength, and Tenable WAS should use the same scan configuration.
What can break and how to roll back
- Legitimate downloads. If the site publishes .zip or .tar.gz files, remove those extensions from the rule. In nginx, a location ^~ /downloads/ block also skips the regex.
- Server-side reads are unaffected. The rules block HTTP requests only, so code that reads a .sql schema from disk keeps working.
- Rule order. An earlier nginx regex location (for example a caching rule for .zip) silently wins over the block.
Back up configs before editing (sudo cp -a /etc/nginx/sites-available/example.conf /root/) and roll back by restoring and reloading. Restore a quarantined file with mv. Never roll back rotated credentials; update their consumers instead.
Common false positive reasons
- Soft 404. The server answers every path with 200 and the same page. Compare sha256sum of the flagged URL and a random one.
- ZAP at LOW threshold. ZAP then counts non-success responses too, so a 403 from your new rule can still trigger the alert on a server that returns 404 for misses. Return 404 or use the default threshold.
- Login or WAF pages. A block or sign-in page returned with status 200 looks like a file.
- Intentional archives. A public release .zip is technically a true positive; document it as an exception.
- Different target. The scanner hit another virtual host or the origin IP.
FAQ
Is blocking the extensions enough?
No. The files remain on disk and in copies of the web root, and another virtual host or a later config change can expose them again. Delete them and keep the rule as a backstop.
Which extensions should I block?
Start with the list above plus anything your report names. ZAP’s source contains a much longer list of archive formats if you need more.
Do I have to rotate passwords?
Only if the file contained credentials and could have been downloaded. Without complete logs proving no 200 responses, rotate.
Why does Acunetix say High when Nessus says Medium?
Each vendor scores its own check. The real severity is set by what the file contains, so read it before you prioritize.
Tracking this finding across many hosts
If you track this finding in SITEY, a self-hosted vulnerability management platform, results from its 16 scanner integrations land in one list (Nessus results arrive as uploaded .nessus exports), but duplicates are merged per scanner, so a Nessus 11411 and an Acunetix finding for the same URL stay separate. Per-finding retest is available for Nessus, Acunetix and Burp results; for other tools, rerun the scan. AI triage can suggest likely false positives with supporting evidence, and a human makes the final call.