Skip to content

Commit 4958b6f

Browse files
committed
fix(rules): improve precision of 4 high-FP dotnet Semgrep rules
Addresses JumpCloud SAST analysis feedback where 4 rules produced 150/170 false positives (88% of all FPs), inflating the reported FP rate to 91%. Rules fixed: - dotnet-xss-response-write: Convert to taint mode. Previously matched any .Write() call including Serilog ITextFormatter log sinks. Now requires data flow from user input sources to Response.Write sinks. - dotnet-hardcoded-credentials: Add value inspection and credential API patterns. Previously matched on variable names alone, flagging config key paths like "UseCaptchaOnResetPassword". - dotnet-crypto-failures: Target actual weak algorithms (3DES, DES, RC2, RijndaelManaged) instead of Encoding.UTF8.GetBytes() which flagged the recommended SHA256.HashData(Encoding.UTF8.GetBytes(...)) pattern. - dotnet-path-traversal: Convert to taint mode. Previously matched all Path.Combine() calls including those using framework-provided paths like _env.WebRootPath. Juliet C# benchmark results (before -> after): xss-response-write: Prec 41.6% -> 100%, Recall 47.8% -> 24.3% hardcoded-credentials: Prec 0.0% -> 100%, Recall 0.0% -> 3.6% crypto-failures: Prec 36.7% -> 100%, Recall 51.4% -> 50.0% path-traversal: Prec 0.0% -> 100%, Recall 0.0% -> 45.2%
1 parent 5aa26ce commit 4958b6f

1 file changed

Lines changed: 150 additions & 31 deletions

File tree

socket_basics/rules/dotnet.yml

Lines changed: 150 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -141,23 +141,37 @@ rules:
141141

142142
# === High Severity Rules ===
143143

144-
# Hardcoded credentials
144+
# Hardcoded credentials - matches both variable-name patterns AND credential API usage
145145
- id: dotnet-hardcoded-credentials
146146
message: "Hard-coded credentials detected. Embedding secrets in source code makes them easily discoverable and impossible to rotate. Use environment variables or a secrets manager instead."
147147
severity: HIGH
148148
languages: [csharp]
149-
patterns:
150-
- pattern-either:
151-
# C#
152-
- pattern: |
153-
private const string $VAR = "...";
154-
- pattern: |
155-
public const string $VAR = "...";
156-
- pattern: |
157-
string $VAR = "...";
158-
- metavariable-regex:
159-
metavariable: $VAR
160-
regex: (?i).*(password|passwd|pwd|secret|token|key|api_key|connection_string).*
149+
pattern-either:
150+
# Pattern 1: Variable name suggests credential AND value is a non-empty literal
151+
- patterns:
152+
- pattern-either:
153+
- pattern: |
154+
private const string $VAR = "$VALUE";
155+
- pattern: |
156+
public const string $VAR = "$VALUE";
157+
- pattern: |
158+
string $VAR = "$VALUE";
159+
- metavariable-regex:
160+
metavariable: $VAR
161+
regex: (?i).*(password|passwd|pwd|secret|token|api_key|connection_string).*
162+
- metavariable-regex:
163+
metavariable: $VALUE
164+
# Must look like an actual secret: 6+ chars, not a config path or empty
165+
regex: ^(?!$).{6,}
166+
- metavariable-regex:
167+
metavariable: $VALUE
168+
# Exclude config key paths and property names
169+
regex: ^(?!.*\.\w+\.\w+)(?!.*\b(Use|Enable|Disable|Is|Has|Get|Set|On)\w+).*$
170+
# Pattern 2: Credential APIs called with hardcoded string literals
171+
- pattern: new NetworkCredential($USER, "...", ...)
172+
- pattern: new NetworkCredential("...", "...", ...)
173+
- pattern: new SqlConnection("...");
174+
- pattern: new PasswordDeriveBytes("...", ...)
161175
metadata:
162176
category: security
163177
cwe: CWE-798
@@ -223,35 +237,129 @@ rules:
223237
owasp: "A07:2021"
224238
fix: "Never return true from ServerCertificateCustomValidationCallback. Use the default certificate validation from ServicePointManager."
225239

226-
# XSS vulnerabilities
240+
# XSS vulnerabilities - taint mode for accurate user-input tracking
227241
- id: dotnet-xss-response-write
228242
message: "Cross-site scripting (XSS) vulnerability detected. User input is rendered in HTML output without proper escaping, allowing attackers to inject malicious scripts. Sanitize or escape all user input before rendering."
229243
severity: HIGH
230244
languages: [csharp]
231-
pattern-either:
232-
- pattern: Response.Write($USER_INPUT)
233-
- pattern: $RESPONSE.Write($USER_INPUT)
234-
- pattern: HttpContext.Response.Write($USER_INPUT)
245+
mode: taint
246+
pattern-sources:
247+
- pattern-either:
248+
# ASP.NET request input sources
249+
- pattern: Request.QueryString[...]
250+
- pattern: Request.Form[...]
251+
- pattern: Request.Params[...]
252+
- pattern: Request.Cookies[...]
253+
- pattern: Request[$KEY]
254+
- pattern: Request.Headers[...]
255+
- pattern: Request.UserAgent
256+
- pattern: Request.RawUrl
257+
- pattern: Request.Url
258+
- pattern: Request.Path
259+
- pattern: Request.PathInfo
260+
- pattern: (HttpRequest $REQ).QueryString[...]
261+
- pattern: (HttpRequest $REQ).Form[...]
262+
- pattern: (HttpRequest $REQ).Params[...]
263+
- pattern: (HttpRequest $REQ)[$KEY]
264+
# ASP.NET Core model binding
265+
- pattern: $REQ.Query[...]
266+
- pattern: $REQ.Form[...]
267+
- pattern: $REQ.Headers[...]
268+
- pattern: $REQ.Cookies[...]
269+
# Network input sources (Juliet-style)
270+
- pattern: (StreamReader $SR).ReadLine()
271+
- pattern: (TextReader $TR).ReadLine()
272+
- pattern: Console.ReadLine()
273+
pattern-propagators:
274+
- pattern: (string $A) + (string $B)
275+
from: $B
276+
to: $A
277+
- pattern: String.Format($FMT, ..., (string $X), ...)
278+
from: $X
279+
to: $FMT
280+
- pattern: String.Concat(..., (string $X), ...)
281+
from: $X
282+
to: String.Concat
283+
pattern-sinks:
284+
- pattern-either:
285+
- pattern: Response.Write(...)
286+
- pattern: HttpContext.Response.Write(...)
287+
# HttpResponse parameter pattern (Juliet, ASP.NET handlers)
288+
- pattern: $RESP.Write(...)
289+
pattern-sanitizers:
290+
- pattern-either:
291+
- pattern: HttpUtility.HtmlEncode(...)
292+
- pattern: HtmlEncoder.Default.Encode(...)
293+
- pattern: WebUtility.HtmlEncode(...)
294+
- pattern: Server.HtmlEncode(...)
295+
- pattern: AntiXssEncoder.HtmlEncode(...)
235296
metadata:
236297
category: security
237298
cwe: CWE-79
238-
confidence: medium
299+
confidence: high
239300
subcategory: xss
240301
vulnerability_class: "Cross-Site Scripting (XSS)"
241302
owasp: "A03:2021"
242303
fix: "Use Razor auto-encoding or HtmlEncoder.Default.Encode(). Never use Html.Raw() with user input. Validate input on both client and server."
243304

244-
# Path traversal
305+
# Path traversal - taint mode for accurate user-input tracking
245306
- id: dotnet-path-traversal
246307
message: "Path traversal vulnerability detected. User input is used in file paths without validation, allowing attackers to access files outside the intended directory. Validate and canonicalize paths before use."
247308
severity: HIGH
248309
languages: [csharp]
249-
pattern-either:
250-
- pattern: File.ReadAllText($PATH + $USER_INPUT)
251-
- pattern: File.ReadAllBytes($PATH + $USER_INPUT)
252-
- pattern: File.WriteAllText($PATH + $USER_INPUT, ...)
253-
- pattern: new FileStream($PATH + $USER_INPUT, ...)
254-
- pattern: Path.Combine($BASE, $USER_INPUT)
310+
mode: taint
311+
pattern-sources:
312+
- pattern-either:
313+
# ASP.NET request sources
314+
- pattern: Request.QueryString[...]
315+
- pattern: Request.Form[...]
316+
- pattern: Request.Params[...]
317+
- pattern: Request[$KEY]
318+
- pattern: Request.Path
319+
- pattern: Request.PathInfo
320+
# ASP.NET Core
321+
- pattern: $REQ.Query[...]
322+
- pattern: $REQ.Form[...]
323+
- pattern: $REQ.RouteValues[...]
324+
# Network input sources (Juliet-style)
325+
- pattern: (StreamReader $SR).ReadLine()
326+
- pattern: (TextReader $TR).ReadLine()
327+
- pattern: Console.ReadLine()
328+
# Environment and system sources
329+
- pattern: Environment.GetEnvironmentVariable(...)
330+
pattern-propagators:
331+
- pattern: (string $A) + (string $B)
332+
from: $B
333+
to: $A
334+
- pattern: Path.Combine(..., (string $X), ...)
335+
from: $X
336+
to: Path.Combine
337+
- pattern: String.Format($FMT, ..., (string $X), ...)
338+
from: $X
339+
to: $FMT
340+
pattern-sinks:
341+
- pattern-either:
342+
- pattern: File.ReadAllText(...)
343+
- pattern: File.ReadAllBytes(...)
344+
- pattern: File.WriteAllText(...)
345+
- pattern: File.WriteAllBytes(...)
346+
- pattern: File.Open(...)
347+
- pattern: File.OpenRead(...)
348+
- pattern: File.OpenWrite(...)
349+
- pattern: File.Exists(...)
350+
- pattern: File.Delete(...)
351+
- pattern: new FileStream(...)
352+
- pattern: new StreamReader(...)
353+
- pattern: new StreamWriter(...)
354+
- pattern: Directory.GetFiles(...)
355+
- pattern: Directory.EnumerateFiles(...)
356+
pattern-sanitizers:
357+
- pattern-either:
358+
- pattern: Path.GetFullPath(...)
359+
- pattern: Path.GetFileName(...)
360+
# Framework-provided base paths are safe sources, not sanitizers,
361+
# but if the result is validated against a base we consider it sanitized
362+
- pattern: $X.StartsWith($BASE)
255363
metadata:
256364
category: security
257365
cwe: CWE-22
@@ -623,23 +731,34 @@ rules:
623731
vulnerability_class: "Access Control Violation"
624732
fix: "Review authorization logic for bypass conditions. Use policy-based authorization with IAuthorizationHandler. Test authorization with different user roles."
625733

626-
# A02: Cryptographic Failures
734+
# A02: Cryptographic Failures - targets actual weak algorithm usage
627735
- id: dotnet-crypto-failures
628736
message: "Weak cryptographic algorithm detected. Using broken or outdated algorithms may allow attackers to decrypt data or forge signatures. Use modern algorithms like AES-256, SHA-256, or Ed25519."
629737
severity: HIGH
630738
languages: [csharp]
631739
pattern-either:
632-
- pattern: Encoding.UTF8.GetBytes($PASSWORD)
633-
- pattern: Convert.ToBase64String(Encoding.UTF8.GetBytes($SECRET))
740+
# Weak symmetric ciphers
741+
- pattern: new TripleDESCryptoServiceProvider()
742+
- pattern: new DESCryptoServiceProvider()
743+
- pattern: new RC2CryptoServiceProvider()
744+
- pattern: TripleDES.Create()
745+
- pattern: DES.Create()
746+
- pattern: RC2.Create()
747+
# Obsolete RijndaelManaged (use Aes.Create() instead)
748+
- pattern: new RijndaelManaged()
749+
# Using raw password bytes directly as crypto key (no KDF)
634750
- pattern: new RijndaelManaged() { Key = Encoding.UTF8.GetBytes($KEY) }
751+
- pattern: new AesCryptoServiceProvider() { Key = Encoding.UTF8.GetBytes($KEY) }
752+
# Encoding password for storage without hashing (storing plaintext)
753+
- pattern: Convert.ToBase64String(Encoding.UTF8.GetBytes($SECRET))
635754
metadata:
636755
category: security
637756
owasp: A02
638757
cwe: CWE-327
639-
confidence: medium
758+
confidence: high
640759
subcategory: crypto
641760
vulnerability_class: "Cryptographic Weakness"
642-
fix: "Use SHA256.Create() instead of MD5/SHA1. Use Aes.Create() with CipherMode.CBC or AesGcm for encryption."
761+
fix: "Use Aes.Create() instead of 3DES/DES/RC2. Use Rfc2898DeriveBytes or HKDF for key derivation from passwords. Never use raw Encoding.GetBytes() as a crypto key."
643762

644763
# A03: Injection (additional patterns)
645764
- id: dotnet-xpath-injection

0 commit comments

Comments
 (0)