Ниже приведены практические примеры, как уязвимый код превращается в защищенный. Примеры даны для Node.js (JS/TS), Python, PHP и Go.
Защита от внедрения скриптов через пользовательский ввод. Основные меры: экранирование вывода, шаблонизаторы с автоэкранированием, CSP.
app.get('/search', (req, res) => {
const q = req.query.q || '';
// VULN: unescaped HTML
res.send(`Search: ${q}
`);
});const escape = require('escape-html');
app.get('/search', (req, res) => {
const q = req.query.q || '';
res.send(`Search: ${escape(q)}
`);
});app.get('/search', (req, res) => {
const q = String(req.query.q || '');
// VULN: unescaped HTML
res.send(`Search: ${q}
`);
});import escape from 'escape-html';
app.get('/search', (req, res) => {
const q = String(req.query.q || '');
res.send(`Search: ${escape(q)}
`);
});@app.get('/search')
def search():
q = request.args.get('q', '')
# VULN: unescaped HTML
return f"Search: {q}
"from markupsafe import escape
@app.get('/search')
def search():
q = request.args.get('q', '')
return f"Search: {escape(q)}
"$q = $_GET['q'] ?? '';
// VULN: unescaped HTML
echo "Search: $q
";$q = $_GET['q'] ?? '';
echo 'Search: ' . htmlspecialchars($q, ENT_QUOTES, 'UTF-8') . '
';q := r.URL.Query().Get("q")
// VULN: unescaped HTML
fmt.Fprintf(w, "Search: %s
", q)q := r.URL.Query().Get("q")
fmt.Fprintf(w, "Search: %s
", template.HTMLEscapeString(q))Защита от подмены SQL-запросов. Основные меры: параметризованные запросы, ORM, минимальные привилегии БД.
const email = req.body.email;
// VULN: string concatenation
db.query(`SELECT * FROM users WHERE email = '${email}'`);const email = req.body.email;
await db.execute('SELECT * FROM users WHERE email = ?', [email]);const email = String(req.body.email);
// VULN: string concatenation
await db.query(`SELECT * FROM users WHERE email = '${email}'`);const email = String(req.body.email);
await db.execute('SELECT * FROM users WHERE email = ?', [email]);email = request.form['email']
# VULN: string interpolation
cursor.execute(f"SELECT * FROM users WHERE email = '{email}'")email = request.form['email']
cursor.execute('SELECT * FROM users WHERE email = %s', (email,))$email = $_POST['email'];
// VULN: concatenation
mysqli_query($db, "SELECT * FROM users WHERE email = '$email'");$stmt = $db->prepare('SELECT * FROM users WHERE email = ?');
$stmt->bind_param('s', $email);
$stmt->execute();email := r.FormValue("email")
// VULN: concatenation
rows, _ := db.Query("SELECT * FROM users WHERE email='" + email + "'")email := r.FormValue("email")
rows, _ := db.Query("SELECT * FROM users WHERE email = ?", email)Защита от подделки запросов. Основные меры: CSRF-токены, SameSite cookies, проверка Origin/Referer.
app.post('/transfer', (req, res) => {
// VULN: no CSRF check
transfer(req.body);
res.send('ok');
});const csrf = require('csurf');
app.use(csrf());
app.post('/transfer', (req, res) => {
transfer(req.body);
res.send('ok');
});app.post('/transfer', (req, res) => {
// VULN: no CSRF check
transfer(req.body);
res.send('ok');
});import csrf from 'csurf';
app.use(csrf());
app.post('/transfer', (req, res) => {
transfer(req.body);
res.send('ok');
});@app.post('/transfer')
def transfer_funds():
# VULN: no CSRF check
do_transfer(request.form)
return 'ok'from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
@app.post('/transfer')
def transfer_funds():
do_transfer(request.form)
return 'ok'$amount = $_POST['amount'];
// VULN: no CSRF token check
transfer($amount);session_start();
if (!hash_equals($_SESSION['csrf'], $_POST['csrf'] ?? '')) {
http_response_code(403);
exit('CSRF');
}
transfer($_POST['amount']);func transfer(w http.ResponseWriter, r *http.Request) {
// VULN: no CSRF check
doTransfer(r.FormValue("amount"))
}csrf := csrf.Protect([]byte(csrfKey))
router.HandleFunc('/transfer', transfer)
// Middleware csrf(router)Защита от подмены HTTP-заголовков. Основные меры: удалять CR/LF, использовать allowlist.
const filename = req.query.name || '';
// VULN: header injection
res.set('X-File', filename);const filename = String(req.query.name || '').replace(/[
]/g, '');
res.set('X-File', filename);const filename = String(req.query.name || '');
// VULN: header injection
res.set('X-File', filename);const filename = String(req.query.name || '').replace(/[
]/g, '');
res.set('X-File', filename);name = request.args.get('name', '')
# VULN: header injection
resp.headers['X-File'] = namename = request.args.get('name', '')
name = name.replace('
', '').replace('
', '')
resp.headers['X-File'] = name$name = $_GET['name'] ?? '';
// VULN: header injection
header("X-File: $name");$name = str_replace(["
", "
"], '', $_GET['name'] ?? '');
header("X-File: $name");name := r.URL.Query().Get("name")
// VULN: header injection
w.Header().Set("X-File", name)name := strings.NewReplacer("
", "", "
", "").Replace(r.URL.Query().Get("name"))
w.Header().Set("X-File", name)Защита от доступа к файлам вне разрешенной директории. Основные меры: нормализация пути, проверка префикса, allowlist.
const file = req.query.file;
// VULN: path traversal
res.send(fs.readFileSync('uploads/' + file));const base = path.resolve('uploads');
const target = path.resolve(base, req.query.file || '');
if (!target.startsWith(base + path.sep)) return res.sendStatus(403);
res.sendFile(target);const file = String(req.query.file);
// VULN: path traversal
res.send(fs.readFileSync('uploads/' + file));const base = path.resolve('uploads');
const target = path.resolve(base, String(req.query.file || ''));
if (!target.startsWith(base + path.sep)) return res.sendStatus(403);
res.sendFile(target);name = request.args.get('file')
# VULN: path traversal
return open('uploads/' + name).read()base = os.path.abspath('uploads')
name = request.args.get('file', '')
target = os.path.abspath(os.path.join(base, name))
if not target.startswith(base + os.sep):
abort(403)
return open(target).read()$file = $_GET['file'] ?? '';
// VULN: path traversal
echo file_get_contents('uploads/' . $file);$base = realpath(__DIR__ . '/uploads');
$target = realpath($base . '/' . ($_GET['file'] ?? ''));
if ($target === false || strncmp($target, $base, strlen($base)) !== 0) {
http_response_code(403);
exit;
}
echo file_get_contents($target);file := r.URL.Query().Get("file")
// VULN: path traversal
b, _ := os.ReadFile("uploads/" + file)
w.Write(b)base, _ := filepath.Abs("uploads")
target, _ := filepath.Abs(filepath.Join(base, r.URL.Query().Get("file")))
if !strings.HasPrefix(target, base+string(os.PathSeparator)) {
http.Error(w, "forbidden", 403)
return
}
http.ServeFile(w, r, target)Защита от перехвата трафика. Основные меры: только HTTPS, проверка сертификатов, HSTS, TLS-минимум.
https.request({
hostname: 'api.example.com',
rejectUnauthorized: false // VULN
});https.request({
hostname: 'api.example.com',
rejectUnauthorized: true,
minVersion: 'TLSv1.2'
});
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');https.request({
hostname: 'api.example.com',
rejectUnauthorized: false // VULN
});https.request({
hostname: 'api.example.com',
rejectUnauthorized: true,
minVersion: 'TLSv1.2'
});
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');requests.get('https://api.example.com', verify=False) # VULNrequests.get('https://api.example.com', verify=True)
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'$ch = curl_init('https://api.example.com');
// VULN
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);$ch = curl_init('https://api.example.com');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} // VULN
client := &http.Client{Transport: tr}tr := &http.Transport{TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12}}
client := &http.Client{Transport: tr}
w.Header().Set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')Защита от подмены cookies. Основные меры: хранить права на сервере, подписывать cookies, использовать HttpOnly/Secure/SameSite.
// VULN: trusting client cookie
res.cookie('role', 'admin');
if (req.cookies.role === 'admin') grantAccess();// Server-side session
req.session.userId = user.id;
// Access check on server side// VULN: trusting client cookie
res.cookie('role', 'admin');
if (req.cookies.role === 'admin') grantAccess();// Server-side session
req.session.userId = user.id;
// Access check on server sideresp.set_cookie('role', 'admin') # VULN
if request.cookies.get('role') == 'admin':
grant_access()session['user_id'] = user.id
# Access check from server-side sessionsetcookie('role', 'admin'); // VULN
if (($_COOKIE['role'] ?? '') === 'admin') {
grant_access();
}$_SESSION['user_id'] = $userId;
// Проверка прав по данным из БДhttp.SetCookie(w, &http.Cookie{Name: 'role', Value: 'admin'}) // VULN
if c, _ := r.Cookie('role'); c.Value == 'admin' {
grantAccess()
}// Server-side session store
setSessionUser(w, r, user.ID)
// Access check on serverЗащита от перебора паролей. Основные меры: rate limit, задержки, блокировки, 2FA.
app.post('/login', (req, res) => {
// VULN: no rate limit
authenticate(req.body);
});const rateLimit = require('express-rate-limit');
app.use('/login', rateLimit({ windowMs: 60_000, max: 5 }));
app.post('/login', (req, res) => authenticate(req.body));app.post('/login', (req, res) => {
// VULN: no rate limit
authenticate(req.body);
});import rateLimit from 'express-rate-limit';
app.use('/login', rateLimit({ windowMs: 60_000, max: 5 }));
app.post('/login', (req, res) => authenticate(req.body));@app.post('/login')
def login():
# VULN: no rate limit
return auth(request.form)from flask_limiter import Limiter
limiter = Limiter(app, key_func=get_remote_address)
@app.post('/login')
@limiter.limit('5/minute')
def login():
return auth(request.form)// VULN: no rate limit
login($_POST);$key = 'login:' . $_SERVER['REMOTE_ADDR'];
$tries = (int)apcu_fetch($key);
if ($tries > 5) { http_response_code(429); exit; }
apcu_store($key, $tries + 1, 60);
login($_POST);func login(w http.ResponseWriter, r *http.Request) {
// VULN: no rate limit
auth(r)
}limiter := tollbooth.NewLimiter(5, &limiter.ExpirableOptions{DefaultExpirationTTL: time.Minute})
http.Handle('/login', tollbooth.LimitFuncHandler(limiter, login))Защита от обхода удаленных страниц. Основные меры: allowlist маршрутов, запрет на произвольные редиректы.
const next = req.query.next;
// VULN: open redirect
res.redirect(next);const allow = new Set(['/shop', '/profile']);
const next = req.query.next || '/';
res.redirect(allow.has(next) ? next : '/');const next = String(req.query.next);
// VULN: open redirect
res.redirect(next);const allow = new Set(['/shop', '/profile']);
const next = String(req.query.next || '/');
res.redirect(allow.has(next) ? next : '/');next_url = request.args.get('next')
# VULN: open redirect
return redirect(next_url)allow = {'/shop', '/profile'}
next_url = request.args.get('next', '/')
return redirect(next_url if next_url in allow else '/')$next = $_GET['next'] ?? '/';
// VULN: open redirect
header('Location: ' . $next);$allow = ['/shop', '/profile'];
$next = $_GET['next'] ?? '/';
header('Location: ' . (in_array($next, $allow, true) ? $next : '/'));next := r.URL.Query().Get("next")
// VULN: open redirect
http.Redirect(w, r, next, http.StatusFound)allow := map[string]bool{"/shop": true, "/profile": true}
next := r.URL.Query().Get("next")
if !allow[next] { next = "/" }
http.Redirect(w, r, next, http.StatusFound)Защита от скрытых админ-функций. Основные меры: удалять debug-флаги, отделять админ-панель, строгая авторизация.
// VULN: debug backdoor
if (req.query.debug === 'show_users') return res.json(users);// Remove debug backdoors
if (!req.user || !req.user.isAdmin) return res.sendStatus(403);// VULN: debug backdoor
if (req.query.debug === 'show_users') return res.json(users);if (!req.user?.isAdmin) return res.sendStatus(403);# VULN: debug backdoor
if request.args.get('debug') == 'show_users':
return jsonify(users)if not current_user.is_admin:
abort(403)// VULN: debug backdoor
if (($_GET['debug'] ?? '') === 'show_users') { echo json_encode($users); }if (!is_admin()) { http_response_code(403); exit; }if r.URL.Query().Get("debug") == "show_users" { // VULN
json.NewEncoder(w).Encode(users)
return
}if !isAdmin(r) { http.Error(w, "forbidden", 403); return }Защита от перегрузки: rate limit, очереди, кэширование, таймауты, CDN/WAF.
app.get('/expensive', (req, res) => {
// VULN: no limits
res.json(doHeavyWork());
});const rateLimit = require('express-rate-limit');
app.use('/expensive', rateLimit({ windowMs: 10_000, max: 20 }));
app.get('/expensive', (req, res) => res.json(doHeavyWork()));app.get('/expensive', (req, res) => {
// VULN: no limits
res.json(doHeavyWork());
});import rateLimit from 'express-rate-limit';
app.use('/expensive', rateLimit({ windowMs: 10_000, max: 20 }));
app.get('/expensive', (req, res) => res.json(doHeavyWork()));@app.get('/expensive')
def expensive():
# VULN: no limits
return jsonify(do_heavy_work())@app.get('/expensive')
@limiter.limit('20/10 seconds')
def expensive():
return jsonify(do_heavy_work())// VULN: no limits
echo json_encode(do_heavy_work());$key = 'exp:' . $_SERVER['REMOTE_ADDR'];
$cnt = (int)apcu_fetch($key);
if ($cnt > 20) { http_response_code(429); exit; }
apcu_store($key, $cnt + 1, 10);
echo json_encode(do_heavy_work());func expensive(w http.ResponseWriter, r *http.Request) {
// VULN: no limits
json.NewEncoder(w).Encode(doHeavyWork())
}limiter := tollbooth.NewLimiter(20, &limiter.ExpirableOptions{DefaultExpirationTTL: 10 * time.Second})
http.Handle('/expensive', tollbooth.LimitFuncHandler(limiter, expensive))Защита от уязвимостей логирования. Основные меры: обновление зависимостей, фильтрация опасных шаблонов, запрет lookups.
// VULN: logging raw headers
logger.info(`UA: ${req.headers['user-agent']}`);const ua = String(req.headers['user-agent'] || '').replace(/\$\{jndi:[^}]+\}/gi, '[redacted]');
logger.info({ ua });// VULN: logging raw headers
logger.info(`UA: ${req.headers['user-agent']}`);const ua = String(req.headers['user-agent'] || '').replace(/\$\{jndi:[^}]+\}/gi, '[redacted]');
logger.info({ ua });# VULN: logging raw headers
logger.info(f"UA: {request.headers.get('User-Agent')}")ua = (request.headers.get('User-Agent') or '').replace('${jndi:', '[redacted]')
logger.info('UA: %s', ua)// VULN: logging raw headers
error_log('UA: ' . ($_SERVER['HTTP_USER_AGENT'] ?? ''));$ua = str_replace('${jndi:', '[redacted]', $_SERVER['HTTP_USER_AGENT'] ?? '');
error_log('UA: ' . $ua);// VULN: logging raw headers
log.Printf("UA: %s", r.Header.Get("User-Agent"))ua := strings.ReplaceAll(r.Header.Get("User-Agent"), "${jndi:", "[redacted]")
log.Printf("UA: %s", ua)Защита от доступа к старым версиям страниц. Основные меры: удалять бэкапы из публичной зоны, возвращать 410, ограничивать доступ.
// VULN: public backups
app.use('/backup', express.static('backup'));app.use('/backup', (req, res) => res.sendStatus(410));// VULN: public backups
app.use('/backup', express.static('backup'));app.use('/backup', (req, res) => res.sendStatus(410));# VULN: public backups
app.send_static_file('backup/index.html')@app.get('/backup')
def backup():
abort(410)// VULN: public backups
readfile(__DIR__ . '/backup/index.html');http_response_code(410);
exit;// VULN: public backups
http.Handle('/backup/', http.StripPrefix('/backup/', http.FileServer(http.Dir('backup'))))http.HandleFunc('/backup/', func(w http.ResponseWriter, r *http.Request){
w.WriteHeader(410)
})Защита от выполнения команд. Основные меры: allowlist, execFile/args, запрет shell.
const host = req.query.host;
// VULN: command injection
exec(`ping -c 1 ${host}`);const host = String(req.query.host || '');
if (!/^[a-zA-Z0-9.-]+$/.test(host)) return res.sendStatus(400);
execFile('ping', ['-c', '1', host]);const host = String(req.query.host);
// VULN: command injection
exec(`ping -c 1 ${host}`);const host = String(req.query.host || '');
if (!/^[a-zA-Z0-9.-]+$/.test(host)) return res.sendStatus(400);
execFile('ping', ['-c', '1', host]);host = request.args.get('host')
# VULN: command injection
os.system(f"ping -c 1 {host}")host = request.args.get('host', '')
if not re.match(r'^[a-zA-Z0-9.-]+$', host):
abort(400)
subprocess.run(['ping', '-c', '1', host], check=True)$host = $_GET['host'] ?? '';
// VULN: command injection
exec('ping -c 1 ' . $host);$host = $_GET['host'] ?? '';
if (!preg_match('/^[a-zA-Z0-9.-]+$/', $host)) { http_response_code(400); exit; }
exec('ping -c 1 ' . escapeshellarg($host));host := r.URL.Query().Get("host")
// VULN: command injection
exec.Command("/bin/sh", "-c", "ping -c 1 " + host).Run()host := r.URL.Query().Get("host")
if !regexp.MustCompile(`^[a-zA-Z0-9.-]+$`).MatchString(host) {
http.Error(w, "bad request", 400); return
}
exec.Command("ping", "-c", "1", host).Run()