В моей предыдущей статье я показал вам, как защитить свой сервер от атак и вредоносного программного обеспечения. Эта часть будет полностью сосредоточена на третьем уровне безопасности — самом приложении. Итак, здесь я покажу вам методы, которые вы можете использовать для защиты своего приложения от атак и вторжений.
Использование базы данных
При связи с базой данных и для обеспечения безопасности ваших данных, помните о следующих ключевых моментах:
Всегда избегай запросов
Чтобы запретить злоумышленникам использовать SQL-инъекцию , необходимо скрыть ввод всех пользователей, чтобы они не могли внедрять SQL-запросы в ваше приложение (например, во время входа в систему). Практически во всех драйверах базы данных для всех языков есть возможность избежать пользовательского ввода. Например, в node-mysql для Node.js, вместо выполнения ваших запросов следующим образом:
1
|
connection.query(‘SELECT * FROM users WHERE name = \»+ username +’\’ AND password = \»+ password ‘\’;’, function (err, rows, fields) {
|
Вы можете автоматически избежать их, используя этот синтаксис:
1
|
connection.query(‘SELECT * FROM users WHERE name = ? AND password = ?;’, [ username, password ], function (err, rows, fields) {
|
Переменные из массива, предоставленные в качестве второго аргумента метода query
будут вставлены вместо знаков вопроса в строке запроса и будут автоматически экранированы. То же самое можно сделать в PHP, используя подготовленные операторы PDO:
1
2
3
4
5
|
$stmt = $dbh->prepare(«SELECT * FROM users WHERE name = ? AND password = ?;»);
$stmt->bindParam(1, $username);
$stmt->bindParam(2, $password);
$stmt->execute();
|
Никогда не доверяйте контенту из базы данных
Даже если вы избегаете запросов к базе данных, вы никогда не узнаете, пропустила ли ошибка в вашем приложении какой-либо вредоносный код. Если вы написали свое приложение так, что вы всегда предполагаете, что содержимое в базе данных безопасно, то ваши пользователи подвергаются опасности. Например, этот код:
1
2
3
4
5
6
7
|
if ($result = $mysqli->query(‘SELECT * FROM users WHERE name = «‘. $mysqli->escape_string($username) .’»;’)) {
if ($row = $result->fetch_assoc()) {
if ($result = $mysqli->query(‘SELECT * FROM someData WHERE uid = ‘. $row[‘id’] .’;’)) {
…
}
}
}
|
Небезопасно! Если что-то пойдет не так, и идентификатор пользователя содержит некоторый код SQL-инъекции, у вас будут проблемы. $row['id']
также следует экранировать с помощью escape_string()
, например:
1
2
3
4
5
6
7
|
if ($result = $mysqli->query(‘SELECT * FROM users WHERE name = «‘. $mysqli->escape_string($username) .’»;’)) {
if ($row = $result->fetch_assoc()) {
if ($result = $mysqli->query(‘SELECT * FROM someData WHERE uid = ‘. $mysqli->escape_string($row[‘id’]) .’;’)) {
…
}
}
}
|
Используйте соль при хешировании
А теперь представьте себе такую ситуацию: некоторые гениальные хакеры взломали вашу тщательно созданную систему безопасности и взялись за вашу базу данных. Затем они начинают перебор всех паролей ваших пользователей. Если вы использовали соль при хешировании, вы можете спокойно спать и уверять своих пользователей в безопасности своих данных. Потому что, если у злоумышленника нет квантового компьютера в подвале, ему понадобятся годы, чтобы взломать любой из паролей.
Использование соли означает, что вы добавляете несколько случайных символов к паролю перед хэшированием (эти символы называются солью) и сохраняете их с паролем. Вы можете подумать, что это не очень безопасно, поскольку злоумышленник узнает соль, если получит доступ к базе данных. Но так как соль различна для каждого пользователя, даже если два из них используют один и тот же пароль, их хэши будут разными. Это заставляет атакующего взломать их один за другим, что отнимает много времени и обычно не стоит потраченного времени. Это также означает, что злоумышленник не может использовать радужные таблицы или словарь для поиска паролей.
Создание пароля в Node.js: node-mysql
Сначала вы должны генерировать соль. Например, вы можете использовать crypto.randomBytes()
:
1
|
var crypto = require(‘crypto’);
|
1
2
3
4
5
6
|
/*
this should be done on request with the user’s data for registration,
instead of just adding it to the database
*/
crypto.randomBytes(16, function (e, salt) {
|
Эта функция выдаст ошибку, если, согласно документации Node.js , «энтропии недостаточно для генерации криптографически стойких данных». В таком случае вам следует либо повторить попытку, либо использовать crypto.pseudoRandomBytes()
, который генерирует не криптографически сильные случайные байты, но, поскольку это будет происходить только изредка, мы можем использовать его:
1
2
3
|
if (e) {
salt = crypto.pseudoRandomBytes(16);
}
|
Теперь давайте добавим данные. Для простоты я буду использовать только имя пользователя, пароль и соль в этом примере:
1
2
3
4
5
6
|
/* password and username should be extracted from request body */
var hash = crypto.createHash(‘sha-256’).update(password + salt.toString()).digest(‘hex’);
sql.query(‘INSERT INTO users VALUES(?, ?, ?);’, [ username, password, salt ], function (err, rows, fields) {
/* your other app logic here, what happes after the user data is into the database */
});
});
|
Когда ваш пользователь хочет войти в систему, вы должны получить соль из базы данных, а затем проверить пароль:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
sql.query(‘SELECT salt FROM users WHERE name = ?;’, [ username ], function (err, rows, fields) {
if (rows.length < 1) {
/* the user does not exist */
} else {
var hash = crypto.createHash(‘sha-256’).update(password + rows[0].salt).digest(‘hex’);
sql.query(‘SELECT 1 FROM users WHERE name = ? AND password = ?;’, [ username, hash ], function (err, rows, fields) {
if (rows.length < 1) {
/* wrong password */
} else {
/* password ok */
}
});
}
});
|
И это в значительной степени так. Теперь давайте посмотрим, как мы можем сделать это с помощью PHP.
Соление пароля в PHP
В PHP это намного проще, потому что встроенные функции password_hash()
и password_verify()
автоматически используют соль, а возвращаемый хеш содержит ее, поэтому вам даже не нужен еще один столбец в базе данных (или поле в случае NoSQL) , Это будет выглядеть примерно так:
1
2
3
4
5
6
|
$stmt = $dbh->prepare(«INSERT INTO users VALUES (?, ?);»);
$stmt->bindParam(1, $username);
/* hash the password, the function adds random salt automatically */
$stmt->bindParam(2, password_hash($password));
$stmt->execute();
|
Чтобы проверить логин, вам просто нужно вызвать password_verify()
. Он автоматически получает соль из хэша, поэтому вам нужно только указать хеш из вашей базы данных и пароль в виде простого текста для сравнения. Необходимый код выглядит следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
$stmt = $dbh->prepare(«SELECT password FROM users WHERE name = ?;»);
$stmt->bindParam(1, $username);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row) {
/* check if the password matches */
if (password_verify($password, $row[‘password’])) {
/* password ok!
}
} else {
/* the user does not exist */
}
|
POSIX: отбрасывайте привилегии, когда они вам не нужны
Это один из лучших защитных механизмов, которые вы можете использовать для защиты своей машины и, следовательно, ваших пользователей. Вам нужны повышенные привилегии для пары вещей, в основном для прослушивания номеров портов ниже 1024 или работы с системными файлами. Но вам никогда не следует запускать какое-либо приложение от имени пользователя root, когда вам это не нужно, на случай, если злоумышленник обнаружит в вашем коде ошибку, позволяющую ему выполнять команды на вашем сервере, и если код выполняется как привилегированный пользователь, игра окончена. , Злоумышленник может делать все, что ему захочется, и, вероятно, это будет сделано до того, как вы заметите что-либо Вот почему вы должны отказаться от привилегий как можно быстрее. В Node.js это будет выглядеть так:
1
2
3
4
5
6
|
var app = http.createServer(…);
…
app.listen(80);
process.setuid(‘app_user’);
|
Функция process.setuid()
изменит идентификатор пользователя процесса на тот, который ему передан — это может быть либо числовой идентификатор, либо строка имени пользователя (во втором случае эта функция будет блокироваться при получении идентификатора пользователя). ). Пользователь должен быть непривилегированным и иметь доступ только к файлам приложения, чтобы ограничить риск предоставления злоумышленнику доступа ко всему другому на вашей машине.
Альтернативный способ: использование authbind
Некоторые люди говорят (например, в этом комментарии об удалении привилегий root ), что приведенное выше решение не идеально, и вместо этого они предпочитают использовать authbind
. Это ваше решение, какое вы выберете, и это в значительной степени зависит от ваших настроек. Но в любом случае, authbind
— это команда, которая была разработана для этой цели — она позволяет вашему приложению связываться с портами ниже 1024 без привилегий суперпользователя (так что она охватывает только этот сценарий). Чтобы использовать его, сначала создайте файл: /etc/authbind/byport/port
, где port — это номер /etc/authbind/byport/port
, к которому вы хотите привязаться, и сделайте его исполняемым пользователем, которого вы будете использовать для запуска вашего приложения. Затем переключитесь на своего пользователя и запустите приложение следующим образом:
1
|
authbind node yourapp.js
|
Или как это из корня:
1
|
su -c ‘authbind node yourapp.js’ youruser
|
Благодаря этому вы можете достичь той же конечной цели, что и в решении POSIX, просто используя вместо этого authbind
, если вы этого предпочитаете.
Заворачивать
Надеюсь, что вы выбрали новые методы для работы с конфиденциальными данными в этих двух статьях. В предыдущей статье мы начали с изучения того, как защитить наши данные на сервере, выбрав правильного поставщика сервера, обновив нашу ОС, защитив порты и используя антивирус. В этой статье мы завершили наше обсуждение разговорами о защите самого приложения за счет использования надлежащей защиты базы данных, защиты от перебора паролей и привилегий пользователя.
Не стесняйтесь поделиться своими собственными советами по безопасности в комментариях и спасибо за чтение.