github / codeql

CodeQL: the libraries and queries that power security researchers around the world, as well as code scanning in GitHub Advanced Security
https://codeql.github.com
MIT License
7.67k stars 1.54k forks source link

Problems encountered by Java projects when constructing queries in codeql #9843

Closed ernongxizhu closed 2 years ago

ernongxizhu commented 2 years ago

Some Java projects use third-party jar packages. My command to build the database is codeql database create ofcms-ql --language=java --command="mvn clean install dependency:sources"

When querying, it is found that the jar package of the third party cannot be found And prompt: Unable to handleMsgFromView: cannot open codeql-zip-archive://1-40/d%3A%5Ccodeql%5Cofcms-V1.1.2%5Cofcms-ql%5Csrc.zip/C_/Users/User/.m2/repository/com/jfinal/jfinal/3.2/jfinal-3.2.jar/com/jfinal/plugin/activerecord/Db.class. Detail: Unable to read file 'codeql-zip-archive://1-40/d:\codeql\ofcms-V1.1.2\ofcms-ql\src.zip/C_/Users/User/.m2/repository/com/jfinal/jfinal/3.2/jfinal-3.2.jar/com/jfinal/plugin/activerecord/Db.class' (Error: Unable to resolve nonexistent file 'codeql-zip-archive://1-40/d:\codeql\ofcms-V1.1.2\ofcms-ql\src.zip/C_/Users/User/.m2/repository/com/jfinal/jfinal/3.2/jfinal-3.2.jar/com/jfinal/plugin/activerecord/Db.class')

Code address: https://gitee.com/oufu/ofcms/repository/archive/V1.1.2?format=zip

Query statement:

import java

class UserMapper extends RefType{
    UserMapper(){
        this.hasQualifiedName("com.jfinal.plugin.activerecord", "Db")
    }
}
predicate sql(MethodAccess ma) {
    ma.getMethod().getName() = "update"
    and ma.getMethod().getDeclaringType() instanceof UserMapper
}

from MethodAccess ma
where sql(ma)
select ma.getMethod(),ma.getEnclosingCallable().getQualifiedName()
smowton commented 2 years ago

This is expected: that zip file contains source code that was compiled during the build process, and the dependency:sources goal only retrieves the source code of dependencies; it doesn't actually build the dependency (in this case, JFinal). The selected ma.getMethod() is outside OFCMS's source code (it's the update method of the JFinal class Db), so we don't have source code for it in the database.

What you should do depends on what you want to achieve: you're looking for calls to Db.update, and presumably you're only interested in calls made from within the OFCMS codebase? If so then you don't need the source of JFinal; you can remove the dependency:sources goal, and select ma (the call-site) rather than ma.getMethod() to get working links to the place where OFCMS calls the JFinal method in question.

On the other hand if you do care about a third-party project's calls to update, then you need to build (or download an existing) database for that project instead of OFCMS. The case where you need a single database for both projects occurs if you need to do things like track data-flow through third-party code: in that case you should place both project sources under a common root directory, make a build.sh like (cd project1 && mvn clean install); (cd project2 && mvn clean install), then use that for your -c build command. This will mean that CodeQL sees both projects get built and indexes the full source code of both.

ernongxizhu commented 2 years ago

创建一个build.shlike (cd project1 && mvn clean install); (cd project2 && mvn clean install)

If this method is used,The databases that generate ofcms and jfinal are not associated. Data flow is disconnected.

ernongxizhu commented 2 years ago

I want to define a source and a sink, but this sink is in the jar package of jfinal, which makes my data flow construction unsuccessful. Do you have any solutions?

image

The sink I want to define is this preparestatement (sql), which I cannot define in the jfinal package

smowton commented 2 years ago

Works for me:

Method:

  1. Create a root directory /tmp/work
  2. Unzip your gitee zip file to /tmp/work/ofcms-V1.1.2
  3. Clone https://github.com/jfinal/jfinal to /tmp/work/jfinal, and git checkout jfinal-3.2 to match the version you're using
  4. Create /tmp/work/build.sh:
#!/bin/bash

(cd ofcms-V1.1.2 && mvn clean package -DskipTests)
(cd jfinal && mvn clean package -DskipTests)
  1. Create a database: cd /tmp/work && codeql database create -l java -c "./build.sh" db
  2. Query:
/**
 * @kind path-problem
 */

import java
import semmle.code.java.dataflow.DataFlow

import DataFlow::PathGraph

class Config extends DataFlow::Configuration {

  Config() {
    this = "MyTestConfig"
  }

  override predicate isSource(DataFlow::Node n) {
    exists(StringLiteral l |
      n.asExpr() = l and
      l.getLocation().getFile().getAbsolutePath().matches("%ofcms%")
    )
  }

  override predicate isSink(DataFlow::Node n) {
    n.asExpr().(Argument).getCall().getCallee().getName() = "prepareStatement"
  }

}

from DataFlow::PathNode n1, DataFlow::PathNode n2, Config c
where c.hasFlowPath(n1, n2)
select n1, n1, n2, "path"

This is just an example that will find string constants in your code that eventually find their way to a prepareStatement call.

Running this reveals the first result shows "system.user.delete" from SysUserController.java flowing to Db.update then DbPro.update then finally the prepareStatement call you flagged.

Of course you would want to change the isSource to track something more interesting than any string literal in OFCMS code, but I hope this helps you get started.

ernongxizhu commented 2 years ago

Works for me:

Method:

  1. Create a root directory /tmp/work
  2. Unzip your gitee zip file to /tmp/work/ofcms-V1.1.2
  3. Clone https://github.com/jfinal/jfinal to /tmp/work/jfinal, and git checkout jfinal-3.2 to match the version you're using
  4. Create /tmp/work/build.sh:
#!/bin/bash

(cd ofcms-V1.1.2 && mvn clean package -DskipTests)
(cd jfinal && mvn clean package -DskipTests)
  1. Create a database: cd /tmp/work && codeql database create -l java -c "./build.sh" db
  2. Query:
/**
 * @kind path-problem
 */

import java
import semmle.code.java.dataflow.DataFlow

import DataFlow::PathGraph

class Config extends DataFlow::Configuration {

  Config() {
    this = "MyTestConfig"
  }

  override predicate isSource(DataFlow::Node n) {
    exists(StringLiteral l |
      n.asExpr() = l and
      l.getLocation().getFile().getAbsolutePath().matches("%ofcms%")
    )
  }

  override predicate isSink(DataFlow::Node n) {
    n.asExpr().(Argument).getCall().getCallee().getName() = "prepareStatement"
  }

}

from DataFlow::PathNode n1, DataFlow::PathNode n2, Config c
where c.hasFlowPath(n1, n2)
select n1, n1, n2, "path"

This is just an example that will find string constants in your code that eventually find their way to a prepareStatement call.

Running this reveals the first result shows "system.user.delete" from SysUserController.java flowing to Db.update then DbPro.update then finally the prepareStatement call you flagged.

Of course you would want to change the isSource to track something more interesting than any string literal in OFCMS code, but I hope this helps you get started.

OHHHHHH!!!!! Thank you!