Use of Hard-coded Password — использование жестко закодированных паролей
Добавлять пароли или учетные данные прямо в код программы небезопасно — там их легко найти и взломать.
Следуйте безопасным практикам для хранения секретов и конфиденциальной информации. Вместо жестко закодированных паролей лучше использовать:
- переменные окружения: они хранят пароли в системе, а не в коде;
- управление секретами: специальные инструменты или облачные сервисы для безопасного хранения паролей.
Покажу на примере:
@Slf4j
@Component
public class CheckApiKeyFilter implements Filter {
private static final String API_KEY = "strong_api_key";
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String xApiKey = httpRequest.getHeader("x-api-key");
// Проверка наличия и корректности API-ключа
if (checkApiKey(xApiKey)) {
log.info("x-api-key: {}", xApiKey);
chain.doFilter(request, response);
} else {
// Отбрасывание ответа с кодом 403
httpResponse.sendError(403);
}
}
private boolean checkApiKey(String xApiKey) {
return xApiKey != null && xApiKey.equals(apiKey);
}
}
API_KEY прописан прямо в коде. Такая практика крайне небезопасна: если злоумышленник получит доступ к исходному коду или декомпилирует приложение, он сможет скомпрометировать этот ключ.
Как исправить?
Использовать Environment Variables, или переменные окружения. Для этого в Spring Boot есть properties-файл. Он содержит ключевую информацию, например пароли от баз данных, API-ключи и прочее, а сформировать его можно из данных в защищенном хранилище.
Примерно так может выглядеть содержимое properties-файла:
spring.application.name=demo
api.key=strong_api_key
spring.datasource.url=jdbc:postgresql://localhost:5432/<DB_NAME>
spring.datasource.username=<DB_USER>
spring.datasource.password=<DB_PASSWORD>
spring.jpa.hibernate.ddl-auto=create
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
Все ключевые переменные добавляются в этот файл на этапе сборки приложения. При разработке вы сможете использовать собственную БД и тестовые данные для подключения к ней. Такой подход позволяет избежать нарушения безопасности Use of Hard-coded Password.
Чтобы получить доступ к переменной, используйте аннотацию @Value:
@Slf4j
@Component
public class CheckApiKeyFilter implements Filter {
@Value("${api.key}")
private String apiKey;
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String xApiKey = httpRequest.getHeader("x-api-key");
// Проверка наличия и корректности API-ключа
if (checkApiKey(xApiKey)) {
log.info("x-api-key: {}", xApiKey);
chain.doFilter(request, response);
} else {
// Отбрасывание ответа с кодом 403
httpResponse.sendError(403);
}
}
private boolean checkApiKey(String xApiKey) {
return xApiKey != null && xApiKey.equals(apiKey);
}
}
Exposure of sensitive information — раскрытие конфиденциальной информации
В приложении не должно быть доступа к конфиденциальным данным без явного разрешения. Доступ к чувствительной информации нужно строго контролировать и подвергать тщательной проверке.
Как избежать раскрытия конфиденциальной информации:
- Ограничить доступ. Необходимо проводить регулярные проверки для уменьшения риска несанкционированного доступа.
- Авторизация и аутентификация. Дополнительные способы защиты данных, например двухфакторная аутентификация, помогут предотвратить несанкционированные действия.
- Защита на разных уровнях. Защита должна быть реализована не только на уровне приложения, но и для всех его составляющих — облачных ресурсов, баз данных, сетевого оборудования и т. д.
- Понимание данных. Важно знать, какие данные являются чувствительными и как их защищать. Это может включать классификацию данных по степени конфиденциальности и разработку стратегий безопасности.
- Регулярные аудиты. Необходимо проводить аудиты систем для обнаружения и устранения уязвимостей, связанных с раскрытием конфиденциальной информации.
Еще раз обратите внимание на пример выше. В классе CheckApiKeyFilter мы регистрируем имя пользователя, который успешно вошел в систему, с помощью оператора
‘
log.info(“x-api-key: {}“, xApiKey);’.
Регистрация конфиденциальной информации подобного рода может быть рискованной, поскольку файлы журналов могут быть доступны неавторизованным пользователям или храниться небезопасно.
Как исправить?
Избегайте логирования конфиденциальных данных, а если это необходимо, то маскируйте весь ключ или его часть.
@Slf4j
@Component
public class CheckApiKeyFilter implements Filter {
@Value("${api.key}")
private String apiKey;
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String xApiKey = httpRequest.getHeader("x-api-key");
// Проверка наличия и корректности API-ключа
if (checkApiKey(xApiKey)) {
log.info("x-api-key: {}", obfuscateApiKey());
chain.doFilter(request, response);
} else {
// Отбрасывание ответа с кодом 403
httpResponse.sendError(403);
}
}
private boolean checkApiKey(String xApiKey) {
return xApiKey != null && xApiKey.equals(apiKey);
}
private String obfuscateApiKey() {
return "*****";
}
}
Insertion of Sensitive Information Into Sent Data — вставка конфиденциальной информации в отправляемые данные
Это правило предписывает, как обрабатывать и передавать конфиденциальные данные. В DevSecOps для реализации этого правила используются различные инструменты и практики: шифрование, использование безопасных каналов связи (например, HTTPS), безопасное хранение секретов (например, с помощью Kubernetes или AWS Secrets Manager) и т. д.
Например, в приложении есть список клиентов. При регистрации возвращается открытый номер карты клиента, что нарушает безопасность данных.
Так выглядит обработчик запроса:
@GetMapping("/{id}")
public ResponseEntity<Client> getEmployee(@PathVariable String id){
return ResponseEntity.ok(employeeStorage.get(id));
}
А так — возвращаемый объект, который содержит конфиденциальные данные:
@Data
@Builder
@FieldDefaults(level = AccessLevel.PRIVATE)
public class Client {
String id;
String name;
String surname;
Integer age;
String address;
String phone;
String email;
String cardNumber;
}
Класс содержит критическое поле ‘cardNumber’, а также Ф. И. О., номер телефона, адрес и другие чувствительные данные.
Как исправить?
Ф. И. О., номер телефона и адрес можно передавать в ответе, заменив на звездочки. Номер карты лучше обезличить не только в логгере, но и в ответе.
Обработчик запроса должен выглядеть так:
@GetMapping("/{id}")
public ResponseEntity<Client> getEmployee(@PathVariable String id){
return ResponseEntity.ok(new Client(employeeStorage.get(id)));
}
А так — возвращаемый объект, который содержит конфиденциальные данные:
@Data
@Builder
@FieldDefaults(level = AccessLevel.PRIVATE)
public class Client {
String id;
String name;
String surname;
Integer age;
String address;
String phone;
String email;
String cardNumber;
public Client(Client c) {
String cNumber = c.cardNumber;
this.id = c.getId();
this.name = c.getName();
this.surname = c.getSurname();
this.age = c.getAge();
this.address = c.getAddress();
this.phone = c.getPhone();
this.email = c.getEmail();
this.cardNumber = "*****-****-****-" + cNumber.substring(cNumber.length() - 4, cNumber.length() - 1);
}
}
Insufficient Entropy — недостаточная энтропия
Если создаваемые пароли или сессии слишком простые, недостаточно случайные, это снижает безопасность системы и увеличивает риск взлома.
Ниже пример недостаточной энтропии: в коде есть слабый пароль strong_api_key, который легко взломать:
spring.application.name=demo
api.key=strong_api_key
spring.datasource.url=jdbc:postgresql://localhost:5432/<DB_NAME>
spring.datasource.username=<DB_USER>
spring.datasource.password=<DB_PASSWORD>
spring.jpa.hibernate.ddl-auto=create
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
Как исправить?
Используйте для генерации паролей сложные алгоритмы, которые создают комбинации букв, цифр и специальных символов. Такие пароли будут надежными и трудными для взлома, что значительно обезопасит вашу систему.
Хороший пароль содержит большие и маленькие буквы, цифры и специальные символы и может выглядеть так: ‘$troNg-aP1-K3y-123!’. Еще лучше использовать пароли вроде “Tk0ub4d3!”, где нет понятных слов.
spring.application.name=demo
api.key=Tk0ub4d3!
spring.datasource.url=jdbc:postgresql://localhost:5432/<DB_NAME>
spring.datasource.username=<DB_USER>
spring.datasource.password=<DB_PASSWORD>
spring.jpa.hibernate.ddl-auto=create
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
В DevSecOps недостаточная энтропия также подразумевает использование безопасных методов хранения паролей — шифрования и хранения с помощью криптографических ключей. Это помогает минимизировать риск утечек конфиденциальной информации.
При сохранении паролей в базе данных обязательно обфусцируйте их. В Spring Security для этого есть метод ‘PasswordEncoder’, который используется для обработки пароля перед его сохранением.
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
private PasswordEncoder passwordEncoder;
public void saveUser(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
// ... save the user
}
Generation of Error Message Containing Sensitive Information — генерация сообщения об ошибке с конфиденциальной информацией
Создавая сообщения об ошибках и другой информации, связанной с безопасностью, используйте шифрование, ограничивайте объем передаваемых данных и применяйте политику доступа.
Пример плохого сообщения об ошибке:
@Service
public class MyUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public MyUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found with username: " + user.toString());
}
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.roles(user.getRoles())
.build();
}
}
Сообщение “User not found with: “ + user.toString() выведет в журнал данные о пользователе, которые лучше обезличить.
Как исправить?
@Service
public class MyUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public MyUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found with username: " + username);
}
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.roles(user.getRoles())
.build();
}
}
Подведем итоги
- В DevSecOps безопасность — ключевой элемент разработки ПО.
- Применение правил безопасности помогает снизить риски и предотвратить утечки данных на всех этапах.
- Не забывайте о распространенных ошибках, которые снижают уровень информационной безопасности.
- Пароли или учетные данные, включенные прямо в код программы, легко найти и взломать, поэтому их лучше не использовать.
- В приложении не должно быть доступа к конфиденциальной информации без явного разрешения.
- Используйте надежные способы хранения и передачи паролей, API-ключей или других секретов.
- Если создаваемые пароли или сессии слишком просты и не содержат достаточной случайности, безопасность системы снижается, а риск взлома увеличивается.
- При создании сообщений об ошибках и другой информации, связанной с безопасностью, защищайте конфиденциальную информацию. Используйте шифрование, ограничивайте объем передаваемых данных и применяйте политику доступа, чтобы защитить личные и конфиденциальные данные.