고객 지원 문의

이메일로 답장드리며, 보통 이틀 안에 회신합니다.

Google reCAPTCHA가 이 제출을 악용 여부에 대해 확인하며, 이 과정에서 데이터가 Google로 전송됩니다. 스크립트는 이 양식을 열 때만 불러옵니다.

← 전체 글

A backup in the web root is a source leak

Before editing a large file, the natural move is to copy it first. cp index.php index.php.vor-aenderung, then work. If that file sits in the directory the web server publishes, the entire source code has just become downloadable as plain text — database credentials, secrets, everything the file happens to contain.

Apache runs index.php through PHP because it ends in .php. index.php.vor-aenderung does not end in .php. It ends in .vor-aenderung, which Apache has never heard of, so it does the default thing and sends the bytes.

The rule most people write, and why it is not enough

The usual guard is a deny list of extensions: .bak, .orig, .save, .sql, .old. It catches the file that came to mind. Tested here, that list gave the right answer for probe.bak and the wrong answer for every one of these:

  • index.php.vor-xyz — how it actually happened
  • index.php.2026-08-19
  • index.php.kopie
  • style.css.alt
  • index.php.1
file nameanswerprobe.bak403index.php.vor-xyz200index.php.2026-08-19200index.php.kopie200style.css.alt200the naive list catches one of five

All served with 200. The naming pattern nobody thought of is the one that gets used, because backups get named after whatever was going on at the time.

Invert the cut

The rule that works does not ask which extensions are forbidden. It asks a structural question: does this filename contain a source-code extension that is not at the end?

If it does, it is a copy of something that was meant to be executed, whatever the suffix after it says. index.php.anything matches. style.css.anything matches. The pattern covers the names nobody has invented yet, which is the entire point.

Checked against the live server today, all five of the names above now return 403, including the ones that do not exist — the rule denies the shape, not the file.

The exception, and what it teaches

One legitimate filename here has exactly that shape: pre-compressed assets are written as style.css.1787777226.gz, an extension in the middle by design. So the rule carries a single narrow exception for a numeric segment followed by .gz, and that file returns 200 as it should.

An exception like that is where these rules usually go wrong, so it is worth stating what makes this one safe: it is narrow enough that no index.php.something.gz can be constructed to fetch the source back, because the middle segment must be digits only. A broader exception — "anything ending in .gz" — would have handed the whole rule back.

And the simplest advice remains the one that makes all of this unnecessary: keep backups outside the published directory. The rule is a net for the day it gets forgotten, not a licence to stop caring where the copy lands.

광고