diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java index 37242dfa1d..48b0efe16b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.Base64; import java.util.List; import java.util.function.Function; +import java.util.regex.Pattern; import static org.apache.commons.lang3.StringUtils.repeat; @@ -37,6 +38,12 @@ public class StringUtils { public static final int INDEX_NOT_FOUND = -1; + public static final Pattern CONTROL_CHARS = Pattern.compile("[\\x00-\\x1F\\x7F]"); + + public static boolean containsControlChars(String source) { + return source != null && CONTROL_CHARS.matcher(source).find(); + } + public static boolean isEmpty(String source) { return source == null || source.isEmpty(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java index 96ffa613c7..c053f36d5b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java @@ -30,13 +30,9 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.dao.service.DataValidator; -import java.util.regex.Pattern; - @Component public class DeviceCredentialsDataValidator extends DataValidator { - private static final Pattern CONTROL_CHARS = Pattern.compile("[\\x00-\\x1F\\x7F]"); - @Autowired private DeviceCredentialsDao deviceCredentialsDao; @@ -92,7 +88,7 @@ public class DeviceCredentialsDataValidator extends DataValidator validator.validateDataImpl(tenantId, creds)) + .doesNotThrowAnyException(); + } + private DeviceCredentials accessToken(String token) { DeviceCredentials c = new DeviceCredentials(); c.setDeviceId(deviceId); diff --git a/dao/src/test/java/org/thingsboard/server/dao/util/DeviceConnectivityUtilTest.java b/dao/src/test/java/org/thingsboard/server/dao/util/DeviceConnectivityUtilTest.java index b69fefeea0..2c6fee2fcf 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/util/DeviceConnectivityUtilTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/util/DeviceConnectivityUtilTest.java @@ -81,6 +81,39 @@ class DeviceConnectivityUtilTest { assertNoInjectedSiblingKeys(yaml); } + @Test + void mqttBasicQuoteInUserNameIsEscapedInPublishCommand() { + String command = DeviceConnectivityUtil.getMqttPublishCommand( + "mqtt", "localhost", "1883", "v1/devices/me/telemetry", + mqttBasic("cid", "u\";touch pwned;echo \"", "pwd")); + + // the double quote must be backslash-escaped so it cannot terminate the -u "..." argument + assertThat(command).contains("-u \"u\\\";touch pwned;echo \\\"\""); + assertThat(command).doesNotContain("-u \"u\";"); + } + + @Test + void controlCharsInMqttClientIdAreStrippedInPublishCommand() { + String command = DeviceConnectivityUtil.getMqttPublishCommand( + "mqtt", "localhost", "1883", "v1/devices/me/telemetry", + mqttBasic("c\nid", "user", "pwd")); + + assertThat(command).doesNotContain("\n"); + assertThat(command).contains("-i \"c_id\""); + } + + @Test + void controlCharsInAccessTokenAreStrippedInHttpAndCoapCommands() { + DeviceCredentials creds = accessToken("tok\nen"); + + assertThat(DeviceConnectivityUtil.getHttpPublishCommand("http", "localhost", ":8080", creds)) + .doesNotContain("\n") + .contains("/api/v1/tok_en/telemetry"); + assertThat(DeviceConnectivityUtil.getCoapPublishCommand("coap", "localhost", ":5683", creds)) + .doesNotContain("\n") + .contains("/api/v1/tok_en/telemetry"); + } + private static String renderCompose(DeviceCredentials credentials) throws Exception { var resource = DeviceConnectivityUtil.getGatewayDockerComposeFile( "host.docker.internal", "3.8-stable", credentials);