Lesson 3 / الدرس 3

What the web can reach, and who owns it / ما يستطيع الويب بلوغه، ومن يملكه

Two questions decide most of a server's security, and both have short answers: which directory does a URL map onto, and which user is the code running as. Getting them right is structural — it protects you from mistakes you have not made yet.

سؤالان يقرران معظم أمن الخادم، ولكليهما جواب قصير: أيُّ مجلد يقابله الرابط، وأيُّ مستخدم يعمل الكود بصفته. وضبطهما بنيوي — فهو يحميك من أخطاء لم ترتكبها بعد.

A web server maps URLs onto one directory. Everything inside it is fetchable by anyone who guesses the name; everything outside it cannot be named by a URL at all. That single boundary is the cheapest security you will ever get, and it is set once when the site is configured.

/home/site/
  config.php          not reachable — no URL maps here
  src/                not reachable
  views/              not reachable
  content/            not reachable
  storage/
    logs/             not reachable  (and must NOT be)
    uploads/          not reachable  (see chapter 4)
  vendor/             not reachable

  public_html/        <- the document root. THIS is the web.
    index.php         the front controller
    assets/           css, js, images
This is this site's real layout, and bin/deploy.sh exists to maintain it: the repository's public/ goes to public_html/, and everything else goes one level above. The split is not a convention, it is the security model — a mistake in a view cannot expose config.php because no URL reaches it.

Whoever the code runs as, is who an attacker becomes

# Who am I, on this server?
$ php -r 'echo get_current_user();'
$ ps aux | grep php-fpm

# Directories: enter and list.  Files: read.
$ find /home/site -type d -exec chmod 755 {} +
$ find /home/site -type f -exec chmod 644 {} +

# Only what genuinely has to be written to.
$ chmod 775 /home/site/storage/logs /home/site/storage/uploads

# The one that is only for you.
$ chmod 600 /home/site/config.php

# 777 is not a fix. It is 'anyone on this machine may rewrite this',
# and it is the answer to a permissions error roughly never.
Permissions decide what a bug can do. If PHP can write to its own source directory, then any hole that writes a file has just added code to your application — and that is the difference between an attacker reading something and an attacker owning the site. Writable should be the exception you can list from memory.

Things that end up in a document root by accident

WhatWhat it gives away
.git/Your entire source history, including deleted secrets
.env, config.bak, config.php~Credentials — editors and editors' backups leave these
phpinfo.phpPaths, extensions, versions — a map for choosing an exploit
adminer.php, db.phpDirect database access, often left after "just for a minute"
error_log, debug.logYour bugs, with file paths and sometimes user data
*.sql, backup.zipThe whole database, downloadable

.git/ is the one that surprises people: rsync and FTP copy hidden directories, so a deploy that syncs the repository puts it in the document root, and anyone can then reconstruct your full source and every secret you ever committed. This site's deploy script excludes it explicitly.

Check yourself / اختبر نفسك

1. Why must .git/ never be inside the document root?

2. Why should PHP not be able to write to its own source directory?

3. How do you know your web root boundary is actually configured correctly?