open-saml bumps (#5805)

This commit is contained in:
Anthony Stirling
2026-02-27 15:03:12 +00:00
committed by GitHub
parent 6a1597bb8d
commit 930d7a0df8
6 changed files with 262 additions and 7 deletions
@@ -191,6 +191,20 @@ public class DatabaseController {
if (fileName == null || fileName.isEmpty()) {
throw new IllegalArgumentException("File must not be null or empty");
}
// Validate that file is a legitimate backup file
// Only allow files matching the backup naming pattern
if (!fileName.startsWith("backup_") || !fileName.endsWith(".sql")) {
log.warn("Attempted download of non-backup file: {}", fileName);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(
java.util.Map.of(
"error",
"invalidFileName",
"message",
"Only backup files are allowed"));
}
try {
Path filePath = databaseService.getBackupFilePath(fileName);
InputStreamResource resource = new InputStreamResource(Files.newInputStream(filePath));
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.sql.DataSource;
@@ -47,6 +48,48 @@ public class DatabaseService implements DatabaseServiceInterface {
public static final String SQL_SUFFIX = ".sql";
private final Path BACKUP_DIR;
// Whitelist of allowed SQL patterns for H2 database backups (generated by SCRIPT command)
// Only standard DDL/DML operations are permitted - anything else is rejected by default
private static final java.util.List<Pattern> ALLOWED_PATTERNS =
java.util.List.of(
Pattern.compile("(?i)\\bCREATE\\b"),
Pattern.compile("(?i)\\bSET\\s+SCHEMA\\b"),
Pattern.compile("(?i)\\bALTER\\s+SEQUENCE\\b"),
Pattern.compile("(?i)\\bDROP\\s+TABLE\\b"),
Pattern.compile("(?i)\\bALTER\\s+TABLE\\b"),
Pattern.compile("(?i)\\bADD\\s+COLUMN\\b"),
Pattern.compile("(?i)\\bINSERT\\s+INTO\\b"),
Pattern.compile("(?i)\\bVALUES\\b"),
Pattern.compile("(?i)\\bADD\\s+CONSTRAINT\\b"),
Pattern.compile("(?i)\\bPRIMARY\\s+KEY\\b"),
Pattern.compile("(?i)\\bFOREIGN\\s+KEY\\b"),
Pattern.compile("(?i)\\bCONSTRAINT\\b"),
Pattern.compile("(?i)\\bCHECK\\b"),
Pattern.compile(
"(?i)\\bGENERATED\\s+(BY\\s+DEFAULT|ALWAYS)\\s+AS\\s+IDENTITY\\b"),
Pattern.compile("(?i)\\bDEFAULT\\b"),
Pattern.compile(
"(?i)\\bSET\\s+(REFERENTIAL_INTEGRITY|MODE|DATABASE|IGNORECASE|AUTOINCREMENT|DB_CLOSE_DELAY)\\b"),
Pattern.compile("(?i)\\bIF\\s+EXISTS\\b"),
Pattern.compile("(?i)\\bIF\\s+NOT\\s+EXISTS\\b"),
Pattern.compile("(?i)\\bNULLS\\s+(FIRST|LAST)\\b"),
Pattern.compile("(?i)\\bREFERENCES\\b"),
Pattern.compile("(?i)\\bNOCHECK\\b"),
Pattern.compile("(?i)\\bRESTART\\s+WITH\\b"),
Pattern.compile("(?i)\\bCASCADE\\b"),
Pattern.compile("(?i)\\bON\\b"),
Pattern.compile("(?i)\\bSTART\\s+WITH\\b"),
Pattern.compile("(?i)\\bAS\\b"),
Pattern.compile("(?i)\\bUNIQUE\\b"),
Pattern.compile("(?i)\\bDISTINCT\\b"),
Pattern.compile("(?i)\\bWITH\\s+TIME\\s+ZONE\\b"),
Pattern.compile("(?i)\\bTIMESTAMP\\b"),
Pattern.compile("(?i)\\bVARYING\\b"),
Pattern.compile("(?i)\\bNOT\\s+NULL\\b"),
Pattern.compile("(?i)\\bTRUE\\b"),
Pattern.compile("(?i)\\bFALSE\\b"),
Pattern.compile("(?i)\\bNULL\\b"));
private final ApplicationProperties.Datasource datasourceProps;
private final DataSource dataSource;
private final DatabaseNotificationServiceInterface backupNotificationService;
@@ -450,6 +493,9 @@ public class DatabaseService implements DatabaseServiceInterface {
throw new IllegalArgumentException("Backup verification failed for: " + scriptPath);
}
// Validate SQL content before execution to prevent injection attacks
validateSqlContent(scriptPath);
String query = "RUNSCRIPT from ?;";
try (Connection conn = dataSource.getConnection();
@@ -466,6 +512,68 @@ public class DatabaseService implements DatabaseServiceInterface {
log.info("Database import completed: {}", scriptPath);
}
/**
* Validates SQL content to prevent execution of dangerous functions. Uses an allowlist approach
* - only allows standard DML/DDL operations generated by H2's SCRIPT command.
*
* @param scriptPath the path to the SQL file
* @throws IllegalArgumentException if dangerous SQL or disallowed patterns are detected
*/
private void validateSqlContent(Path scriptPath) {
try {
String content = Files.readString(scriptPath);
String normalizedContent = normalizeSqlContent(content);
// Validate that content only contains allowed operations (whitelist approach)
// Split by semicolons to check individual statements
String[] statements = normalizedContent.split(";");
for (String statement : statements) {
statement = statement.trim();
if (statement.isEmpty()) {
continue;
}
// Check if statement matches any allowed pattern
boolean isAllowed = false;
for (Pattern allowedPattern : ALLOWED_PATTERNS) {
if (allowedPattern.matcher(statement).find()) {
isAllowed = true;
break;
}
}
if (!isAllowed) {
log.error(
"Unrecognized SQL statement in backup file: {}",
statement.substring(0, Math.min(50, statement.length())));
throw new IllegalArgumentException(
"SQL script contains unrecognized or disallowed SQL statements. File may be"
+ " corrupted or tampered with.");
}
}
} catch (IOException e) {
log.error("Error reading SQL script for validation: {}", e.getMessage());
throw new IllegalArgumentException("Failed to validate SQL script: " + e.getMessage());
}
}
/**
* Normalizes SQL content by removing comments to prevent bypass attacks.
*
* @param sql the SQL content to normalize
* @return normalized SQL without comments
*/
private String normalizeSqlContent(String sql) {
// Remove block comments (/* ... */)
sql = sql.replaceAll("/\\*[\\s\\S]*?\\*/", " ");
// Remove line comments (--....)
sql = sql.replaceAll("--[^\r\n]*", " ");
// Collapse multiple whitespaces
sql = sql.replaceAll("\\s+", " ");
return sql.trim();
}
/**
* Checks for invalid characters or sequences
*