Parameterize into methodcalls on object

The methodOnObject extraction will extract the untrusted input into method calls. A well known example of this type of binding is the SQL Prepared Statement API.

methods

The methods property allows the configuration of what methods should be used when extracting the untrusted input. Each method can be configured with a name and the arguments it should have. The type property specifies for what type of untrusted input this method should be used.

Note

Not adding a type means that method will be used for all elements that don't have a specific method defined for its type.

parameterize:
  placeholderFormat: "?"
  extractUntrustedInput:
    methodsOnObject:
      methods:
      - type: "java.lang.String"
        methodName: "setString"
        args:
          1: "{{{index}}}"
          2: "{{{.}}}"
      - type: "int"
        methodName: "setInt"
        args:
          1: "{{{index}}}"
          2: "{{{.}}}"
    target:
      returnValue: {}
- String sql = "SELECT count(*) FROM feedback WHERE email = '" + email + "' AND id = '" + id + "'";
+ String sql = "SELECT count(*) FROM feedback WHERE email = ? AND id = ?";
- Statement statement = this.connection.createStatement();
- ResultSet resultSet = statement.executeQuery(sql);
+ PreparedStatement statement = this.connection.prepareStatement(sql);
+ statement.setString(1, email);
+ statement.setInt(2, id);
+ ResultSet resultSet = statement.executeQuery();
  resultSet.next();
  return resultSet.getInt(0);

Some variables that can be used:

variable

functionality

{{{index}}}

starts at 1 and increments for each untrusted input

{{{indexFrom0}}}

starts at 0 and increments for each untrusted input

{{{indexFrom1}}}

same as {{{index}}}

{{{name}}}

a suitable name for the untrusted input it replaces

Warning

Only {{{index}}} is available on Sensei versions prior to 4.25

target

To have configurability it is possible to change the target. The target is the element on which the methods will be called.

argument

The argument target takes one of the arguments and places the binding methods on that argument. Usages may include collecting elements inside a Map or List.

parameterize:
  placeholderFormat: ":{{{name}}}"
  extractUntrustedInput:
    methodsOnObject:
      methods:
      - methodName: "put"
        args:
          1: "\"{{{name}}}\""
          2: "{{{.}}}"
    target:
      argument:
        type: java.util.HashMap<String, Object>
        atArgumentPosition: 2
- String sql = "SELECT * FROM users WHERE email = '" + email + "'";
+ String sql = "SELECT * FROM users WHERE email = :email";
+ HashMap<String, Object> hashMap = new HashMap<>();
+ hashMap.put("email", email);
- getSimpleJdbcTemplate().update(sql);
+ getSimpleJdbcTemplate().update(sql, hashMap);

Note

atArgumentPosition is 1-based, not 0-based.

Note

type is only used to create a new variable when there is no existing argument present at the specified position. When neither argument or type are present, Sensei will try to determine the type of the new variable based of the method declarations available.

returnValue

The returnValue target uses the return value of the marked element as a subject to perform the binding method calls on.

parameterize:
  placeholderFormat: ":{{{name}}}"
  extractUntrustedInput:
    methodsOnObject:
      methods:
      - methodName: "bind"
        args:
          1: "\"{{{name}}}\""
          2: "{{{.}}}"
    target:
      returnValue:
        variableName: "myQuery"
        useMethodChaining: false
   abstract Query createQuery(String sql);
   abstract Query bind(String key, Object value);

   public User findUser(String email) {
-      String sql = "SELECT * FROM users WHERE email = '" + email + "'";
+      String sql = "SELECT * FROM users WHERE email = :email";
-      createQuery(sql);
+      Query myQuery = createQuery(sql);
+      myQuery.bind("email", email);
   }

The useMethodChaining property can be used to change or enforce the behaviour when adding the method bindings. Sensei will check if method chaining is available and use it by default. Placing useMethodChaining explicitly to false will force Sensei to create a variable to use as a subject to the binding methods, for this the variableName property will be used.

subject

It uses the subject of the marked element and applies the method bindings on this subject.

Note

The subject target is only available when the marked element is a method call.

availableFixes:
- actions:
  - parameterize:
      placeholderFormat: ""
      extractUntrustedInput:
        methodsOnObject:
          methods:
          - methodName: "appendWhereEscapeString"
            args:
              "1": "{{{.}}}"
          target:
            subject:
              insertBefore: false
  User fetchUser(String id) {
      SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
      builder.setTables(TABLE_USERS);
-     builder.appendWhere("id = " + id);
+     builder.appendWhere("id = ");
+     builder.appendWhereEscapeString(id);
      // ...
  }

In the example below the insertBefore property is used to add the setString binding before the marked method call (statement.executeQuery).

search:
  methodcall:
    name:
      matches: execute.*
    declaration:
      type: java.sql.Statement
    args:
      1:
        containsUntrustedInput: true
        type: java.lang.String
public int getFeedbackCount(String email)
{
    String sql = "SELECT count(*) FROM feedback WHERE email = '" + email + "'";
    Statement statement = this.connection.createStatement();
    ResultSet resultSet = statement.executeQuery(sql);
    resultSet.next();
    return resultSet.getInt(0);
}
availableFixes:
- name: "Use parameterized queries"
  actions:
  - parameterize:
      placeholderFormat: "?"
      extractUntrustedInput:
        methodsOnObject:
          methods:
          - type: "java.lang.String"
            methodName: "setString"
            args:
              "1": "{{{ index }}}"
              "2": "{{{ . }}}"
        target:
          subject:
            insertBefore: true
  - changeTypeOfCallObject:
      rewriteLastAssignment: "{{{ qualifier }}}.prepareStatement({{{ markedElement.arguments.0}}}
        {{#arguments}},\\ {{{.}}}{{/arguments}})"
      type: "java.sql.PreparedStatement"
  - modifyArguments:
      remove:
      - 1
  public int getFeedbackCount(String email)
  {
-     String sql = "SELECT count(*) FROM feedback WHERE email = '" + email + "'";
+     String sql = "SELECT count(*) FROM feedback WHERE email = ?";
-     Statement statement = this.connection.createStatement();
+     PreparedStatement statement = this.connection.prepareStatement(sql);
+     statement.setString(1, email);
-     ResultSet resultSet = statement.executeQuery(sql);
+     ResultSet resultSet = statement.executeQuery();
      resultSet.next();
      return resultSet.getInt(0);
  }