보안에서의 인젝션 공격에는 어떤 것들이 있나요?

김상욱·2024년 12월 31일
0

보안에서의 인젝션 공격에는 어떤 것들이 있나요?

인젝션 공격은 공격자가 애플리케이션에 악의적인 코드를 삽입하여 의도하지 않은 동작을 유발하는 공격 방식입니다.

SQL Injection

데이터베이스와 상호작용할 때, 사용자 입력을 제대로 검증하지 않으면 공격자가 악의적인 SQL 코드를 삽입할 수 있습니다. 이를 통해 데이터베이스의 데이터를 탈취하거나 조작할 수 있습니다.

// 취약한 코드 예시
String query = "SELECT * FROM users WHERE username = '" + userInput + "' AND password = '" + password + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query);

방어 방법

  • Prepared Statements(준비된 문장) 사용
  • ORM 프레임워크 활용 (예: Hibernate)
  • 입력값 검증 및 정제

명령어 인젝션 (Command Injection)

애플리케이션이 시스템 명령어를 실행할 때, 사용자 입력을 통해 악의적인 명령어를 삽입하는 공격입니다. 이를 통해 서버의 제어를 탈취할 수 있습니다.

// 취약한 코드 예시
String command = "ping " + userInput;
Runtime.getRuntime().exec(command);

사용자가 userInput에 ; rm -rf /와 같은 명령어를 입력할 수 있습니다.
방어 방법

  • 외부 명령어 실행을 최소화
  • 입력값 철저히 검증
  • Whitelist 사용

LDAT 인젝션 (LDAP Injection)

LDAP 쿼리를 사용하는 애플리케이션에서 사용자 입력을 통해 악의적인 LDAP 코드를 삽입하는 공격입니다. 이를 통해 디렉토리 정보를 탈취하거나 조작할 수 있습니다.

// 취약한 코드 예시
String filter = "(uid=" + userInput + ")";
DirContext ctx = new InitialDirContext(env);
ctx.search("ou=users,dc=example,dc=com", filter, searchControls);

방어 방법

  • LDAP 라이브러리의 안전한 API 사용
  • 입력값 이스케이프 처리

NoSQL 인젝션

NoSQL 데이터베이스를 사용하는 애플리케이션에서 사용자 입력을 통해 쿼리 구조를 변조하는 공격입니다. MongoDB, Cassandra 등이 대상이 될 수 있습니다.

// 취약한 코드 예시 (MongoDB)
BasicDBObject query = new BasicDBObject("username", userInput);
collection.find(query);

userInput에 JSON 구조를 삽입하여 쿼리를 변경할 수 있습니다.
방어 방법

  • ORM 또는 ODM 사용
  • 입력값 철저히 검증

코드 인젝션 (Code Injection)

애플리케이션이 사용자 입력을 통해 코드를 실행할 수 있는 경우, 악의적인 코드를 삽입하여 실행시키는 공격입니다.

// 취약한 코드 예시
String script = userInput;
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
engine.eval(script);

방어 방법

  • 사용자 입력을 코드로 실행하지 않기
  • Sandboxing 기술 사용

템플릿 인젝션(Template Injection)

템플릿 엔진을 사용하는 애플리케이션에서 사용자 입력을 통해 템플릿 코드를 삽입하여 실행시키는 공격입니다.

// 취약한 코드 예시 (Thymeleaf)
String template = "Hello, " + userInput;
model.addAttribute("message", template);

사용자가 템플릿 언어의 구문을 삽입할 수 있습니다.
방어 방법

  • 템플릿 엔진의 자동 이스케이프 기능 사용
  • 사용자 입력을 데이터로 처리하고, 코드로 실행하지 않기

취업 준비 중인 신입 Java/Spring 백엔드 개발자라면, 이론을 이해하는 것뿐만 아니라 실습을 통해 보안 개념을 직접 체험해보는 것이 매우 중요합니다. 아래에 제시하는 실습 과제들은 인젝션 공격의 다양한 유형을 직접 구현하고 방어 방법을 적용해보면서 실력을 키울 수 있도록 도와줄 것입니다.

1. SQL 인젝션 실습

목표: SQL 인젝션의 취약점을 이해하고, 이를 방어하는 방법을 학습합니다.

실습 단계:

  1. 취약한 애플리케이션 구현:

    • Spring Boot 프로젝트를 생성합니다.

    • 간단한 사용자 로그인 기능을 구현합니다.

    • JDBC를 사용하여 사용자 입력을 통해 SQL 쿼리를 생성합니다.

    • 예시 코드:

      @RestController
      public class UserController {
          @Autowired
          private DataSource dataSource;
      
          @GetMapping("/login")
          public String login(@RequestParam String username, @RequestParam String password) throws SQLException {
              String query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
              try (Connection conn = dataSource.getConnection();
                   Statement stmt = conn.createStatement();
                   ResultSet rs = stmt.executeQuery(query)) {
                  if (rs.next()) {
                      return "Login successful!";
                  } else {
                      return "Invalid credentials.";
                  }
              }
          }
      }
    • 이 코드는 사용자 입력을 제대로 검증하지 않아 SQL 인젝션에 취약합니다.

  2. SQL 인젝션 공격 시도:

    • 브라우저에서 다음과 같은 URL을 통해 로그인 시도를 해봅니다:
      http://localhost:8080/login?username=admin'--&password=anything
    • '--는 SQL 쿼리를 주석 처리하여 비밀번호 검증을 우회할 수 있습니다.
  3. 방어 방법 적용:

    • Prepared Statements를 사용하여 SQL 인젝션을 방어합니다.
    • 수정된 코드 예시:
      @GetMapping("/login")
      public String login(@RequestParam String username, @RequestParam String password) throws SQLException {
          String query = "SELECT * FROM users WHERE username = ? AND password = ?";
          try (Connection conn = dataSource.getConnection();
               PreparedStatement pstmt = conn.prepareStatement(query)) {
              pstmt.setString(1, username);
              pstmt.setString(2, password);
              try (ResultSet rs = pstmt.executeQuery()) {
                  if (rs.next()) {
                      return "Login successful!";
                  } else {
                      return "Invalid credentials.";
                  }
              }
          }
      }
  4. 검증:

    • 동일한 공격을 다시 시도해보고, 이제는 로그인 우회가 되지 않는 것을 확인합니다.

2. 명령어 인젝션 실습

목표: 명령어 인젝션의 취약점을 이해하고, 이를 방어하는 방법을 학습합니다.

실습 단계:

  1. 취약한 애플리케이션 구현:

    • Spring Boot 프로젝트에서 사용자로부터 입력을 받아 시스템 명령어를 실행하는 기능을 추가합니다.

    • 예시 코드:

      @RestController
      public class CommandController {
      
          @GetMapping("/ping")
          public String ping(@RequestParam String host) {
              String command = "ping -c 4 " + host;
              try {
                  Process process = Runtime.getRuntime().exec(command);
                  BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                  StringBuilder output = new StringBuilder();
                  String line;
                  while ((line = reader.readLine()) != null) {
                      output.append(line).append("\n");
                  }
                  process.waitFor();
                  return output.toString();
              } catch (Exception e) {
                  return "Error: " + e.getMessage();
              }
          }
      }
    • 이 코드는 host 파라미터에 악의적인 명령어를 삽입할 수 있어 명령어 인젝션에 취약합니다.

  2. 명령어 인젝션 공격 시도:

    • 다음과 같은 URL을 통해 공격을 시도합니다:
      http://localhost:8080/ping?host=google.com;rm -rf /
    • ;rm -rf /는 시스템 명령어를 추가로 실행하려는 시도입니다.
  3. 방어 방법 적용:

    • 화이트리스트 검증을 통해 허용된 호스트만 처리하도록 합니다.
    • 수정된 코드 예시:
      @GetMapping("/ping")
      public String ping(@RequestParam String host) {
          // 허용된 호스트 목록
          List<String> allowedHosts = Arrays.asList("google.com", "localhost", "example.com");
          if (!allowedHosts.contains(host)) {
              return "Invalid host.";
          }
          String command = "ping -c 4 " + host;
          try {
              Process process = Runtime.getRuntime().exec(command);
              BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
              StringBuilder output = new StringBuilder();
              String line;
              while ((line = reader.readLine()) != null) {
                  output.append(line).append("\n");
              }
              process.waitFor();
              return output.toString();
          } catch (Exception e) {
              return "Error: " + e.getMessage();
          }
      }
  4. 검증:

    • 공격 시도를 다시 해보고, 이제는 허용되지 않은 호스트에 대한 명령어가 실행되지 않는 것을 확인합니다.

3. NoSQL 인젝션 실습

목표: NoSQL 인젝션의 취약점을 이해하고, 이를 방어하는 방법을 학습합니다.

실습 단계:

  1. 취약한 애플리케이션 구현:

    • Spring Boot와 MongoDB를 사용하여 간단한 사용자 검색 기능을 구현합니다.

    • 예시 코드:

      @RestController
      public class UserController {
      
          @Autowired
          private MongoTemplate mongoTemplate;
      
          @GetMapping("/search")
          public List<User> search(@RequestParam String username) {
              Query query = new Query();
              query.addCriteria(Criteria.where("username").is(username));
              return mongoTemplate.find(query, User.class);
          }
      }
    • 이 코드는 username 파라미터를 통해 MongoDB 쿼리를 생성하므로, JSON 구조를 삽입할 수 있는 경우 NoSQL 인젝션에 취약할 수 있습니다.

  2. NoSQL 인젝션 공격 시도:

    • 예를 들어, username 파라미터에 JSON 구조를 삽입하여 쿼리를 변경할 수 있습니다:
      http://localhost:8080/search?username[$ne]=
    • 이는 모든 사용자를 반환하게 만들 수 있습니다.
  3. 방어 방법 적용:

    • 입력값 검증Whitelist 사용을 통해 허용된 형식만 처리합니다.
    • 수정된 코드 예시:
      @GetMapping("/search")
      public List<User> search(@RequestParam String username) {
          if (!username.matches("^[a-zA-Z0-9_]{3,20}$")) {
              throw new IllegalArgumentException("Invalid username format.");
          }
          Query query = new Query();
          query.addCriteria(Criteria.where("username").is(username));
          return mongoTemplate.find(query, User.class);
      }
  4. 검증:

    • 공격 시도를 다시 해보고, 이제는 허용되지 않은 형식의 입력이 차단되는 것을 확인합니다.

4. 템플릿 인젝션 실습

목표: 템플릿 인젝션의 취약점을 이해하고, 이를 방어하는 방법을 학습합니다.

실습 단계:

  1. 취약한 애플리케이션 구현:

    • Spring Boot와 Thymeleaf를 사용하여 사용자 입력을 템플릿에 직접 삽입하는 기능을 구현합니다.

    • 예시 코드 (컨트롤러):

      @Controller
      public class TemplateController {
      
          @GetMapping("/greet")
          public String greet(@RequestParam String name, Model model) {
              model.addAttribute("message", "Hello, " + name);
              return "greet";
          }
      }
    • 예시 코드 (Thymeleaf 템플릿 greet.html):

      <!DOCTYPE html>
      <html xmlns:th="http://www.thymeleaf.org">
      <head>
          <title>Greet</title>
      </head>
      <body>
          <p th:text="${message}">Hello, User</p>
      </body>
      </html>
    • 이 코드는 name 파라미터를 제대로 이스케이프하지 않아 템플릿 인젝션에 취약할 수 있습니다.

  2. 템플릿 인젝션 공격 시도:

    • 예를 들어, name 파라미터에 Thymeleaf 표현식을 삽입할 수 있습니다:
      http://localhost:8080/greet?name=${7*7}
    • 만약 설정이 잘못되었다면, 49가 출력될 수 있습니다.
  3. 방어 방법 적용:

    • Thymeleaf의 자동 이스케이프 기능을 활용하거나, 사용자 입력을 철저히 검증합니다.
    • Thymeleaf는 기본적으로 HTML 이스케이프를 수행하므로, 추가 설정이 필요하지 않을 수 있습니다. 그러나 사용자 입력을 안전하게 처리하는 습관을 가지는 것이 중요합니다.
    • 추가적으로, 사용자 입력을 특정 패턴으로 제한할 수 있습니다:
      @GetMapping("/greet")
      public String greet(@RequestParam String name, Model model) {
          if (!name.matches("^[a-zA-Z0-9_]{3,20}$")) {
              throw new IllegalArgumentException("Invalid name format.");
          }
          model.addAttribute("message", "Hello, " + name);
          return "greet";
      }
  4. 검증:

    • 동일한 공격을 다시 시도해보고, 이제는 템플릿 인젝션이 발생하지 않는 것을 확인합니다.

5. LDAP 인젝션 실습

목표: LDAP 인젝션의 취약점을 이해하고, 이를 방어하는 방법을 학습합니다.

실습 단계:

  1. 취약한 애플리케이션 구현:

    • Spring Boot와 LDAP를 사용하여 사용자 인증 기능을 구현합니다.

    • 예시 코드:

      @RestController
      public class LdapController {
      
          @Autowired
          private LdapTemplate ldapTemplate;
      
          @GetMapping("/ldapLogin")
          public String ldapLogin(@RequestParam String username, @RequestParam String password) {
              String filter = "(uid=" + username + ")";
              List<Person> persons = ldapTemplate.search("ou=users,dc=example,dc=com", filter, new PersonContextMapper());
              if (!persons.isEmpty()) {
                  // 패스워드 검증 로직 (간단히 예시)
                  return "Login successful!";
              } else {
                  return "Invalid credentials.";
              }
          }
      }
  2. LDAP 인젝션 공격 시도:

    • 예를 들어, username 파라미터에 LDAP 표현식을 삽입할 수 있습니다:
      http://localhost:8080/ldapLogin?username=*)(uid=*&password=anything
    • 이는 모든 사용자 계정을 인증하게 만들 수 있습니다.
  3. 방어 방법 적용:

    • LDAP 쿼리 파라미터화 또는 입력값 이스케이프를 적용합니다.
    • Spring Security LDAP를 사용하는 것이 권장됩니다.
    • 수정된 코드 예시:
      @GetMapping("/ldapLogin")
      public String ldapLogin(@RequestParam String username, @RequestParam String password) {
          if (!username.matches("^[a-zA-Z0-9_]{3,20}$")) {
              throw new IllegalArgumentException("Invalid username format.");
          }
          String filter = "(uid=" + LdapEncoder.filterEncode(username) + ")";
          List<Person> persons = ldapTemplate.search("ou=users,dc=example,dc=com", filter, new PersonContextMapper());
          if (!persons.isEmpty()) {
              // 패스워드 검증 로직 (간단히 예시)
              return "Login successful!";
          } else {
              return "Invalid credentials.";
          }
      }
    • LdapEncoder는 Spring LDAP에서 제공하는 필터 이스케이프 유틸리티입니다.
  4. 검증:

    • 공격 시도를 다시 해보고, 이제는 LDAP 인젝션이 발생하지 않는 것을 확인합니다.

6. 코드 인젝션 실습

목표: 코드 인젝션의 취약점을 이해하고, 이를 방어하는 방법을 학습합니다.

실습 단계:

Java에서는 코드 인젝션이 직접적으로 발생하기보다는 스크립팅 언어나 동적 코드 실행을 통해 발생할 수 있습니다. 하지만 Spring 애플리케이션에서는 주로 평가 함수나 동적 코드 실행을 피함으로써 방어할 수 있습니다.

  1. 취약한 애플리케이션 구현:

    • 사용자 입력을 받아 스크립트를 실행하는 기능을 구현합니다.

    • 예시 코드:

      @RestController
      public class ScriptController {
      
          @GetMapping("/execute")
          public String execute(@RequestParam String script) {
              ScriptEngineManager manager = new ScriptEngineManager();
              ScriptEngine engine = manager.getEngineByName("javascript");
              try {
                  Object result = engine.eval(script);
                  return result.toString();
              } catch (ScriptException e) {
                  return "Error: " + e.getMessage();
              }
          }
      }
    • 이 코드는 사용자 입력을 통해 JavaScript 코드를 실행할 수 있어 코드 인젝션에 취약합니다.

  2. 코드 인젝션 공격 시도:

    • 다음과 같은 URL을 통해 악의적인 스크립트를 실행할 수 있습니다:
      http://localhost:8080/execute?script=java.lang.Runtime.getRuntime().exec('calc')
    • 이는 서버에서 임의의 명령어를 실행하게 만들 수 있습니다.
  3. 방어 방법 적용:

    • 사용자 입력을 코드로 실행하지 않기.

    • 만약 꼭 필요하다면, 입력값을 철저히 검증하고 Sandbox 환경을 구축합니다.

    • 수정된 코드 예시:

      @RestController
      public class ScriptController {
      
          @GetMapping("/execute")
          public String execute(@RequestParam String script) {
              return "Code execution is disabled for security reasons.";
          }
      }
    • 또는, 스크립트 실행이 꼭 필요하다면, 제한된 명령어만 허용하도록 합니다.

  4. 검증:

    • 공격 시도를 다시 해보고, 이제는 코드가 실행되지 않는 것을 확인합니다.

추가 실습: 보안 도구 활용

OWASP ZAP 또는 Burp Suite 사용:

  • OWASP ZAP은 무료로 사용할 수 있는 웹 애플리케이션 보안 테스트 도구입니다.
  • 실습한 애플리케이션을 ZAP으로 스캔하여 취약점을 자동으로 찾아보고, 이를 수정해보는 과정을 통해 보안 테스트에 대한 이해를 높일 수 있습니다.

실습 단계:

  1. OWASP ZAP 설치 및 설정:

  2. 프록시 설정:

    • 브라우저의 프록시 설정을 ZAP이 제공하는 프록시 서버로 변경합니다 (기본적으로 localhost:8080).
  3. 애플리케이션 테스트:

    • 앞서 구현한 취약한 애플리케이션을 실행하고, 브라우저를 통해 다양한 입력을 시도합니다.
    • ZAP에서 자동 스캔을 실행하여 취약점을 탐지합니다.
  4. 취약점 수정 및 재검증:

    • ZAP이 발견한 취약점을 수정한 후, 다시 스캔하여 취약점이 해결되었는지 확인합니다.

결론

위의 실습 과제들을 통해 다양한 인젝션 공격 유형을 직접 구현하고 방어 방법을 적용해보면서 실전 감각을 키울 수 있습니다. 실습을 진행하면서 다음 사항들을 항상 염두에 두세요:

  • 입력 검증: 모든 사용자 입력을 철저히 검증하고, 예상된 형식과 길이를 확인하세요.
  • 보안 프레임워크 활용: Spring Security와 같은 보안 프레임워크를 적극 활용하여 보안성을 강화하세요.
  • 최소 권한 원칙: 애플리케이션이 데이터베이스나 시스템 명령어를 실행할 때 최소한의 권한만 부여하세요.
  • 지속적인 학습: 보안은 지속적으로 변화하는 분야이므로, 최신 보안 트렌드와 취약점에 대해 꾸준히 학습하세요.

이러한 실습을 통해 보안에 대한 이해를 깊이 있게 하고, 실제 프로젝트에서도 안전한 코드를 작성할 수 있는 능력을 키우시길 바랍니다. 화이팅입니다!

0개의 댓글