Finding 0-day vulnerabilities in apps using the Red Team approach(RTC0005)

Finding 0-day vulnerabilities in apps using the Red Team approach(RTC0005)

Apache Log4j2 Deserialization of Untrusted Data

CVE-2021-44228

CVE-2021-45046 is a security vulnerability that affects the Apache Web Server. Specifically, it is a path traversal vulnerability that could allow an attacker to access sensitive files and directories outside the web root directory. The vulnerability exists due to a flaw in the way that the mod_rewrite module in Apache processes certain URLs. The mod_rewrite module is a powerful tool that allows website administrators to rewrite URLs, which can be useful for search engine optimization, user-friendly URLs, and other purposes. However, if mod_rewrite is not properly configured, it can be exploited by attackers to access files and directories outside the web root directory.

Exploiting this vulnerability requires an attacker to send a specially crafted HTTP request to the Apache Web Server. If successful, the attacker could potentially access sensitive files, including configuration files, user data, and other confidential information. As such, it is recommended that all users of the Apache Web Server apply the patch provided by the ASF to mitigate the risk of exploitation.

Root Cause

Lookups in messages are confusing, and muddy the line between logging APIs
and implementation. Given a particular API, there’s an expectation that a
particular shape of call will result in specific results. However, lookups
in messages can be passed into JUL and will result in resolved output in
log4j formatted output, but not any other implementations despite no direct
dependency on those implementations.

1
2
3
4
5
6
7
8
9
10
11
 public static MessagePatternConverter newInstance(final Configuration config, final String[] options) {
        int noLookupsIdx = loadNoLookups(options);
        boolean noLookups = Constants.FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS || noLookupsIdx >= 0;
        String[] formats = noLookupsIdx >= 0 ? ArrayUtils.remove(options, noLookupsIdx) : options;
        TextRenderer textRenderer = loadMessageRenderer(noLookupsIdx >= 0 ? ArrayUtils.remove(options, noLookupsIdx) : options);
        MessagePatternConverter result = formats == null || formats.length == 0
                ? SimpleMessagePatternConverter.INSTANCE
                : new FormattedMessagePatternConverter(formats);
        if (!noLookups && config != null) {
            result = new LookupMessagePatternConverter(result, config);
        }

https://github.com/apache/logging-log4j2/pull/607/commits/2731a64d1f3e70001f6be61ba5f9b6eb55f88822

CodeQL rule that could detect insecure JNDI lookups

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.sources.JndiLookup
import semmle.code.java.security.JndiInjection

/**
 * Looks for insecure JNDI lookups that could result in JNDI injection attacks.
 */
from MethodAccess ma, JndiLookup jndiLookup, MethodAccess source
where ma = jndiLookup.getAccess()
  and source = ma.getAnAccess()
  and not source.isConstructor()
  and source.getDeclaringType().isSubtypeOf("org.apache.logging.log4j.core.pattern.MessagePatternConverter")
  and jndiLookup.isInsecure()
  and JndiInjection.isSource(source)
select jndiLookup, source, jndiLookup.getArgument(0), "Insecure JNDI lookup found"

CVE-2023-1389

TP-Link Archer AX21 (AX1800) firmware versions before 1.1.4 Build 20230219 contained a command injection vulnerability in the country form of the /cgi-bin/luci;stok=/locale endpoint on the web management interface. Specifically, the country parameter of the write operation was not sanitized before being used in a call to popen(), allowing an unauthenticated attacker to inject commands, which would be run as root, with a simple POST request.

Root Cause

The country parameter, of the write callback for the country form at the /cgi-bin/luci/;stok=/locale endpoint is vulnerable to a simple command injection vulnerability. 
The country parameter was used in a call to popen(), which executes as root, but only after first being set in an initial request.

1
2
3
4
5
6
7
8
import subprocess

command = "ls -l"
try:
    output = subprocess.check_output(command, shell=True)
    print(output)
except subprocess.CalledProcessError as e:
    print("Error executing command:", e)

CodeQL rule to detect potential OS command injection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import python
import DataFlow::PathGraph
import DataFlow::Sanitizers

from CommandInjection import CommandInjectionConfig

class PythonCommandInjection extends python.Method {
  PythonCommandInjection() {
    // Define a string pattern that should not be used in shell command
    let forbidden_pattern = [";", "&", "|", "`", "$", "(", ")", "<", ">", "[", "]", "{", "}", "'", '"']

    // Check if the command string is constructed using user input
    let cmd = getDirectCommandArgument(0)
    let sources = DataFlow::PathGraph::getSources(cmd)
    let tainted_strings = sources.getTaintStrings(DataFlow::Sanitizers::allSanitizers())

    // Check if the command string contains forbidden patterns
    let patterns = forbidden_pattern.toSet()
    let taint_string = tainted_strings.toString().toLowerCase()
    exists(p | patterns.intersection(taint_string.toSet()).getSize() > 0) or
    (
      // Check if the command string is not sanitized
      exists(sanitizer |
        SanitizerFlow::findTransitiveSanitizer(cmd, sanitizer) and
        DataFlow::Sanitizers::toString(sanitizer).toLowerCase().contains("escape_shell_arg") = false
      ) and
      // Check if the subprocess call is vulnerable to command injection
      CommandInjectionConfig config |
      config.hasVulnerableCall(getEnclosingExpr(), "subprocess.check_output", 0)
    )
  }
}

Oracle WebLogic Server Broken Access Control

CVE-2023-21839

Vulnerability in the Oracle WebLogic Server product of Oracle Fusion Middleware (component: Core). Supported versions that are affected are 12.2.1.3.0, 12.2.1.4.0 and 14.1.1.0.0. Easily exploitable vulnerability allows unauthenticated attacker with network access via T3, IIOP to compromise Oracle WebLogic Server. Successful attacks of this vulnerability can result in unauthorized access to critical data or complete access to all Oracle WebLogic Server accessible data.

Root Cause

context

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//iiop
public static void main (String args []) throws Exception {
InitialContextc=getInitialContext("t3://127.0.0.1:7001");
Hashtable<String, String> env = new Hashtable<String, String>();
env. put (Context. INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi. registry. RegistryContextFactory");
weblogic.deployment. jms. Foreign0paqueReference f=new weblogic.deployment.jms. Foreign0paqueReference() ;
Field jndiEnvironment=weblogic.deployment.jms.Foreign0paqueReference.class.getDeclaredField("jndiEnvironment'
jndiEnvironment. setAccessible(true);
jndiEnvironment.set(f, env);
Field remoteJNDIName=weblogic.deployment.jms.Foreign0paqueReference.class.getDeclaredField("remoteJNDIName")
remoteJNDIName.setAccessible(true);
remoteJNDIName.set(f,"ldap://xxxxxxxx/xxx");
c.bind("xxxx", f);
c. lookup ("xxx") ;

https://github.com/advisories/GHSA-f8x3-c29w-wfmj

CodeQL rule that checks for broken access control

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java
import DataFlow::PathGraph
import DataFlow::Sanitizers

// Define the whitelist of allowed remote JNDI names
let allowedJndiNames = ["ldap://127.0.0.1/", "ldap://localhost/"]

class JndiAccessControl extends Method {
  JndiAccessControl() {
    // Find the JMS ForeignOpaqueReference object
    weblogic.deployment.jms.ForeignOpaqueReference f =
      weblogic.deployment.jms.ForeignOpaqueReference.class.getDeclaredField("jndiEnvironment")
      .setAccessible(true)
      .get(thisEnclosingObject())

    // Find the remote JNDI name used in the code
    String remoteJndiName = weblogic.deployment.jms.ForeignOpaqueReference.class.getDeclaredField("remoteJNDIName")
      .setAccessible(true)
      .get(f)

    // Check if the remote JNDI name is from a trusted source
    exists(allowedJndiName | remoteJndiName.startsWith(allowedJndiName)) or
    (
      // Check if the remote JNDI name is constructed from user input
      let sources = DataFlow::PathGraph::getSources(remoteJndiName)
      let taintedStrings = sources.getTaintStrings(DataFlow::Sanitizers::allSanitizers())
      taintedStrings.getSize() > 0
    )
  }
}

MinIO Information Disclosure

CVE-2023-28432

Minio is a Multi-Cloud Object Storage framework. In a cluster deployment starting with RELEASE.2019-12-17T23-16-33Z and prior to RELEASE.2023-03-20T20-16-18Z, MinIO returns all environment variables, including MINIO_SECRET_KEY and MINIO_ROOT_PASSWORD, resulting in information disclosure. All users of distributed deployment are impacted. All users are advised to upgrade to RELEASE.2023-03-20T20-16-18Z.

Root Cause

context

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// minio/cmd/bootstrap-peer-server.go
func (b *bootstrapRESTServer) VerifyHandler(w http.ResponseWriter, r *http.Request) {
  ctx := newContext(r, w, "VerifyHandler")
  cfg := getServerSystemCfg()
  logger.LogIf(ctx, json.NewEncoder(w).Encode(&cfg))
}

// minio/cmd/bootstrap-peer-server.go
func getServerSystemCfg() ServerSystemConfig {
  envs := env.List("MINIO_")
  envValues := make(map[string]string, len(envs))
  for _, envK := range envs {
    // skip certain environment variables as part
    // of the whitelist and could be configured
    // differently on each nodes, update skipEnvs()
    // map if there are such environment values
    if _, ok := skipEnvs[envK]; ok {
      continue
    }
    envValues[envK] = env.Get(envK, "")
  }
  return ServerSystemConfig{
    MinioEndpoints: globalEndpoints,
    MinioEnv:       envValues,
  }
}

https://github.com/minio/minio/security/advisories/GHSA-6xvq-wj2x-3h3q

CodeQL rule that checks for information disclosure vulnerabilities

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import http
import json

class InformationDisclosure extends http.Handler {
  InformationDisclosure() {
    // Find the VerifyHandler method
    let verifyHandlerMethod = exists(Method m | m.getName() = "VerifyHandler" and m.hasParameterOfType("http.Request"))

    // Find the call to getServerSystemCfg()
    let getServerSystemCfgCall = verifyHandlerMethod.getAChild()*.getALocalMethodInvocable()
    .where(callTargetHasName("getServerSystemCfg"))

    // Find the JSON encoder used to serialize the ServerSystemConfig object
    let jsonEncoder = getServerSystemCfgCall.getAChild()*.getALocalMethodInvocable()
    .where(callTargetHasQualifiedName("json.NewEncoder"))

    // Check if the JSON encoder encodes sensitive data
    exists(DataFlow::Path p | p = jsonEncoder.getDataFlow().getPaths().getAPath()
      and p.getDestinationNode().getType().getName() = "http.ResponseWriter"
      and p.getDestinationNode().getIncoming().getAnEdge().getKind() = "HTTP_RESPONSE"
      and p.getSourceNode().getType().hasQualifiedName("minio/cmd/bootstrap-peer-server", "getServerSystemCfg")
      and p.getSourceNode().getIncoming().getAnEdge().getKind() = "NORMAL"
      and p.hasSanitizerFunction(DataFlow::Sanitizers::jsonSanitizer())
      and p.hasTaintLabel("secret"))
  }
}

Apache Spark Command Injection

CVE-2022-33891

The Apache Spark UI offers the possibility to enable ACLs via the configuration option spark.acls.enable. With an authentication filter, this checks whether a user has access permissions to view or modify the application. If ACLs are enabled, a code path in HttpSecurityFilter can allow someone to perform impersonation by providing an arbitrary user name. A malicious user might then be able to reach a permission check function that will ultimately build a Unix shell command based on their input, and execute it. This will result in arbitrary shell command execution as the user Spark is currently running as. This affects Apache Spark versions 3.0.3 and earlier, versions 3.1.1 to 3.1.2, and versions 3.2.0 to 3.2.1.

Root Cause

avoid using bash -c in ShellBasedGroupsMappingProvider. This could allow users a command injection.

1
2
3
4
5
private def getUnixGroups(username: String): Set[String] = {
    val cmdSeq = Seq("bash", "-c", "id -Gn " + username)
    // we need to get rid of the trailing "\n" from the result of command execution
    Utils.executeAndGetOutput(cmdSeq).stripLineEnd.split(" ").toSet
  }

https://github.com/advisories/GHSA-59hw-j9g6-mfg3

CodeQL rule that checks for Apache Spark command injection vulnerabilities

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import scala.meta._
import semmle.code.scala.dataflow.ControlFlow
import semmle.code.scala.dataflow.DataFlow

/**
 * This rule detects Apache Spark command injection vulnerabilities
 * in the 'getUnixGroups' method that executes a shell command
 * without proper input validation or sanitation.
 */
class SparkCommandInjection extends tainttracking.TaintTracking {

  override predicate isSource(DataFlow::Node source) {
    exists(MethodAccess call | call.getTarget().getName() = "executeAndGetOutput"
        and call.getArgument(0) = source)
  }

  override predicate isSink(DataFlow::Node sink) {
    exists(MethodAccess call | call.getTarget().getName() = "bash"
        and call.getArgument(0) = sink)
  }

  override predicate isTaintPropagator(DataFlow::Node source, DataFlow::Node sink) {
    exists(Expr callExpr, ControlFlow::Node sourceNode, ControlFlow::Node sinkNode |
      callExpr.matches(CallExpr) and
      callExpr.getEnclosingMethod().getName() = "getUnixGroups" and
      sourceNode.asExpr() = callExpr.getArgument(0) and
      sinkNode.asExpr() = callExpr.getArgument(1) and
      sourceNode.asExpr().hasTaintLabel("commandInjection") and
      sinkNode.asExpr().hasTaintLabel("commandInjection")
    )
  }

  override predicate isSanitizer(DataFlow::Node node) {
    node.asExpr().matches(LiteralExpr)
  }

  override predicate isEntryPoint(DataFlow::Node node) {
    node.asExpr().getEnclosingMethod().getName() = "getUnixGroups"
  }

  /**
   * This rule taints the argument to the 'executeAndGetOutput' method
   * if it comes from an external source, such as a user input or a file.
   */
  override TaintTracking::Configuration getInitialConfiguration() {
    return TaintTracking::Configuration {
      sources: { Stdin(), EnvironmentVariable() },
      sanitize: { StringLiteral() }
    }
  }
}

Fortra GoAnywhere MFT Remote Code Execution

CVE-2023-0669

Fortra (formerly, HelpSystems) GoAnywhere MFT suffers from a pre-authentication command injection vulnerability in the License Response Servlet due to deserializing an arbitrary attacker-controlled object. This issue was patched in version 7.1.2.

Root Cause

Once the decrypt() function completes, the unbundle() function passes the decrypted stream into verify() That code loads the decrypted object as a Java object, specifically a SignedObject; however, the objectInputStream2.readObject() call is enough to know that this is a deserialization issue.

1
private static byte[] verify(byte[] bDecryptedObject, KeyConfig keyConfig) throws IOException, ClassNotFoundException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, UnrecoverableKeyException, CertificateException, KeyStoreException {        ObjectInputStream objectInputStream = null;        try {            String str = JCAConstants.SIGNATURE_DSA_SHA1;            if ("2".equals(keyConfig.getVersion())) {                str = JCAConstants.SIGNATURE_RSA_SHA512;            }            PublicKey publicKey = getPublicKey(keyConfig);            ObjectInputStream objectInputStream2 = new ObjectInputStream(new ByteArrayInputStream(bDecryptedObject));            SignedObject signedObject = (SignedObject) objectInputStream2.readObject();            if (!signedObject.verify(publicKey, Signature.getInstance(str))) {                throw new IOException("Unable to verify signature!");            }            byte[] data = ((SignedContainer) signedObject.getObject()).getData();            if (objectInputStream2 != null) {                objectInputStream2.close();            }            return data;        } catch (Throwable th) {            if (0 != 0) {                objectInputStream.close();            }            throw th;        }    }

https://github.com/advisories/GHSA-6pm2-j2v8-h3cj

CodeQL rule for finding Remote Code Execution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/**
 * @name Potential Remote Code Execution via SignedObject
 * @description Detects potential Remote Code Execution vulnerabilities via SignedObject
 * @kind path-problem
 * @id java/remote-code-execution-via-signedobject
 * @problem.severity critical
 * @precision high
 * @tags security
 *       external/cwe/cwe-094
 *       external/cwe/cwe-502
 *       external/cwe/cwe-506
 */

import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.DataFlow::PathGraph
import semmle.code.java.dataflow.DataFlow::PathNode
import semmle.code.java.security.CryptographicSignature
import semmle.code.java.security.EncryptedDataFlow
import semmle.code.java.security.EncryptionKey
import semmle.code.java.security.KeyConfig
import semmle.code.java.security.RSA
import semmle.code.java.security.SignatureKey
import semmle.code.java.security.SigningKey
import semmle.code.java.security.SignedObject

class PotentialRemoteCodeExecution extends DataFlow::PathProblem {
  PotentialRemoteCodeExecution() {
    this = "Potential remote code execution via SignedObject"
  }

  override predicate isSource(PathNode src) {
    src.asExpr() instanceof ObjectInputStream
  }

  override predicate isSink(PathNode sink) {
    sink.asExpr() instanceof SignedObject.getObject
  }

  override predicate isSanitizer(PathNode node) {
    node.asExpr() instanceof Signature.verify and
    node.asExpr().getNumberOfArgs() >= 2 and
    node.asExpr().getArgument(1) instanceof Signature.getInstance
  }

  override predicate isTaintThrough(PathNode node) {
    node.asExpr() instanceof ObjectInputStream.readObject
  }

  override predicate isTainted(PathNode node) {
    node.asExpr() instanceof SignedObject
  }

  // check whether a SignedObject is signed with a valid SignatureKey
  private boolean isValidSignature(SignedObject signedObject, SignatureKey sigKey) {
    try {
      return signedObject.verify(sigKey, sigKey.getSignatureInstance())
    } catch (SignatureException e) {
      return false
    }
  }

  // check whether a SignedObject is signed with a SignatureKey obtained via a KeyConfig
  private boolean isSignedBy(KeyConfig keyConfig, SignedObject signedObject) {
    SignatureKey sigKey = null
    if (keyConfig instanceof SigningKey) {
      sigKey = keyConfig.asSigningKey()
    } else if (keyConfig instanceof EncryptionKey) {
      sigKey = ((EncryptionKey) keyConfig).getSigningKey()
    }
    return sigKey != null and isValidSignature(signedObject, sigKey)
  }

  override predicate flow(DataFlow::PathNode src, DataFlow::PathNode dst) {
    dst.asExpr() instanceof SignedObject.getObject and
    src.asExpr() instanceof ObjectInputStream and
    exists(SignedObject signedObject |
      dst.asExpr().getQualifier().toString() = signedObject.toString() and
      isSignedBy(EncryptedDataFlow.getEncryptionKey(signedObject), signedObject)
    )
  }

  override PathGraph getEnclosingPathGraph(PathNode node) {
    // use a more general superclass of PathGraph
    node.getEnclosingGraph().getParentGraph().getContainingGraph()
  }
}

from PotentialRemoteCodeExecution p
select p, p.getNode(), p.getSinkNode(), p.getSanitizerNodes(), p.getTaintedNodes()

Atlassian Bitbucket Server and Data Center Command Injection

CVE-2022-36804

Multiple API endpoints in Atlassian Bitbucket Server and Data Center 7.0.0 before version 7.6.17, from version 7.7.0 before version 7.17.10, from version 7.18.0 before version 7.21.4, from version 8.0.0 before version 8.0.3, from version 8.1.0 before version 8.1.3, and from version 8.2.0 before version 8.2.2, and from version 8.3.0 before 8.3.1 allows remote attackers with read permissions to a public or private Bitbucket repository to execute arbitrary code by sending a malicious HTTP request. This vulnerability was reported via our Bug Bounty Program by TheGrandPew.

Root Cause

context

1
private void prepareProcess(List < String > command, String[] environment, Path cwd) throws IOException { String[] cmdarray = command.toArray(new String[0]); // See https://github.com/JetBrains/jdk8u_jdk/blob/master/src/solaris/classes/java/lang/ProcessImpl.java#L71-L83 byte[][] args = new byte[cmdarray.length - 1][]; int size = args.length; // For added NUL bytes for (int i = 0; i < args.length; i++) { args[i] = cmdarray[i + 1].getBytes(); size += args[i].length; } byte[] argBlock = new byte[size]; int i = 0; for (byte[] arg: args) { System.arraycopy(arg, 0, argBlock, i, arg.length); i += arg.length + 1; // No need to write NUL bytes explicitly } // See https://github.com/JetBrains/jdk8u_jdk/blob/master/src/solaris/classes/java/lang/ProcessImpl.java#L86 byte[] envBlock = toEnvironmentBlock(environment); createPipes(); try { // createPipes() returns the parent ends of the pipes, but forkAndExec requires the child ends int[] child_fds = { stdinWidow, stdoutWidow, stderrWidow }; if (JVM_MAJOR_VERSION >= 10) { pid = com.zaxxer.nuprocess.internal.LibJava10.Java_java_lang_ProcessImpl_forkAndExec( JNIEnv.CURRENT, this, LaunchMechanism.VFORK.ordinal() + 1, toCString(System.getProperty("java.home") + "/lib/jspawnhelper"), // used on Linux toCString(cmdarray[0]), argBlock, args.length, envBlock, environment.length, (cwd != null ? toCString(cwd.toString()) : null), child_fds, (byte) 0 /*redirectErrorStream*/ ); } else { // See https://github.com/JetBrains/jdk8u_jdk/blob/master/src/solaris/classes/java/lang/UNIXProcess.java#L247 // Native source code: https://github.com/JetBrains/jdk8u_jdk/blob/master/src/solaris/native/java/lang/UNIXProcess_md.c#L566 pid = com.zaxxer.nuprocess.internal.LibJava8.Java_java_lang_UNIXProcess_forkAndExec( JNIEnv.CURRENT, this, LaunchMechanism.VFORK.ordinal() + 1, toCString(System.getProperty("java.home") + "/lib/jspawnhelper"), // used on Linux toCString(cmdarray[0]), argBlock, args.length, envBlock, environment.length, (cwd != null ? toCString(cwd.toString()) : null), child_fds, (byte) 0 /*redirectErrorStream*/ ); } } finally { // If we call createPipes, even if launching the process then fails, we need to ensure // the child side of the pipes are closed. The parent side will be closed in onExit closePipes(); } }

https://github.com/advisories/GHSA-vcm2-j8f4-m7fj

CodeQL rule to find command injection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/**
 * @name Command injection via process preparation
 * @description Detects command injection vulnerabilities in the prepareProcess method that constructs the command to be executed by the process.
 * @kind path-problem
 * @problem.severity warning
 * @id java/command-injection/prepareProcess
 */

import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.DataFlow::PathGraph

class CommandInjection extends JavaMethod {
  CommandInjection() { this.getQualifiedName().matches(".*prepareProcess") }

  override predicate hasSink(DataFlow::Node sink) {
    exists(MethodAccess methodAccess |
      sink.asExpr() = methodAccess.getTarget() and
      methodAccess.getMethod().getName() = "getRuntime" and
      this = methodAccess.getQualifier().getASource() and
      sink.getArgument(0).getValue() instanceof String and
      // Only consider arguments that contain whitespace
      sink.getArgument(0).getValue().toString().matches("\\s") and
      // Check if the argument is concatenated with other strings
      exists(BinaryExpression concat |
        concat.getKind() = BinaryOperator::PLUS and
        sink.getArgument(0).getAChild() = concat.getLeftOperand() and
        concat.getRightOperand().getValue() instanceof String and
        concat.getRightOperand().getValue().toString().matches(".*\\s.*") and
        // Check if the concatenated strings are derived from method parameters
        exists(Parameter param |
          param = concat.getLeftOperand().getAChild().getASource() or
          param = concat.getRightOperand().getAChild().getASource()
        )
      )
    )
  }

  override predicate hasSource(DataFlow::Node source) {
    exists(MethodAccess methodAccess |
      source.asExpr() = methodAccess.getTarget() and
      methodAccess.getMethod().getName() = "toArray" and
      this = methodAccess.getQualifier().getASource()
    )
  }

  override predicate flow(PathGraph graph) {
    // Source -> sink flow
    exists(DataFlow::Node source |
      this.hasSource(source) and
      graph.getStart() = source and
      exists(DataFlow::Node sink |
        this.hasSink(sink) and
        graph.getSink() = sink
      )
    )

    // Taint propagation
    exists(DataFlow::Node source, DataFlow::Node intermediate, DataFlow::Node sink |
      this.hasSource(source) and
      graph.getStart() = source and
      graph.containsFlows(intermediate, sink) and
      this.hasSink(sink) and
      intermediate.asExpr() instanceof BinaryExpression and
      intermediate.asExpr().getKind() = BinaryOperator::PLUS and
      intermediate.asExpr().getRightOperand().getValue() instanceof String and
      intermediate.asExpr().getRightOperand().getValue().toString().matches(".*\\s.*") and
      // Check if the left operand of the concatenation is derived from the source
      exists(Parameter param |
        param = intermediate.asExpr().getLeftOperand().getAChild().getASource()
      )
    )
  }
}

Grafana Authentication Bypass

In affected versions unauthenticated and authenticated users are able to view the snapshot with the lowest database key by accessing the literal paths: /dashboard/snapshot/:key, or /api/snapshots/:key. If the snapshot “public_mode” configuration setting is set to true (vs default of false), unauthenticated users are able to delete the snapshot with the lowest database key by accessing the literal path: /api/snapshots-delete/:deleteKey. Regardless of the snapshot “public_mode” setting, authenticated users are able to delete the snapshot with the lowest database key by accessing the literal paths: /api/snapshots/:key, or /api/snapshots-delete/:deleteKey. The combination of deletion and viewing enables a complete walk through all snapshot data while resulting in complete snapshot data loss. This issue has been resolved in versions 8.1.6 and 7.5.11. If for some reason you cannot upgrade you can use a reverse proxy or similar to block access to the literal paths: /api/snapshots/:key, /api/snapshots-delete/:deleteKey, /dashboard/snapshot/:key, and /api/snapshots/:key. They have no normal function and can be disabled without side effects.

CVE-2021-39226

Root Cause

Unauthenticated and authenticated users are able to view the snapshot with the lowest database key by accessing the literal paths:

  • /dashboard/snapshot/:key, or
  • /api/snapshots/:key

If the snapshot “public_mode” configuration setting is set to true (vs default of false), unauthenticated users are able to delete the snapshot with the lowest database key by accessing the literal path:

  • /api/snapshots-delete/:deleteKey

Regardless of the snapshot “public_mode” setting, authenticated users are able to delete the snapshot with the lowest database key by accessing the literal paths:

  • /api/snapshots/:key, or
  • /api/snapshots-delete/:deleteKey

The combination of deletion and viewing enables a complete walk through all snapshot data while resulting in complete snapshot data loss.

https://github.com/advisories/GHSA-69j6-29vr-p3j9

CodeQL rule for detecting an Authentication Bypass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
 * @name Unauthenticated and authenticated access to lowest snapshot key
 * @description Detects accesses to the snapshot with the lowest database key without authentication.
 * @kind path-problem
 * @id java/unauthenticated-snapshot-access
 * @problem.severity warning
 * @precision medium
 * @tags security
 */

import java
import web

// Define the paths that correspond to unauthenticated snapshot access
// (i.e., access to the lowest snapshot key without authentication)
// The paths to detect are:
// - /dashboard/snapshot/:key
// - /api/snapshots/:key
//
// Note: in this rule we assume that the lowest snapshot key is '1'.
//
// Note: we use the `literals` function to identify literal path segments in the URLs.
// If you want to make the rule more precise, you can refine this pattern to match only
// the snapshot URLs of interest (e.g., based on their structure or the HTTP method used).

let unauthenticatedSnapshotPaths = {
  "dashboard": "/dashboard/snapshot/" + literals("1"),
  "api": "/api/snapshots/" + literals("1")
}

// Find HTTP requests that access unauthenticated snapshot paths
from
  // Get all HTTP requests
  http::Request req,
  // Get the URL of the request
  let url = req.getUrl(),
  // Check if the URL matches one of the unauthenticated snapshot paths
  unauthenticatedSnapshotPathName = url.getPath().getSegment(1),
  unauthenticatedSnapshotPaths.containsKey(unauthenticatedSnapshotPathName),
  // Check if the user is authenticated
  not exists(req.getHeader("Authorization"))
select req, "Unauthenticated access to snapshot with key 1"

FortiOS Authentication Bypass

CVE-2022-40684

An authentication bypass using an alternate path or channel [CWE-288] in Fortinet FortiOS version 7.2.0 through 7.2.1 and 7.0.0 through 7.0.6, FortiProxy version 7.2.0 and version 7.0.0 through 7.0.6 and FortiSwitchManager version 7.2.0 and 7.0.0 allows an unauthenticated atttacker to perform operations on the administrative interface via specially crafted HTTP or HTTPS requests.

The return value of the api_check_access function is determined by several sub-functions. When we traced the function process according to the debugging information, we found that no debugging information was output in api_check_access, but the handle_cli_request was called in the sub_c929F0 function to output debugging information. At the same time, after the handle_cli_request function outputs the vdom “root”, the handler function is executed and returns to execute the fweb_debug_final function, and then ends the event response.

determine whether the value of User-Agent in the form is the same as that of Node.js If it is not “Node.js”, judge whether the value of User-Agent is the same as that of Report Runner After entering one of the above two methods, the user is assigned the value “Local_Process_Access”, and the identity authentication will be bypassed at this time. That is to say, if you want to attack successfully, you need to set the Forwarded header value must be “for=”, and then you can set 127.0.0.1 to hide the attack records in the firewall, and the value of User-Agent can be set to “Node.js” and “ One of the two types of Report Runner.

1
2
3
4
5
6
7
8
9
10
11
12
size_t fastcall sub_28994D0 (void *a1)
int v1; // eax
size_t result; // rax
sub_289A450();
v1 = getpid();
sub_2899CE0 ("Local_Process_Access", "Local_Process_Access", al, v1);
result = sec_menkpy (aDaemonAdmin_0, "Local_Process_Access", 0x41uLL);
BYTE2 (dword_46753DC) | = 2u;
LOBYTE (dword_46753DC)
=
1;
return result;

https://github.com/advisories/GHSA-m3wm-rjvf-m778

CodeQL rule for finding possible authentication bypass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import cpp

from Expr e, BinaryExpr be, StringLiteral str
where
  exists(FnDef api_check_access) and
  api_check_access.hasDescendant(
    be.getOperand(0) = e and
    e.getType().toString().find("std::string") >= 0 and
    e.toString() = "User-Agent" and
    exists(StringLiteral userAgent | 
      be.getOperand(1) = userAgent and
      not (userAgent.getValue().toString() = "Node.js" or
           userAgent.getValue().toString() = "Report Runner"))
  ) and
  exists(FnDef sub_c929F0) and
  sub_c929F0.hasDescendant(
    exists(CallExpr handle_cli_request |
      handle_cli_request.getTarget().toString() = "fweb_debug" and
      handle_cli_request.getArgument(0) = str and
      str.getValue().toString() = "root" and
      exists(CallExpr handler | 
        handler.getTarget().toString() = "sub_28994D0" and
        handler.hasReturnValue() and
        handler.getReturnValue().getType().toString().find("size_t") >= 0 and
        handler.getReturnValue().toString() = "1"
      )
    )
  )
select api_check_access, sub_c929F0, handle_cli_request, handler, str

Zimbra Webmail path traversal vulnerability

CVE-2022-24682

An issue was discovered in the Calendar feature in Zimbra Collaboration Suite 8.8.x before 8.8.15 patch 30 (update 1), as exploited in the wild starting in December 2021. An attacker could place HTML containing executable JavaScript inside element attributes. This markup becomes unescaped, causing arbitrary markup to be injected into the document.

Root Cause

There is a path traversal vulnerability in the decompression process of the ZIP archive, which makes it possible to write the shell to any path.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void unzipToTempFiles) throws I0Exception {
Log. mboxmove. debug ( 0: "RestoreAcctSession. unzipToTempFiles) started*) ;
java. util. zip. ZipEntry ze
=
null;
while ((ze =
ZipBackupTarget. this. mZipIn. getNextEntry0)) != null) -
String zn = ze. getName () :
Log. mboxmove. debug( 0: "Unzipping
+ zn) ;
zn
=
zn. replace ( oldChar: '/', File. separatorChar) ;
File file = new File (this. mTempDir, zn) ;
File dir
=
file. getParentFile0;
if (!dir. exists)) {
dir. mkdirs0) ;
FileUtil. copy (ZipBackupTarget. this. mZipIn, closeln: false, file);
ZipBackupTarget. this. mZipIn. closeEntry0);

https://github.com/advisories/GHSA-j5r7-6rm3-99mm

CodeQL rule to find path traversal vulnerabilities

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
 * @name Detect potential path traversal in unzipToTempFiles method
 * @description Detects if the `getName` method can be manipulated to include directory traversal characters
 * @tags java security
 *       path-traversal
 *       file-system
 *       code-scanning
 */
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.TaintTracking::Configuration
import semmle.code.java.dataflow.sources.FileSource
import semmle.code.java.dataflow.sinks.FileSink

class ZipTraversal extends DataFlow::DataFlowQuery {
  ZipTraversal() { this = "ZipTraversal" }

  override predicate isSource(DataFlow::Node source) {
    source.asExpr() instanceof MethodAccess and
    exists(String name | name.matches(".*\\.getName") and source.asExpr().getTarget().getType().getQualifiedName().matches("java.util.zip.ZipEntry"))
  }

  override predicate isSink(DataFlow::Node sink) {
    exists(FileSink fs | fs.getNode() = sink)
  }

  override predicate isSanitizer(DataFlow::Node sanitizer) {
    exists(MethodAccess ma |
      ma.getTarget().hasQualifiedName("java.io.File", "separatorChar") and
      sanitizer.asExpr() = ma.getQualifier()
    )
  }

  from
    MethodAccess ze_getName, File_ze_parentFile, File_dir_exists,
    MethodAccess dir_mkdirs, MethodAccess fu_copy
  where
    ze_getName.getTarget().hasQualifiedName("java.util.zip.ZipEntry", "getName") and
    ze_getName.getQualifier() = ZipBackupTarget.this and
    ze_getName.getResult() = $zipEntryName and
    exists(DataFlow::Node source | source.asExpr() = $zipEntryName.getAnAccessPath().getBase() and source = isSource()) and

    // Traverse file path
    exists(String dir | dir = $zipEntryName.toString().replace('/', java.io.File.separatorChar) and
      File_ze_parentFile.getTarget().hasQualifiedName("java.io.File", "getParentFile") and
      File_ze_parentFile.getQualifier() = file and
      File_ze_parentFile.getResult() = dir and
      File_dir_exists.getTarget().hasQualifiedName("java.io.File", "exists") and
      File_dir_exists.getQualifier() = dir.getParent() and
      not exists(FileSink sink | sink.getNode() = FileSink(file)) and
      dir_mkdirs.getTarget().hasQualifiedName("java.io.File", "mkdirs") and
      dir_mkdirs.getQualifier() = dir.getParent() and
      not exists(FileSink sink | sink.getNode() = FileSink(dir)) and
      fu_copy.getTarget().hasQualifiedName("com.atlassian.jira.util.FileUtil", "copy") and
      fu_copy.getArgument(2) = file
    )

  where not exists(Configuration config | config.hasSink(this))
}

RARLAB UnRAR Directory Traversal

CVE-2022-30333

RARLAB UnRAR before 6.12 on Linux and UNIX allows directory traversal to write to files during an extract (aka unpack) operation, as demonstrated by creating a ~/.ssh/authorized_keys file. NOTE: WinRAR and Android RAR are unaffected.

Root Cause

The DosSlashToUnix() function simply converts all backslashes to forward slashes. Attackers can exploit this behavior as this operation is breaking previous assumptions of the validation step.

1
if (hd->RedirType==FSREDIR_WINSYMLINK || hd->RedirType==FSREDIR_JUNCTION)  { // … DosSlashToUnix(Target,Target,ASIZE(Target)); }

https://www.sonarsource.com/blog/zimbra-pre-auth-rce-via-unrar-0day/

https://github.com/advisories/GHSA-h4mr-p94x-gf79

CodeQL rule that can find instances of Directory Traversal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * @name Directory Traversal Rule
 * @description Detects instances of directory traversal vulnerabilities in C/C++ code
 * @kind path-probing
 * @id c/directory-traversal
 * @tags security
 *       external/cwe/cwe-22
 *       external/cwe/cwe-23
 *       external/cwe/cwe-36
 *       external/cwe/cwe-73
 */

import cpp

from FunctionCall fc, Function f
where
  fc.getTarget().getName() = "DosSlashToUnix" and
  fc.getArgument(1) = "ASIZE(Target)" and
  (fc.getArgument(0).getValue().matches("*../*") or
   fc.getArgument(0).getValue().matches("*../../*") or
   fc.getArgument(0).getValue().matches("*../../../*") or
   fc.getArgument(0).getValue().matches("*../../../../*"))
select fc, f, "Possible directory traversal vulnerability"

XStream Remote Code Execution

CVE-2021-39144

XStream is a simple library to serialize objects to XML and back again. In affected versions this vulnerability may allow a remote attacker has sufficient rights to execute commands of the host only by manipulating the processed input stream. No user is affected, who followed the recommendation to setup XStream’s security framework with a whitelist limited to the minimal required types. XStream 1.4.18 uses no longer a blacklist by default, since it cannot be secured for general purpose.

Root Cause

This code reads an object from an ObjectInputStream that is created using a Reader object. The Reader object is usually created from an input source (e.g. a network socket or a file) that is under the attacker’s control. The readObject() method of the ObjectInputStream class deserializes the input stream and creates an object based on the serialized data. Since the serialized data can contain arbitrary Java objects and code, this can lead to remote code execution if the input is not properly validated.

To fix this vulnerability, the input data should be validated and filtered before it is passed to the ObjectInputStream for deserialization. In general, deserialization should only be performed on trusted input data from a known and secure source.

1
ObjectInputStream objectInputStream = xstream.createObjectInputStream(reader); Object object = objectInputStream.readObject();

CodeQL query that finds instances of deserialization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import java
import java.io.*
import java.net.*

// Find potential deserialization vulnerabilities
// that could lead to code execution.
// This query looks for ObjectInputStream instances that
// are created from untrusted sources.
// It checks if the readObject() method is called on the stream,
// which can lead to arbitrary code execution if the
// deserialized object is untrusted.

// Find all instances of ObjectInputStream being created
// from an untrusted source.
class ObjectInputStreamSource extends DataFlow::DataSource {
  ObjectInputStreamSource() { }

  override predicate isSource(DataFlow::Node source) {
    exists(BinaryExpression expr |
      expr.getAnOperand().(MethodCall m |
        m.getTarget().getName() = "createObjectInputStream" and
        m.getTarget().getDeclaringType().hasQualifiedName("com.thoughtworks.xstream.XStream") and
        m.getArgument(0) = source.asExpr()
      ) and
      expr.getAnOperand().(MethodCall m |
        m.getTarget().getName() = "readObject" and
        m.getTarget().getDeclaringType().hasQualifiedName("java.io.ObjectInputStream")
      )
    )
  }
}

// Find all instances of ObjectInputStream that lead to
// code execution.
class ObjectInputStreamSink extends DataFlow::Sink {
  ObjectInputStreamSink() { }

  override predicate isSink(DataFlow::Node sink) {
    exists(MethodAccess readObject |
      sink.asExpr() = readObject.getAnArgument(0) and
      readObject.getTarget().getName() = "readObject" and
      readObject.getTarget().getDeclaringType().hasQualifiedName("java.io.ObjectInputStream")
    )
  }
}

// Find all paths from the source to the sink.
from SourceNode source, SinkNode sink, DataFlow::PathNode path
where
  source instanceof ObjectInputStreamSource and

October CMS Improper Authentication

CVE-2021-32648

octobercms in a CMS platform based on the Laravel PHP Framework. In affected versions of the october/system package an attacker can request an account password reset and then gain access to the account using a specially crafted request. The issue has been patched in Build 472 and v1.1.5.

Root Cause

Type juggling is a technique used by attackers to manipulate the weak type-checking mechanism of certain programming languages in order to force a comparison between two different types of variables to evaluate as true. In PHP, this is often done by using loose comparison operators, such as '==' instead of '==='.

1
if ($credential == 'password') {

CodeQL query that checks for improper authentication and type juggling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import php
import DataFlow::PathGraph

from FunctionCall call
where
  call.getTarget().getName() = "==" and
  exists(BinaryOperation op | op = call.getArgument(1).getExpr() and op.getKind() = "===") and
  exists(VariableAccess var | var = op.getLeftOperand() and var.getName() = "$credential") and
  exists(StringLiteral string | string = op.getRightOperand() and string.getValue() = "password") and
  exists(MethodAccess xstream | xstream.getQualifier().getType().toString() = "XStream" and xstream.getName() = "createObjectInputStream") and
  exists(MethodAccess readObject | readObject.getQualifier().getType().toString() = "ObjectInputStream" and readObject.getName() = "readObject") and
  exists(DataFlow::PathNode param | param = readObject.getArgument(0).getAnAssignedNode() and param.toString() = "$reader") and
  exists(MethodAccess getHeader | getHeader.getQualifier().getType().toString() = "HTTP" and getHeader.getName() = "getHeader") and
  exists(StringLiteral headerName | headerName.getValue() = "Authorization" and headerName = getHeader.getArgument(0)) and
  exists(StringLiteral headerValue | headerValue = getHeader.getArgument(1) and headerValue.matches("^Basic .*$")) and
  exists(MethodAccess base64decode | base64decode.getName() = "base64_decode" and base64decode.getArgument(0) = headerValue)
select call, "Improper authentication and type juggling detected"

Rating:

comments powered by Disqus