mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 11:23:10 +02:00
open-saml bumps (#5805)
This commit is contained in:
+14
@@ -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));
|
||||
|
||||
+108
@@ -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
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user