Skip to content

Commit 1d9bbc6

Browse files
authored
Add rules 002_0010, 002_0011 and 002_0012
- `002_0010` checks for attributes with ReadWrite access that are used in XPath queries - `002_0011` checks for attributes with no max length that can be edited by anonymous users - `002_0012` checks for anonymous users that can create/instantiate persistent entities - Recognizing anonymous user roles is a bit implicit, so double check on that
1 parent fc135cf commit 1d9bbc6

6 files changed

Lines changed: 387 additions & 0 deletions
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# METADATA
2+
# scope: package
3+
# title: Access rules using XPath should only contain attributes and associations with Read access
4+
# description: If an XPath uses attributes or associations with read and write access, it may cause a security breach
5+
# authors:
6+
# - Bart Zantingh <bart.zantingh@nl.abnamro.com>
7+
# custom:
8+
# category: Security
9+
# rulename: AccessRuleXpathConstraintsWithReadWrite
10+
# severity: HIGH
11+
# rulenumber: "002_0010"
12+
# remediation: Ensure that all attributes and associations used in XPath constraints have read-only access
13+
# input: "*/DomainModels$DomainModel.yaml"
14+
package app.mendix.domain_model.access_rules_with_read_write_in_xpath
15+
16+
import rego.v1
17+
18+
annotation := rego.metadata.chain()[1].annotations
19+
20+
default allow := false
21+
22+
allow if count(errors) == 0
23+
24+
# check XPaths for attributes with ReadWrite access
25+
errors contains error if {
26+
some entity in input.Entities
27+
entity_name := entity.Name
28+
29+
attributes := [attribute |
30+
some attribute in entity.Attributes
31+
attribute["$Type"] == "DomainModels$Attribute"
32+
]
33+
34+
some attribute in attributes
35+
has_read_write_access_to_attribute(entity, attribute.Name)
36+
used_in_xpath(entity, attribute.Name)
37+
38+
error := sprintf(
39+
"[%v, %v, %v] Entity %v has an XPath constraint that uses one or more attributes with read/write access",
40+
[
41+
annotation.custom.severity,
42+
annotation.custom.category,
43+
annotation.custom.rulenumber,
44+
entity_name,
45+
],
46+
)
47+
}
48+
49+
# check XPaths for associations with ReadWrite access
50+
errors contains error if {
51+
some association in input.Associations
52+
53+
some entity in input.Entities
54+
has_read_write_access_to_association(entity, association.Name)
55+
used_in_xpath(entity, association.Name)
56+
57+
entity_name := entity.Name
58+
59+
error := sprintf(
60+
"[%v, %v, %v] Entity %v has an XPath constraint that uses one or more assocations with read/write access",
61+
[
62+
annotation.custom.severity,
63+
annotation.custom.category,
64+
annotation.custom.rulenumber,
65+
entity_name,
66+
],
67+
)
68+
}
69+
70+
has_read_write_access_to_attribute(entity, attribute_name) if {
71+
some access_rule in entity.AccessRules
72+
73+
some member_access in access_rule.MemberAccesses
74+
member_access.AccessRights == "ReadWrite"
75+
contains(member_access.Attribute, attribute_name)
76+
}
77+
78+
has_read_write_access_to_association(entity, association_name) if {
79+
some access_rule in entity.AccessRules
80+
81+
some member_access in access_rule.MemberAccesses
82+
member_access.AccessRights == "ReadWrite"
83+
contains(member_access.Association, association_name)
84+
}
85+
86+
# check if there are XPath constraints that contain the name of the attribute or association
87+
used_in_xpath(entity, search_term) if {
88+
some access_rule in entity.AccessRules
89+
contains(access_rule.XPathConstraint, search_term)
90+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package app.mendix.domain_model.access_rules_with_read_write_in_xpath_test
2+
3+
import data.app.mendix.domain_model.access_rules_with_read_write_in_xpath
4+
import rego.v1
5+
6+
# Test data
7+
attribute_with_read_used_in_xpath := {"Entities": [{
8+
"AccessRules": [{
9+
"MemberAccesses": [{
10+
"AccessRights": "Read",
11+
"Attribute": "MxLintTest.TestEntity.Attribute1",
12+
"Association": "",
13+
}],
14+
"XPathConstraint": "[Attribute1 = true]",
15+
}],
16+
"Attributes": [{
17+
"$Type": "DomainModels$Attribute",
18+
"Name": "Attribute1",
19+
}],
20+
"Name": "TestEntity",
21+
}]}
22+
23+
attribute_with_read_write_used_in_xpath := {"Entities": [{
24+
"AccessRules": [{
25+
"MemberAccesses": [{
26+
"AccessRights": "ReadWrite",
27+
"Attribute": "MxLintTest.TestEntity.Attribute1",
28+
"Association": "",
29+
}],
30+
"XPathConstraint": "[Attribute1 = true]",
31+
}],
32+
"Attributes": [{
33+
"$Type": "DomainModels$Attribute",
34+
"Name": "Attribute1",
35+
}],
36+
"Name": "TestEntity",
37+
}]}
38+
39+
association_with_read_used_in_xpath := {
40+
"Associations": [{"Name": "TestEntity_Entity"}],
41+
"Entities": [{
42+
"AccessRules": [{
43+
"MemberAccesses": [{
44+
"AccessRights": "Read",
45+
"Attribute": "",
46+
"Association": "MxLintTest.TestEntity_Entity",
47+
}],
48+
"XPathConstraint": "[MxLintTest.TestEntity_Entity/MxLintTest.Entity]",
49+
}],
50+
"Name": "TestEntity",
51+
}],
52+
}
53+
54+
association_with_read_write_used_in_xpath := {
55+
"Associations": [{"Name": "TestEntity_Entity"}],
56+
"Entities": [{
57+
"AccessRules": [{
58+
"MemberAccesses": [{
59+
"AccessRights": "ReadWrite",
60+
"Attribute": "",
61+
"Association": "MxLintTest.TestEntity_Entity",
62+
}],
63+
"XPathConstraint": "[MxLintTest.TestEntity_Entity/MxLintTest.Entity]",
64+
}],
65+
"Name": "TestEntity",
66+
}],
67+
}
68+
69+
# Test cases
70+
test_should_allow_when_default_access_rights_none_or_read if {
71+
access_rules_with_read_write_in_xpath.allow with input as attribute_with_read_used_in_xpath
72+
access_rules_with_read_write_in_xpath.allow with input as association_with_read_used_in_xpath
73+
}
74+
75+
test_should_deny_when_attribute_with_read_write_in_xpath if {
76+
not access_rules_with_read_write_in_xpath.allow with input as attribute_with_read_write_used_in_xpath
77+
}
78+
79+
test_should_deny_when_association_with_read_write_in_xpath if {
80+
not access_rules_with_read_write_in_xpath.allow with input as association_with_read_write_used_in_xpath
81+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# METADATA
2+
# scope: package
3+
# title: Unlimited length string attributes should not be editable by anonymous users
4+
# description: A malicious agent could set a very long value for the attribute causing the database to run out of space
5+
# authors:
6+
# - Bart Zantingh <bart.zantingh@nl.abnamro.com>
7+
# custom:
8+
# category: Security
9+
# rulename: UnlimitedLengthAttributesEditableByAnonymous
10+
# severity: CRITICAL
11+
# rulenumber: "002_0011"
12+
# remediation: Ensure that anonymous users have, at most, only read access to attributes with unlimited length
13+
# input: "*/DomainModels$DomainModel.yaml"
14+
package app.mendix.domain_model.unlimited_length_attributes_editable_by_anonymous
15+
16+
import rego.v1
17+
18+
annotation := rego.metadata.chain()[1].annotations
19+
20+
default allow := false
21+
22+
allow if count(errors) == 0
23+
24+
errors contains error if {
25+
some entity in input.Entities
26+
27+
anon_has_access(entity)
28+
29+
attributes := [attribute |
30+
some attribute in entity.Attributes
31+
attribute["$Type"] == "DomainModels$Attribute"
32+
attribute.NewType["$Type"] == "DomainModels$StringAttributeType"
33+
attribute.NewType.Length == 0
34+
]
35+
36+
some access_rule in entity.AccessRules
37+
some member_access in access_rule.MemberAccesses
38+
member_access.AccessRights == "ReadWrite"
39+
40+
some attribute in attributes
41+
contains(member_access.Attribute, attribute.Name)
42+
43+
error := sprintf(
44+
"[%v, %v, %v] String attribute %v in entity %v has unlimited length and seems to be editable by anonymous users",
45+
[
46+
annotation.custom.severity,
47+
annotation.custom.category,
48+
annotation.custom.rulenumber,
49+
attribute.Name,
50+
entity.Name,
51+
],
52+
)
53+
}
54+
55+
anon_has_access(entity) if {
56+
some access_rule in entity.AccessRules
57+
some module_role in access_rule.AllowedModuleRoles
58+
contains(lower(module_role), "anon")
59+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package app.mendix.domain_model.unlimited_length_attributes_editable_by_anonymous_test
2+
3+
import data.app.mendix.domain_model.unlimited_length_attributes_editable_by_anonymous
4+
import rego.v1
5+
6+
# Test data
7+
anon_with_readonly_access := {"Entities": [{
8+
"AccessRules": [{
9+
"AllowedModuleRoles": ["MxLintTest.Anonymous"],
10+
"MemberAccesses": [{
11+
"$Type": "DomainModels$MemberAccess",
12+
"AccessRights": "ReadOnly",
13+
"Attribute": "MxLintTest.TestEntity.Attribute",
14+
}],
15+
}],
16+
"Attributes": [{
17+
"$Type": "DomainModels$Attribute",
18+
"Name": "Attribute",
19+
"NewType": {
20+
"$Type": "DomainModels$StringAttributeType",
21+
"Length": 0,
22+
},
23+
}],
24+
"Name": "TestEntity",
25+
}]}
26+
27+
anon_with_readwrite_access := {"Entities": [{
28+
"AccessRules": [{
29+
"AllowedModuleRoles": ["MxLintTest.Anonymous"],
30+
"MemberAccesses": [{
31+
"$Type": "DomainModels$MemberAccess",
32+
"AccessRights": "ReadWrite",
33+
"Attribute": "MxLintTest.TestEntity.Attribute",
34+
}],
35+
}],
36+
"Attributes": [{
37+
"$Type": "DomainModels$Attribute",
38+
"Name": "Attribute",
39+
"NewType": {
40+
"$Type": "DomainModels$StringAttributeType",
41+
"Length": 0,
42+
},
43+
}],
44+
"Name": "TestEntity",
45+
}]}
46+
47+
# Test cases
48+
test_should_allow_when_anon_has_readonly_access if {
49+
unlimited_length_attributes_editable_by_anonymous.allow with input as anon_with_readonly_access
50+
}
51+
52+
test_should_deny_when_anon_has_readwrite_access if {
53+
not unlimited_length_attributes_editable_by_anonymous.allow with input as anon_with_readwrite_access
54+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# METADATA
2+
# scope: package
3+
# title: Anonymous users should not be allowed to create persistent entities
4+
# description: Anonymous users with create access to persistent entities can pose security risks
5+
# authors:
6+
# - Bart Zantingh <bart.zantingh@nl.abnamro.com>
7+
# custom:
8+
# category: Security
9+
# rulename: AnonymousUsersWithCreateAccess
10+
# severity: HIGH
11+
# rulenumber: "002_0012"
12+
# remediation: Remove create access or make entity non-persistent
13+
# input: "*/DomainModels$DomainModel.yaml"
14+
package app.mendix.domain_model.anonymous_users_with_create_access
15+
16+
import rego.v1
17+
18+
annotation := rego.metadata.chain()[1].annotations
19+
20+
default allow := false
21+
22+
allow if count(errors) == 0
23+
24+
errors contains error if {
25+
some entity in input.Entities
26+
entity.MaybeGeneralization.Persistable == true
27+
entity_name := entity.Name
28+
29+
some access_rule in entity.AccessRules
30+
access_rule.AllowCreate == true
31+
32+
some module_role in access_rule.AllowedModuleRoles
33+
contains(lower(module_role), "anon") # converts module role name to lower case so it catches all variants of spelling
34+
35+
module_name := split(module_role, ".")[0]
36+
module_role_name := split(module_role, ".")[1]
37+
38+
error := sprintf(
39+
"[%v, %v, %v] Module role %v in module %v seems to be for anonymous users, but has Create access to persistent entity %v",
40+
[
41+
annotation.custom.severity,
42+
annotation.custom.category,
43+
annotation.custom.rulenumber,
44+
module_role_name,
45+
module_name,
46+
entity_name,
47+
],
48+
)
49+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package app.mendix.domain_model.anonymous_users_with_create_access_test
2+
3+
import data.app.mendix.domain_model.anonymous_users_with_create_access
4+
import rego.v1
5+
6+
# Test data
7+
anon_role_with_create_access_to_persistent := {
8+
"$Type": "DomainModels$DomainModel",
9+
"Entities": [{
10+
"AccessRules": [{
11+
"AllowCreate": true,
12+
"AllowedModuleRoles": ["MyFirstModule.Anonymous"],
13+
}],
14+
"MaybeGeneralization": {"Persistable": true},
15+
"Name": "Entity",
16+
}],
17+
}
18+
19+
anon_role_without_create_access_to_persistent := {
20+
"$Type": "DomainModels$DomainModel",
21+
"Entities": [{
22+
"AccessRules": [{
23+
"AllowCreate": false,
24+
"AllowedModuleRoles": ["MyFirstModule.Anonymous"],
25+
}],
26+
"MaybeGeneralization": {"Persistable": true},
27+
"Name": "Entity",
28+
}],
29+
}
30+
31+
anon_role_with_create_access_to_nonpersistent := {
32+
"$Type": "DomainModels$DomainModel",
33+
"Entities": [{
34+
"AccessRules": [{
35+
"AllowCreate": true,
36+
"AllowedModuleRoles": ["MyFirstModule.Anonymous"],
37+
}],
38+
"MaybeGeneralization": {"Persistable": false},
39+
"Name": "Entity",
40+
}],
41+
}
42+
43+
# Test cases
44+
test_should_allow_when_anon_role_has_no_create_access_to_persistent_entity if {
45+
anonymous_users_with_create_access.allow with input as anon_role_without_create_access_to_persistent
46+
}
47+
48+
test_should_allow_when_anon_role_has_create_access_to_nonpersistent_entity if {
49+
anonymous_users_with_create_access.allow with input as anon_role_with_create_access_to_nonpersistent
50+
}
51+
52+
test_should_deny_when_anon_role_has_create_access_to_persistent_entity if {
53+
not anonymous_users_with_create_access.allow with input as anon_role_with_create_access_to_persistent
54+
}

0 commit comments

Comments
 (0)