-
Notifications
You must be signed in to change notification settings - Fork 1
Implement DevSecOps5 page with latest GHAS features and security vulnerability demos #100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: CalinL <[email protected]>
Co-authored-by: CalinL <[email protected]>
Dependency ReviewThe following issues were found:
Vulnerabilitiessrc/webapp01/webapp01.csproj
Only included vulnerabilities with severity moderate or higher. OpenSSF Scorecard
Scanned Files
|
// LOG FORGING: User input directly logged without sanitization | ||
string userAgent = Request.Headers.UserAgent.ToString(); | ||
string ipAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown"; | ||
string userInput = Request.Query.ContainsKey("user") ? Request.Query["user"].ToString() ?? "anonymous" : "anonymous"; |
Check notice
Code scanning / CodeQL
Inefficient use of ContainsKey Note
indexer
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 24 days ago
To fix the flagged inefficiency on line 40, replace the current pattern:
string userInput = Request.Query.ContainsKey("user") ? Request.Query["user"].ToString() ?? "anonymous" : "anonymous";
with the more efficient approach using TryGetValue
:
if (Request.Query.TryGetValue("user", out var userValue))
string userInput = userValue.ToString() ?? "anonymous";
else
string userInput = "anonymous";
However, since the original code is a single-line assignment of userInput
, and we want to keep the assignment style, we can use a short inline replacement:
Request.Query.TryGetValue("user", out var userValue);
string userInput = userValue.ToString() ?? "anonymous";
But this does not handle the case where TryGetValue
fails; it would leave userValue
as default
(i.e., an empty StringValues, not null; its .ToString()
is ""), which would change behavior (returns empty string rather than "anonymous"). It's best to use an if/else or a conditional operator. So, this is the ideal inline replacement:
string userInput = Request.Query.TryGetValue("user", out var userValue) && !string.IsNullOrEmpty(userValue)
? userValue.ToString()
: "anonymous";
This preserves semantics—if the "user" query string has a value, use it; otherwise, use "anonymous".
Only the code at line 40 in src/webapp01/Pages/DevSecOps5.cshtml.cs needs changing; no new imports or methods are required.
-
Copy modified lines R40-R42
@@ -37,7 +37,9 @@ | ||
// LOG FORGING: User input directly logged without sanitization | ||
string userAgent = Request.Headers.UserAgent.ToString(); | ||
string ipAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown"; | ||
string userInput = Request.Query.ContainsKey("user") ? Request.Query["user"].ToString() ?? "anonymous" : "anonymous"; | ||
string userInput = Request.Query.TryGetValue("user", out var userValue) && !string.IsNullOrEmpty(userValue) | ||
? userValue.ToString() | ||
: "anonymous"; | ||
|
||
// INSECURE: Direct user input in logs | ||
_logger.LogInformation($"DevSecOps5 page accessed by user: {userInput} from IP: {ipAddress} with UserAgent: {userAgent}"); |
string userInput = Request.Query.ContainsKey("user") ? Request.Query["user"].ToString() ?? "anonymous" : "anonymous"; | ||
|
||
// INSECURE: Direct user input in logs | ||
_logger.LogInformation($"DevSecOps5 page accessed by user: {userInput} from IP: {ipAddress} with UserAgent: {userAgent}"); |
Check failure
Code scanning / CodeQL
Log entries created from user input High
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 24 days ago
To fix this problem, sanitize user-provided input before including it in any log statement. For logs intended to be stored and reviewed as plain text, newline and carriage return characters should be removed or replaced from the user-provided strings to prevent log forging. The simplest way is to replace them with empty strings or with visible delimiters (like a space). In this code, sanitize the userInput
variable before logging it, both on the main log line (line 43) and also in the error logging path (line 63). Make the minimal change: define a sanitized version of userInput
just before the logging statements, using userInput.Replace("\r", "").Replace("\n", "")
. No external packages are needed, nor architectural changes.
-
Copy modified lines R42-R44 -
Copy modified line R64
@@ -39,8 +39,9 @@ | ||
string ipAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown"; | ||
string userInput = Request.Query.ContainsKey("user") ? Request.Query["user"].ToString() ?? "anonymous" : "anonymous"; | ||
|
||
// INSECURE: Direct user input in logs | ||
_logger.LogInformation($"DevSecOps5 page accessed by user: {userInput} from IP: {ipAddress} with UserAgent: {userAgent}"); | ||
// Sanitize user input to prevent log forging | ||
string sanitizedUserInput = userInput.Replace("\r", "").Replace("\n", ""); | ||
_logger.LogInformation($"DevSecOps5 page accessed by user: {sanitizedUserInput} from IP: {ipAddress} with UserAgent: {userAgent}"); | ||
|
||
LoadLatestGHASNews(); | ||
GenerateSecurityStats(); | ||
@@ -60,7 +61,7 @@ | ||
catch (Exception ex) | ||
{ | ||
// LOG FORGING: Exception details with user input | ||
_logger.LogError($"Database connection failed for user {userInput}: {ex.Message}"); | ||
_logger.LogError($"Database connection failed for user {sanitizedUserInput}: {ex.Message}"); | ||
} | ||
|
||
// INSECURE: Test vulnerable regex patterns |
catch (Exception ex) | ||
{ | ||
// LOG FORGING: Exception details with user input | ||
_logger.LogError($"Database connection failed for user {userInput}: {ex.Message}"); | ||
} |
Check notice
Code scanning / CodeQL
Generic catch clause Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 24 days ago
To fix the problem, the catch clause on line 60 should be changed to catch only specific exception types that are expected from the simulated database connection code. Since the code is using SqlConnection
, the most relevant exception is SqlException
. If there are other expected exceptions (such as configuration errors), those can be added as additional catch blocks. The catch block should be updated to catch SqlException
instead of the generic Exception
. No additional imports are needed, as SqlException
is already imported.
Edit only the catch clause on line 60 in src/webapp01/Pages/DevSecOps5.cshtml.cs
to catch SqlException
instead of Exception
.
-
Copy modified line R60
@@ -57,7 +57,7 @@ | ||
|
||
_logger.LogInformation("Database connection simulation completed"); | ||
} | ||
catch (Exception ex) | ||
catch (SqlException ex) | ||
{ | ||
// LOG FORGING: Exception details with user input | ||
_logger.LogError($"Database connection failed for user {userInput}: {ex.Message}"); |
catch (Exception ex) | ||
{ | ||
// LOG FORGING: Exception details with user input | ||
_logger.LogError($"Database connection failed for user {userInput}: {ex.Message}"); |
Check failure
Code scanning / CodeQL
Log entries created from user input High
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 24 days ago
To fix the problem, sanitize the user input before logging it. Since the logs are likely plain text, the best practice is to remove or replace newline characters (\r
, \n
) and other potentially problematic characters from the user input before including it in log entries. This can be done using String.Replace
or a regular expression. The fix should be applied to the assignment of userInput
or immediately before logging. The minimal and clearest fix is to sanitize userInput
in the log statements on lines 43 and 63. No new methods or external dependencies are required.
-
Copy modified lines R43-R45 -
Copy modified line R65
@@ -40,7 +40,9 @@ | ||
string userInput = Request.Query.ContainsKey("user") ? Request.Query["user"].ToString() ?? "anonymous" : "anonymous"; | ||
|
||
// INSECURE: Direct user input in logs | ||
_logger.LogInformation($"DevSecOps5 page accessed by user: {userInput} from IP: {ipAddress} with UserAgent: {userAgent}"); | ||
// Sanitize userInput to prevent log forging | ||
string sanitizedUserInput = userInput.Replace("\r", "").Replace("\n", ""); | ||
_logger.LogInformation($"DevSecOps5 page accessed by user: {sanitizedUserInput} from IP: {ipAddress} with UserAgent: {userAgent}"); | ||
|
||
LoadLatestGHASNews(); | ||
GenerateSecurityStats(); | ||
@@ -60,7 +62,7 @@ | ||
catch (Exception ex) | ||
{ | ||
// LOG FORGING: Exception details with user input | ||
_logger.LogError($"Database connection failed for user {userInput}: {ex.Message}"); | ||
_logger.LogError($"Database connection failed for user {sanitizedUserInput}: {ex.Message}"); | ||
} | ||
|
||
// INSECURE: Test vulnerable regex patterns |
{ | ||
string jsonData = JsonConvert.SerializeObject(LatestGHASNews); | ||
// INSECURE: Deserializing without type validation | ||
var deserializedData = JsonConvert.DeserializeObject<List<string>>(jsonData); |
Check warning
Code scanning / CodeQL
Useless assignment to local variable Warning
deserializedData
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 24 days ago
To fix this problem, we should remove the useless assignment to the local variable deserializedData
on line 89, as its value is not used after assignment. The right-hand side method call (JsonConvert.DeserializeObject<List<string>>(jsonData)
) can be executed by itself, since any exception thrown will still be caught and logged. This change will not alter the existing functional behavior, only make the code cleaner and clearer by eliminating the unnecessary variable. Only line 89 of the method LoadLatestGHASNews
in src/webapp01/Pages/DevSecOps5.cshtml.cs
needs to be altered; no imports, definitions, or other code changes are required.
-
Copy modified line R89
@@ -86,7 +86,7 @@ | ||
{ | ||
string jsonData = JsonConvert.SerializeObject(LatestGHASNews); | ||
// INSECURE: Deserializing without type validation | ||
var deserializedData = JsonConvert.DeserializeObject<List<string>>(jsonData); | ||
JsonConvert.DeserializeObject<List<string>>(jsonData); | ||
|
||
_logger.LogInformation($"Successfully loaded {LatestGHASNews.Count} latest GHAS news items"); | ||
} |
|
||
try | ||
{ | ||
bool isMatch = VulnerableRegex.IsMatch(testPattern); |
Check failure
Code scanning / CodeQL
Denial of Service from comparison of user input against expensive regex High
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 24 days ago
To mitigate the risk of regex-based denial of service, the best fix is to specify a timeout for the regex operation. In C#, this can be done by constructing the Regex
object with a TimeSpan
timeout parameter. Since the code uses a pre-existing VulnerableRegex
object, we should ensure that it is constructed with a timeout. If VulnerableRegex
is a field or property, update its initialization to include a timeout (e.g., 1 second). If it is constructed elsewhere, ensure the timeout is set wherever it is created. If the code only uses static methods, switch to using a Regex
instance with a timeout. All changes should be made in src/webapp01/Pages/DevSecOps5.cshtml.cs
, specifically where VulnerableRegex
is defined and used.
Required changes:
- Update the definition/initialization of
VulnerableRegex
to include a timeout (e.g.,TimeSpan.FromSeconds(1)
). - If
VulnerableRegex
is not defined in the shown code, add its definition in the class, using a safe timeout. - No new imports are needed, as
System.Text.RegularExpressions
is already imported.
-
Copy modified lines R12-R17
@@ -9,6 +9,12 @@ | ||
{ | ||
public class DevSecOps5Model : PageModel | ||
{ | ||
// FIX: Use a timeout to prevent regex DoS | ||
private static readonly Regex VulnerableRegex = new Regex( | ||
"(a+)+", // Example vulnerable pattern; replace with actual pattern if different | ||
RegexOptions.None, | ||
TimeSpan.FromSeconds(1) | ||
); | ||
private readonly ILogger<DevSecOps5Model> _logger; | ||
|
||
// INSECURE: Hardcoded database credentials for demo purposes |
try | ||
{ | ||
bool isMatch = VulnerableRegex.IsMatch(testPattern); | ||
_logger.LogInformation($"Vulnerable regex test result: {isMatch} for pattern: {testPattern}"); |
Check failure
Code scanning / CodeQL
Log entries created from user input High
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 24 days ago
To fix the problem, we need to sanitize the user input (testPattern
) before logging it. Since the logs are likely plain text, the recommended approach is to remove or replace newline characters (\n
, \r
) from the user input before including it in the log entry. This can be done using String.Replace
or a regular expression. The fix should be applied directly in the log statement on line 121, ensuring that only sanitized input is logged. No changes to existing functionality are required, and no new dependencies are needed.
-
Copy modified lines R121-R123
@@ -118,7 +118,9 @@ | ||
try | ||
{ | ||
bool isMatch = VulnerableRegex.IsMatch(testPattern); | ||
_logger.LogInformation($"Vulnerable regex test result: {isMatch} for pattern: {testPattern}"); | ||
// Sanitize user input to prevent log forging | ||
string sanitizedPattern = testPattern.Replace("\r", "").Replace("\n", ""); | ||
_logger.LogInformation($"Vulnerable regex test result: {isMatch} for pattern: {sanitizedPattern}"); | ||
} | ||
catch (Exception ex) | ||
{ |
catch (Exception ex) | ||
{ | ||
// LOG FORGING: User input in error logs | ||
_logger.LogError($"Regex evaluation failed for pattern: {testPattern}. Error: {ex.Message}"); | ||
} |
Check notice
Code scanning / CodeQL
Generic catch clause Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 24 days ago
To fix the problem, the catch clause should be narrowed to only handle exceptions that are expected from the Regex.IsMatch
call. In .NET, the most common exceptions thrown by regex operations are ArgumentException
(for invalid patterns) and RegexMatchTimeoutException
(for timeouts). The catch block should be updated to catch these specific exceptions instead of the generic Exception
. This change should be made in the TestVulnerableRegex
method, specifically replacing the catch (Exception ex)
block at lines 123–127 with two catch blocks: one for ArgumentException
and one for RegexMatchTimeoutException
. No new imports are needed, as these exception types are part of the standard library.
-
Copy modified line R123 -
Copy modified line R126 -
Copy modified lines R128-R131
@@ -120,11 +120,15 @@ | ||
bool isMatch = VulnerableRegex.IsMatch(testPattern); | ||
_logger.LogInformation($"Vulnerable regex test result: {isMatch} for pattern: {testPattern}"); | ||
} | ||
catch (Exception ex) | ||
catch (ArgumentException ex) | ||
{ | ||
// LOG FORGING: User input in error logs | ||
_logger.LogError($"Regex evaluation failed for pattern: {testPattern}. Error: {ex.Message}"); | ||
_logger.LogError($"Regex evaluation failed for pattern: {testPattern}. Invalid regex pattern. Error: {ex.Message}"); | ||
} | ||
catch (RegexMatchTimeoutException ex) | ||
{ | ||
_logger.LogError($"Regex evaluation timed out for pattern: {testPattern}. Error: {ex.Message}"); | ||
} | ||
} | ||
|
||
public IActionResult OnPostTestSql(string sqlInput) |
catch (Exception ex) | ||
{ | ||
// LOG FORGING: User input in error logs | ||
_logger.LogError($"Regex evaluation failed for pattern: {testPattern}. Error: {ex.Message}"); |
Check failure
Code scanning / CodeQL
Log entries created from user input High
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 24 days ago
To fix the log forging vulnerability, we must sanitize the user input (testPattern
) before including it in the log message. Since the logs are likely plain text, the recommended approach is to remove or replace newline characters (\r
, \n
) from the user input. This can be done using String.Replace
or a regular expression. The fix should be applied directly in the logging statement on line 126, ensuring that only sanitized input is logged. No changes to functionality are required, and no new methods are needed. If not already present, we may need to add a using System;
for Environment.NewLine
, but this is already covered by the default imports.
-
Copy modified lines R126-R127
@@ -123,7 +123,8 @@ | ||
catch (Exception ex) | ||
{ | ||
// LOG FORGING: User input in error logs | ||
_logger.LogError($"Regex evaluation failed for pattern: {testPattern}. Error: {ex.Message}"); | ||
var sanitizedPattern = testPattern.Replace("\r", "").Replace("\n", ""); | ||
_logger.LogError($"Regex evaluation failed for pattern: {sanitizedPattern}. Error: {ex.Message}"); | ||
} | ||
} | ||
|
catch (Exception ex) | ||
{ | ||
// LOG FORGING: Exception with user input | ||
_logger.LogError($"Regex test failed for pattern '{regexPattern}': {ex.Message}"); | ||
TempData["SecurityTest"] = $"Regex test failed: {ex.Message}"; | ||
} |
Check notice
Code scanning / CodeQL
Generic catch clause Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 24 days ago
To fix the problem, we should replace the generic catch (Exception ex)
clause with more specific exception types that are expected when compiling or using a regular expression. For the Regex
constructor and IsMatch
method, the most common exceptions are ArgumentException
(for invalid patterns) and RegexMatchTimeoutException
(if a timeout is set, though in this code no timeout is specified, but it's still good practice). We should catch these specific exceptions and handle them as before. Any other exceptions should not be caught here, allowing them to propagate. The changes are limited to the OnPostTestRegex
method in src/webapp01/Pages/DevSecOps5.cshtml.cs
. No new imports are needed, as ArgumentException
and RegexMatchTimeoutException
are in System
and System.Text.RegularExpressions
, which are already imported.
-
Copy modified line R170 -
Copy modified lines R176-R180
@@ -167,12 +167,17 @@ | ||
|
||
TempData["SecurityTest"] = $"Regex pattern '{regexPattern}' processed in {duration.TotalMilliseconds:F2}ms - Result: {result}"; | ||
} | ||
catch (Exception ex) | ||
catch (ArgumentException ex) | ||
{ | ||
// LOG FORGING: Exception with user input | ||
_logger.LogError($"Regex test failed for pattern '{regexPattern}': {ex.Message}"); | ||
TempData["SecurityTest"] = $"Regex test failed: {ex.Message}"; | ||
} | ||
catch (RegexMatchTimeoutException ex) | ||
{ | ||
_logger.LogError($"Regex test timed out for pattern '{regexPattern}': {ex.Message}"); | ||
TempData["SecurityTest"] = $"Regex test timed out: {ex.Message}"; | ||
} | ||
} | ||
|
||
return RedirectToPage(); |
This PR implements a comprehensive DevSecOps5 demonstration page to showcase the latest GitHub Advanced Security (GHAS) features and capabilities for 2025. The implementation includes intentionally vulnerable code patterns designed to be detected by GHAS code scanning tools.
Key Features Added
DevSecOps5 Page (
/DevSecOps5
)Intentional Security Vulnerabilities for GHAS Detection
The backend implementation includes several categories of security issues designed for demonstration:
Log Forging Vulnerabilities:
Hardcoded Credentials:
ReDoS Vulnerabilities:
SQL Injection Simulation:
Technical Changes
.NET Compatibility Fixes
net9.0
tonet8.0
for environment compatibilityMapStaticAssets
,WithStaticAssets
) with .NET 8 equivalentsNewtonsoft.Json
to version12.0.2
System.Text.Json
version8.0.4
Microsoft.Data.SqlClient
version5.0.2
Navigation Integration
Comprehensive Logging Implementation
The page includes extensive
ILogger<DevSecOps5Model>
usage that captures:Security Warnings Generated
The implementation intentionally triggers multiple NuGet security warnings for demonstration:
NU1903
warnings for known vulnerabilities inMicrosoft.Data.SqlClient
,Newtonsoft.Json
, andSystem.Text.Json
Testing Verified
This implementation provides a comprehensive demonstration environment for showcasing GHAS capabilities while maintaining clean, functional code structure.
Fixes #97.
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.