mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-07 03:29:19 +02:00
Phase 1: Consolidate shared infrastructure - Create shared/ directory for common code - Consolidate Database.php from front-backend and formulaire into unified shared/Database.php - Smart path detection for test.db vs posterg.db - Secure search with wildcard escaping and input validation - Support both singleton and direct instantiation patterns - Full CRUD methods for admin functionality - Move RateLimit.php to shared/ (30 requests/min) - Update all require paths across apps to use shared/ Phase 2: Reorganize directory structure - Rename front-backend/ → apps/public/ - Rename formulaire/ → apps/admin/ - Rename db/ → database/ - Update all file paths for new structure - Create root .gitignore excluding databases, cache, logs Implement secure search feature - Add apps/public/search.php with full-text search across theses - Search filters: query, year, orientation, AP program, keywords - Security features: - SQL injection prevention (prepared statements) - Wildcard injection prevention (escape % and _) - Input validation (max 200 chars, year range 1900-2100) - Rate limiting (30 req/min per IP) - Pagination limited to 100 results/page - XSS protection (htmlspecialchars on output) Add comprehensive test suite - Create apps/public/tests/ with proper structure - tests/Integration/SearchTest.php - 12 search scenarios - tests/Security/SecurityTest.php - vulnerability testing - tests/Unit/RateLimitTest.php - rate limit behavior - Create database/fixtures/CreateTestDatabase.php - Add apps/public/run-tests.php test runner - All tests passing (4/4 suites) Update deployment configuration - Rename justfile 'sync' recipe to 'deploy' - Create deploy group with separate deploy-public and deploy-admin - Add test-deploy recipe for test database - Exclude *.db, tests/, cache/, *.md from production deploy - Deploy shared/ to both public and admin locations Stats: +4482 insertions, -654 deletions across 72 files
278 lines
6.6 KiB
Markdown
278 lines
6.6 KiB
Markdown
# Security Analysis - Search Feature
|
|
|
|
## Current Security Status
|
|
|
|
### ✅ Protections in Place
|
|
|
|
1. **SQL Injection Prevention**
|
|
- ✅ Uses PDO prepared statements
|
|
- ✅ All parameters bound with `bindValue()`
|
|
- ✅ No direct concatenation of user input into SQL
|
|
- ✅ Dynamic WHERE clause built from hardcoded strings only
|
|
|
|
2. **XSS (Cross-Site Scripting) Prevention**
|
|
- ✅ All output uses `htmlspecialchars()`
|
|
- ✅ Form values escaped when displayed
|
|
- ✅ Search results escaped before rendering
|
|
|
|
3. **Access Control**
|
|
- ✅ Only published theses searchable (`is_published = 1`)
|
|
- ✅ Uses read-only view (`v_theses_public`)
|
|
|
|
4. **Type Safety**
|
|
- ✅ Year parameter uses `intval()`
|
|
- ✅ Boolean values properly cast
|
|
|
|
---
|
|
|
|
## ⚠️ Security Vulnerabilities
|
|
|
|
### 1. LIKE Wildcard Injection (Low Severity)
|
|
|
|
**Issue:** Users can inject SQL LIKE wildcards (`%`, `_`) to match unintended patterns.
|
|
|
|
**Example Attack:**
|
|
```
|
|
Search query: "%"
|
|
Result: Matches ALL theses (bypasses search intent)
|
|
|
|
Search query: "a%b%c%d%e%f%g%h%i%j%k%l%m%n%o%p%q%r%s%t%u%v%w%x%y%z"
|
|
Result: Forces inefficient pattern matching, potential DoS
|
|
```
|
|
|
|
**Current Code:**
|
|
```php
|
|
$bindings[':query'] = '%' . $params['query'] . '%';
|
|
```
|
|
|
|
**Impact:**
|
|
- Not SQL injection (still uses prepared statements)
|
|
- Allows overly broad searches
|
|
- Performance degradation with complex patterns
|
|
- Information disclosure through pattern matching
|
|
|
|
**Fix:** Escape wildcards before using in LIKE:
|
|
```php
|
|
private function escapeLikeString($string) {
|
|
return str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $string);
|
|
}
|
|
|
|
// In query:
|
|
$bindings[':query'] = '%' . $this->escapeLikeString($params['query']) . '%';
|
|
|
|
// In SQL:
|
|
"title LIKE :query ESCAPE '\\'"
|
|
```
|
|
|
|
---
|
|
|
|
### 2. No Input Length Validation (Medium Severity)
|
|
|
|
**Issue:** No limits on search string length.
|
|
|
|
**Example Attack:**
|
|
```php
|
|
// 10MB query string
|
|
$query = str_repeat('a', 10 * 1024 * 1024);
|
|
```
|
|
|
|
**Impact:**
|
|
- Memory exhaustion
|
|
- Database query slowdown
|
|
- Denial of Service (DoS)
|
|
|
|
**Fix:** Validate input length:
|
|
```php
|
|
if (strlen($params['query']) > 200) {
|
|
throw new InvalidArgumentException("Search query too long");
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 3. No Rate Limiting (Medium Severity)
|
|
|
|
**Issue:** Unlimited search requests allowed.
|
|
|
|
**Example Attack:**
|
|
```bash
|
|
# Spam 10,000 requests
|
|
for i in {1..10000}; do
|
|
curl "http://site.com/search.php?query=test&page=$i" &
|
|
done
|
|
```
|
|
|
|
**Impact:**
|
|
- Database overload
|
|
- Server resource exhaustion
|
|
- Denial of Service for legitimate users
|
|
|
|
**Fix:** Implement rate limiting (see solution below)
|
|
|
|
---
|
|
|
|
### 4. No Pagination Limits (Low Severity)
|
|
|
|
**Issue:** Users can request excessive offset values.
|
|
|
|
**Example:**
|
|
```
|
|
search.php?page=999999999
|
|
```
|
|
|
|
**Impact:**
|
|
- Database scans large result sets
|
|
- Wasted resources on impossible pages
|
|
|
|
**Fix:** Validate pagination:
|
|
```php
|
|
$limit = max(1, min(100, intval($limit))); // Max 100 per page
|
|
$offset = max(0, intval($offset));
|
|
|
|
// Optionally limit max offset
|
|
if ($offset > 10000) {
|
|
throw new InvalidArgumentException("Page too high");
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🔒 Recommended Security Improvements
|
|
|
|
### Priority 1: Apply Input Validation (HIGH)
|
|
|
|
Use the enhanced `Database_secure.php` class which includes:
|
|
- Wildcard escaping
|
|
- Length validation
|
|
- Range validation
|
|
- ESCAPE clause in LIKE queries
|
|
|
|
### Priority 2: Implement Rate Limiting (MEDIUM)
|
|
|
|
Example using simple file-based rate limiting:
|
|
|
|
```php
|
|
<?php
|
|
// rate_limit.php - Simple rate limiter
|
|
|
|
function checkRateLimit($identifier, $maxRequests = 10, $timeWindow = 60) {
|
|
$cacheDir = __DIR__ . '/cache/rate_limit';
|
|
if (!is_dir($cacheDir)) {
|
|
mkdir($cacheDir, 0755, true);
|
|
}
|
|
|
|
$file = $cacheDir . '/' . md5($identifier) . '.json';
|
|
|
|
$data = file_exists($file) ? json_decode(file_get_contents($file), true) : [];
|
|
|
|
// Clean old entries
|
|
$now = time();
|
|
$data = array_filter($data, function($timestamp) use ($now, $timeWindow) {
|
|
return ($now - $timestamp) < $timeWindow;
|
|
});
|
|
|
|
// Check if limit exceeded
|
|
if (count($data) >= $maxRequests) {
|
|
return false;
|
|
}
|
|
|
|
// Add new request
|
|
$data[] = $now;
|
|
file_put_contents($file, json_encode($data));
|
|
|
|
return true;
|
|
}
|
|
|
|
// In search.php:
|
|
$userIP = $_SERVER['REMOTE_ADDR'];
|
|
if (!checkRateLimit($userIP, 20, 60)) { // 20 requests per minute
|
|
http_response_code(429);
|
|
die('Too many requests. Please try again later.');
|
|
}
|
|
```
|
|
|
|
### Priority 3: Add Content Security Policy (LOW)
|
|
|
|
Add to header:
|
|
```php
|
|
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' cdn.jsdelivr.net;");
|
|
header("X-Content-Type-Options: nosniff");
|
|
header("X-Frame-Options: DENY");
|
|
header("X-XSS-Protection: 1; mode=block");
|
|
```
|
|
|
|
### Priority 4: Add Query Logging (LOW)
|
|
|
|
Log suspicious search patterns:
|
|
```php
|
|
// Detect potential attacks
|
|
if (preg_match('/[%_]{10,}/', $params['query'])) {
|
|
error_log("Suspicious search pattern from {$_SERVER['REMOTE_ADDR']}: {$params['query']}");
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Security Best Practices Checklist
|
|
|
|
- [x] Use prepared statements (SQL injection)
|
|
- [x] Escape output with htmlspecialchars() (XSS)
|
|
- [ ] Escape LIKE wildcards (wildcard injection)
|
|
- [ ] Validate input lengths (DoS)
|
|
- [ ] Implement rate limiting (DoS)
|
|
- [ ] Validate pagination limits (resource waste)
|
|
- [x] Restrict to published data only (access control)
|
|
- [ ] Add security headers (defense in depth)
|
|
- [ ] Log suspicious activity (monitoring)
|
|
- [ ] Use HTTPS in production (encryption)
|
|
|
|
---
|
|
|
|
## Testing Security
|
|
|
|
### Test 1: SQL Injection
|
|
```bash
|
|
# These should NOT cause errors or expose data
|
|
curl "search.php?query=' OR 1=1--"
|
|
curl "search.php?query='; DROP TABLE theses;--"
|
|
curl "search.php?year=' OR '1'='1"
|
|
```
|
|
**Expected:** Treated as literal search strings, no SQL execution
|
|
|
|
### Test 2: XSS
|
|
```bash
|
|
curl "search.php?query=<script>alert('XSS')</script>"
|
|
```
|
|
**Expected:** Script tags displayed as text, not executed
|
|
|
|
### Test 3: Wildcard Injection
|
|
```bash
|
|
curl "search.php?query=%"
|
|
```
|
|
**Current:** Returns all results ❌
|
|
**After fix:** Searches for literal "%" character ✅
|
|
|
|
### Test 4: DoS via Long Input
|
|
```bash
|
|
curl "search.php?query=$(python3 -c 'print("a"*100000)')"
|
|
```
|
|
**Current:** Processes full string ❌
|
|
**After fix:** Rejects with error ✅
|
|
|
|
---
|
|
|
|
## Conclusion
|
|
|
|
**Current Status:** The search system has **good baseline security** against SQL injection and XSS, but needs hardening for production use.
|
|
|
|
**Recommended Actions:**
|
|
1. Apply wildcard escaping (use `Database_secure.php`)
|
|
2. Add input length validation
|
|
3. Implement rate limiting
|
|
4. Add security headers
|
|
5. Monitor for suspicious patterns
|
|
|
|
**Risk Level:**
|
|
- Current: **Medium** (suitable for internal/development use)
|
|
- After improvements: **Low** (production-ready)
|