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 `_. .. contents:: 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. .. code-block:: yaml :emphasize-lines: 5-15 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: {} .. code-block:: diff - 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: .. list-table:: :header-rows: 1 :widths: 25 50 * - 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. .. code-block:: yaml :emphasize-lines: 11-13 parameterize: placeholderFormat: ":{{{name}}}" extractUntrustedInput: methodsOnObject: methods: - methodName: "put" args: 1: "\"{{{name}}}\"" 2: "{{{.}}}" target: argument: type: java.util.HashMap atArgumentPosition: 2 .. code-block:: diff - String sql = "SELECT * FROM users WHERE email = '" + email + "'"; + String sql = "SELECT * FROM users WHERE email = :email"; + HashMap 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. .. code-block:: yaml :emphasize-lines: 11-13 parameterize: placeholderFormat: ":{{{name}}}" extractUntrustedInput: methodsOnObject: methods: - methodName: "bind" args: 1: "\"{{{name}}}\"" 2: "{{{.}}}" target: returnValue: variableName: "myQuery" useMethodChaining: false .. code-block:: diff 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. .. code-block:: yaml :emphasize-lines: 12-13 availableFixes: - actions: - parameterize: placeholderFormat: "" extractUntrustedInput: methodsOnObject: methods: - methodName: "appendWhereEscapeString" args: "1": "{{{.}}}" target: subject: insertBefore: false .. code-block:: diff 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``). .. code-block:: yaml search: methodcall: name: matches: execute.* declaration: type: java.sql.Statement args: 1: containsUntrustedInput: true type: java.lang.String .. code-block:: java :emphasize-lines: 5 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); } .. code-block:: yaml :emphasize-lines: 15-16 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 .. code-block:: diff 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); }