- HTTPs (i.e. Apache2)
- Apache2
- Database
- Authorization
- Cookies
- PHP
- Node.js/npm
- Docker
- Ubuntu VPS
- HTTP Headers
- HTML DOM sanitization
- Analysis tools
- Sources and resources
This document is a concise guide that aims to list the main web vulnerabilities, particularly JavaScript, and some solutions. However, it is exhaustive and should be supplemented with quality, up-to-date documentation.
This guide is intended for full-stack developers working with JavaScript technologies (React, Vue, etc.) and a Node.js/PHP backend.
.NET, JAVA, Django or Ruby are therefore not included in this guide.
Write in /etc/apache2/apache2.conf:
Redirect permanent / <https://domain.com/>
General purpose web applications should default to TLS 1.3 (support TLS 1.2 if necessary) with all other protocols disabled. Only enable TLS 1.2 and 1.3. Go to /etc/apache2/conf-available/ssl.conf and write:
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
HTTP Strict Transport Security (HSTS) is a mechanism for websites to instruct web browsers that the site should only be accessed over HTTPs. This mechanism works by sites sending a Strict-Transport-Security HTTP response header containing the site's policy. Write in /etc/apache2/apache2.conf:
Header set strict-transport-security "max-age=31536000; includesubdomains; preload"
Reload Apache and submit your website to https://hstspreload.org/
Disable all non-secure encryption algorithms. Go to /etc/apache2/conf-available/ssl.conf and write:
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384
Online Certificate Status Protocol stapling is a crucial technology that enhances both the speed and privacy of SSL/TLS connections. Go to /etc/apache2/conf-available/ssl.conf and write:
SSLUseStapling on
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off
SSLStaplingCache "shmcb:ssl_stapling(32768)" <- before VirtualHost
HTTP/2 provides a solution to several problems that the creators of HTTP/1.1 had not anticipated. In particular, HTTP/2 is much faster and more efficient than HTTP/1.1.
sudo a2enmod http2
Mod security is a free Web Application Firewall (WAF) that works with Apache2 or nginx.
sudo apt install libapache2-modsecurity
SecRuleEngine On <- /etc/modsecurity/modsecurity.conf
Revealing web server signature with server/PHP version info can be a security risk as you are essentially telling attackers known vulnerabilities of your system. Write in /etc/apache2/apache2.conf:
ServerTokens Prod
ServerSignature Off
Write in /etc/apache2/apache2.conf:
<Directory />
Options FollowSymLinks
AllowOverride None
Require all denied
</Directory>
<Directory /var/www>
Options -Indexes
AllowOverride None
Require all granted
</Directory>
- Use a strong database password and restrict user permissions
- Hash all user login passwords before storing them in the database
- For MySQL/MariaDB databases, use prepared queries to prevent injections
$query = $PDO->prepare("SELECT name FROM users WHERE name=:NameConnect LIMIT 1");
$query->execute([':NameConnect' => $name]);
$row = $query->fetch();
- For MySQL/MariaDB databases, use mysql_secure_installation
- For NoSQL databases, like MongoDB, use a typed model to prevent injections
- Avoid $accumulator, $function, $where in MongoDB
- Use .env for database and server secrets
- Encrypt all user data (e.g. AES-256-GCM), store encryption keys in a secure vault like AWS Secrets Manager, Google Secrets Manager or Azure KeyVault
- Deny by default
- Enforce least privileges
- Validate all permissions
- Validate files access
- Sanitize files upload
- Require user password for sensitive actions
Domain=domain.com; Path=/; Secure; HttpOnly; SameSite=Lax or Strict
Secure: All cookies must be set with the Secure directive, indicating that they should only be sent over HTTPs
HttpOnly: Cookies that don't require access from JavaScript should have the HttpOnly directive set to block access
Domain: Cookies should only have a Domain set if they need to be accessible on other domains; this should be set to the most restrictive domain possible
Path: Cookies should be set to the most restrictive Path possible
SameSite:
- Strict: Only send the cookie in same-site contexts. Cookies are omitted in cross-site requests and cross-site navigation
- Lax: Send the cookie in same-site requests and when navigating to your website. Use this value if Strict is too restrictive
PHP-FPM (FastCGI Process Manager) is often preferred over Apache mod_php due to its superior performance, process isolation, and flexible configuration.
sudo apt install php<version>-fpm
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event proxy_fcgi proxy
PDO (PHP Data Objects) is a Database Access Abstraction Layer that provides a unified interface for accessing various databases.
A secure MySQL database connection with PDO:
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
$dsn = "mysql:host=$host;dbname=$db";
try {
$PDO = new PDO($dsn, $user, $pass, $options);
} catch (Exception $e) {
throw new Exception('Connection failed');
return;
}
A hardened template for PHP-FPM, write in /etc/php/<version>/fpm/php.ini:
expose_php = off
error_reporting = e_all & ~e_deprecated & ~e_strict
display_errors = off
display_startup_errors = off
ignore_repeated_errors = off
allow_url_fopen = off
allow_url_include = off
session.use_strict_mode = 1
session.use_only_cookies = 1
session.cookie_secure = 1
session.cookie_httponly = 1
session.cookie_samesite = strict
session.sid_length = > 128
- Always keep all npm dependencies up to date
- Limit the use of dependencies
- Use npm doctor to ensure that your npm installation has what it needs to manage your JavaScript packages
- Use eslint to write quality code
- To manage user cookies, use express.js and passport.js with JWT tokens
- Use official and minimal images
- Use .dockerignore to hide server secrets
- Run containers with a read-only filesystem using --read-only flag
- Avoid the use of ADD in favor of COPY
- Set a user with restricted permissions in DockerFile
RUN groupadd -r myuser && useradd -r -g myuser myuser
# HERE DO WHAT YOU HAVE TO DO AS A ROOT USER LIKE INSTALLING PACKAGES ETC.
USER myuser
- Use a strong passwords for all users
- Disable root login
- Create a user with restricted permissions and 2FA or physical key
- Always update all packages and limit their number
- Disable unused network ports
- Change SSH port and use Fail2Ban to prevent DoS and Bruteforce attacks, disable SSH root login in sshd_config
PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin no
- Always make secure backups
- Log everything
- Use SFTP instead of FTP
- Use a firewall like iptables or ufw
- Use robots.txt to disallow all by default and don't disclose sensitive URLs
User-agent: \*
Disallow: /admin <- don’t do this
A hardened template for Apache2 and nginx:
x-content-type-options: "nosniff"
access-control-allow-origin "https://domain.com"
referrer-policy "no-referrer"
content-security-policy "upgrade-insecure-requests; default-src 'none'; base-uri 'none'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; img-src ‘self’; media-src 'self'; object-src ‘none’ ; script-src 'self'; script-src-attr 'none'; style-src 'self'"
permissions-policy "geolocation=(), …"
cross-origin-embedder-policy: "require-corp"
cross-origin-opener-policy "same-origin"
cross-origin-resource-policy "cross-origin"
In addition be sure to remove Server and X-Powered-By headers.
Note
Never use X-XSS-Protection, it is depracated and can create XSS vulnerabilities in otherwise safe websites. X-Frame-Options is depracated and replaced by frame-ancestors 'none'. Always start with default-src 'none', avoid unsafe-inline and unsafe-eval. Use hashes or nonces for inline scripts/styles.
Always use rel="noreferrer noopener" to prevent the referrer header from being sent to the new page.
Never trust user inputs, validate and sanitize all data. Prefer POST requests instead of GET requests and sanitize/encode user form data with a strong regex and URLSearchParams() or encodeURIComponent().
try {
const data = new URLSearchParams({ name, psswd })
const res = await fetch('api/connectUser.php', {
method: 'POST',
mode: 'same-origin',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: data,
})
if (!res.ok) {
//
return
}
//
} catch {
//
}
Never use innerHTML, use innerText or textContent instead. You can also create your element with document.createElement().
Never use these JavaScript function. Executing JavaScript from a string is an enormous security risk. It is far too easy for a bad actor to run arbitrary code when you use eval().
DOMPurify sanitizes HTML and prevents XSS attacks. You can feed DOMPurify with string full of dirty HTML and it will return a string (unless configured otherwise) with clean HTML. DOMPurify will strip out everything that contains dangerous HTML and thereby prevent XSS attacks and other nastiness.
import DOMPurify from 'dompurify'
const purifyConfig: {
SANITIZE_NAMED_PROPS: true,
ALLOW_DATA_ATTR: false,
FORBID_TAGS: [
'dialog', 'footer', 'form', 'header', 'main', 'nav', 'style'
]
}
const clean = DOMPurify.sanitize(dirty, purifyconfig)
https://developer.mozilla.org/en-US/
https://www.cnil.fr/fr/securiser-vos-sites-web-vos-applications-et-vos-serveurs
https://cyber.gouv.fr/publications/recommandations-de-securite-relatives-tls
https://owasp.org/www-project-top-ten/
https://cheatsheetseries.owasp.org/