JavaRush /Java Blog /Random-KO /Java의 보안: 모범 사례
Roman Beekeeper
레벨 35

Java의 보안: 모범 사례

Random-KO 그룹에 게시되었습니다
서버 애플리케이션에서 가장 중요한 지표 중 하나는 보안입니다. 이는 비기능적 요구사항 유형 중 하나입니다 . Java의 보안: 모범 사례 - 1보안에는 많은 구성요소가 있습니다. 물론, 알려진 모든 보호 원칙과 조치를 완전히 다루려면 두 개 이상의 기사를 작성해야 하므로 가장 중요한 것에 집중하겠습니다. 이 주제에 정통한 사람은 모든 프로세스를 설정하고 새로운 보안 허점이 발생하지 않도록 보장할 수 있으며 모든 팀에 필요합니다. 물론, 이러한 관행을 따른다면 애플리케이션이 완전히 안전할 것이라고 생각해서는 안 됩니다. 아니요! 그러나 그들과 함께라면 확실히 더 안전할 것입니다. 가다.

1. Java 언어 수준에서 보안 제공

우선, Java의 보안은 언어 기능 수준에서 바로 시작됩니다. 접근 수정자가 없다면 이것이 우리가 할 일입니까?... 무정부 상태도 마찬가지입니다. 프로그래밍 언어는 보안 코드를 작성하는 데 도움이 되며 다양한 암시적 보안 기능도 활용합니다.
  1. 강력한 타이핑. Java 는 런타임 시 유형 오류를 감지하는 기능을 제공하는 정적으로 유형이 지정된 언어입니다.
  2. 액세스 수정자. 덕분에 우리는 필요한 방식으로 클래스, 메서드 및 클래스 필드에 대한 액세스를 구성할 수 있습니다.
  3. 자동 메모리 관리. 이를 위해 우리(Javaists;))는 수동 구성이 필요 없는 Garbage Collector를 제공합니다. 예, 때때로 문제가 발생합니다.
  4. 바이트코드 검사: Java는 바이트코드로 컴파일되며 이를 실행하기 전에 런타임 에 의해 검사됩니다 .
무엇보다도 보안에 대한 Oracle의 권장 사항이 있습니다 . 물론, "고급 스타일"로 쓰여진 것이 아니고 읽는 동안 여러 번 잠들 수도 있지만 그만한 가치가 있습니다. 특히 중요한 문서는 보안 코드 작성 방법에 대한 팁을 제공하는 Java SE용 보안 코딩 지침 입니다. 이 문서에는 유용한 정보가 많이 포함되어 있습니다. 가능하다면 읽어 볼 가치가 있습니다. 이 자료에 대한 관심을 불러일으키기 위해 다음과 같은 몇 가지 흥미로운 팁을 제시합니다.
  1. 보안에 민감한 클래스를 직렬화하지 마세요. 이 경우 직렬화되는 데이터는 물론 직렬화된 파일에서도 클래스 인터페이스를 얻을 수 있습니다.
  2. 변경 가능한 데이터 클래스를 피하십시오. 이는 불변 클래스의 모든 이점(예: 스레드 안전성)을 제공합니다. 변경 가능한 개체가 있으면 예기치 않은 동작이 발생할 수 있습니다.
  3. 반환된 변경 가능한 객체의 복사본을 만듭니다. 메서드가 내부 변경 가능 개체에 대한 참조를 반환하는 경우 클라이언트 코드는 개체의 내부 상태를 변경할 수 있습니다.
  4. 등등…
일반적으로 Java SE용 보안 코딩 지침에는 Java로 코드를 정확하고 안전하게 작성하는 방법에 대한 일련의 팁과 요령이 포함되어 있습니다.

2. SQL 주입 취약점 제거

독특한 취약점. 이 취약점의 독창성은 가장 유명하고 가장 일반적인 취약점 중 하나라는 사실에 있습니다. 보안 문제에 관심이 없다면 이에 대해 알지 못할 것입니다. SQL 주입이란 무엇입니까? 이는 예상하지 못한 곳에 추가 SQL 코드를 삽입하여 데이터베이스에 대한 공격입니다. 데이터베이스를 쿼리하기 위해 일부 매개변수를 사용하는 메서드가 있다고 가정해 보겠습니다. 예를 들어 사용자 이름입니다. 취약점이 있는 코드는 다음과 같습니다.
// Метод достает из базы данных всех пользователей с определенным именем
public List<User> findByFirstName(String firstName) throws SQLException {
   // Создается связь с базой данных
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);

   // Пишем sql request в базу данных с нашим firstName
   String query = "SELECT * FROM USERS WHERE firstName = " + firstName;

   // выполняем request
   Statement statement = connection.createStatement();
   ResultSet result = statement.executeQuery(query);

   // при помощи mapToUsers переводит ResultSet в коллекцию юзеров.
   return mapToUsers(result);
}

private List<User> mapToUsers(ResultSet resultSet) {
   //переводит в коллекцию юзеров
}
이 예에서는 SQL 쿼리가 별도의 줄에 미리 작성되어 있습니다. 문제가 무엇인 것 같죠? 어쩌면 문제는 사용하는 것이 더 낫다는 것입니까 String.format? 아니요? 그럼 어쩌지? 테스터의 입장에서 그 가치로 무엇을 전달할 수 있는지 생각해 봅시다 firstName. 예를 들어:
  1. 예상되는 사용자 이름을 전달할 수 있습니다. 그러면 데이터베이스는 해당 이름을 가진 모든 사용자를 반환합니다.
  2. 빈 문자열을 전달할 수 있습니다. 그러면 모든 사용자가 반환됩니다.
  3. 또는 다음을 전달할 수 있습니다: “''; 드롭 테이블 사용자;”. 그리고 여기에는 더 큰 문제가 있을 것입니다. 이 쿼리는 데이터베이스에서 테이블을 제거합니다. 모든 데이터가 포함되어 있습니다. 모든 사람.
이것이 어떤 문제를 일으킬 수 있는지 상상할 수 있습니까? 그러면 원하는 대로 쓰시면 됩니다. 모든 사용자의 이름을 변경할 수 있고 주소를 삭제할 수도 있습니다. 방해 행위의 범위는 광범위합니다. 이를 방지하려면 이미 만들어진 쿼리 삽입을 중단하고 대신 매개변수를 사용하여 쿼리를 빌드해야 합니다. 이것이 데이터베이스를 쿼리하는 유일한 방법이어야 합니다. 이렇게 하면 이 취약점을 제거할 수 있습니다. 예:
// Метод достает из базы данных всех пользователей с определенным именем
public List<User> findByFirstName(String firstName) throws SQLException {
   // Создается связь с базой данных
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);

   // Создаем параметризированный request.
   String query = "SELECT * FROM USERS WHERE firstName = ?";

   // Создаем подготовленный стейтмент с параметризованным requestом
   PreparedStatement statement = connection.prepareStatement(query);

   // Передаем meaning параметра
   statement.setString(1, firstName);

   // выполняем request
   ResultSet result = statement.executeQuery(query);

   // при помощи mapToUsers переводим ResultSet в коллекцию юзеров.
   return mapToUsers(result);
}

private List<User> mapToUsers(ResultSet resultSet) {
   //переводим в коллекцию юзеров
}
이렇게 하면 이 취약점을 피할 수 있습니다. 이 기사에 대해 더 자세히 알아보고 싶은 분들을 위해 여기 좋은 예가 있습니다 . 이 부분을 이해했는지 어떻게 알 수 있나요? 아래의 농담이 명확해졌다면 이는 취약점의 본질이 분명하다는 확실한 신호입니다. :D Java의 보안: 모범 사례 - 2

3. 종속성을 검색하고 업데이트된 상태로 유지

무슨 뜻이에요? 종속성이 무엇인지 모르는 사람들을 위해 설명하겠습니다. 다른 사람의 솔루션을 재사용하기 위해 자동 빌드 시스템(Maven, Gradle, Ant)을 사용하여 프로젝트에 연결된 코드가 포함된 jar 아카이브입니다. 예를 들어, 런타임 시 getter, setter 등을 생성하는 Project Lombok이 있습니다. 대규모 애플리케이션에 관해 이야기하면 다양한 종속성을 사용합니다. 일부는 전이적입니다. 즉, 각 종속성은 자체 종속성을 가질 수 있습니다. 따라서 공격자들은 오픈 소스 종속성에 점점 더 많은 관심을 기울이고 있습니다. 오픈 소스 종속성은 정기적으로 사용되고 많은 클라이언트에 문제를 일으킬 수 있기 때문입니다. 전체 종속성 트리에 알려진 취약점이 없는지 확인하는 것이 중요합니다(정확히 보이는 것과 같습니다). 이를 수행하는 방법에는 여러 가지가 있습니다.

모니터링에 Snyk 사용

Snyk 도구는 모든 프로젝트 종속성을 확인하고 알려진 취약점에 플래그를 지정합니다. 예를 들어 GitHub를 통해 프로젝트를 등록하고 가져올 수 있습니다. Java의 보안: 모범 사례 - 3또한 위 그림에서 볼 수 있듯이 최신 버전에 이 취약점에 대한 솔루션이 있는 경우 Snyk는 이를 제안하고 Pull-Request를 생성할 것입니다. 오픈 소스 프로젝트에 무료로 사용할 수 있습니다. 프로젝트는 일주일에 한 번, 한 달에 한 번 등 특정 빈도로 스캔됩니다. 나는 모든 공개 저장소를 Snyk 스캔에 등록하고 추가했습니다(이에 대해 위험한 것은 없습니다. 저장소는 이미 모든 사람에게 열려 있습니다). 다음으로 Snyk는 스캔 결과를 보여주었습니다. Java의 보안: 모범 사례 - 4그리고 잠시 후 Snyk-bot은 종속성을 업데이트해야 하는 프로젝트에서 여러 개의 풀 요청을 준비했습니다. Java의 보안: 모범 사례 - 5그리고 또 다른 것이 있습니다. Java의 보안: 모범 사례 - 6따라서 이것은 취약점을 검색하고 업데이트를 모니터링하는 데 탁월한 도구입니다. 새로운 버전.

GitHub 보안 랩 사용

GitHub에서 작업하는 사람들은 내장된 도구도 활용할 수 있습니다. 이 접근 방식에 대한 자세한 내용은 GitHub Security Lab 발표 블로그의 번역에서 읽을 수 있습니다 . 물론 이 도구는 Snyk보다 간단하지만 절대 무시해서는 안 됩니다. 또한 알려진 취약점의 수는 계속 늘어날 것이므로 Snyk와 GitHub Security Lab은 모두 확장되고 개선될 것입니다.

소나타입 DepShield 활성화

GitHub를 사용하여 리포지토리를 저장하는 경우 MarketPlace - Sonatype DepShield에서 프로젝트에 애플리케이션 중 하나를 추가할 수 있습니다. 도움을 받으면 프로젝트에서 종속성을 검색할 수도 있습니다. 또한, 무언가를 발견하면 아래와 같이 해당 설명과 함께 GitHub 이슈가 생성됩니다. Java의 보안: 모범 사례 - 7

4. 민감한 데이터를 주의해서 다루세요

Java의 보안: 모범 사례 - 8영어에서는 "민감한 데이터"라는 문구가 더 일반적입니다. 고객의 개인정보, 신용카드 번호, 기타 개인정보가 공개되면 회복할 수 없는 피해를 입을 수 있습니다. 우선, 애플리케이션 디자인을 자세히 살펴보고 실제로 필요한 데이터가 있는지 확인해야 합니다. 어쩌면 그들 중 일부는 필요하지 않을 수도 있지만 아직 오지 않았고 오지 않을 것 같은 미래를 위해 추가되었습니다. 또한 프로젝트 로깅 중에 이러한 데이터가 유출될 수 있습니다. 중요한 데이터가 로그에 들어가는 것을 방지하는 간단한 방법은 toString()도메인 엔터티(예: 사용자, 학생, 교사 등)의 메서드를 정리하는 것입니다. 이렇게 하면 민감한 필드가 실수로 인쇄되는 것을 방지할 수 있습니다. Lombok을 사용하여 메소드를 생성하는 경우 toString()주석을 사용하여 @ToString.Exclude해당 필드가 메소드를 통한 출력에 사용되는 것을 방지 할 수 있습니다 toString(). 또한 외부 세계와 데이터를 공유할 때는 각별히 주의하세요. 예를 들어 모든 사용자의 이름을 표시하는 http 엔드포인트가 있습니다. 사용자의 내부 고유 ID를 표시할 필요는 없습니다. 왜? 이를 사용하면 공격자는 각 사용자에 대한 더 많은 기밀 정보를 얻을 수 있습니다. 예를 들어 Jackson을 사용하여 POJO를 JSON 으로 직렬화 및 역직렬화하는 경우 @JsonIgnore및 주석을 사용하여 @JsonIgnoreProperties특정 필드가 직렬화 또는 역직렬화되는 것을 방지할 수 있습니다. 일반적으로 장소에 따라 다른 POJO 클래스를 사용해야 합니다. 무슨 뜻이에요?
  1. 데이터베이스 작업을 하려면 POJO - Entity만 사용하세요.
  2. 비즈니스 로직을 사용하려면 엔터티를 모델로 전송하세요.
  3. 외부 세계와 작업하고 http 요청을 보내려면 세 번째 엔터티인 DTO를 사용하세요.
이렇게 하면 외부에 표시되는 필드와 표시되지 않는 필드를 명확하게 정의할 수 있습니다.

강력한 암호화 및 해싱 알고리즘 사용

기밀 고객 데이터는 안전하게 저장되어야 합니다. 이렇게 하려면 암호화를 사용해야 합니다. 작업에 따라 사용할 암호화 유형을 결정해야 합니다. 또한 더 강력한 암호화에는 더 많은 시간이 걸리므로 이에 대한 필요성이 이에 소요되는 시간을 얼마나 정당화하는지 다시 한번 고려해야 합니다. 물론 알고리즘을 직접 작성할 수도 있습니다. 그러나 이것은 불필요합니다. 이 영역의 기존 솔루션을 활용할 수 있습니다. 예를 들어 Google Tink는 다음과 같습니다 .
<!-- https://mvnrepository.com/artifact/com.google.crypto.tink/tink -->
<dependency>
   <groupId>com.google.crypto.tink</groupId>
   <artifactId>tink</artifactId>
   <version>1.3.0</version>
</dependency>
한 방향과 다른 방향으로 암호화하는 방법의 예를 사용하여 사용 방법을 살펴보겠습니다.
private static void encryptDecryptExample() {
   AeadConfig.register();
   KeysetHandle handle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_CTR_HMAC_SHA256);

   String plaintext = "Цой жив!";
   String aad = "Юрий Клинских";

   Aead aead = handle.getPrimitive(Aead.class);
   byte[] encrypted = aead.encrypt(plaintext.getBytes(), aad.getBytes());
   String encryptedString = Base64.getEncoder().encodeToString(encrypted);
   System.out.println(encryptedString);

   byte[] decrypted = aead.decrypt(Base64.getDecoder().decode(encrypted), aad.getBytes());
   System.out.println(new String(decrypted));
}

비밀번호 암호화

이 작업에는 비대칭 암호화를 사용하는 것이 가장 안전합니다. 왜? 왜냐하면 애플리케이션은 실제로 비밀번호를 다시 해독할 필요가 없기 때문입니다. 이것이 일반적인 접근 방식입니다. 실제로 사용자가 비밀번호를 입력하면 시스템은 이를 암호화하여 비밀번호 저장소에 있는 것과 비교합니다. 암호화는 동일한 수단을 사용하여 수행되므로 일치할 것으로 예상할 수 있습니다(물론 올바른 비밀번호를 입력한 경우 ;). BCrypt와 SCrypt가 이러한 목적에 적합합니다. 둘 다 시간이 많이 걸리는 계산적으로 복잡한 알고리즘을 사용하는 단방향 함수(암호화 해시)입니다. 정면으로 해독하는 데 시간이 오래 걸리기 때문에 이것이 바로 당신에게 필요한 것입니다. 예를 들어 Spring Security는 다양한 알고리즘을 지원합니다. 을 사용할 SCryptPasswordEncoder수도 있습니다 BCryptPasswordEncoder. 현재 강력한 암호화 알고리즘은 내년에는 약해질 수 있습니다. 결과적으로, 사용된 알고리즘을 확인하고 알고리즘이 포함된 라이브러리를 업데이트하는 것이 필요하다는 결론을 내렸습니다.

출력 대신

오늘 우리는 안전에 대해 이야기했는데, 물론 뒤에는 많은 것들이 남아 있었습니다. 나는 방금 여러분에게 새로운 세상, 즉 자신만의 삶을 사는 세상의 문을 열었습니다. 안보는 정치와 동일합니다. 즉, 정치에 참여하지 않으면 정치가 참여하게 됩니다. 전통적으로 저는 Github 계정을 구독하는 것을 제안합니다 . 거기에는 제가 연구하고 직장에서 적용하는 다양한 기술에 대한 작업을 게시합니다.

유용한 링크

예, 사이트의 거의 모든 기사는 영어로 작성되었습니다. 우리가 원하든 원하지 않든 영어는 프로그래머가 의사소통하는 언어입니다. 프로그래밍에 관한 모든 최신 기사, 책, 잡지는 영어로 작성되었습니다. 이것이 바로 추천 링크가 대부분 영어로 되어 있는 이유입니다.
  1. Habr: 초보자를 위한 SQL 주입
  2. Oracle: Java 보안 리소스 센터
  3. Oracle: Java SE에 대한 보안 코딩 지침
  4. Baeldung: Java 보안의 기본
  5. 중간: Java 보안을 강화하는 10가지 팁
  6. Snyk: 10가지 Java 보안 모범 사례
  7. JR: GitHub Security Lab 발표: 모든 코드를 함께 보호
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION