38
0
0
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
Назад

Как гарантировать безопасность конфиденциальной информации

Время чтения 39 минут
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
38
0
0
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники

Привет. Меня зовут Николай Пискунов, я руководитель направления Big Data. 

При разработке и использовании программного обеспечения самое главное — защита данных. Соблюдение нескольких простых правил позволяет гарантировать безопасность конфиденциальной информации как в процессе работы над продуктом, так и после его запуска. На что обратить внимание — расскажу в этой статье. 

А если вы хотите более подробно разобраться в вопросах безопасной разработки, рекомендую пройти бесплатный курс Cloud DevSecOps, где я рассказываю и про другие важные детали.

Как гарантировать безопасность конфиденциальной информации

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-ключей или других секретов.
  • Если создаваемые пароли или сессии слишком просты и не содержат достаточной случайности, безопасность системы снижается, а риск взлома увеличивается.
  • При создании сообщений об ошибках и другой информации, связанной с безопасностью, защищайте конфиденциальную информацию. Используйте шифрование, ограничивайте объем передаваемых данных и применяйте политику доступа, чтобы защитить личные и конфиденциальные данные.
Комментарии0
Тоже интересно
Комментировать
Поделиться
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники