Skip to main content

input-validation

Implement comprehensive input validation to prevent XSS, SQL injection, and command injection attacks with sanitization strategies

معلومات المصدر

المستودع
Hack23/cia
آخر نشاط في المصدر
١٠ فبراير ٢٠٢٦ في ٢٢:٣٥
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٢٤١
التفرعات
٥٦

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
input-validation
description
Implement comprehensive input validation to prevent XSS, SQL injection, and command injection attacks with sanitization strategies
license
Apache-2.0
# Input Validation Skill ## Purpose This skill provides systematic approaches to validate, sanitize, and encode all user inputs to prevent injection attacks, XSS, and data corruption. It implements defense-in-depth validation at multiple layers aligned with OWASP best practices and Hack23 ISMS secure coding standards. ## When to Use This Skill Apply this skill when: - ✅ Processing any user-provided input (forms, APIs, URL parameters) - ✅ Handling data from external systems (Riksdagen API, World Bank) - ✅ Constructing database queries or commands - ✅ Rendering user-generated content in UI - ✅ Processing file uploads - ✅ Implementing search functionality - ✅ Building dynamic SQL, LDAP, or OS commands ## Validation Principles ### Principle #1: Validate Early, Validate Often **Multi-Layer Validation:** ``` Client Side (JavaScript) → Basic UX validation ↓ Server Side (Controller) → Format and type validation ↓ Service Layer → Business logic validation ↓ Database Layer → Constraint enforcement ``` ### Principle #2: Allowlist Over Blocklist ❌ **INSECURE - Blocklist Approach:** ```java // BAD: Trying to block malicious patterns (incomplete, bypassable) public boolean isValidInput(String input) { String[] badPatterns = {"<script>", "javascript:", "onerror=", "' OR '1'='1"}; for (String pattern : badPatterns) { if (input.toLowerCase().contains(pattern)) { return false; } } return true; } ``` ✅ **SECURE - Allowlist Approach:** ```java // GOOD: Define what IS valid public boolean isValidPoliticianName(String name) { // Only allow letters, spaces, hyphens, and apostrophes return name != null && name.matches("^[A-Za-zÀ-ÿ\\s'-]{2,50}$") && name.length() >= 2 && name.length() <= 50; } public boolean isValidEmail(String email) { // Use standard email regex String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"; return email != null && email.matches(emailRegex); } public boolean isValidYear(Integer year) { // Define valid range int currentYear = Year.now().getValue(); return year != null && year >= 1900 && year <= currentYear; } ``` ### Principle #3: Fail Securely ```java @ControllerAdvice public class ValidationExceptionHandler { private static final Logger log = LoggerFactory.getLogger(ValidationExceptionHandler.class); @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ErrorResponse> handleValidationException( MethodArgumentNotValidException ex) { // Log detailed error for debugging (but don't expose to user) log.warn("Validation failed: {}", ex.getBindingResult().getAllErrors()); // Return generic error to user (don't leak validation logic) ErrorResponse error = new ErrorResponse( "Invalid input", "One or more fields contain invalid data" ); return ResponseEntity.badRequest().body(error); } @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity<ErrorResponse> handleConstraintViolation( ConstraintViolationException ex) { log.warn("Constraint violation: {}", ex.getMessage()); ErrorResponse error = new ErrorResponse( "Invalid input", "The provided data does not meet requirements" ); return ResponseEntity.badRequest().body(error); } } ``` ## Input Validation Patterns ### Pattern #1: Bean Validation (JSR-380) ```java // Domain model with validation constraints @Entity @Table(name = "politician") public class Politician { @Id @NotNull @Pattern(regexp = "^[0-9]{12}$", message = "Personal ID must be 12 digits") private String personalId; @NotBlank(message = "First name is required") @Size(min = 2, max = 50, message = "First name must be 2-50 characters") @Pattern(regexp = "^[A-Za-zÀ-ÿ\\s'-]+$", message = "First name contains invalid characters") private String firstName; @NotBlank(message = "Last name is required") @Size(min = 2, max = 50, message = "Last name must be 2-50 characters") @Pattern(regexp = "^[A-Za-zÀ-ÿ\\s'-]+$", message = "Last name contains invalid characters") private String lastName; @NotNull(message = "Birth date is required") @Past(message = "Birth date must be in the past") private LocalDate birthDate; @Email(message = "Invalid email format") @Pattern(regexp = "^[A-Za-z0-9+_.-]+@riksdagen\\.se$", message = "Only riksdagen.se emails allowed") private String officialEmail; @Pattern(regexp = "^\\+46[0-9]{9}$", message = "Invalid Swedish phone number") private String phoneNumber; @NotNull(message = "Party is required") @Enumerated(EnumType.STRING) private PoliticalParty party; } // DTO for API requests public class CreatePoliticianRequest { @NotBlank @Size(min = 2, max = 50) @Pattern(regexp = "^[A-Za-zÀ-ÿ\\s'-]+$") private String firstName; @NotBlank @Size(min = 2, max = 50) @Pattern(regexp = "^[A-Za-zÀ-ÿ\\s'-]+$") private String lastName; @NotNull @Past @JsonFormat(pattern = "yyyy-MM-dd") private LocalDate birthDate; @Email private String email; @Pattern(regexp = "^\\+46[0-9]{9}$") private String phoneNumber; @NotNull private String partyCode; // Getters and setters } // Controller with validation @RestController @RequestMapping("/api/politicians") @Validated public class PoliticianController { @PostMapping public ResponseEntity<Politician> createPolitician( @Valid @RequestBody CreatePoliticianRequest request) { // If validation passes, create politician Politician politician = politicianService.create(request); return ResponseEntity.status(HttpStatus.CREATED).body(politician); } @GetMapping("/{id}") public ResponseEntity<Politician> getPolitician( @PathVariable @Pattern(regexp = "^[0-9]{12}$", message = "Invalid politician ID") String id) { Politician politician = politicianService.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Politician not found")); return ResponseEntity.ok(politician); } } ``` ### Pattern #2: Custom Validators ```java // Custom annotation for Swedish Personal ID @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = SwedishPersonalIdValidator.class) public @interface ValidSwedishPersonalId { String message() default "Invalid Swedish personal ID"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } // Validator implementation with checksum public class SwedishPersonalIdValidator implements ConstraintValidator<ValidSwedishPersonalId, String> { @Override public boolean isValid(String personalId, ConstraintValidatorContext context) { if (personalId == null || personalId.length() != 12) { return false; } // Check format: YYYYMMDDNNNN if (!personalId.matches("^[0-9]{12}$")) { return false; } // Validate date part try { int year = Integer.parseInt(personalId.substring(0, 4)); int month = Integer.parseInt(personalId.substring(4, 6)); int day = Integer.parseInt(personalId.substring(6, 8)); LocalDate.of(year, month, day); } catch (DateTimeException e) { return false; } // Validate Luhn checksum return validateLuhnChecksum(personalId); } private boolean validateLuhnChecksum(String number) { int sum = 0; boolean alternate = false; for (int i = number.length() - 1; i >= 0; i--) { int digit = Character.getNumericValue(number.charAt(i)); if (alternate) { digit *= 2; if (digit > 9) { digit = (digit % 10) + 1; } } sum += digit; alternate = !alternate; } return (sum % 10 == 0); } } // Usage @Entity public class Politician { @Id @ValidSwedishPersonalId private String personalId; } ``` ### Pattern #3: Sanitization for XSS Prevention ```java @Component public class InputSanitizer { private final Policy antiSamyPolicy; public InputSanitizer() throws PolicyException { // Load OWASP AntiSamy policy this.antiSamyPolicy = Policy.getInstance( getClass().getResourceAsStream("/antisamy-policy.xml") ); } /** * Sanitize HTML input to prevent XSS */ public String sanitizeHtml(String input) { if (input == null) return null; try { AntiSamy antiSamy = new AntiSamy(); CleanResults results = antiSamy.scan(input, antiSamyPolicy); if (results.getNumberOfErrors() > 0) { log.warn("Sanitized HTML input. Errors: {}", results.getErrorMessages()); } return results.getCleanHTML(); } catch (Exception e) { log.error("Error sanitizing HTML", e); // Fail securely - return empty string return ""; } } /** * Encode for HTML output */ public String encodeForHtml(String input) { if (input == null) return null; return StringEscapeUtils.escapeHtml4(input); } /** * Encode for JavaScript output */ public String encodeForJavaScript(String input) { if (input == null) return null; return StringEscapeUtils.escapeEcmaScript(input); } /** * Encode for URL parameter */ public String encodeForUrl(String input) { if (input == null) return null; try { return URLEncoder.encode(input, StandardCharsets.UTF_8); } catch (Exception e) { return ""; } } /** * Remove all non-alphanumeric characters */ public String alphanumericOnly(String input) { if (input == null) return null; return input.replaceAll("[^A-Za-z0-9]", ""); } /** * Truncate to maximum length */ public String truncate(String input, int maxLength) { if (input == null) return null; if (input.length() <= maxLength) return input; return input.substring(0, maxLength); } } // Usage in service layer @Service public class CommentService { @Autowired private InputSanitizer sanitizer; public Comment createComment(String authorId, String content) { // Sanitize user-generated content String sanitizedContent = sanitizer.sanitizeHtml(content); // Truncate to reasonable length sanitizedContent = sanitizer.truncate(sanitizedContent, 5000); Comment comment = new Comment(); comment.setAuthorId(authorId); comment.setContent(sanitizedContent); comment.setCreatedAt(Instant.now()); return commentRepository.save(comment); } } // Usage in Vaadin UI @Component public class CommentView extends VerticalLayout { @Autowired private InputSanitizer sanitizer; public void displayComment(Comment comment) { // Encode for HTML display String safeContent = sanitizer.encodeForHtml(comment.getContent()); Html contentHtml = new Html("<div>" + safeContent + "</div>"); add(contentHtml); } } ``` ### Pattern #4: SQL Injection Prevention ✅ **SECURE - Parameterized Queries:** ```java @Repository public interface PoliticianRepository extends JpaRepository<Politician, String> { // JPA Query with named parameters (SAFE) @Query("SELECT p FROM Politician p WHERE " + "LOWER(p.firstName) LIKE LOWER(CONCAT('%', :searchTerm, '%')) OR " + "LOWER(p.lastName) LIKE LOWER(CONCAT('%', :searchTerm, '%'))") List<Politician> search(@Param("searchTerm") String searchTerm); // Spring Data query methods (SAFE) List<Politician> findByPartyAndDistrictOrderByLastNameAsc(String party, String district); } // Criteria API for dynamic queries (SAFE) @Repository public class PoliticianSearchRepository { @PersistenceContext private EntityManager entityManager; public List<Politician> advancedSearch(PoliticianSearchCriteria criteria) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Politician> query = cb.createQuery(Politician.class); Root<Politician> politician = query.from(Politician.class); List<Predicate> predicates = new ArrayList<>(); // Safe parameter binding if (criteria.getFirstName() != null) { predicates.add(cb.like( cb.lower(politician.get("firstName")), "%" + criteria.getFirstName().toLowerCase() + "%" )); } if (criteria.getParty() != null) { predicates.add(cb.equal(politician.get("party"), criteria.getParty())); } if (criteria.getMinBirthYear() != null) { predicates.add(cb.greaterThanOrEqualTo( politician.get("birthDate"), LocalDate.of(criteria.getMinBirthYear(), 1, 1) )); }
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub