실행 방법입니다. 외부 jar 는 필요 없습니다.
cd java-src\extension\20_web_security
javac -encoding UTF-8 *.java
java Main --demoGuard — 이스케이프·경로 검증·매직 바이트·화이트리스트// 경로 탐색: 정규화 후 기준 폴더 밖이면 예외
public static Path safePath(Path baseDir, String name) {
Path base = baseDir.normalize().toAbsolutePath();
Path target = base.resolve(name).normalize().toAbsolutePath();
if (!target.startsWith(base)) {
throw new SecurityException("경로 탐색 시도: " + name);
}
return target;
}
// 업로드 검증: 확장자와 매직 바이트가 일치해야 통과
public static boolean checkMagic(byte[] bytes, String ext) {
if (!ALLOWED_EXT.contains(ext.toLowerCase())) return false;
return switch (ext.toLowerCase()) {
case "png" -> startsWith(bytes, 0x89, 0x50, 0x4E, 0x47);
case "pdf" -> startsWith(bytes, 0x25, 0x50, 0x44, 0x46);
case "xlsx" -> startsWith(bytes, 0x50, 0x4B, 0x03, 0x04);
default -> false;
};
}Main — IDOR 방어 핸들러// 6. IDOR: 남의 주문을 user 파라미터로 조회하면 403
static void handleOrders(HttpExchange ex) throws IOException {
Guard.secureHeaders(ex);
ex.getResponseHeaders().set("Cache-Control", "no-store");
String id = path.substring(path.lastIndexOf('/') + 1);
String owner = ORDER_OWNER.get(id);
if (!owner.equals(queryParam(ex, "user"))) {
respond(ex, 403, "text/plain; charset=UTF-8", "다른 사용자의 주문입니다");
return;
}
respond(ex, 200, "text/plain; charset=UTF-8", "주문 " + id + " 소유자: " + owner);
}java Main --demo 는 서버를 띄우고 공격 6가지와 정상 요청 1개를 실행한 뒤 스스로 종료합니다.
[XSS 시도] status=200 body=<html>...<script>alert(1)</script>...
[경로 탐색 시도] status=403 body=경로 탐색 차단
[IDOR 시도(남의 주문)] status=403 body=다른 사용자의 주문입니다
[위조 업로드] status=400 body=매직 바이트 불일치, 업로드 거부
[정렬 컬럼 인젝션 시도] status=400 body=허용되지 않은 정렬 컬럼
[정상 요청] status=200 body=주문 1001 소유자: kim
[응답 헤더] X-Content-Type-Options=nosniff
[응답 헤더] Content-Security-Policy=default-src 'self'