diff --git a/java/ql/lib/semmle/code/java/StringCheck.qll b/java/ql/lib/semmle/code/java/StringCheck.qll new file mode 100644 index 0000000000000..eaf15fdfa2907 --- /dev/null +++ b/java/ql/lib/semmle/code/java/StringCheck.qll @@ -0,0 +1,14 @@ +/** + * Provides classes and predicates for reasoning about checking characterizations about strings. + */ + +import java + +/** + * Holds if `ma` is a call to a method that checks a partial string match. + */ +predicate isStringPartialMatch(MethodAccess ma) { + ma.getMethod().getDeclaringType() instanceof TypeString and + ma.getMethod().getName() = + ["contains", "startsWith", "matches", "regionMatches", "indexOf", "lastIndexOf"] + } diff --git a/java/ql/lib/semmle/code/java/frameworks/apache/Lang.qll b/java/ql/lib/semmle/code/java/frameworks/apache/Lang.qll index b536539670eeb..f691952ac1fe7 100644 --- a/java/ql/lib/semmle/code/java/frameworks/apache/Lang.qll +++ b/java/ql/lib/semmle/code/java/frameworks/apache/Lang.qll @@ -37,3 +37,12 @@ private class ApacheStrBuilderFluentMethod extends FluentMethod { this.getReturnType().(RefType).hasQualifiedName("org.apache.commons.lang3.text", "StrBuilder") } } + +/** + * The class ``org.apache.commons.lang.SystemUtils` or `org.apache.commons.lang3.SystemUtils`. + */ +class ApacheSystemUtilis extends Class { + ApacheSystemUtilis() { + this.hasQualifiedName(["org.apache.commons.lang", "org.apache.commons.lang3"], "SystemUtils") + } +} diff --git a/java/ql/lib/semmle/code/java/os/OSCheck.qll b/java/ql/lib/semmle/code/java/os/OSCheck.qll new file mode 100644 index 0000000000000..e6d384b7c84db --- /dev/null +++ b/java/ql/lib/semmle/code/java/os/OSCheck.qll @@ -0,0 +1,93 @@ +/** + * Provides classes and predicates for guards that check for the current OS. + */ + +import java +import semmle.code.java.controlflow.Guards +private import semmle.code.java.frameworks.apache.Lang +private import semmle.code.java.dataflow.DataFlow +private import semmle.code.java.StringCheck + +/** + * A complimentatry guard that checks if the current platform is Windows. + */ +abstract class IsWindowsGuard extends Guard { } + +/** + * A complimentatry guard that checks if the current platform is unix or unix-like. + */ +abstract class IsUnixGuard extends Guard { } + +/** + * Holds when the MethodAccess is a call to check the current OS using either the upper case `osUpperCase` or lower case `osLowerCase` string constants. + */ +bindingset[osUpperCase, osLowerCase] +private predicate isOsFromSystemProp(MethodAccess ma, string osUpperCase, string osLowerCase) { + exists(MethodAccessSystemGetProperty sgpMa | + sgpMa.hasCompileTimeConstantGetPropertyName("os.name") + | + DataFlow::localExprFlow(sgpMa, ma.getQualifier()) and // Call from System.getProperty to some partial match method + ma.getAnArgument().(CompileTimeConstantExpr).getStringValue().matches(osUpperCase) + or + exists(MethodAccess toLowerCaseMa | + toLowerCaseMa.getMethod() = + any(Method m | m.getDeclaringType() instanceof TypeString and m.hasName("toLowerCase")) + | + DataFlow::localExprFlow(sgpMa, toLowerCaseMa.getQualifier()) and // Call from System.getProperty to toLowerCase + DataFlow::localExprFlow(toLowerCaseMa, ma.getQualifier()) and // Call from toLowerCase to some partial match method + ma.getAnArgument().(CompileTimeConstantExpr).getStringValue().matches(osLowerCase) + ) + ) and + isStringPartialMatch(ma) +} + +private class IsWindowsFromSystemProp extends IsWindowsGuard instanceof MethodAccess { + IsWindowsFromSystemProp() { isOsFromSystemProp(this, "Window%", "window%") } +} + +private class IsUnixFromSystemProp extends IsUnixGuard instanceof MethodAccess { + IsUnixFromSystemProp() { + isOsFromSystemProp(this, ["Mac%", "Linux%", "LINUX%"], ["mac%", "linux%"]) + } +} + +private predicate isOsFromApacheCommons(FieldAccess fa, string fieldName) { + exists(Field f | f = fa.getField() | + f.getDeclaringType() instanceof ApacheSystemUtilis and + f.hasName(fieldName) + ) +} + +private class IsWindowsFromApacheCommons extends IsWindowsGuard instanceof FieldAccess { + IsWindowsFromApacheCommons() { isOsFromApacheCommons(this, "IS_OS_WINDOWS%") } +} + +private class IsUnixFromApacheCommons extends IsUnixGuard instanceof FieldAccess { + IsUnixFromApacheCommons() { isOsFromApacheCommons(this, ["IS_OS_UNIX"]) } +} + +/** + * A guard that checks if the `java.nio.file.FileSystem` supports posix file permissions. + * This is often used to infer if the OS is unix-based. + * Looks for calls to `contains("poxix")` on the `supportedFileAttributeViews` method returned by `FileSystem`. + */ +private class IsUnixFromPosixFromFileSystem extends IsUnixGuard instanceof MethodAccess { + IsUnixFromPosixFromFileSystem() { + exists(Method m | m = this.getMethod() | + m.getDeclaringType() + .getASupertype*() + .getSourceDeclaration() + .hasQualifiedName("java.util", "Set") and + m.hasName("contains") + ) and + this.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "posix" and + exists(Method supportedFileAttribtueViewsMethod | + supportedFileAttribtueViewsMethod.hasName("supportedFileAttributeViews") and + supportedFileAttribtueViewsMethod.getDeclaringType() instanceof TypeFileSystem + | + DataFlow::localExprFlow(any(MethodAccess ma | + ma.getMethod() = supportedFileAttribtueViewsMethod + ), super.getQualifier()) + ) + } +} diff --git a/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.qll b/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.qll index 5c07b67f43d97..9ca5f3af0a4e7 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.qll @@ -5,6 +5,7 @@ import semmle.code.java.dataflow.FlowSources import semmle.code.java.frameworks.Servlets import semmle.code.java.frameworks.spring.SpringWeb import semmle.code.java.security.RequestForgery +private import semmle.code.java.StringCheck private import semmle.code.java.dataflow.StringPrefixes /** A sink for unsafe URL forward vulnerabilities. */ @@ -167,15 +168,6 @@ private class URLEncodingBarrierGuard extends UnsafeUrlForwardBarrierGuard insta } } -/** - * Holds if `ma` is a call to a method that checks a partial string match. - */ -private predicate isStringPartialMatch(MethodAccess ma) { - ma.getMethod().getDeclaringType() instanceof TypeString and - ma.getMethod().getName() = - ["contains", "startsWith", "matches", "regionMatches", "indexOf", "lastIndexOf"] -} - /** * Holds if `ma` is a call to a method of `java.nio.file.Path` that checks a partial path match. */ diff --git a/java/ql/test/library-tests/os/Test.java b/java/ql/test/library-tests/os/Test.java new file mode 100644 index 0000000000000..ce78bbe63785d --- /dev/null +++ b/java/ql/test/library-tests/os/Test.java @@ -0,0 +1,34 @@ +import java.nio.file.FileSystems; +import java.nio.file.Path; + +public class Test { + void test() { + if (System.getProperty("os.name").contains("Windows")) { + + } + + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + + } + + if (System.getProperty("os.name").contains("Linux")) { + + } + + if (System.getProperty("os.name").contains("Mac OS X")) { + + } + + if (System.getProperty("os.name").toLowerCase().contains("mac")) { + + } + + if (Path.of("whatever").getFileSystem().supportedFileAttributeViews().contains("posix")) { + + } + + if (FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) { + + } + } +} diff --git a/java/ql/test/library-tests/os/unix-test.expected b/java/ql/test/library-tests/os/unix-test.expected new file mode 100644 index 0000000000000..8fa5944143d8c --- /dev/null +++ b/java/ql/test/library-tests/os/unix-test.expected @@ -0,0 +1,5 @@ +| Test.java:14:13:14:59 | contains(...) | +| Test.java:18:13:18:62 | contains(...) | +| Test.java:22:13:22:71 | contains(...) | +| Test.java:26:13:26:95 | contains(...) | +| Test.java:30:13:30:84 | contains(...) | diff --git a/java/ql/test/library-tests/os/unix-test.ql b/java/ql/test/library-tests/os/unix-test.ql new file mode 100644 index 0000000000000..b2b48d2df7e6f --- /dev/null +++ b/java/ql/test/library-tests/os/unix-test.ql @@ -0,0 +1,5 @@ +import default +import semmle.code.java.os.OSCheck + +from IsUnixGuard isUnix +select isUnix \ No newline at end of file diff --git a/java/ql/test/library-tests/os/windows-test.expected b/java/ql/test/library-tests/os/windows-test.expected new file mode 100644 index 0000000000000..38fa6b7a17930 --- /dev/null +++ b/java/ql/test/library-tests/os/windows-test.expected @@ -0,0 +1,2 @@ +| Test.java:6:13:6:61 | contains(...) | +| Test.java:10:13:10:75 | contains(...) | diff --git a/java/ql/test/library-tests/os/windows-test.ql b/java/ql/test/library-tests/os/windows-test.ql new file mode 100644 index 0000000000000..4027d6da8a1e1 --- /dev/null +++ b/java/ql/test/library-tests/os/windows-test.ql @@ -0,0 +1,5 @@ +import default +import semmle.code.java.os.OSCheck + +from IsWindowsGuard isWindows +select isWindows \ No newline at end of file