* **Source** – Where you get user data. You should always consider user data tainted and vulnerable to injection attacks.
Example: Calling `HttpServletRequest#getParam("foo")` will return tainted content
-* **Sanitizer** – Finds and removes malicious content from tainted data.
+* **Sanitizer** – Finds and removes malicious content from one or more potentially tainted arguments.
+ Example: `DatabaseUtils#sqlEscapeString(String str)` returns a modified version of `str` where characters used in an SQL injection attack are removed.
+* **Validator** - Marks one or more arguments as safe from malicious content.
+ Example: `String#matches(String str)` can be used to verify that `str` does not contain any content which may be used in an injection attack.
* **Passthrough** – Allows you to keep track of tainted data sent to a library outside the current function. When you pass a tainted value to a library function outside the current function, SonarQube automatically assumes it's being passed to a sanitizer. If the tainted data isn't being passed to a sanitizer, you can set up a passthrough to keep track of the data.
* **Sink** – A piece of code that can perform a security-sensitive task. Data should not contain any malicious content once it reaches a sink.
Example: Running an SQL query with `java.sql.Statement#execute`
| ],
| "sanitizers": [
| {
-| "methodId": "my.package.StringUtils#stringReplace(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"
+| "methodId": "my.package.StringUtils#stringReplace(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
+| "args": [
+| 2
+| ]
+| }
+| ],
+| "validators": [
+| {
+| "methodId": "my.package.StringUtils#equals(Ljava/lang/String;)Z",
+| "args": [
+| 1
+| ]
| }
| ],
| "passthroughs": [
|
| The `args` is the index of the parameter that can receive a tainted variable. Index starts:
| * `1` for a function call.
-| * `0` for a method call, index `0` being the current instance (`this`)
+| * `0` for a method call, index `0` being the current instance (`this`).
+| The `args` field must be a non-empty array of non-negative integers, and it is a mandatory field for sanitizers and validators.
[[collapse]]
| ## PHP JSON file example
| ],
| "sanitizers": [
| {
-| "methodId": "str_replace"
+| "methodId": "str_replace",
+| "args": [
+| 3
+| ]
+| }
+| ],
+| "validators": [
+| {
+| "methodId": "My\\Namespace\\Validator\\inArray::isValid",
+| "args": [
+| 1
+| ]
| }
| ],
| "passthroughs": [
|
| The `args` is the index of the parameter that can receive a tainted variable. Index starts:
| * `1` for a function call.
-| * `0` for a method call, index `0` being the current instance (`this`)
+| * `0` for a method call, index `0` being the current instance (`this`).
+| The `args` field must be a non-empty array of non-negative integers, and it is a mandatory field for sanitizers and validators.
[[collapse]]
| ## C# JSON file example
| ],
| "sanitizers": [
| {
-| "methodId": "My.Namespace.StringUtils.StringReplace(string, string)"
+| "methodId": "My.Namespace.StringUtils.StringReplace(string, string)",
+| "args": [
+| 0
+| ]
+| }
+| ],
+| "validators": [
+| {
+| "methodId": "My.Namespace.StringUtils.Regex.Matches(string)",
+| "args": [
+| 0
+| ]
| }
| ],
| "passthroughs": [
|
| The `args` is the index of the parameter that can receive a tainted variable. Index starts:
| * `1` for a function call.
-| * `0` for a method call, index `0` being the current instance (`this`)
+| * `0` for a method call, index `0` being the current instance (`this`).
+| The `args` field must be a non-empty array of non-negative integers, and it is a mandatory field for sanitizers and validators.
[[collapse]]
| ## Python JSON file example
| ],
| "sanitizers": [
| {
-| "methodId": "str_replace"
+| "methodId": "str_replace",
+| "args": [
+| 1
+| ]
+| }
+| ],
+| "validators": [
+| {
+| "methodId": "my.namespace.regex.matches",
+| "args": [
+| 1
+| ]
| }
| ],
| "passthroughs": [
|
| The `args` is the index of the parameter that can receive a tainted variable. Index starts:
| * `1` for a function call.
-| * `0` for a method call, index `0` being the current instance (`this`)
+| * `0` for a method call, index `0` being the current instance (`this`).
+| The `args` field must be a non-empty array of non-negative integers, and it is a mandatory field for sanitizers and validators.
### (Deprecated) Customizing through analysis parameters
| ],
| "sanitizers": [
| {
-| "methodId": "str_replace"
+| "methodId": "str_replace",
+| "args": [
+| 3
+| ]
+| }
+| ],
+| "validators": [
+| {
+| "methodId": "My\\Namespace\\Validator\\inArray::isValid",
+| "args": [
+| 1
+| ]
| }
| ],
| "passthroughs": [
|
| The `args` is the index of the parameter that can receive a tainted variable. Index starts:
| * `1` for a function call.
-| * `0` for a method call, index `0` being the current instance (`this`)
+| * `0` for a method call, index `0` being the current instance (`this`) .
+| The `args` field must be a non-empty array of non-negative integers, and it is a mandatory field for sanitizers and validators.
## Deactivating the core configuration
" ],\n" +
" \"sanitizers\": [\n" +
" {\n" +
- " \"methodId\": \"str_replace\"\n" +
+ " \"methodId\": \"str_replace\"," +
+ " \"args\": [\n" +
+ " 0\n" +
+ " ]\n" +
+ " }\n" +
+ " ],\n" +
+ " \"validators\": [\n" +
+ " {\n" +
+ " \"methodId\": \"is_valid\"," +
+ " \"args\": [\n" +
+ " 1\n" +
+ " ]\n" +
" }\n" +
" ],\n" +
" \"sinks\": [\n" +
.hasMessageContaining("S3649/sinks/0/methodId: expected type: String, found: Integer");
}
+ @Test
+ @UseDataProvider("securityJsonProperties")
+ public void fail_json_schema_validation_when_sanitizers_have_no_args(String securityPropertyKey) {
+ String security_custom_config = "{\n" +
+ " \"S3649\": {\n" +
+ " \"sources\": [\n" +
+ " {\n" +
+ " \"methodId\": \"My\\\\Namespace\\\\ClassName\\\\ServerRequest::getQuery\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"sanitizers\": [\n" +
+ " {\n" +
+ " \"methodId\": \"SomeSanitizer\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ "}";
+ definitions.addComponent(PropertyDefinition
+ .builder(securityPropertyKey)
+ .name("foo")
+ .description("desc")
+ .category("cat")
+ .subCategory("subCat")
+ .type(PropertyType.JSON)
+ .build());
+
+ assertThatThrownBy(() -> callForGlobalSetting(securityPropertyKey, security_custom_config))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Provided JSON is invalid [#/S3649/sanitizers/0: #: only 1 subschema matches out of 2]");
+ }
+
+ @Test
+ @UseDataProvider("securityJsonProperties")
+ public void fail_json_schema_validation_when_validators_have_empty_args_array(String securityPropertyKey) {
+ String security_custom_config = "{\n" +
+ " \"S3649\": {\n" +
+ " \"sources\": [\n" +
+ " {\n" +
+ " \"methodId\": \"My\\\\Namespace\\\\ClassName\\\\ServerRequest::getQuery\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"validators\": [\n" +
+ " {\n" +
+ " \"methodId\": \"SomeValidator\",\n" +
+ " \"args\": []\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ "}";
+ definitions.addComponent(PropertyDefinition
+ .builder(securityPropertyKey)
+ .name("foo")
+ .description("desc")
+ .category("cat")
+ .subCategory("subCat")
+ .type(PropertyType.JSON)
+ .build());
+
+ assertThatThrownBy(() -> callForGlobalSetting(securityPropertyKey, security_custom_config))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Provided JSON is invalid [#/S3649/validators/0: #: only 1 subschema matches out of 2]");
+ }
+
@Test
@UseDataProvider("securityJsonProperties")
public void fail_json_schema_validation_when_property_has_unknown_attribute(String securityPropertyKey) {