diff --git a/sonaranalyzer-dotnet/its/expected/Nancy/Nancy-{34576216-0DCA-4B0F-A0DC-9075E75A676F}-S4834.json b/sonaranalyzer-dotnet/its/expected/Nancy/Nancy-{34576216-0DCA-4B0F-A0DC-9075E75A676F}-S4834.json new file mode 100644 index 00000000000..6d6089da86a --- /dev/null +++ b/sonaranalyzer-dotnet/its/expected/Nancy/Nancy-{34576216-0DCA-4B0F-A0DC-9075E75A676F}-S4834.json @@ -0,0 +1,69 @@ +{ +"issues": [ +{ +"id": "S4834", +"message": "Make sure that permissions are controlled safely here.", +"location": { +"uri": "sources\Nancy\src\Nancy\Owin\NancyMiddleware.cs", +"region": { +"startLine": 253, +"startColumn": 57, +"endLine": 253, +"endColumn": 73 +} +} +}, +{ +"id": "S4834", +"message": "Make sure that permissions are controlled safely here.", +"location": { +"uri": "sources\Nancy\src\Nancy\Security\ClaimsPrincipalExtensions.cs", +"region": { +"startLine": 18, +"startColumn": 28, +"endLine": 18, +"endColumn": 43 +} +} +}, +{ +"id": "S4834", +"message": "Make sure that permissions are controlled safely here.", +"location": { +"uri": "sources\Nancy\src\Nancy\Security\ClaimsPrincipalExtensions.cs", +"region": { +"startLine": 29, +"startColumn": 28, +"endLine": 29, +"endColumn": 37 +} +} +}, +{ +"id": "S4834", +"message": "Make sure that permissions are controlled safely here.", +"location": { +"uri": "sources\Nancy\src\Nancy\Security\ClaimsPrincipalExtensions.cs", +"region": { +"startLine": 40, +"startColumn": 28, +"endLine": 40, +"endColumn": 39 +} +} +}, +{ +"id": "S4834", +"message": "Make sure that permissions are controlled safely here.", +"location": { +"uri": "sources\Nancy\src\Nancy\Security\ClaimsPrincipalExtensions.cs", +"region": { +"startLine": 52, +"startColumn": 28, +"endLine": 52, +"endColumn": 42 +} +} +} +] +} diff --git a/sonaranalyzer-dotnet/its/expected/Nancy/Nancy.Demo.Authentication-{5F2DD52D-471B-4946-8F7B-F050C88EFC52}-S4834.json b/sonaranalyzer-dotnet/its/expected/Nancy/Nancy.Demo.Authentication-{5F2DD52D-471B-4946-8F7B-F050C88EFC52}-S4834.json new file mode 100644 index 00000000000..0085d68febd --- /dev/null +++ b/sonaranalyzer-dotnet/its/expected/Nancy/Nancy.Demo.Authentication-{5F2DD52D-471B-4946-8F7B-F050C88EFC52}-S4834.json @@ -0,0 +1,30 @@ +{ +"issues": [ +{ +"id": "S4834", +"message": "Make sure that permissions are controlled safely here.", +"location": { +"uri": "sources\Nancy\src\Nancy.Demo.Authentication\AuthenticationBootstrapper.cs", +"region": { +"startLine": 27, +"startColumn": 39, +"endLine": 27, +"endColumn": 116 +} +} +}, +{ +"id": "S4834", +"message": "Make sure that permissions are controlled safely here.", +"location": { +"uri": "sources\Nancy\src\Nancy.Demo.Authentication\AuthenticationBootstrapper.cs", +"region": { +"startLine": 27, +"startColumn": 59, +"endLine": 27, +"endColumn": 115 +} +} +} +] +} diff --git a/sonaranalyzer-dotnet/its/expected/Nancy/Nancy.Demo.Authentication.Basic-{911196F4-B88F-4940-B1FD-D30F5290D06D}-S4834.json b/sonaranalyzer-dotnet/its/expected/Nancy/Nancy.Demo.Authentication.Basic-{911196F4-B88F-4940-B1FD-D30F5290D06D}-S4834.json new file mode 100644 index 00000000000..7a813f2872b --- /dev/null +++ b/sonaranalyzer-dotnet/its/expected/Nancy/Nancy.Demo.Authentication.Basic-{911196F4-B88F-4940-B1FD-D30F5290D06D}-S4834.json @@ -0,0 +1,30 @@ +{ +"issues": [ +{ +"id": "S4834", +"message": "Make sure that permissions are controlled safely here.", +"location": { +"uri": "sources\Nancy\src\Nancy.Demo.Authentication.Basic\UserValidator.cs", +"region": { +"startLine": 14, +"startColumn": 24, +"endLine": 14, +"endColumn": 74 +} +} +}, +{ +"id": "S4834", +"message": "Make sure that permissions are controlled safely here.", +"location": { +"uri": "sources\Nancy\src\Nancy.Demo.Authentication.Basic\UserValidator.cs", +"region": { +"startLine": 14, +"startColumn": 44, +"endLine": 14, +"endColumn": 73 +} +} +} +] +} diff --git a/sonaranalyzer-dotnet/its/expected/Nancy/Nancy.Demo.Authentication.Forms-{98940A30-1B48-4F71-A6BA-85F0AAF31A2F}-S4834.json b/sonaranalyzer-dotnet/its/expected/Nancy/Nancy.Demo.Authentication.Forms-{98940A30-1B48-4F71-A6BA-85F0AAF31A2F}-S4834.json new file mode 100644 index 00000000000..ce30e3ce37e --- /dev/null +++ b/sonaranalyzer-dotnet/its/expected/Nancy/Nancy.Demo.Authentication.Forms-{98940A30-1B48-4F71-A6BA-85F0AAF31A2F}-S4834.json @@ -0,0 +1,30 @@ +{ +"issues": [ +{ +"id": "S4834", +"message": "Make sure that permissions are controlled safely here.", +"location": { +"uri": "sources\Nancy\src\Nancy.Demo.Authentication.Forms\UserDatabase.cs", +"region": { +"startLine": 27, +"startColumn": 26, +"endLine": 27, +"endColumn": 84 +} +} +}, +{ +"id": "S4834", +"message": "Make sure that permissions are controlled safely here.", +"location": { +"uri": "sources\Nancy\src\Nancy.Demo.Authentication.Forms\UserDatabase.cs", +"region": { +"startLine": 27, +"startColumn": 46, +"endLine": 27, +"endColumn": 83 +} +} +} +] +} diff --git a/sonaranalyzer-dotnet/its/expected/Nancy/Nancy.Demo.Authentication.Stateless-{BAE74CD5-57C2-40E3-8F7A-EDE5721C2ACC}-S4834.json b/sonaranalyzer-dotnet/its/expected/Nancy/Nancy.Demo.Authentication.Stateless-{BAE74CD5-57C2-40E3-8F7A-EDE5721C2ACC}-S4834.json new file mode 100644 index 00000000000..9eb5c5af70f --- /dev/null +++ b/sonaranalyzer-dotnet/its/expected/Nancy/Nancy.Demo.Authentication.Stateless-{BAE74CD5-57C2-40E3-8F7A-EDE5721C2ACC}-S4834.json @@ -0,0 +1,30 @@ +{ +"issues": [ +{ +"id": "S4834", +"message": "Make sure that permissions are controlled safely here.", +"location": { +"uri": "sources\Nancy\src\Nancy.Demo.Authentication.Stateless\UserDatabase.cs", +"region": { +"startLine": 30, +"startColumn": 20, +"endLine": 30, +"endColumn": 91 +} +} +}, +{ +"id": "S4834", +"message": "Make sure that permissions are controlled safely here.", +"location": { +"uri": "sources\Nancy\src\Nancy.Demo.Authentication.Stateless\UserDatabase.cs", +"region": { +"startLine": 30, +"startColumn": 40, +"endLine": 30, +"endColumn": 90 +} +} +} +] +} diff --git a/sonaranalyzer-dotnet/its/expected/Nancy/Nancy.Hosting.Self-{AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}-S4834.json b/sonaranalyzer-dotnet/its/expected/Nancy/Nancy.Hosting.Self-{AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}-S4834.json new file mode 100644 index 00000000000..65da8131e69 --- /dev/null +++ b/sonaranalyzer-dotnet/its/expected/Nancy/Nancy.Hosting.Self-{AA7F66EB-EC2C-47DE-855F-30B3E6EF2134}-S4834.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S4834", +"message": "Make sure that permissions are controlled safely here.", +"location": { +"uri": "sources\Nancy\src\Nancy.Hosting.Self\NancyHost.cs", +"region": { +"startLine": 213, +"startColumn": 19, +"endLine": 213, +"endColumn": 47 +} +} +} +] +} diff --git a/sonaranalyzer-dotnet/rspec/cs/S4834_c#.html b/sonaranalyzer-dotnet/rspec/cs/S4834_c#.html new file mode 100644 index 00000000000..1f58876d597 --- /dev/null +++ b/sonaranalyzer-dotnet/rspec/cs/S4834_c#.html @@ -0,0 +1,80 @@ +

Controlling permissions is security-sensitive. It has led in the past to the following vulnerabilities:

+ +

Attackers can only damage what they have access to. Thus limiting their access is a good way to prevent them from wreaking havoc, but it has to be +done properly.

+

This rule flags code that controls the access to resources and actions or configures this access. The goal is to guide security code reviews.

+

Ask Yourself Whether

+ +

You are at risk if you answered yes to the first question and any of the following ones.

+

Recommended Secure Coding Practices

+

The first step is to restrict all sensitive actions to authenticated users.

+

Each user should have the lowest privileges possible. The access control granularity should match the sensitivity of each resource or action. The +more sensitive it is, the less people should have access to it.

+

Do not base the access control on a user input or on a value which might have been tampered with. For example, the developer should not read a +user's permissions from an HTTP cookie as it can be modified client-side.

+

Check that the access to each action and resource is properly restricted.

+

Enable administrators to swiftly remove permissions when necessary. This enables them to reduce the time an attacker can have access to your +systems when a breach occurs.

+

Log and monitor refused access requests as they can reveal an attack.

+

Questionable Code Example

+
+using System.Threading;
+using System.Security.Permissions;
+using System.Security.Principal;
+using System.IdentityModel.Tokens;
+
+class SecurityPrincipalDemo
+{
+    class MyIdentity : IIdentity // Questionable, custom IIdentity implementations should be reviewed
+    {
+        // ...
+    }
+
+    class MyPrincipal : IPrincipal // Questionable, custom IPrincipal implementations should be reviewed
+    {
+        // ...
+    }
+    [System.Security.Permissions.PrincipalPermission(SecurityAction.Demand, Role = "Administrators")] // Questionable. The access restrictions enforced by this attribute should be reviewed.
+    static void CheckAdministrator()
+    {
+        WindowsIdentity MyIdentity = WindowsIdentity.GetCurrent(); // Questionable
+        HttpContext.User = ...; // Questionable: review all reference (set and get) to System.Web HttpContext.User
+        AppDomain domain = AppDomain.CurrentDomain;
+        domain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); // Questionable
+        MyIdentity identity = new MyIdentity(); // Questionable
+        MyPrincipal MyPrincipal = new MyPrincipal(MyIdentity); // Questionable
+        Thread.CurrentPrincipal = MyPrincipal; // Questionable
+        domain.SetThreadPrincipal(MyPrincipal); // Questionable
+
+        // All instantiation of PrincipalPermission should be reviewed.
+        PrincipalPermission principalPerm = new PrincipalPermission(null, "Administrators"); // Questionable
+        principalPerm.Demand();
+
+        SecurityTokenHandler handler = ...;
+        // Questionable: this creates an identity.
+        ReadOnlyCollection<ClaimsIdentity> identities = handler.ValidateToken(…);
+    }
+
+     // Questionable: review how this function uses the identity and principal.
+    void modifyPrincipal(MyIdentity identity, MyPrincipal principal)
+    {
+        // ...
+    }
+}
+
+

See

+ + diff --git a/sonaranalyzer-dotnet/rspec/cs/S4834_c#.json b/sonaranalyzer-dotnet/rspec/cs/S4834_c#.json new file mode 100644 index 00000000000..575c781753f --- /dev/null +++ b/sonaranalyzer-dotnet/rspec/cs/S4834_c#.json @@ -0,0 +1,21 @@ +{ + "title": "Controlling permissions is security-sensitive", + "type": "SECURITY_HOTSPOT", + "status": "ready", + "tags": [ + "sans-top25-porous", + "owasp-a5" + ], + "standards": [ + "OWASP Top Ten" + ], + "defaultSeverity": "Critical", + "ruleSpecification": "RSPEC-4834", + "sqKey": "S4834", + "scope": "Main", + "securityStandards": { + "OWASP": [ + "A5" + ] + } +} diff --git a/sonaranalyzer-dotnet/rspec/cs/Sonar_way_profile.json b/sonaranalyzer-dotnet/rspec/cs/Sonar_way_profile.json index 825507be2b5..8e3c1accd1c 100644 --- a/sonaranalyzer-dotnet/rspec/cs/Sonar_way_profile.json +++ b/sonaranalyzer-dotnet/rspec/cs/Sonar_way_profile.json @@ -233,6 +233,7 @@ "S4818", "S4823", "S4825", - "S4829" + "S4829", + "S4834" ] } diff --git a/sonaranalyzer-dotnet/rspec/vbnet/S4834_vb.net.html b/sonaranalyzer-dotnet/rspec/vbnet/S4834_vb.net.html new file mode 100644 index 00000000000..3f0c62bbd89 --- /dev/null +++ b/sonaranalyzer-dotnet/rspec/vbnet/S4834_vb.net.html @@ -0,0 +1,76 @@ +

Controlling permissions is security-sensitive. It has led in the past to the following vulnerabilities:

+ +

Attackers can only damage what they have access to. Thus limiting their access is a good way to prevent them from wreaking havoc, but it has to be +done properly.

+

This rule flags code that controls the access to resources and actions or configures this access. The goal is to guide security code reviews.

+

Ask Yourself Whether

+ +

You are at risk if you answered yes to the first question and any of the following ones.

+

Recommended Secure Coding Practices

+

The first step is to restrict all sensitive actions to authenticated users.

+

Each user should have the lowest privileges possible. The access control granularity should match the sensitivity of each resource or action. The +more sensitive it is, the less people should have access to it.

+

Do not base the access control on a user input or on a value which might have been tampered with. For example, the developer should not read a +user's permissions from an HTTP cookie as it can be modified client-side.

+

Check that the access to each action and resource is properly restricted.

+

Enable administrators to swiftly remove permissions when necessary. This enables them to reduce the time an attacker can have access to your +systems when a breach occurs.

+

Log and monitor refused access requests as they can reveal an attack.

+

Questionable Code Example

+
+Imports System.Threading
+Imports System.Security.Permissions
+Imports System.Security.Principal
+Imports System.IdentityModel.Tokens
+
+Class SecurityPrincipalDemo
+    Class MyIdentity
+        Implements IIdentity ' Questionable, custom IIdentity implementations should be reviewed
+    End Class
+
+    Class MyPrincipal
+        Implements IPrincipal ' Questionable, custom IPrincipal implementations should be reviewed
+    End Class
+
+    <System.Security.Permissions.PrincipalPermission(SecurityAction.Demand, Role:="Administrators")> ' Questionable. The access restrictions enforced by this attribute should be reviewed.
+    Private Shared Sub CheckAdministrator()
+        Dim MyIdentity As WindowsIdentity = WindowsIdentity.GetCurrent() ' Questionable
+
+        HttpContext.User = ... ' Questionable: review all reference (set and get) to System.Web HttpContext.User
+
+        Dim domain As AppDomain = AppDomain.CurrentDomain
+        domain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal) ' Questionable
+
+        Dim identity As MyIdentity = New MyIdentity() ' Questionable
+        Dim MyPrincipal As MyPrincipal = New MyPrincipal(MyIdentity) ' Questionable
+        Thread.CurrentPrincipal = MyPrincipal ' Questionable
+        domain.SetThreadPrincipal(MyPrincipal) ' Questionable
+
+        Dim principalPerm As PrincipalPermission = New PrincipalPermission(Nothing, "Administrators")  ' Questionable
+        principalPerm.Demand()
+
+        Dim handler As SecurityTokenHandler = ...
+        Dim identities As ReadOnlyCollection(Of ClaimsIdentity) = handler.ValidateToken()  ' Questionable, this creates identity
+    End Sub
+
+    ' Questionable: review how this function uses the identity and principal.
+    Private Sub modifyPrincipal(ByVal identity As MyIdentity, ByVal principal As MyPrincipal)
+    End Sub
+End Class
+
+

See

+ + diff --git a/sonaranalyzer-dotnet/rspec/vbnet/S4834_vb.net.json b/sonaranalyzer-dotnet/rspec/vbnet/S4834_vb.net.json new file mode 100644 index 00000000000..575c781753f --- /dev/null +++ b/sonaranalyzer-dotnet/rspec/vbnet/S4834_vb.net.json @@ -0,0 +1,21 @@ +{ + "title": "Controlling permissions is security-sensitive", + "type": "SECURITY_HOTSPOT", + "status": "ready", + "tags": [ + "sans-top25-porous", + "owasp-a5" + ], + "standards": [ + "OWASP Top Ten" + ], + "defaultSeverity": "Critical", + "ruleSpecification": "RSPEC-4834", + "sqKey": "S4834", + "scope": "Main", + "securityStandards": { + "OWASP": [ + "A5" + ] + } +} diff --git a/sonaranalyzer-dotnet/rspec/vbnet/Sonar_way_profile.json b/sonaranalyzer-dotnet/rspec/vbnet/Sonar_way_profile.json index 7d11037403f..c4c2818bf92 100644 --- a/sonaranalyzer-dotnet/rspec/vbnet/Sonar_way_profile.json +++ b/sonaranalyzer-dotnet/rspec/vbnet/Sonar_way_profile.json @@ -87,6 +87,7 @@ "S4818", "S4823", "S4825", - "S4829" + "S4829", + "S4834" ] } diff --git a/sonaranalyzer-dotnet/src/SonarAnalyzer.CSharp/Helpers/CSharpMethodDeclarationTracker.cs b/sonaranalyzer-dotnet/src/SonarAnalyzer.CSharp/Helpers/CSharpMethodDeclarationTracker.cs index 0235dab29cf..240024ba08c 100644 --- a/sonaranalyzer-dotnet/src/SonarAnalyzer.CSharp/Helpers/CSharpMethodDeclarationTracker.cs +++ b/sonaranalyzer-dotnet/src/SonarAnalyzer.CSharp/Helpers/CSharpMethodDeclarationTracker.cs @@ -51,7 +51,8 @@ public override MethodDeclarationCondition ParameterAtIndexIsUsed(int index) => } var methodDeclaration = context.MethodSymbol.DeclaringSyntaxReferences - .Select(r => (MethodDeclarationSyntax)r.GetSyntax()) + .Select(r => r.GetSyntax()) + .OfType() .FirstOrDefault(declaration => declaration.HasBodyOrExpressionBody()); if (methodDeclaration == null) @@ -74,8 +75,27 @@ public override MethodDeclarationCondition ParameterAtIndexIsUsed(int index) => }); }; - - protected override SyntaxToken GetMethodIdentifier(SyntaxNode methodDeclaration) => - ((MethodDeclarationSyntax)methodDeclaration).Identifier; + protected override SyntaxToken? GetMethodIdentifier(SyntaxNode methodDeclaration) + { + switch (methodDeclaration?.Kind()) + { + case SyntaxKind.MethodDeclaration: + return ((MethodDeclarationSyntax)methodDeclaration).Identifier; + case SyntaxKind.ConstructorDeclaration: + return ((ConstructorDeclarationSyntax)methodDeclaration).Identifier; + case SyntaxKind.DestructorDeclaration: + return ((DestructorDeclarationSyntax)methodDeclaration).Identifier; + case SyntaxKind.AddAccessorDeclaration: + case SyntaxKind.RemoveAccessorDeclaration: + return ((EventDeclarationSyntax)methodDeclaration.Parent.Parent).Identifier; + case SyntaxKind.GetAccessorDeclaration: + case SyntaxKind.SetAccessorDeclaration: + return ((PropertyDeclarationSyntax)methodDeclaration.Parent.Parent).Identifier; + case SyntaxKind.OperatorDeclaration: + return ((OperatorDeclarationSyntax)methodDeclaration).OperatorToken; + default: + return null; + } + } } } diff --git a/sonaranalyzer-dotnet/src/SonarAnalyzer.CSharp/Rules/Hotspots/ControllingPermissions.cs b/sonaranalyzer-dotnet/src/SonarAnalyzer.CSharp/Rules/Hotspots/ControllingPermissions.cs new file mode 100644 index 00000000000..3dc8b085502 --- /dev/null +++ b/sonaranalyzer-dotnet/src/SonarAnalyzer.CSharp/Rules/Hotspots/ControllingPermissions.cs @@ -0,0 +1,55 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2018 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using SonarAnalyzer.Common; +using SonarAnalyzer.Helpers; + +namespace SonarAnalyzer.Rules.CSharp +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + [Rule(DiagnosticId)] + public sealed class ControllingPermissions : ControllingPermissionsBase + { + private static readonly DiagnosticDescriptor rule = + DiagnosticDescriptorBuilder.GetDescriptor(DiagnosticId, MessageFormat, RspecStrings.ResourceManager) + .WithNotConfigurable(); + + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(rule); + + public ControllingPermissions() + : this(new DefaultAnalyzerConfiguration()) + { + } + + internal /*for testing*/ ControllingPermissions(IAnalyzerConfiguration analysisConfiguration) + { + ObjectCreationTracker = new CSharpObjectCreationTracker(analysisConfiguration, rule); + InvocationTracker = new CSharpInvocationTracker(analysisConfiguration, rule); + PropertyAccessTracker = new CSharpPropertyAccessTracker(analysisConfiguration, rule); + MethodDeclarationTracker = new CSharpMethodDeclarationTracker(analysisConfiguration, rule); + BaseTypeTracker = new CSharpBaseTypeTracker(analysisConfiguration, rule); + } + } +} diff --git a/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Helpers/BaseTypeTracker.cs b/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Helpers/BaseTypeTracker.cs index e6c1a504b31..003ce478b26 100644 --- a/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Helpers/BaseTypeTracker.cs +++ b/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Helpers/BaseTypeTracker.cs @@ -103,7 +103,7 @@ internal BaseClassCondition WhenDerivesFrom(KnownType type) => { foreach(var baseTypeNode in context.AllBaseTypeNodes) { - if (context.Model.GetTypeInfo(baseTypeNode).Type?.DerivesFrom(type) ?? false) + if (context.Model.GetTypeInfo(baseTypeNode).Type?.DerivesOrImplements(type) ?? false) { issueLocation = baseTypeNode.GetLocation(); return true; // assume there won't be more than one matching node diff --git a/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Helpers/KnownType.cs b/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Helpers/KnownType.cs index 8dd1485737b..54850732b14 100644 --- a/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Helpers/KnownType.cs +++ b/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Helpers/KnownType.cs @@ -86,6 +86,7 @@ internal sealed class KnownType internal static readonly KnownType System_Action_T1_T2_T3_T4 = new KnownType("System.Action"); internal static readonly KnownType System_Activator = new KnownType("System.Activator"); internal static readonly KnownType System_ApplicationException = new KnownType("System.ApplicationException"); + internal static readonly KnownType System_AppDomain = new KnownType("System.AppDomain"); internal static readonly KnownType System_ArgumentException = new KnownType("System.ArgumentException"); internal static readonly KnownType System_ArgumentNullException = new KnownType("System.ArgumentNullException"); internal static readonly KnownType System_ArgumentOutOfRangeException = new KnownType("System.ArgumentOutOfRangeException"); @@ -202,6 +203,7 @@ internal sealed class KnownType internal static readonly KnownType System_Guid = new KnownType("System.Guid"); internal static readonly KnownType System_IComparable = new KnownType("System.IComparable"); internal static readonly KnownType System_IComparable_T = new KnownType("System.IComparable"); + internal static readonly KnownType System_IdentityModel_Tokens_SecurityTokenHandler = new KnownType("System.IdentityModel.Tokens.SecurityTokenHandler"); internal static readonly KnownType System_IDisposable = new KnownType(SpecialType.System_IDisposable, "System.IDisposable"); internal static readonly KnownType System_IEquatable_T = new KnownType("System.IEquatable"); internal static readonly KnownType System_IFormatProvider = new KnownType("System.IFormatProvider"); @@ -297,7 +299,12 @@ internal sealed class KnownType internal static readonly KnownType System_Security_Cryptography_SymmetricAlgorithm = new KnownType("System.Security.Cryptography.SymmetricAlgorithm"); internal static readonly KnownType System_Security_Cryptography_TripleDES = new KnownType("System.Security.Cryptography.TripleDES"); internal static readonly KnownType System_Security_Permissions_CodeAccessSecurityAttribute = new KnownType("System.Security.Permissions.CodeAccessSecurityAttribute"); + internal static readonly KnownType System_Security_Permissions_PrincipalPermission = new KnownType("System.Security.Permissions.PrincipalPermission"); + internal static readonly KnownType System_Security_Permissions_PrincipalPermissionAttribute = new KnownType("System.Security.Permissions.PrincipalPermissionAttribute"); internal static readonly KnownType System_Security_PermissionSet = new KnownType("System.Security.PermissionSet"); + internal static readonly KnownType System_Security_Principal_IIdentity = new KnownType("System.Security.Principal.IIdentity"); + internal static readonly KnownType System_Security_Principal_IPrincipal = new KnownType("System.Security.Principal.IPrincipal"); + internal static readonly KnownType System_Security_Principal_WindowsIdentity = new KnownType("System.Security.Principal.WindowsIdentity"); internal static readonly KnownType System_Security_SecurityCriticalAttribute = new KnownType("System.Security.SecurityCriticalAttribute"); internal static readonly KnownType System_Security_SecuritySafeCriticalAttribute = new KnownType("System.Security.SecuritySafeCriticalAttribute"); internal static readonly KnownType System_SerializableAttribute = new KnownType("System.SerializableAttribute"); @@ -328,6 +335,7 @@ internal sealed class KnownType internal static readonly KnownType System_ValueType = new KnownType(SpecialType.System_ValueType, "ValueType"); internal static readonly KnownType System_Web_HttpApplication = new KnownType("System.Web.HttpApplication"); internal static readonly KnownType System_Web_HttpCookie = new KnownType("System.Web.HttpCookie"); + internal static readonly KnownType System_Web_HttpContext = new KnownType("System.Web.HttpContext"); internal static readonly KnownType System_Web_Http_ApiController = new KnownType("System.Web.Http.ApiController"); internal static readonly KnownType System_Web_HttpRequestBase = new KnownType("System.Web.HttpRequestBase"); internal static readonly KnownType System_Web_HttpResponseBase = new KnownType("System.Web.HttpResponseBase"); diff --git a/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Helpers/MethodDeclarationTracker.cs b/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Helpers/MethodDeclarationTracker.cs index 995bc42cce4..a5d2ccb3836 100644 --- a/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Helpers/MethodDeclarationTracker.cs +++ b/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Helpers/MethodDeclarationTracker.cs @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -35,7 +36,7 @@ protected MethodDeclarationTracker(IAnalyzerConfiguration analysisConfiguration, { } - protected abstract SyntaxToken GetMethodIdentifier(SyntaxNode methodDeclaration); + protected abstract SyntaxToken? GetMethodIdentifier(SyntaxNode methodDeclaration); public void Track(SonarAnalysisContext context, params MethodDeclarationCondition[] conditions) { @@ -55,7 +56,10 @@ void TrackMethodDeclaration(SymbolAnalysisContext c) foreach (var declaration in c.Symbol.DeclaringSyntaxReferences) { var methodIdentifier = GetMethodIdentifier(declaration.GetSyntax()); - c.ReportDiagnosticWhenActive(Diagnostic.Create(Rule, methodIdentifier.GetLocation())); + if (methodIdentifier.HasValue) + { + c.ReportDiagnosticWhenActive(Diagnostic.Create(Rule, methodIdentifier.Value.GetLocation())); + } } } } @@ -67,12 +71,28 @@ bool IsTrackedMethod(IMethodSymbol methodSymbol, Compilation compilation) } } + internal MethodDeclarationCondition AnyParameterIsOfType(params KnownType[] types) + { + var typesArray = types.ToImmutableArray(); + return (context) => + context.MethodSymbol.Parameters.Length > 0 && + context.MethodSymbol.Parameters.Any(parameter => parameter.Type.DerivesOrImplementsAny(typesArray)); + } + public abstract MethodDeclarationCondition ParameterAtIndexIsUsed(int index); + internal MethodDeclarationCondition DecoratedWithAnyAttribute(params KnownType[] attributeTypes) => + (context) => + context.MethodSymbol.GetAttributes().Any(a => a.AttributeClass.IsAny(attributeTypes)); + public MethodDeclarationCondition MatchSimpleNames(params string[] methodNames) => (context) => methodNames.Contains(context.MethodSymbol.Name); + public MethodDeclarationCondition IsOrdinaryMethod() => + (context) => + context.MethodSymbol.MethodKind == MethodKind.Ordinary; + public MethodDeclarationCondition IsMainMethod() => (context) => context.MethodSymbol.IsMainMethod(); diff --git a/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Helpers/ObjectCreationTracker.cs b/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Helpers/ObjectCreationTracker.cs index 0190dc205b9..1d426d4b03e 100644 --- a/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Helpers/ObjectCreationTracker.cs +++ b/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Helpers/ObjectCreationTracker.cs @@ -19,6 +19,7 @@ */ using System; +using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -72,6 +73,15 @@ internal ObjectCreationCondition FirstArgumentIs(KnownType type) => internal abstract ObjectCreationCondition FirstArgumentIsConstant(); + internal ObjectCreationCondition MatchAnyTypeThatImplements(params KnownType[] types) + { + var typesArray = types.ToImmutableArray(); + return (context) => + context.InvokedConstructorSymbol.Value != null && + context.InvokedConstructorSymbol.Value.IsConstructor() && + context.InvokedConstructorSymbol.Value.ContainingType.DerivesOrImplementsAny(typesArray); + } + internal ObjectCreationCondition MatchConstructors(params KnownType[] types) { // We cannot do a syntax check first because a type name can be aliased with diff --git a/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Rules/Hotspots/ControllingPermissionsBase.cs b/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Rules/Hotspots/ControllingPermissionsBase.cs new file mode 100644 index 00000000000..97554d8fd3e --- /dev/null +++ b/sonaranalyzer-dotnet/src/SonarAnalyzer.Common/Rules/Hotspots/ControllingPermissionsBase.cs @@ -0,0 +1,79 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2018 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using SonarAnalyzer.Helpers; + +namespace SonarAnalyzer.Rules +{ + public abstract class ControllingPermissionsBase : SonarDiagnosticAnalyzer + where TSyntaxKind : struct + { + protected const string DiagnosticId = "S4834"; + protected const string MessageFormat = "Make sure that permissions are controlled safely here."; + + protected ObjectCreationTracker ObjectCreationTracker { get; set; } + protected InvocationTracker InvocationTracker { get; set; } + protected PropertyAccessTracker PropertyAccessTracker { get; set; } + protected MethodDeclarationTracker MethodDeclarationTracker { get; set; } + protected BaseTypeTracker BaseTypeTracker { get; set; } + + protected override void Initialize(SonarAnalysisContext context) + { + ObjectCreationTracker.Track(context, + ObjectCreationTracker.MatchConstructors( + KnownType.System_Security_Permissions_PrincipalPermission)); + + ObjectCreationTracker.Track(context, + ObjectCreationTracker.MatchAnyTypeThatImplements( + KnownType.System_Security_Principal_IIdentity, + KnownType.System_Security_Principal_IPrincipal)); + + InvocationTracker.Track(context, + InvocationTracker.MatchSimpleNames( + new MethodSignature(KnownType.System_Security_Principal_WindowsIdentity, "GetCurrent"), + new MethodSignature(KnownType.System_IdentityModel_Tokens_SecurityTokenHandler, "ValidateToken"), + new MethodSignature(KnownType.System_AppDomain, "SetPrincipalPolicy"), + new MethodSignature(KnownType.System_AppDomain, "SetThreadPrincipal"))); + + PropertyAccessTracker.Track(context, + PropertyAccessTracker.MatchSimpleNames( + new MethodSignature(KnownType.System_Web_HttpContext, "User"), + new MethodSignature(KnownType.System_Threading_Thread, "CurrentPrincipal"))); + + MethodDeclarationTracker.Track(context, + MethodDeclarationTracker.AnyParameterIsOfType( + KnownType.System_Security_Principal_IIdentity, + KnownType.System_Security_Principal_IPrincipal), + MethodDeclarationTracker.IsOrdinaryMethod()); + + MethodDeclarationTracker.Track(context, + MethodDeclarationTracker.DecoratedWithAnyAttribute( + KnownType.System_Security_Permissions_PrincipalPermissionAttribute)); + + BaseTypeTracker.Track(context, + BaseTypeTracker.WhenDerivesFrom( + KnownType.System_Security_Principal_IIdentity)); + + BaseTypeTracker.Track(context, + BaseTypeTracker.WhenDerivesFrom( + KnownType.System_Security_Principal_IPrincipal)); + } + } +} diff --git a/sonaranalyzer-dotnet/src/SonarAnalyzer.Utilities/Rules.Description/S4834_cs.html b/sonaranalyzer-dotnet/src/SonarAnalyzer.Utilities/Rules.Description/S4834_cs.html new file mode 100644 index 00000000000..1f58876d597 --- /dev/null +++ b/sonaranalyzer-dotnet/src/SonarAnalyzer.Utilities/Rules.Description/S4834_cs.html @@ -0,0 +1,80 @@ +

Controlling permissions is security-sensitive. It has led in the past to the following vulnerabilities:

+ +

Attackers can only damage what they have access to. Thus limiting their access is a good way to prevent them from wreaking havoc, but it has to be +done properly.

+

This rule flags code that controls the access to resources and actions or configures this access. The goal is to guide security code reviews.

+

Ask Yourself Whether

+
    +
  • at least one accessed action or resource is security-sensitive.
  • +
  • there is no access control in place or it does not cover all sensitive actions and resources.
  • +
  • users have permissions they don't need.
  • +
  • the access control is based on a user input or on some other unsafe data.
  • +
  • permissions are difficult to remove or take a long time to be updated.
  • +
+

You are at risk if you answered yes to the first question and any of the following ones.

+

Recommended Secure Coding Practices

+

The first step is to restrict all sensitive actions to authenticated users.

+

Each user should have the lowest privileges possible. The access control granularity should match the sensitivity of each resource or action. The +more sensitive it is, the less people should have access to it.

+

Do not base the access control on a user input or on a value which might have been tampered with. For example, the developer should not read a +user's permissions from an HTTP cookie as it can be modified client-side.

+

Check that the access to each action and resource is properly restricted.

+

Enable administrators to swiftly remove permissions when necessary. This enables them to reduce the time an attacker can have access to your +systems when a breach occurs.

+

Log and monitor refused access requests as they can reveal an attack.

+

Questionable Code Example

+
+using System.Threading;
+using System.Security.Permissions;
+using System.Security.Principal;
+using System.IdentityModel.Tokens;
+
+class SecurityPrincipalDemo
+{
+    class MyIdentity : IIdentity // Questionable, custom IIdentity implementations should be reviewed
+    {
+        // ...
+    }
+
+    class MyPrincipal : IPrincipal // Questionable, custom IPrincipal implementations should be reviewed
+    {
+        // ...
+    }
+    [System.Security.Permissions.PrincipalPermission(SecurityAction.Demand, Role = "Administrators")] // Questionable. The access restrictions enforced by this attribute should be reviewed.
+    static void CheckAdministrator()
+    {
+        WindowsIdentity MyIdentity = WindowsIdentity.GetCurrent(); // Questionable
+        HttpContext.User = ...; // Questionable: review all reference (set and get) to System.Web HttpContext.User
+        AppDomain domain = AppDomain.CurrentDomain;
+        domain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); // Questionable
+        MyIdentity identity = new MyIdentity(); // Questionable
+        MyPrincipal MyPrincipal = new MyPrincipal(MyIdentity); // Questionable
+        Thread.CurrentPrincipal = MyPrincipal; // Questionable
+        domain.SetThreadPrincipal(MyPrincipal); // Questionable
+
+        // All instantiation of PrincipalPermission should be reviewed.
+        PrincipalPermission principalPerm = new PrincipalPermission(null, "Administrators"); // Questionable
+        principalPerm.Demand();
+
+        SecurityTokenHandler handler = ...;
+        // Questionable: this creates an identity.
+        ReadOnlyCollection<ClaimsIdentity> identities = handler.ValidateToken(…);
+    }
+
+     // Questionable: review how this function uses the identity and principal.
+    void modifyPrincipal(MyIdentity identity, MyPrincipal principal)
+    {
+        // ...
+    }
+}
+
+

See

+
    +
  • OWASP Top 10 2017 Category A5 - Broken Access Control
  • +
  • SANS Top 25 - Porous Defenses
  • +
+ diff --git a/sonaranalyzer-dotnet/src/SonarAnalyzer.Utilities/Rules.Description/S4834_vb.html b/sonaranalyzer-dotnet/src/SonarAnalyzer.Utilities/Rules.Description/S4834_vb.html new file mode 100644 index 00000000000..3f0c62bbd89 --- /dev/null +++ b/sonaranalyzer-dotnet/src/SonarAnalyzer.Utilities/Rules.Description/S4834_vb.html @@ -0,0 +1,76 @@ +

Controlling permissions is security-sensitive. It has led in the past to the following vulnerabilities:

+ +

Attackers can only damage what they have access to. Thus limiting their access is a good way to prevent them from wreaking havoc, but it has to be +done properly.

+

This rule flags code that controls the access to resources and actions or configures this access. The goal is to guide security code reviews.

+

Ask Yourself Whether

+
    +
  • at least one accessed action or resource is security-sensitive.
  • +
  • there is no access control in place or it does not cover all sensitive actions and resources.
  • +
  • users have permissions they don't need.
  • +
  • the access control is based on a user input or on some other unsafe data.
  • +
  • permissions are difficult to remove or take a long time to be updated.
  • +
+

You are at risk if you answered yes to the first question and any of the following ones.

+

Recommended Secure Coding Practices

+

The first step is to restrict all sensitive actions to authenticated users.

+

Each user should have the lowest privileges possible. The access control granularity should match the sensitivity of each resource or action. The +more sensitive it is, the less people should have access to it.

+

Do not base the access control on a user input or on a value which might have been tampered with. For example, the developer should not read a +user's permissions from an HTTP cookie as it can be modified client-side.

+

Check that the access to each action and resource is properly restricted.

+

Enable administrators to swiftly remove permissions when necessary. This enables them to reduce the time an attacker can have access to your +systems when a breach occurs.

+

Log and monitor refused access requests as they can reveal an attack.

+

Questionable Code Example

+
+Imports System.Threading
+Imports System.Security.Permissions
+Imports System.Security.Principal
+Imports System.IdentityModel.Tokens
+
+Class SecurityPrincipalDemo
+    Class MyIdentity
+        Implements IIdentity ' Questionable, custom IIdentity implementations should be reviewed
+    End Class
+
+    Class MyPrincipal
+        Implements IPrincipal ' Questionable, custom IPrincipal implementations should be reviewed
+    End Class
+
+    <System.Security.Permissions.PrincipalPermission(SecurityAction.Demand, Role:="Administrators")> ' Questionable. The access restrictions enforced by this attribute should be reviewed.
+    Private Shared Sub CheckAdministrator()
+        Dim MyIdentity As WindowsIdentity = WindowsIdentity.GetCurrent() ' Questionable
+
+        HttpContext.User = ... ' Questionable: review all reference (set and get) to System.Web HttpContext.User
+
+        Dim domain As AppDomain = AppDomain.CurrentDomain
+        domain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal) ' Questionable
+
+        Dim identity As MyIdentity = New MyIdentity() ' Questionable
+        Dim MyPrincipal As MyPrincipal = New MyPrincipal(MyIdentity) ' Questionable
+        Thread.CurrentPrincipal = MyPrincipal ' Questionable
+        domain.SetThreadPrincipal(MyPrincipal) ' Questionable
+
+        Dim principalPerm As PrincipalPermission = New PrincipalPermission(Nothing, "Administrators")  ' Questionable
+        principalPerm.Demand()
+
+        Dim handler As SecurityTokenHandler = ...
+        Dim identities As ReadOnlyCollection(Of ClaimsIdentity) = handler.ValidateToken()  ' Questionable, this creates identity
+    End Sub
+
+    ' Questionable: review how this function uses the identity and principal.
+    Private Sub modifyPrincipal(ByVal identity As MyIdentity, ByVal principal As MyPrincipal)
+    End Sub
+End Class
+
+

See

+
    +
  • OWASP Top 10 2017 Category A5 - Broken Access Control
  • +
  • SANS Top 25 - Porous Defenses
  • +
+ diff --git a/sonaranalyzer-dotnet/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicMethodDeclarationTracker.cs b/sonaranalyzer-dotnet/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicMethodDeclarationTracker.cs index 793fc73cf91..95f8dcd44aa 100644 --- a/sonaranalyzer-dotnet/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicMethodDeclarationTracker.cs +++ b/sonaranalyzer-dotnet/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicMethodDeclarationTracker.cs @@ -75,7 +75,26 @@ public override MethodDeclarationCondition ParameterAtIndexIsUsed(int index) => private static bool HasImplementation(MethodBlockSyntax methodBlock) => methodBlock.Statements.Count > 0; - protected override SyntaxToken GetMethodIdentifier(SyntaxNode methodDeclaration) => - ((MethodStatementSyntax)methodDeclaration).Identifier; + protected override SyntaxToken? GetMethodIdentifier(SyntaxNode methodDeclaration) + { + switch (methodDeclaration?.Kind()) + { + case SyntaxKind.SubStatement: + case SyntaxKind.FunctionStatement: + return ((MethodStatementSyntax)methodDeclaration).Identifier; + case SyntaxKind.SubNewStatement: + return ((SubNewStatementSyntax)methodDeclaration).NewKeyword; + case SyntaxKind.AddHandlerAccessorBlock: + case SyntaxKind.RemoveHandlerAccessorBlock: + return ((EventBlockSyntax)methodDeclaration.Parent.Parent).EventStatement?.Identifier; + case SyntaxKind.GetAccessorBlock: + case SyntaxKind.SetAccessorBlock: + return ((PropertyBlockSyntax)methodDeclaration.Parent.Parent).PropertyStatement?.Identifier; + case SyntaxKind.OperatorStatement: + return ((OperatorStatementSyntax)methodDeclaration).OperatorToken; + default: + return null; + } + } } } diff --git a/sonaranalyzer-dotnet/src/SonarAnalyzer.VisualBasic/Rules/Hotspots/ControllingPermissions.cs b/sonaranalyzer-dotnet/src/SonarAnalyzer.VisualBasic/Rules/Hotspots/ControllingPermissions.cs new file mode 100644 index 00000000000..49035adce28 --- /dev/null +++ b/sonaranalyzer-dotnet/src/SonarAnalyzer.VisualBasic/Rules/Hotspots/ControllingPermissions.cs @@ -0,0 +1,55 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2018 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.VisualBasic; +using SonarAnalyzer.Common; +using SonarAnalyzer.Helpers; + +namespace SonarAnalyzer.Rules.VisualBasic +{ + [DiagnosticAnalyzer(LanguageNames.VisualBasic)] + [Rule(DiagnosticId)] + public sealed class ControllingPermissions : ControllingPermissionsBase + { + private static readonly DiagnosticDescriptor rule = + DiagnosticDescriptorBuilder.GetDescriptor(DiagnosticId, MessageFormat, RspecStrings.ResourceManager) + .WithNotConfigurable(); + + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(rule); + + public ControllingPermissions() + : this(new DefaultAnalyzerConfiguration()) + { + } + + internal /*for testing*/ ControllingPermissions(IAnalyzerConfiguration analysisConfiguration) + { + ObjectCreationTracker = new VisualBasicObjectCreationTracker(analysisConfiguration, rule); + InvocationTracker = new VisualBasicInvocationTracker(analysisConfiguration, rule); + PropertyAccessTracker = new VisualBasicPropertyAccessTracker(analysisConfiguration, rule); + MethodDeclarationTracker = new VisualBasicMethodDeclarationTracker(analysisConfiguration, rule); + BaseTypeTracker = new VisualBasicBaseTypeTracker(analysisConfiguration, rule); + } + } +} diff --git a/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/FrameworkMetadataReference.cs b/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/FrameworkMetadataReference.cs index 4b14e617660..44d1c96670b 100644 --- a/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/FrameworkMetadataReference.cs +++ b/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/FrameworkMetadataReference.cs @@ -80,6 +80,9 @@ private static IEnumerable Create(string assemblyName) => internal static IEnumerable SystemGlobalization { get; } = Create("System.Globalization.dll"); + internal static IEnumerable SystemIdentityModel { get; } + = Create("System.IdentityModel.dll"); + internal static IEnumerable SystemReflection { get; } = Create("System.Reflection.dll"); diff --git a/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/PackagingTests/CsRuleTypeMapping.cs b/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/PackagingTests/CsRuleTypeMapping.cs index 6fd5823acf1..3c98c8624fe 100644 --- a/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/PackagingTests/CsRuleTypeMapping.cs +++ b/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/PackagingTests/CsRuleTypeMapping.cs @@ -4831,7 +4831,7 @@ internal static class CsRuleTypeMapping //["4831"], //["4832"], //["4833"], - //["4834"], + ["4834"] = "SECURITY_HOTSPOT", //["4835"], //["4836"], //["4837"], diff --git a/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/PackagingTests/VbRuleTypeMapping.cs b/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/PackagingTests/VbRuleTypeMapping.cs index 9220c63faa8..62d07cf7642 100644 --- a/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/PackagingTests/VbRuleTypeMapping.cs +++ b/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/PackagingTests/VbRuleTypeMapping.cs @@ -4831,7 +4831,7 @@ internal static class VbRuleTypeMapping //["4831"], //["4832"], //["4833"], - //["4834"], + ["4834"] = "SECURITY_HOTSPOT", //["4835"], //["4836"], //["4837"], diff --git a/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/Rules/ControllingPermissionsTest.cs b/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/Rules/ControllingPermissionsTest.cs new file mode 100644 index 00000000000..2234fc12d05 --- /dev/null +++ b/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/Rules/ControllingPermissionsTest.cs @@ -0,0 +1,78 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2018 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +extern alias csharp; +extern alias vbnet; + +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using CSharp = csharp::SonarAnalyzer.Rules.CSharp; +using VisualBasic = vbnet::SonarAnalyzer.Rules.VisualBasic; + +namespace SonarAnalyzer.UnitTest.Rules +{ + [TestClass] + public class ControllingPermissionsTest + { + private static readonly IEnumerable AdditionalReferences = + Enumerable.Empty() + .Concat(FrameworkMetadataReference.SystemIdentityModel) + .Concat(FrameworkMetadataReference.SystemWeb); + + [TestMethod] + [TestCategory("Rule")] + public void ControllingPermissions_CS() + { + Verifier.VerifyAnalyzer(@"TestCases\ControllingPermissions.cs", + new CSharp.ControllingPermissions(new TestAnalyzerConfiguration(null, "S4834")), + additionalReferences: AdditionalReferences); + } + + [TestMethod] + [TestCategory("Rule")] + public void ControllingPermissions_CS_Disabled() + { + Verifier.VerifyNoIssueReported(@"TestCases\ControllingPermissions.cs", + new CSharp.ControllingPermissions(), + additionalReferences: AdditionalReferences); + } + + [TestMethod] + [TestCategory("Rule")] + public void ControllingPermissions_VB() + { + Verifier.VerifyAnalyzer(@"TestCases\ControllingPermissions.vb", + new VisualBasic.ControllingPermissions(new TestAnalyzerConfiguration(null, "S4834")), + additionalReferences: AdditionalReferences); + } + + [TestMethod] + [TestCategory("Rule")] + public void ControllingPermissions_VB_Disabled() + { + Verifier.VerifyNoIssueReported(@"TestCases\ControllingPermissions.vb", + new VisualBasic.ControllingPermissions(), + additionalReferences: AdditionalReferences); + } + } +} + diff --git a/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/TestCases/ControllingPermissions.cs b/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/TestCases/ControllingPermissions.cs new file mode 100644 index 00000000000..b43b4d6128a --- /dev/null +++ b/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/TestCases/ControllingPermissions.cs @@ -0,0 +1,111 @@ +using System; +using System.IdentityModel.Tokens; +using System.Security.Permissions; +using System.Security.Principal; +using System.Threading; +using System.Web; + +namespace Tests.Diagnostics +{ + class Program + { + class MyIdentity : IIdentity // Noncompliant {{Make sure that permissions are controlled safely here.}} +// ^^^^^^^^^ + { + public string Name => throw new NotImplementedException(); + public string AuthenticationType => throw new NotImplementedException(); + public bool IsAuthenticated => throw new NotImplementedException(); + } + + class MyPrincipal : IPrincipal // Noncompliant + { + public IIdentity Identity => throw new NotImplementedException(); + public bool IsInRole(string role) => throw new NotImplementedException(); + } + + // Indirectly implementing IIdentity + class MyWindowsIdentity : WindowsIdentity // Noncompliant + { + public MyWindowsIdentity() : base("") { } + } + + [PrincipalPermission(SecurityAction.Demand, Role = "Administrators")] + void SecuredMethod() { } // Noncompliant, decorated with PrincipalPermission +// ^^^^^^^^^^^^^ + + void ValidateSecurityToken(SecurityTokenHandler handler, SecurityToken securityToken) + { + handler.ValidateToken(securityToken); // Noncompliant + } + + void CreatingPermissions() + { + WindowsIdentity.GetCurrent(); // Noncompliant +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + // All instantiations of PrincipalPermission + PrincipalPermission principalPermission; + principalPermission = new PrincipalPermission(PermissionState.None); // Noncompliant + principalPermission = new PrincipalPermission("", ""); // Noncompliant + principalPermission = new PrincipalPermission("", "", true); // Noncompliant + } + + void HttpContextUser(HttpContext httpContext) + { + var user = httpContext.User; // Noncompliant +// ^^^^^^^^^^^^^^^^ + httpContext.User = user; // Noncompliant +// ^^^^^^^^^^^^^^^^ + } + + void AppDomainSecurity(AppDomain appDomain, IPrincipal principal) // Noncompliant, IPrincipal parameter, see another section with tests + { + appDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); // Noncompliant +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + appDomain.SetThreadPrincipal(principal); // Noncompliant + appDomain.ExecuteAssembly(""); // Compliant, not one of the tracked methods + } + + void ThreadSecurity(IPrincipal principal) // Noncompliant, IPrincipal parameter, see another section with tests + { + Thread.CurrentPrincipal = principal; // Noncompliant +// ^^^^^^^^^^^^^^^^^^^^^^^ + principal = Thread.CurrentPrincipal; // Noncompliant + } + + void CreatingPrincipalAndIdentity(WindowsIdentity windowsIdentity) // Noncompliant, IIdentity parameter, see another section with tests + { + IIdentity identity; + identity = new MyIdentity(); // Noncompliant, creation of type that implements IIdentity +// ^^^^^^^^^^^^^^^^ + identity = new WindowsIdentity(""); // Noncompliant + IPrincipal principal; + principal = new MyPrincipal(); // Noncompliant, creation of type that implements IPrincipal + principal = new WindowsPrincipal(windowsIdentity); // Noncompliant + } + + // Method declarations that accept IIdentiry or IPrincipal + void AcceptIdentity(MyIdentity identity) { } // Noncompliant +// ^^^^^^^^^^^^^^ + void AcceptIdentity(IIdentity identity) { } // Noncompliant + void AcceptPrincipal(MyPrincipal principal) { } // Noncompliant + void AcceptPrincipal(IPrincipal principal) { } // Noncompliant + } + + public class Properties + { + private IIdentity identity; + + public IIdentity Identity + { + get + { + return identity; + } + set // Compliant, we do not raise for property accessors + { + identity = value; + } + } + } +} diff --git a/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/TestCases/ControllingPermissions.vb b/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/TestCases/ControllingPermissions.vb new file mode 100644 index 00000000000..688288acd18 --- /dev/null +++ b/sonaranalyzer-dotnet/tests/SonarAnalyzer.UnitTest/TestCases/ControllingPermissions.vb @@ -0,0 +1,126 @@ +Imports System +Imports System.IdentityModel.Tokens +Imports System.Security.Permissions +Imports System.Security.Principal +Imports System.Threading +Imports System.Web + +Namespace Tests.Diagnostics + Class Program + Class MyIdentity + Implements IIdentity ' Noncompliant {{Make sure that permissions are controlled safely here.}} +' ^^^^^^^^^ + Public ReadOnly Property Name As String + Get + Throw New NotImplementedException() + End Get + End Property + + Public ReadOnly Property AuthenticationType As String + Get + Throw New NotImplementedException() + End Get + End Property + + Public ReadOnly Property IsAuthenticated As Boolean + Get + Throw New NotImplementedException() + End Get + End Property + End Class + + Class MyPrincipal + Implements IPrincipal ' Noncompliant + + Public ReadOnly Property Identity As IIdentity + Get + Throw New NotImplementedException() + End Get + End Property + + Public Function IsInRole(ByVal role As String) As Boolean + Throw New NotImplementedException() + End Function + End Class + + Class MyWindowsIdentity + Inherits WindowsIdentity ' Noncompliant + + Public Sub New() + MyBase.New("") + End Sub + End Class + + + Private Sub SecuredMethod() ' Noncompliant, decorated with PrincipalPermission +' ^^^^^^^^^^^^^ + End Sub + + Private Sub ValidateSecurityToken(ByVal handler As SecurityTokenHandler, ByVal securityToken As SecurityToken) + handler.ValidateToken(securityToken) ' Noncompliant + End Sub + + Private Sub CreatingPermissions() + WindowsIdentity.GetCurrent() ' Noncompliant +' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + ' All instantiations of PrincipalPermission + Dim principalPermission As PrincipalPermission + principalPermission = New PrincipalPermission(PermissionState.None) ' Noncompliant + principalPermission = New PrincipalPermission("", "") ' Noncompliant + principalPermission = New PrincipalPermission("", "", True) ' Noncompliant + End Sub + + Private Sub HttpContextUser(ByVal httpContext As HttpContext) + Dim user = httpContext.User ' Noncompliant + httpContext.User = user ' Noncompliant + End Sub + + Private Sub AppDomainSecurity(ByVal appDomain As AppDomain, ByVal principal As IPrincipal) ' Noncompliant, IPrincipal parameter, see another section with tests + appDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal) ' Noncompliant + appDomain.SetThreadPrincipal(principal) ' Noncompliant + appDomain.ExecuteAssembly("") ' Compliant, not one of the tracked methods + End Sub + + Private Sub ThreadSecurity(ByVal principal As IPrincipal) ' Noncompliant, IPrincipal parameter, see another section with tests + Thread.CurrentPrincipal = principal ' Noncompliant + principal = Thread.CurrentPrincipal ' Noncompliant + End Sub + + Private Sub CreatingPrincipalAndIdentity(ByVal windowsIdentity As WindowsIdentity) ' Noncompliant, IIdentity parameter, see another section with tests + Dim identity As IIdentity + identity = New MyIdentity() ' Noncompliant, creation of type that implements IIdentity + identity = New WindowsIdentity("") ' Noncompliant + Dim principal As IPrincipal + principal = New MyPrincipal() ' Noncompliant, creation of type that implements IPrincipal + principal = New WindowsPrincipal(windowsIdentity) ' Noncompliant + End Sub + + ' Method declarations that accept IIdentiry or IPrincipal + Private Sub AcceptIdentity(ByVal identity As MyIdentity) ' Noncompliant + End Sub + + Private Sub AcceptIdentity(ByVal identity As IIdentity) ' Noncompliant + End Sub + + Private Sub AcceptPrincipal(ByVal principal As MyPrincipal) ' Noncompliant + End Sub + + Private Sub AcceptPrincipal(ByVal principal As IPrincipal) ' Noncompliant + End Sub + End Class + + Public Class Properties + Private identity As IIdentity + + Public Property Identity As IIdentity + Get + Return identity + End Get + Set(ByVal value As IIdentity) ' Compliant, we do not raise for property accessors + identity = value + End Set + End Property + End Class + +End Namespace