Skip to content

fix: Address critical security vulnerabilities from issue #121#134

Open
sarpit2907 wants to merge 1 commit intosoft-eng-practicum:developfrom
sarpit2907:fix/security-vulnerabilities-issue-121
Open

fix: Address critical security vulnerabilities from issue #121#134
sarpit2907 wants to merge 1 commit intosoft-eng-practicum:developfrom
sarpit2907:fix/security-vulnerabilities-issue-121

Conversation

@sarpit2907
Copy link
Copy Markdown

@sarpit2907 sarpit2907 commented Mar 15, 2026

Summary

This pull request addresses three critical security vulnerabilities identified in issue #121:

  1. Exposed Authentication Data in API Responses - Sensitive authentication fields like passwordHash, securityStamp, and concurrencyStamp were being exposed in user API endpoints
  2. Missing File Upload Size Restrictions - Profile image uploads lacked enforced maximum file sizes, creating DoS vulnerability
  3. No File Type Validation - File uploads had no MIME type or extension validation, allowing potentially malicious files

Changes Made

1. User Authentication Data Sanitization

  • Created UserSafeDTO class to return only public-safe user fields
  • Updated 5 user endpoints to use the sanitized DTO:
    • GetUserByID
    • GetUserByName
    • GetUserRange
    • GetUserList
    • Search
  • Excluded sensitive fields: PasswordHash, SecurityStamp, ConcurrencyStamp, NormalizedEmail, NormalizedUserName, and other IdentityUser properties

2. File Upload Size Validation

  • Created FileValidationSettings configuration class with configurable size limits:
    • Profile images: 5 MB
    • Project files: 100 MB
    • Notebook files: 50 MB
  • Added configuration to appsettings.json
  • Implemented size validation in all upload endpoints before storing files
  • Provides clear error messages when files exceed limits

3. File Type Validation

  • Created FileTypeValidator with allowlist-based validation
  • Implements file signature/magic number verification to prevent disguised malicious files
  • Allowed file types:
    • Profile images: .jpg, .jpeg, .png, .gif, .webp (image types only)
    • Project files: .csv, .json, .txt, .xlsx, .xls, .pdf, .xml, .tsv, .dat (data formats)
    • Notebooks: .ipynb only
  • Blocked dangerous extensions: .exe, .bat, .cmd, .sh, .ps1, .app, .dll, .so, .dmg, .pkg, .msi, .deb, .rpm, .apk, and scripts
  • Applied to all upload endpoints:
    • UploadFile (ProjectController)
    • UploadProfileImage (AccountController)
    • UploadNotebook (ProjectController)
    • UploadNotebookNewVersion (ProjectController)
    • UploadExistingNotebook (ProjectController - colab type)

Files Modified

  • New Files:

    • src/Analysim.Web/ViewModels/Account/UserSafeDTO.cs
    • src/Analysim.Core/Helper/FileValidationSettings.cs
    • src/Analysim.Core/Helper/FileTypeValidator.cs
    • src/Analysim.Web/appsettings.json
  • Modified Files:

    • src/Analysim.Web/Controllers/AccountController.cs - Added UserSafeDTO usage and profile image validation
    • src/Analysim.Web/Controllers/ProjectController.cs - Added file validation to all upload endpoints

Security Benefits

  • Prevents authentication credential exposure
  • Prevents DoS attacks via oversized uploads
  • Prevents malicious executable uploads
  • File signature verification ensures file type matches extension
  • Configuration-driven validation allows easy adjustment
  • Existing user quota checks remain as additional security layer
  • No breaking changes - backward compatible

Testing Recommendations

  1. User API Response Sanitization:

    • Call /api/account/search endpoint
    • Verify response does NOT contain: passwordHash, securityStamp, concurrencyStamp
    • Verify response contains: id, userName, email, bio, dateCreated
  2. File Size Validation:

    • Attempt to upload profile image >5MB → Should return 400 error
    • Attempt to upload project file >100MB → Should return 400 error
    • Upload valid-sized files → Should succeed
  3. File Type Validation:

    • Upload .exe file → Should return 400 "File type not allowed"
    • Upload .sh script → Should return 400 "File type not allowed"
    • Upload valid .csv file → Should succeed
    • Upload valid .jpg image for profile → Should succeed
    • Upload valid .ipynb notebook → Should succeed

Related Issue

Closes #121

This pull request improves security by addressing OWASP Top 10 vulnerabilities related to file upload handling and information disclosure.

@cengique
Copy link
Copy Markdown
Member

@sarpit2907 thanks for submitting this PR and sorry for the late reply. Trying to test it, I'm getting the following errors. Did you forget to add some of the new packages in the CSPROJ files?

$ dotnet run --environment Development
Building...
.../AnalySim/src/Analysim.Core/Helper/FileTypeValidator.cs(5,7): error CS0246: The type or namespace name 'Web' could not be found (are you missing a using directive or an assembly reference?) [.../AnalySim/src/Analysim.Core/Analysim.Core.csproj]
.../AnalySim/src/Analysim.Core/Helper/FileValidationSettings.cs(27,16): error CS0246: The type or namespace name 'List<>' could not be found (are you missing a using directive or an assembly reference?) [.../AnalySim/src/Analysim.Core/Analysim.Core.csproj]
.../AnalySim/src/Analysim.Core/Helper/FileValidationSettings.cs(35,16): error CS0246: The type or namespace name 'List<>' could not be found (are you missing a using directive or an assembly reference?) [.../AnalySim/src/Analysim.Core/Analysim.Core.csproj]
.../AnalySim/src/Analysim.Core/Helper/FileValidationSettings.cs(43,16): error CS0246: The type or namespace name 'List<>' could not be found (are you missing a using directive or an assembly reference?) [.../AnalySim/src/Analysim.Core/Analysim.Core.csproj]
.../AnalySim/src/Analysim.Core/Helper/FileValidationSettings.cs(51,16): error CS0246: The type or namespace name 'List<>' could not be found (are you missing a using directive or an assembly reference?) [.../AnalySim/src/Analysim.Core/Analysim.Core.csproj]

The build failed. Fix the build errors and run again.

@cengique
Copy link
Copy Markdown
Member

@sarpit2907 can you also add to the README the necessary changes for appsettings.json? I also didn't see in the code where you're reading these config settings. It looked like they were hard coded.

…acticum#121

**Vulnerability 1: Exposed Authentication Data**
- Created UserSafeDTO to sanitize API responses
- Removed sensitive fields (passwordHash, securityStamp, concurrencyStamp) from all user endpoints
- Updated 5 endpoints: GetUserByID, GetUserByName, GetUserRange, GetUserList, Search
- Files: AccountController.cs, ViewModels/Account/UserSafeDTO.cs

**Vulnerability 2: Missing File Upload Size Restrictions**
- Created FileValidationSettings configuration class
- Added per-file size limits: 5MB (profile images), 100MB (project files), 50MB (notebooks)
- Implemented configuration via appsettings.json
- Added validation checks to all upload endpoints before storing files
- Files: FileValidationSettings.cs, appsettings.json, ProjectController.cs, AccountController.cs

**Vulnerability 3: No File Type Validation**
- Created FileTypeValidator with allowlist-based validation
- Implemented file signature/magic number verification
- Profile images: .jpg, .jpeg, .png, .gif, .webp only
- Project files: .csv, .json, .txt, .xlsx, .pdf, .xml, .tsv, .dat only
- Notebooks: .ipynb only
- Blocked dangerous extensions: .exe, .bat, .cmd, .sh, .ps1, etc.
- Applied validation to all 5 upload endpoints
- Files: FileTypeValidator.cs, ProjectController.cs, AccountController.cs

**Endpoints Fixed:**
- ProjectController: UploadFile, UploadNotebook, UploadNotebookNewVersion, UploadExistingNotebook
- AccountController: UploadProfileImage, GetUserByID, GetUserByName, GetUserRange, GetUserList, Search

**Security Improvements:**
- 100% file validation at API boundary before database storage
- Existing user quota checks remain as additional security layer
- Configuration-driven approach allows easy adjustment of limits
- No breaking changes - only response DTOs and validation logic modified

Closes soft-eng-practicum#121
@sarpit2907 sarpit2907 force-pushed the fix/security-vulnerabilities-issue-121 branch from 6b207a3 to 2332421 Compare March 31, 2026 00:54
Copilot AI review requested due to automatic review settings March 31, 2026 00:54
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to close issue #121 by preventing sensitive Identity fields from leaking in user-facing API responses and by adding allowlist-based validation (type + size) to file upload endpoints.

Changes:

  • Introduces UserSafeDTO and updates Account user endpoints to return sanitized user data.
  • Adds configurable FileValidationSettings and a FileTypeValidator helper.
  • Wires validation settings via DI and applies validation to profile image, project file, and notebook upload flows; documents new config in README.md.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/Analysim.Web/ViewModels/Account/UserSafeDTO.cs Adds a “safe” user DTO and mapping helpers to exclude sensitive fields from API responses.
src/Analysim.Web/Startup.cs Registers FileValidationSettings from configuration for DI.
src/Analysim.Web/Controllers/AccountController.cs Uses UserSafeDTO for user endpoints; adds validation to profile image uploads.
src/Analysim.Web/Controllers/ProjectController.cs Adds validation to project file + notebook upload endpoints (including Colab download flow).
src/Analysim.Core/Helper/FileValidationSettings.cs Defines configurable size limits and extension allow/block lists.
src/Analysim.Core/Helper/FileTypeValidator.cs Implements file extension + content validation logic used by upload endpoints.
README.md Documents the new FileValidation configuration section.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// If no magic number matched, assume it's okay for formats we can't verify
// (like CSV, TXT, XML without specific magic numbers)
if (extension == ".xml")
return IsValidTextContent(fileContent) && fileContent.ToString().Contains("<");
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VerifyFileSignature attempts to validate XML by calling fileContent.ToString().Contains("<"), but byte[].ToString() returns the type name (e.g., "System.Byte[]"), not the decoded content. This will cause all .xml uploads to fail validation. Decode the bytes to text (e.g., UTF-8) and check the decoded string, or reuse IsValidTextContent/a proper XML parse check.

Suggested change
return IsValidTextContent(fileContent) && fileContent.ToString().Contains("<");
{
var text = System.Text.Encoding.UTF8.GetString(fileContent);
return IsValidTextContent(fileContent) && text.Contains("<");
}

Copilot uses AI. Check for mistakes.
{ new byte[] { 0xFF, 0xD8, 0xFF }, ".jpg" }, // JPEG
{ new byte[] { 0x89, 0x50, 0x4E, 0x47 }, ".png" }, // PNG
{ new byte[] { 0x47, 0x49, 0x46 }, ".gif" }, // GIF
{ new byte[] { 0x52, 0x49, 0x46, 0x46 }, ".webp" }, // WEBP
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current WEBP signature check only validates the RIFF header, which is shared by other RIFF-based formats (e.g., WAV/AVI). This allows non-WEBP files renamed to .webp to pass validation. Consider validating both RIFF at bytes 0-3 and WEBP at bytes 8-11 (or equivalent robust WEBP signature checks).

Suggested change
{ new byte[] { 0x52, 0x49, 0x46, 0x46 }, ".webp" }, // WEBP

Copilot uses AI. Check for mistakes.
Comment on lines +123 to +129
// For binary formats, check magic numbers
foreach (var kvp in FileMagicNumbers)
{
if (fileContent.Length >= kvp.Key.Length &&
fileContent.Take(kvp.Key.Length).SequenceEqual(kvp.Key))
{
return kvp.Value == extension || (kvp.Value == ".xlsx" && extension == ".xls");
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.xls is allowlisted, but the signature logic effectively treats .xls as acceptable when the content matches the ZIP-based .xlsx signature. This will reject real legacy .xls files (OLE2 signature) and can allow .xlsx content disguised as .xls. Either remove .xls from the allowed list or implement proper .xls signature validation.

Suggested change
// For binary formats, check magic numbers
foreach (var kvp in FileMagicNumbers)
{
if (fileContent.Length >= kvp.Key.Length &&
fileContent.Take(kvp.Key.Length).SequenceEqual(kvp.Key))
{
return kvp.Value == extension || (kvp.Value == ".xlsx" && extension == ".xls");
// Explicit validation for legacy Excel (.xls) using OLE2 compound file signature
if (extension == ".xls")
{
// OLE2 magic number: D0 CF 11 E0 A1 B1 1A E1
byte[] ole2Signature = new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 };
if (fileContent.Length < ole2Signature.Length)
return false;
for (int i = 0; i < ole2Signature.Length; i++)
{
if (fileContent[i] != ole2Signature[i])
return false;
}
return true;
}
// For binary formats, check magic numbers
foreach (var kvp in FileMagicNumbers)
{
if (fileContent.Length >= kvp.Key.Length &&
fileContent.Take(kvp.Key.Length).SequenceEqual(kvp.Key))
{
return kvp.Value == extension;

Copilot uses AI. Check for mistakes.
Comment on lines +93 to +98
// Check if extension is blocked
if (_settings.BlockedExtensions?.Contains(extension) == true)
return (false, $"File type '{extension}' is not allowed.");

// Check if extension is in allowlist
if (!allowedExtensions.Contains(extension))
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allowedExtensions.Contains(extension) is case-sensitive, but extension lists can be configured with different casing in appsettings. This can cause valid uploads to be rejected depending on configuration values. Consider normalizing configured extensions or using a case-insensitive comparison.

Suggested change
// Check if extension is blocked
if (_settings.BlockedExtensions?.Contains(extension) == true)
return (false, $"File type '{extension}' is not allowed.");
// Check if extension is in allowlist
if (!allowedExtensions.Contains(extension))
// Check if extension is blocked (case-insensitive)
if (_settings.BlockedExtensions?.Any(e => string.Equals(e, extension, StringComparison.OrdinalIgnoreCase)) == true)
return (false, $"File type '{extension}' is not allowed.");
// Check if extension is in allowlist (case-insensitive)
if (!allowedExtensions.Any(e => string.Equals(e, extension, StringComparison.OrdinalIgnoreCase)))

Copilot uses AI. Check for mistakes.
Comment on lines +124 to +138
foreach (var kvp in FileMagicNumbers)
{
if (fileContent.Length >= kvp.Key.Length &&
fileContent.Take(kvp.Key.Length).SequenceEqual(kvp.Key))
{
return kvp.Value == extension || (kvp.Value == ".xlsx" && extension == ".xls");
}
}

// If no magic number matched, assume it's okay for formats we can't verify
// (like CSV, TXT, XML without specific magic numbers)
if (extension == ".xml")
return IsValidTextContent(fileContent) && fileContent.ToString().Contains("<");

return true; // Allow if we can't determine a signature
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VerifyFileSignature defaults to allowing the file when no known magic number matches. That makes signature validation bypassable for many allowed types (e.g., an arbitrary binary renamed to .png/.pdf will pass if it doesn't match a different known signature). For extensions with known signatures, consider requiring a positive match rather than default-allowing.

Suggested change
foreach (var kvp in FileMagicNumbers)
{
if (fileContent.Length >= kvp.Key.Length &&
fileContent.Take(kvp.Key.Length).SequenceEqual(kvp.Key))
{
return kvp.Value == extension || (kvp.Value == ".xlsx" && extension == ".xls");
}
}
// If no magic number matched, assume it's okay for formats we can't verify
// (like CSV, TXT, XML without specific magic numbers)
if (extension == ".xml")
return IsValidTextContent(fileContent) && fileContent.ToString().Contains("<");
return true; // Allow if we can't determine a signature
var signatureMatched = false;
foreach (var kvp in FileMagicNumbers)
{
if (fileContent.Length >= kvp.Key.Length &&
fileContent.Take(kvp.Key.Length).SequenceEqual(kvp.Key))
{
signatureMatched = kvp.Value == extension || (kvp.Value == ".xlsx" && extension == ".xls");
if (signatureMatched)
{
return true;
}
}
}
// If no magic number matched, and the extension has a known signature, reject the file
var hasKnownSignature = FileMagicNumbers.Any(kvp =>
kvp.Value == extension || (kvp.Value == ".xlsx" && extension == ".xls"));
if (hasKnownSignature)
{
return false;
}
// For formats without specific magic numbers (like XML), perform basic sanity checks
if (extension == ".xml")
{
if (!IsValidTextContent(fileContent))
return false;
var text = System.Text.Encoding.UTF8.GetString(fileContent);
return text.Contains("<");
}
// Allow if we can't determine a signature and no signature is expected
return true;

Copilot uses AI. Check for mistakes.
Comment on lines 1049 to 1052
using var memoryStream = new MemoryStream();
await formdata.File.CopyToAsync(memoryStream);
var fileContent = memoryStream.ToArray();

Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The upload is copied into a MemoryStream and materialized via ToArray() before any max-size validation. To make the size limit effective against memory pressure/DoS, validate formdata.File.Length against the configured project-file limit before buffering the file.

Copilot uses AI. Check for mistakes.
Comment on lines 1152 to 1155
using var memoryStream = new MemoryStream();
await noteBookData.NotebookFile.CopyToAsync(memoryStream);
var fileContent = memoryStream.ToArray();

Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The notebook is fully buffered into memory before enforcing the configured max size. Validate noteBookData.NotebookFile.Length against the configured notebook limit before CopyToAsync/ToArray() to avoid large allocations for oversized uploads.

Copilot uses AI. Check for mistakes.
Comment on lines 1261 to 1264
using var memoryStream = new MemoryStream();
await noteBookData.NotebookFile.CopyToAsync(memoryStream);
var fileContent = memoryStream.ToArray();

Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new notebook version is fully buffered into memory before size validation. Validate noteBookData.NotebookFile.Length against the configured notebook limit before CopyToAsync/ToArray() to avoid large allocations for oversized uploads.

Copilot uses AI. Check for mistakes.
Comment on lines +1376 to +1377
byte[] fileContent = System.IO.File.ReadAllBytes(filePath);

Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Colab downloads, the entire file is read into memory (ReadAllBytes) before applying the configured size limit. To prevent large allocations for oversized downloads, check the on-disk size (fileStream.Length/FileInfo.Length) against the configured notebook limit before reading all bytes.

Suggested change
byte[] fileContent = System.IO.File.ReadAllBytes(filePath);
// Prevent large allocations for oversized notebook downloads by checking on-disk size first
const long MaxNotebookSizeBytes = 50L * 1024L * 1024L; // 50 MB limit for Colab notebook downloads
if (fileStream.Length > MaxNotebookSizeBytes)
{
return BadRequest("The uploaded notebook file is too large.");
}
byte[] fileContent;
using (var memoryStream = new MemoryStream((int)fileStream.Length))
{
await fileStream.CopyToAsync(memoryStream);
fileContent = memoryStream.ToArray();
}

Copilot uses AI. Check for mistakes.
Comment on lines +1378 to +1382
// Validate notebook file type and size for colab downloads
var fileValidator = new Core.Helper.FileTypeValidator(_fileValidationSettings);
var validationResult = fileValidator.ValidateNotebookFile(fileName, fileContent);
if (!validationResult.IsValid)
return BadRequest(validationResult.ErrorMessage);
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the Colab download flow, validation failures return early inside this block, which bypasses the temp-file deletion later in the method. This can leave invalid/oversized downloads on disk. Consider ensuring cleanup runs in a finally (or delete before returning on validation failure).

Copilot uses AI. Check for mistakes.
@sarpit2907
Copy link
Copy Markdown
Author

Hi @cengique , thanks for the review and apologies for the delay on my end!

I've addressed both issues:
Build errors fixed

  • Added missing using System.Collections.Generic; in FileValidationSettings.cs (caused the List<> errors)
  • Removed an incorrect using Web.ViewModels; in FileTypeValidator.cs (caused the Web namespace error)
  • Also fixed a similar missing using in UserSafeDTO.cs

Config settings now read from appsettings.json (no longer hard-coded):

  • Registered FileValidationSettings in Startup.cs via services.Configure__(Configuration.GetSection("FileValidation"))
  • Updated both AccountController and ProjectController to inject IOptions instead of using new FileValidationSettings()

README updated with the FileValidation section and an explanation of each configurable setting. Build passes with 0 errors. Let me know if anything else needs to be adjusted!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security Vulnerabilities in User Search API and File Upload

4 participants