인젝션 공격은 공격자가 애플리케이션에 악의적인 코드를 삽입하여 의도하지 않은 동작을 유발하는 공격 방식입니다.
데이터베이스와 상호작용할 때, 사용자 입력을 제대로 검증하지 않으면 공격자가 악의적인 SQL 코드를 삽입할 수 있습니다. 이를 통해 데이터베이스의 데이터를 탈취하거나 조작할 수 있습니다.
// 취약한 코드 예시
String query = "SELECT * FROM users WHERE username = '" + userInput + "' AND password = '" + password + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query);
방어 방법
애플리케이션이 시스템 명령어를 실행할 때, 사용자 입력을 통해 악의적인 명령어를 삽입하는 공격입니다. 이를 통해 서버의 제어를 탈취할 수 있습니다.
// 취약한 코드 예시
String command = "ping " + userInput;
Runtime.getRuntime().exec(command);
사용자가 userInput에 ; rm -rf /와 같은 명령어를 입력할 수 있습니다.
방어 방법
LDAP 쿼리를 사용하는 애플리케이션에서 사용자 입력을 통해 악의적인 LDAP 코드를 삽입하는 공격입니다. 이를 통해 디렉토리 정보를 탈취하거나 조작할 수 있습니다.
// 취약한 코드 예시
String filter = "(uid=" + userInput + ")";
DirContext ctx = new InitialDirContext(env);
ctx.search("ou=users,dc=example,dc=com", filter, searchControls);
방어 방법
NoSQL 데이터베이스를 사용하는 애플리케이션에서 사용자 입력을 통해 쿼리 구조를 변조하는 공격입니다. MongoDB, Cassandra 등이 대상이 될 수 있습니다.
// 취약한 코드 예시 (MongoDB)
BasicDBObject query = new BasicDBObject("username", userInput);
collection.find(query);
userInput에 JSON 구조를 삽입하여 쿼리를 변경할 수 있습니다.
방어 방법
애플리케이션이 사용자 입력을 통해 코드를 실행할 수 있는 경우, 악의적인 코드를 삽입하여 실행시키는 공격입니다.
// 취약한 코드 예시
String script = userInput;
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
engine.eval(script);
방어 방법
템플릿 엔진을 사용하는 애플리케이션에서 사용자 입력을 통해 템플릿 코드를 삽입하여 실행시키는 공격입니다.
// 취약한 코드 예시 (Thymeleaf)
String template = "Hello, " + userInput;
model.addAttribute("message", template);
사용자가 템플릿 언어의 구문을 삽입할 수 있습니다.
방어 방법
취업 준비 중인 신입 Java/Spring 백엔드 개발자라면, 이론을 이해하는 것뿐만 아니라 실습을 통해 보안 개념을 직접 체험해보는 것이 매우 중요합니다. 아래에 제시하는 실습 과제들은 인젝션 공격의 다양한 유형을 직접 구현하고 방어 방법을 적용해보면서 실력을 키울 수 있도록 도와줄 것입니다.
목표: SQL 인젝션의 취약점을 이해하고, 이를 방어하는 방법을 학습합니다.
실습 단계:
취약한 애플리케이션 구현:
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 인젝션에 취약합니다.
SQL 인젝션 공격 시도:
http://localhost:8080/login?username=admin'--&password=anything'--는 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.";
}
}
}
}검증:
목표: 명령어 인젝션의 취약점을 이해하고, 이를 방어하는 방법을 학습합니다.
실습 단계:
취약한 애플리케이션 구현:
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 파라미터에 악의적인 명령어를 삽입할 수 있어 명령어 인젝션에 취약합니다.
명령어 인젝션 공격 시도:
http://localhost:8080/ping?host=google.com;rm -rf /;rm -rf /는 시스템 명령어를 추가로 실행하려는 시도입니다.방어 방법 적용:
@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();
}
}검증:
목표: NoSQL 인젝션의 취약점을 이해하고, 이를 방어하는 방법을 학습합니다.
실습 단계:
취약한 애플리케이션 구현:
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 인젝션에 취약할 수 있습니다.
NoSQL 인젝션 공격 시도:
username 파라미터에 JSON 구조를 삽입하여 쿼리를 변경할 수 있습니다:http://localhost:8080/search?username[$ne]=방어 방법 적용:
@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);
}검증:
목표: 템플릿 인젝션의 취약점을 이해하고, 이를 방어하는 방법을 학습합니다.
실습 단계:
취약한 애플리케이션 구현:
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 파라미터를 제대로 이스케이프하지 않아 템플릿 인젝션에 취약할 수 있습니다.
템플릿 인젝션 공격 시도:
name 파라미터에 Thymeleaf 표현식을 삽입할 수 있습니다:http://localhost:8080/greet?name=${7*7}49가 출력될 수 있습니다.방어 방법 적용:
@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";
}검증:
목표: LDAP 인젝션의 취약점을 이해하고, 이를 방어하는 방법을 학습합니다.
실습 단계:
취약한 애플리케이션 구현:
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.";
}
}
}
LDAP 인젝션 공격 시도:
username 파라미터에 LDAP 표현식을 삽입할 수 있습니다:http://localhost:8080/ldapLogin?username=*)(uid=*&password=anything방어 방법 적용:
@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에서 제공하는 필터 이스케이프 유틸리티입니다.검증:
목표: 코드 인젝션의 취약점을 이해하고, 이를 방어하는 방법을 학습합니다.
실습 단계:
Java에서는 코드 인젝션이 직접적으로 발생하기보다는 스크립팅 언어나 동적 코드 실행을 통해 발생할 수 있습니다. 하지만 Spring 애플리케이션에서는 주로 평가 함수나 동적 코드 실행을 피함으로써 방어할 수 있습니다.
취약한 애플리케이션 구현:
사용자 입력을 받아 스크립트를 실행하는 기능을 구현합니다.
예시 코드:
@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 코드를 실행할 수 있어 코드 인젝션에 취약합니다.
코드 인젝션 공격 시도:
http://localhost:8080/execute?script=java.lang.Runtime.getRuntime().exec('calc')방어 방법 적용:
사용자 입력을 코드로 실행하지 않기.
만약 꼭 필요하다면, 입력값을 철저히 검증하고 Sandbox 환경을 구축합니다.
수정된 코드 예시:
@RestController
public class ScriptController {
@GetMapping("/execute")
public String execute(@RequestParam String script) {
return "Code execution is disabled for security reasons.";
}
}
또는, 스크립트 실행이 꼭 필요하다면, 제한된 명령어만 허용하도록 합니다.
검증:
OWASP ZAP 또는 Burp Suite 사용:
실습 단계:
OWASP ZAP 설치 및 설정:
프록시 설정:
localhost:8080).애플리케이션 테스트:
취약점 수정 및 재검증:
위의 실습 과제들을 통해 다양한 인젝션 공격 유형을 직접 구현하고 방어 방법을 적용해보면서 실전 감각을 키울 수 있습니다. 실습을 진행하면서 다음 사항들을 항상 염두에 두세요:
이러한 실습을 통해 보안에 대한 이해를 깊이 있게 하고, 실제 프로젝트에서도 안전한 코드를 작성할 수 있는 능력을 키우시길 바랍니다. 화이팅입니다!