Loading...
Loading...
Security audit patterns for PHP/OWASP. Use when conducting security assessments, identifying vulnerabilities (XXE, SQL injection, XSS), or CVSS scoring.
npx skill4agent add dirnbauer/webconsulting-skills security-audit| Rank | Category | Description |
|---|---|---|
| A01 | Broken Access Control | Unauthorized access to resources |
| A02 | Cryptographic Failures | Weak encryption, exposed secrets |
| A03 | Injection | SQL, NoSQL, OS, LDAP injection |
| A04 | Insecure Design | Missing security controls by design |
| A05 | Security Misconfiguration | Default configs, verbose errors |
| A06 | Vulnerable Components | Outdated libraries with CVEs |
| A07 | Auth Failures | Broken authentication/session |
| A08 | Data Integrity Failures | Insecure deserialization, CI/CD |
| A09 | Logging Failures | Missing audit logs, monitoring |
| A10 | SSRF | Server-side request forgery |
// ❌ VULNERABLE - External entities enabled
$doc = new DOMDocument();
$doc->loadXML($userInput);// ✅ SECURE - Disable external entities
$doc = new DOMDocument();
$doc->loadXML(
$userInput,
LIBXML_NONET | LIBXML_NOENT | LIBXML_DTDLOAD
);
// Or use libxml_disable_entity_loader for older PHP
libxml_disable_entity_loader(true); // Deprecated in PHP 8.0// ✅ SECURE
$xml = simplexml_load_string(
$userInput,
'SimpleXMLElement',
LIBXML_NONET | LIBXML_NOENT
);// ❌ VULNERABLE - Direct string interpolation
$query = "SELECT * FROM users WHERE id = " . $_GET['id'];
$result = $pdo->query($query);// ✅ SECURE - Prepared statements
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
$result = $stmt->fetchAll();// ✅ SECURE - TYPO3 QueryBuilder with named parameters
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('users');
$result = $queryBuilder
->select('*')
->from('users')
->where(
$queryBuilder->expr()->eq(
'uid',
$queryBuilder->createNamedParameter($id, Connection::PARAM_INT)
)
)
->executeQuery()
->fetchAllAssociative();// ✅ SECURE - Escape all output
echo htmlspecialchars($userInput, ENT_QUOTES | ENT_HTML5, 'UTF-8');<!-- ✅ SAFE - Auto-escaped -->
{variable}
<!-- ❌ DANGEROUS - Raw output, use only for trusted HTML -->
{variable -> f:format.raw()}
<!-- ✅ SAFE - Explicit escaping -->
{variable -> f:format.htmlspecialchars()}// Set CSP header
header("Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';");// Generate token
$token = bin2hex(random_bytes(32));
$_SESSION['csrf_token'] = $token;
// Validate token
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
throw new SecurityException('CSRF token mismatch');
}use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
// Generate
$formProtection = $this->formProtectionFactory->createFromRequest($request);
$token = $formProtection->generateToken('myForm');
// Validate
$isValid = $formProtection->validateToken($token, 'myForm');<?php
declare(strict_types=1);
final class ApiKeyEncryption
{
public function encrypt(string $apiKey, string $key): string
{
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$encrypted = sodium_crypto_secretbox($apiKey, $nonce, $key);
return 'enc:' . base64_encode($nonce . $encrypted);
}
public function decrypt(string $encrypted, string $key): string
{
if (!str_starts_with($encrypted, 'enc:')) {
throw new \InvalidArgumentException('Invalid encrypted format');
}
$decoded = base64_decode(substr($encrypted, 4));
$nonce = substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$decrypted = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
if ($decrypted === false) {
throw new \RuntimeException('Decryption failed');
}
return $decrypted;
}
}// ✅ SECURE - Use password_hash with Argon2id
$hash = password_hash($password, PASSWORD_ARGON2ID);
// Verify
if (password_verify($inputPassword, $storedHash)) {
// Valid password
}
// Check if rehash needed (algorithm upgrade)
if (password_needs_rehash($storedHash, PASSWORD_ARGON2ID)) {
$newHash = password_hash($password, PASSWORD_ARGON2ID);
// Update stored hash
}use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
$hashInstance = GeneralUtility::makeInstance(PasswordHashFactory::class)
->getDefaultHashInstance('BE');
$hash = $hashInstance->getHashedPassword($password);
$isValid = $hashInstance->checkPassword($password, $hash);| Metric | Values |
|---|---|
| Attack Vector (AV) | Network (N), Adjacent (A), Local (L), Physical (P) |
| Attack Complexity (AC) | Low (L), High (H) |
| Privileges Required (PR) | None (N), Low (L), High (H) |
| User Interaction (UI) | None (N), Required (R) |
| Scope (S) | Unchanged (U), Changed (C) |
| Confidentiality (C) | None (N), Low (L), High (H) |
| Integrity (I) | None (N), Low (L), High (H) |
| Availability (A) | None (N), Low (L), High (H) |
| Score | Severity |
|---|---|
| 0.0 | None |
| 0.1 - 3.9 | Low |
| 4.0 - 6.9 | Medium |
| 7.0 - 8.9 | High |
| 9.0 - 10.0 | Critical |
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Score: 9.8 (Critical)
Translation:
- Network accessible
- Low complexity
- No privileges required
- No user interaction
- Unchanged scope
- High impact on C/I/A// config/system/settings.php
return [
'BE' => [
'debug' => false,
'lockIP' => 4,
'lockSSL' => true,
],
'FE' => [
'debug' => false,
'lockSSL' => true,
],
'SYS' => [
'displayErrors' => 0,
'devIPmask' => '',
'trustedHostsPattern' => 'example\\.com|www\\.example\\.com',
'features' => [
'security.backend.enforceReferrer' => true,
'security.frontend.enforceContentSecurityPolicy' => true,
],
],
];