diff --git a/java/ql/lib/semmle/code/java/frameworks/Jdbc.qll b/java/ql/lib/semmle/code/java/frameworks/Jdbc.qll index 2723f3f05f59..37be7dcf09a7 100644 --- a/java/ql/lib/semmle/code/java/frameworks/Jdbc.qll +++ b/java/ql/lib/semmle/code/java/frameworks/Jdbc.qll @@ -34,3 +34,19 @@ class ResultSetGetStringMethod extends Method { this.getReturnType() instanceof TypeString } } + +/** A method with the name `executeUpdate` declared in `java.sql.PreparedStatement`. */ +class PreparedStatementExecuteUpdateMethod extends Method { + PreparedStatementExecuteUpdateMethod() { + this.getDeclaringType() instanceof TypePreparedStatement and + this.hasName("executeUpdate") + } +} + +/** A method with the name `executeLargeUpdate` declared in `java.sql.PreparedStatement`. */ +class PreparedStatementExecuteLargeUpdateMethod extends Method { + PreparedStatementExecuteLargeUpdateMethod() { + this.getDeclaringType() instanceof TypePreparedStatement and + this.hasName("executeLargeUpdate") + } +} diff --git a/java/ql/lib/semmle/code/java/frameworks/MyBatis.qll b/java/ql/lib/semmle/code/java/frameworks/MyBatis.qll index 01c8b829de6b..c7fc09a33b4d 100644 --- a/java/ql/lib/semmle/code/java/frameworks/MyBatis.qll +++ b/java/ql/lib/semmle/code/java/frameworks/MyBatis.qll @@ -128,3 +128,114 @@ private class MyBatisProviderStep extends TaintTracking::AdditionalValueStep { ) } } + +/** + * A MyBatis Mapper XML file. + */ +class MyBatisMapperXmlFile extends XmlFile { + MyBatisMapperXmlFile() { + count(XmlElement e | e = this.getAChild()) = 1 and + this.getAChild().getName() = "mapper" + } +} + +/** + * An XML element in a `MyBatisMapperXMLFile`. + */ +class MyBatisMapperXmlElement extends XmlElement { + MyBatisMapperXmlElement() { this.getFile() instanceof MyBatisMapperXmlFile } + + /** + * Gets the value for this element, with leading and trailing whitespace trimmed. + */ + string getValue() { result = this.allCharactersString().trim() } + + /** + * Gets the reference type bound to MyBatis Mapper XML File. + */ + RefType getNamespaceRefType() { + result.getQualifiedName() = this.getAttribute("namespace").getValue() + } +} + +/** + * An MyBatis Mapper sql operation element. + */ +abstract class MyBatisMapperSqlOperation extends MyBatisMapperXmlElement { + /** + * Gets the value of the `id` attribute of MyBatis Mapper sql operation element. + */ + string getId() { result = this.getAttribute("id").getValue() } + + /** + * Gets the `` element in a `MyBatisMapperSqlOperation`. + */ + MyBatisMapperInclude getInclude() { result = this.getAChild*() } + + /** + * Gets the method bound to MyBatis Mapper XML File. + */ + Method getMapperMethod() { + result.getName() = this.getId() and + result.getDeclaringType() = this.getParent().(MyBatisMapperXmlElement).getNamespaceRefType() + } +} + +/** + * A `` element in a `MyBatisMapperSqlOperation`. + */ +class MyBatisMapperInsert extends MyBatisMapperSqlOperation { + MyBatisMapperInsert() { this.getName() = "insert" } +} + +/** + * A `` element in a `MyBatisMapperSqlOperation`. + */ +class MyBatisMapperUpdate extends MyBatisMapperSqlOperation { + MyBatisMapperUpdate() { this.getName() = "update" } +} + +/** + * A `` element in a `MyBatisMapperSqlOperation`. + */ +class MyBatisMapperDelete extends MyBatisMapperSqlOperation { + MyBatisMapperDelete() { this.getName() = "delete" } +} + +/** + * A `` element in a `MyBatisMapperSqlOperation`. - */ -class MyBatisMapperSelect extends MyBatisMapperSqlOperation { - MyBatisMapperSelect() { this.getName() = "select" } -} - -/** - * A `` element in a `MyBatisMapperXMLElement`. - */ -class MyBatisMapperSql extends MyBatisMapperXmlElement { - MyBatisMapperSql() { this.getName() = "sql" } - - /** - * Gets the value of the `id` attribute of this ``. - */ - string getId() { result = this.getAttribute("id").getValue() } -} - -/** - * A `` element in a `MyBatisMapperXMLElement`. - */ -class MyBatisMapperInclude extends MyBatisMapperXmlElement { - MyBatisMapperInclude() { this.getName() = "include" } - - /** - * Gets the value of the `refid` attribute of this ``. - */ - string getRefid() { result = this.getAttribute("refid").getValue() } -} - -/** - * A `` element in a `MyBatisMapperXMLElement`. - */ -class MyBatisMapperForeach extends MyBatisMapperXmlElement { - MyBatisMapperForeach() { this.getName() = "foreach" } -} diff --git a/java/ql/test/query-tests/security/CWE-352/CsrfUnprotectedRequestTypeTest.expected b/java/ql/test/query-tests/security/CWE-352/CsrfUnprotectedRequestTypeTest.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/java/ql/test/query-tests/security/CWE-352/CsrfUnprotectedRequestTypeTest.java b/java/ql/test/query-tests/security/CWE-352/CsrfUnprotectedRequestTypeTest.java new file mode 100644 index 000000000000..764ce4ecbea6 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-352/CsrfUnprotectedRequestTypeTest.java @@ -0,0 +1,461 @@ +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import static org.springframework.web.bind.annotation.RequestMethod.*; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.beans.factory.annotation.Autowired; +import java.sql.DriverManager; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import org.kohsuke.stapler.WebMethod; +import org.kohsuke.stapler.interceptor.RequirePOST; +import org.kohsuke.stapler.verb.POST; +import org.kohsuke.stapler.verb.GET; +import org.kohsuke.stapler.verb.PUT; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.HttpRedirect; +import org.kohsuke.stapler.HttpResponses; +import org.apache.ibatis.jdbc.SqlRunner; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import java.util.Map; + +@Controller +public class CsrfUnprotectedRequestTypeTest { + public static Connection connection; + + // Test Spring sources with `PreparedStatement.executeUpdate()` as a default database update method call + + // BAD: allows request type not default-protected from CSRF when updating a database + @RequestMapping("/") + public void bad1() { // $ hasCsrfUnprotectedRequestType + try { + String sql = "DELETE"; + Connection conn = DriverManager.getConnection("url"); + PreparedStatement ps = conn.prepareStatement(sql); + ps.executeUpdate(); // database update method call + } catch (SQLException e) { } + } + + // BAD: uses GET request when updating a database + @RequestMapping(value = "", method = RequestMethod.GET) + public void bad2() { // $ hasCsrfUnprotectedRequestType + try { + String sql = "DELETE"; + Connection conn = DriverManager.getConnection("url"); + PreparedStatement ps = conn.prepareStatement(sql); + ps.executeUpdate(); // database update method call + } catch (SQLException e) { } + } + + // BAD: uses GET request when updating a database + @GetMapping(value = "") + public void bad3() { // $ hasCsrfUnprotectedRequestType + try { + String sql = "DELETE"; + Connection conn = DriverManager.getConnection("url"); + PreparedStatement ps = conn.prepareStatement(sql); + ps.executeUpdate(); // database update method call + } catch (SQLException e) { } + } + + // BAD: allows GET request when updating a database + @RequestMapping(value = "", method = { RequestMethod.GET, RequestMethod.POST }) + public void bad4() { // $ hasCsrfUnprotectedRequestType + try { + String sql = "DELETE"; + Connection conn = DriverManager.getConnection("url"); + PreparedStatement ps = conn.prepareStatement(sql); + ps.executeUpdate(); // database update method call + } catch (SQLException e) { } + } + + // BAD: uses request type not default-protected from CSRF when updating a database + @RequestMapping(value = "", method = { GET, HEAD, OPTIONS, TRACE }) + public void bad5() { // $ hasCsrfUnprotectedRequestType + try { + String sql = "DELETE"; + Connection conn = DriverManager.getConnection("url"); + PreparedStatement ps = conn.prepareStatement(sql); + ps.executeUpdate(); // database update method call + } catch (SQLException e) { } + } + + // GOOD: uses POST request when updating a database + @RequestMapping(value = "", method = RequestMethod.POST) + public void good1() { + try { + String sql = "DELETE"; + Connection conn = DriverManager.getConnection("url"); + PreparedStatement ps = conn.prepareStatement(sql); + ps.executeUpdate(); // database update method call + } catch (SQLException e) { } + } + + // GOOD: uses POST request when updating a database + @RequestMapping(value = "", method = POST) + public void good2() { + try { + String sql = "DELETE"; + Connection conn = DriverManager.getConnection("url"); + PreparedStatement ps = conn.prepareStatement(sql); + ps.executeUpdate(); // database update method call + } catch (SQLException e) { } + } + + // GOOD: uses POST request when updating a database + @PostMapping(value = "") + public void good3() { + try { + String sql = "DELETE"; + Connection conn = DriverManager.getConnection("url"); + PreparedStatement ps = conn.prepareStatement(sql); + ps.executeUpdate(); // database update method call + } catch (SQLException e) { } + } + + // GOOD: uses a request type that is default-protected from CSRF when updating a database + @RequestMapping(value = "", method = { POST, PUT, PATCH, DELETE }) + public void good4() { + try { + String sql = "DELETE"; + Connection conn = DriverManager.getConnection("url"); + PreparedStatement ps = conn.prepareStatement(sql); + ps.executeUpdate(); // database update method call + } catch (SQLException e) { } + } + + // Test database update method calls other than `PreparedStatement.executeUpdate()` + + // BAD: allows request type not default-protected from CSRF when + // updating a database using `PreparedStatement.executeLargeUpdate()` + @RequestMapping("/") + public void bad6() { // $ hasCsrfUnprotectedRequestType + try { + String sql = "DELETE"; + Connection conn = DriverManager.getConnection("url"); + PreparedStatement ps = conn.prepareStatement(sql); + ps.executeLargeUpdate(); // database update method call + } catch (SQLException e) { } + } + + // BAD: allows request type not default-protected from CSRF when + // updating a database using `Statement.executeUpdate` + @RequestMapping("/") + public void badStatementExecuteUpdate() { // $ hasCsrfUnprotectedRequestType + try { + String item = "item"; + String price = "price"; + Statement statement = connection.createStatement(); + String sql = "UPDATE PRODUCT SET PRICE='" + price + "' WHERE ITEM='" + item + "'"; + int count = statement.executeUpdate(sql); + } catch (SQLException e) { } + } + + // BAD: allows request type not default-protected from CSRF when + // updating a database using `Statement.executeLargeUpdate` + @RequestMapping("/") + public void badStatementExecuteLargeUpdate() { // $ hasCsrfUnprotectedRequestType + try { + String item = "item"; + String price = "price"; + Statement statement = connection.createStatement(); + String sql = "UPDATE PRODUCT SET PRICE='" + price + "' WHERE ITEM='" + item + "'"; + long count = statement.executeLargeUpdate(sql); + } catch (SQLException e) { } + } + + // BAD: allows request type not default-protected from CSRF when + // updating a database using `Statement.execute` with SQL UPDATE + @RequestMapping("/") + public void badStatementExecute() { // $ hasCsrfUnprotectedRequestType + try { + String item = "item"; + String price = "price"; + Statement statement = connection.createStatement(); + String sql = "UPDATE PRODUCT SET PRICE='" + price + "' WHERE ITEM='" + item + "'"; + boolean bool = statement.execute(sql); + } catch (SQLException e) { } + } + + // GOOD: does not update a database, queries with SELECT + @RequestMapping("/") + public void goodStatementExecute() { + try { + String category = "category"; + Statement statement = connection.createStatement(); + String query = "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" + + category + "' ORDER BY PRICE"; + boolean bool = statement.execute(query); + } catch (SQLException e) { } + } + + // BAD: allows request type not default-protected from CSRF when + // updating a database using `SqlRunner.insert` + @RequestMapping("/") + public void badSqlRunnerInsert() { // $ hasCsrfUnprotectedRequestType + try { + String item = "item"; + String price = "price"; + String sql = "INSERT PRODUCT SET PRICE='" + price + "' WHERE ITEM='" + item + "'"; + SqlRunner sqlRunner = new SqlRunner(connection); + sqlRunner.insert(sql); + } catch (SQLException e) { } + } + + // BAD: allows request type not default-protected from CSRF when + // updating a database using `SqlRunner.update` + @RequestMapping("/") + public void badSqlRunnerUpdate() { // $ hasCsrfUnprotectedRequestType + try { + String item = "item"; + String price = "price"; + String sql = "UPDATE PRODUCT SET PRICE='" + price + "' WHERE ITEM='" + item + "'"; + SqlRunner sqlRunner = new SqlRunner(connection); + sqlRunner.update(sql); + } catch (SQLException e) { } + } + + // BAD: allows request type not default-protected from CSRF when + // updating a database using `SqlRunner.delete` + @RequestMapping("/") + public void badSqlRunnerDelete() { // $ hasCsrfUnprotectedRequestType + try { + String item = "item"; + String price = "price"; + String sql = "DELETE PRODUCT SET PRICE='" + price + "' WHERE ITEM='" + item + "'"; + SqlRunner sqlRunner = new SqlRunner(connection); + sqlRunner.delete(sql); + } catch (SQLException e) { } + } + + // BAD: allows request type not default-protected from CSRF when + // updating a database using `JdbcTemplate.update` + @RequestMapping("/") + public void badJdbcTemplateUpdate() { // $ hasCsrfUnprotectedRequestType + String item = "item"; + String price = "price"; + String sql = "UPDATE PRODUCT SET PRICE='" + price + "' WHERE ITEM='" + item + "'"; + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.update(sql); + } + + // BAD: allows request type not default-protected from CSRF when + // updating a database using `JdbcTemplate.batchUpdate` + @RequestMapping("/") + public void badJdbcTemplateBatchUpdate() { // $ hasCsrfUnprotectedRequestType + String item = "item"; + String price = "price"; + String sql = "UPDATE PRODUCT SET PRICE='" + price + "' WHERE ITEM='" + item + "'"; + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.batchUpdate(sql, null, null); + } + + // BAD: allows request type not default-protected from CSRF when + // updating a database using `JdbcTemplate.execute` + @RequestMapping("/") + public void badJdbcTemplateExecute() { // $ hasCsrfUnprotectedRequestType + String item = "item"; + String price = "price"; + String sql = "UPDATE PRODUCT SET PRICE='" + price + "' WHERE ITEM='" + item + "'"; + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.execute(sql); + } + + // GOOD: does not update a database, queries with SELECT + @RequestMapping("/") + public void goodJdbcTemplateExecute() { + String category = "category"; + String query = "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" + + category + "' ORDER BY PRICE"; + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.execute(query); + } + + // BAD: allows request type not default-protected from CSRF when + // updating a database using `NamedParameterJdbcTemplate.update` + @RequestMapping("/") + public void badNamedParameterJdbcTemplateUpdate() { // $ hasCsrfUnprotectedRequestType + String item = "item"; + String price = "price"; + String sql = "UPDATE PRODUCT SET PRICE='" + price + "' WHERE ITEM='" + item + "'"; + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + NamedParameterJdbcTemplate namedParamJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); + namedParamJdbcTemplate.update(sql, null, null); + } + + // BAD: allows request type not default-protected from CSRF when + // updating a database using `NamedParameterJdbcTemplate.batchUpdate` + @RequestMapping("/") + public void badNamedParameterJdbcTemplateBatchUpdate() { // $ hasCsrfUnprotectedRequestType + String item = "item"; + String price = "price"; + String sql = "UPDATE PRODUCT SET PRICE='" + price + "' WHERE ITEM='" + item + "'"; + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + NamedParameterJdbcTemplate namedParamJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); + namedParamJdbcTemplate.batchUpdate(sql, (Map[]) null); + } + + // BAD: allows request type not default-protected from CSRF when + // updating a database using `NamedParameterJdbcTemplate.execute` + @RequestMapping("/") + public void badNamedParameterJdbcTemplateExecute() { // $ hasCsrfUnprotectedRequestType + String item = "item"; + String price = "price"; + String sql = "UPDATE PRODUCT SET PRICE='" + price + "' WHERE ITEM='" + item + "'"; + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + NamedParameterJdbcTemplate namedParamJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); + namedParamJdbcTemplate.execute(sql, null); + } + + // GOOD: does not update a database, queries with SELECT + @RequestMapping("/") + public void goodNamedParameterJdbcTemplateExecute() { + String category = "category"; + String query = "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" + + category + "' ORDER BY PRICE"; + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + NamedParameterJdbcTemplate namedParamJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); + namedParamJdbcTemplate.execute(query, null); + } + + @Autowired + private MyBatisService myBatisService; + + // BAD: uses GET request when updating a database with MyBatis XML mapper method + @GetMapping(value = "") + public void bad7(@RequestParam String name) { // $ hasCsrfUnprotectedRequestType + myBatisService.bad7(name); + } + + // BAD: uses GET request when updating a database with MyBatis `@DeleteProvider` + @GetMapping(value = "badDelete") + public void badDelete(@RequestParam String name) { // $ hasCsrfUnprotectedRequestType + myBatisService.badDelete(name); + } + + // BAD: uses GET request when updating a database with MyBatis `@UpdateProvider` + @GetMapping(value = "badUpdate") + public void badUpdate(@RequestParam String name) { // $ hasCsrfUnprotectedRequestType + myBatisService.badUpdate(name); + } + + // BAD: uses GET request when updating a database with MyBatis `@InsertProvider` + @GetMapping(value = "badInsert") + public void badInsert(@RequestParam String name) { // $ hasCsrfUnprotectedRequestType + myBatisService.badInsert(name); + } + + // BAD: uses GET request when updating a database with MyBatis `@Delete` + @GetMapping(value = "bad8") + public void bad8(@RequestParam int id) { // $ hasCsrfUnprotectedRequestType + myBatisService.bad8(id); + } + + // BAD: uses GET request when updating a database with MyBatis `@Insert` + @GetMapping(value = "bad9") + public void bad9(@RequestParam String user) { // $ hasCsrfUnprotectedRequestType + myBatisService.bad9(user); + } + + // BAD: uses GET request when updating a database with MyBatis `@Update` + @GetMapping(value = "bad10") + public void bad10(@RequestParam String user) { // $ hasCsrfUnprotectedRequestType + myBatisService.bad10(user); + } + + // Test name-based heuristic for method names that imply a state-change + @GetMapping(value = "transfer") + public String transfer(@RequestParam String user) { return "transfer"; } // $ hasCsrfUnprotectedRequestType + + @GetMapping(value = "transfer") + public String transferData(@RequestParam String user) { return "transfer"; } // $ hasCsrfUnprotectedRequestType + + @GetMapping(value = "transfer") + public String doTransfer(@RequestParam String user) { return "transfer"; } // $ hasCsrfUnprotectedRequestType + + @GetMapping(value = "transfer") + public String doTransferAllData(@RequestParam String user) { return "transfer"; } // $ hasCsrfUnprotectedRequestType + + @GetMapping(value = "transfer") + public String doDataTransfer(@RequestParam String user) { return "transfer"; } // $ hasCsrfUnprotectedRequestType + + @GetMapping(value = "transfer") + public String transfered(@RequestParam String user) { return "transfer"; } // OK: we look for 'transfer' only + + @GetMapping(value = "transfer") + public String dotransfer(@RequestParam String user) { return "transfer"; } // OK: we look for 'transfer' within camelCase only + + @GetMapping(value = "transfer") + public String doTransferdata(@RequestParam String user) { return "transfer"; } // OK: we look for 'transfer' within camelCase only + + @GetMapping(value = "transfer") + public String getTransfer(@RequestParam String user) { return "transfer"; } // OK: starts with 'get' + + // Test Stapler web methods with name-based heuristic + + // BAD: Stapler web method annotated with `@WebMethod` and method name that implies a state-change + @WebMethod(name = "post") + public String doPost(String user) { // $ hasCsrfUnprotectedRequestType + return "post"; + } + + // GOOD: nothing to indicate that this is a Stapler web method + public String postNotAWebMethod(String user) { + return "post"; + } + + // GOOD: Stapler web method annotated with `@RequirePOST` and method name that implies a state-change + @RequirePOST + public String doPost1(String user) { + return "post"; + } + + // GOOD: Stapler web method annotated with `@POST` and method name that implies a state-change + @POST + public String doPost2(String user) { + return "post"; + } + + // BAD: Stapler web method annotated with `@GET` and method name that implies a state-change + @GET + public String doPost3(String user) { // $ hasCsrfUnprotectedRequestType + return "post"; + } + + // BAD: Stapler web method annotated with `@PUT` and method name that implies a state-change + // We treat this case as bad for Stapler since the Jenkins docs only say that @POST/@RequirePOST + // provide default protection against CSRF. + @PUT + public String doPut(String user) { // $ hasCsrfUnprotectedRequestType + return "put"; + } + + // BAD: Stapler web method parameter of type `StaplerRequest` and method name that implies a state-change + public String doPost4(StaplerRequest request) { // $ hasCsrfUnprotectedRequestType + return "post"; + } + + // BAD: Stapler web method parameter annotated with `@QueryParameter` and method name that implies a state-change + public String doPost5(@QueryParameter(value="user", fixEmpty=false, required=false) String user) { // $ hasCsrfUnprotectedRequestType + return "post"; + } + + // BAD: Stapler web method with declared exception type implementing HttpResponse and method name that implies a state-change + public String doPost6(String user) throws HttpResponses.HttpResponseException { // $ hasCsrfUnprotectedRequestType + return "post"; + } + + // BAD: Stapler web method with return type implementing HttpResponse and method name that implies a state-change + public HttpRedirect doPost7(String url) { // $ hasCsrfUnprotectedRequestType + HttpRedirect redirect = new HttpRedirect(url); + return redirect; + } +} diff --git a/java/ql/test/query-tests/security/CWE-352/CsrfUnprotectedRequestTypeTest.ql b/java/ql/test/query-tests/security/CWE-352/CsrfUnprotectedRequestTypeTest.ql new file mode 100644 index 000000000000..5fe26ec04670 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-352/CsrfUnprotectedRequestTypeTest.ql @@ -0,0 +1,18 @@ +import java +import semmle.code.java.security.CsrfUnprotectedRequestTypeQuery +import utils.test.InlineExpectationsTest + +module CsrfUnprotectedRequestTypeTest implements TestSig { + string getARelevantTag() { result = "hasCsrfUnprotectedRequestType" } + + predicate hasActualResult(Location location, string element, string tag, string value) { + tag = "hasCsrfUnprotectedRequestType" and + exists(CallPathNode src | unprotectedStateChange(src, _) | + src.getLocation() = location and + element = src.toString() and + value = "" + ) + } +} + +import MakeTest diff --git a/java/ql/test/query-tests/security/CWE-352/MyBatisMapper.java b/java/ql/test/query-tests/security/CWE-352/MyBatisMapper.java new file mode 100644 index 000000000000..bf0b9cd5890a --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-352/MyBatisMapper.java @@ -0,0 +1,43 @@ +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; +import org.apache.ibatis.annotations.DeleteProvider; +import org.apache.ibatis.annotations.UpdateProvider; +import org.apache.ibatis.annotations.InsertProvider; +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Update; +import org.apache.ibatis.annotations.Insert; + +@Mapper +@Repository +public interface MyBatisMapper { + + void bad7(String name); + + //using providers + @DeleteProvider( + type = MyBatisProvider.class, + method = "badDelete" + ) + void badDelete(String input); + + @UpdateProvider( + type = MyBatisProvider.class, + method = "badUpdate" + ) + void badUpdate(String input); + + @InsertProvider( + type = MyBatisProvider.class, + method = "badInsert" + ) + void badInsert(String input); + + @Delete("DELETE FROM users WHERE id = #{id}") + boolean bad8(int id); + + @Insert("INSERT INTO users (id, name) VALUES(#{id}, #{name})") + void bad9(String user); + + @Update("UPDATE users SET name = #{name} WHERE id = #{id}") + boolean bad10(String user); +} diff --git a/java/ql/test/query-tests/security/CWE-352/MyBatisMapper.xml b/java/ql/test/query-tests/security/CWE-352/MyBatisMapper.xml new file mode 100644 index 000000000000..be8efe9422f5 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-352/MyBatisMapper.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + and name = ${ test . name , jdbcType = VARCHAR } + + + and id = #{test.id} + + + + + + insert into test (name, pass) + + + name = ${name,jdbcType=VARCHAR}, + + + pass = ${pass}, + + + + + diff --git a/java/ql/test/query-tests/security/CWE-352/MyBatisProvider.java b/java/ql/test/query-tests/security/CWE-352/MyBatisProvider.java new file mode 100644 index 000000000000..53b1ca723bdf --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-352/MyBatisProvider.java @@ -0,0 +1,24 @@ +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.jdbc.SQL; + +public class MyBatisProvider { + + public String badDelete(@Param("input") final String input) { + return "DELETE FROM users WHERE username = '" + input + "';"; + } + + public String badUpdate(@Param("input") final String input) { + String s = (new SQL() { + { + this.UPDATE("users"); + this.SET("balance = 0"); + this.WHERE("username = '" + input + "'"); + } + }).toString(); + return s; + } + + public String badInsert(@Param("input") final String input) { + return "INSERT INTO users VALUES (1, '" + input + "', 'hunter2');"; + } +} diff --git a/java/ql/test/query-tests/security/CWE-352/MyBatisService.java b/java/ql/test/query-tests/security/CWE-352/MyBatisService.java new file mode 100644 index 000000000000..20c6c3578b0b --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-352/MyBatisService.java @@ -0,0 +1,37 @@ +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class MyBatisService { + + @Autowired + private MyBatisMapper myBatisMapper; + + public void bad7(String name) { + myBatisMapper.bad7(name); + } + + public void badDelete(String input) { + myBatisMapper.badDelete(input); + } + + public void badUpdate(String input) { + myBatisMapper.badUpdate(input); + } + + public void badInsert(String input) { + myBatisMapper.badInsert(input); + } + + public void bad8(int id){ + myBatisMapper.bad8(id); + } + + public void bad9(String user){ + myBatisMapper.bad9(user); + } + + public void bad10(String user){ + myBatisMapper.bad10(user); + } +} diff --git a/java/ql/test/query-tests/security/CWE-352/options b/java/ql/test/query-tests/security/CWE-352/options index 595ccc6b812b..1fef01772fe9 100644 --- a/java/ql/test/query-tests/security/CWE-352/options +++ b/java/ql/test/query-tests/security/CWE-352/options @@ -1 +1 @@ -semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/springframework-5.3.8 \ No newline at end of file +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/springframework-5.3.8/:${testdir}/../../../stubs/org.mybatis-3.5.4/:${testdir}/../../../stubs/stapler-1.263/:${testdir}/../../../stubs/javax-servlet-2.5:${testdir}/../../../stubs/apache-commons-jelly-1.0.1:${testdir}/../../../stubs/apache-commons-fileupload-1.4:${testdir}/../../../stubs/saxon-xqj-9.x:${testdir}/../../../stubs/apache-commons-beanutils:${testdir}/../../../stubs/dom4j-2.1.1:${testdir}/../../../stubs/apache-commons-lang:${testdir}/../../../stubs/jaxen-1.2.0:${testdir}/../../../stubs/apache-commons-logging-1.2/ diff --git a/java/ql/test/stubs/org.mybatis-3.5.4/org/apache/ibatis/annotations/Delete.java b/java/ql/test/stubs/org.mybatis-3.5.4/org/apache/ibatis/annotations/Delete.java new file mode 100644 index 000000000000..e2681e31d959 --- /dev/null +++ b/java/ql/test/stubs/org.mybatis-3.5.4/org/apache/ibatis/annotations/Delete.java @@ -0,0 +1,14 @@ +package org.apache.ibatis.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface Delete { + String[] value(); +} diff --git a/java/ql/test/stubs/org.mybatis-3.5.4/org/apache/ibatis/annotations/Insert.java b/java/ql/test/stubs/org.mybatis-3.5.4/org/apache/ibatis/annotations/Insert.java new file mode 100644 index 000000000000..52aad4172d19 --- /dev/null +++ b/java/ql/test/stubs/org.mybatis-3.5.4/org/apache/ibatis/annotations/Insert.java @@ -0,0 +1,14 @@ +package org.apache.ibatis.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface Insert { + String[] value(); +} diff --git a/java/ql/test/stubs/org.mybatis-3.5.4/org/apache/ibatis/annotations/Update.java b/java/ql/test/stubs/org.mybatis-3.5.4/org/apache/ibatis/annotations/Update.java new file mode 100644 index 000000000000..6566245971af --- /dev/null +++ b/java/ql/test/stubs/org.mybatis-3.5.4/org/apache/ibatis/annotations/Update.java @@ -0,0 +1,14 @@ +package org.apache.ibatis.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface Update { + String[] value(); +} diff --git a/java/ql/test/stubs/org.mybatis-3.5.4/org/apache/ibatis/jdbc/SqlRunner.java b/java/ql/test/stubs/org.mybatis-3.5.4/org/apache/ibatis/jdbc/SqlRunner.java new file mode 100644 index 000000000000..7738de81e0e9 --- /dev/null +++ b/java/ql/test/stubs/org.mybatis-3.5.4/org/apache/ibatis/jdbc/SqlRunner.java @@ -0,0 +1,37 @@ +package org.apache.ibatis.jdbc; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class SqlRunner { + + public static final int NO_GENERATED_KEY = Integer.MIN_VALUE + 1001; + + private final Connection connection; + private boolean useGeneratedKeySupport; + + public SqlRunner(Connection connection) { + this.connection = connection; + } + + public void setUseGeneratedKeySupport(boolean useGeneratedKeySupport) { } + public Map selectOne(String sql, Object... args) throws SQLException { return null; } + public List> selectAll(String sql, Object... args) throws SQLException { return null; } + public int insert(String sql, Object... args) throws SQLException { return 0; } + public int update(String sql, Object... args) throws SQLException { return 0; } + public int delete(String sql, Object... args) throws SQLException { return 0; } + public void closeConnection() { } + private void setParameters(PreparedStatement ps, Object... args) throws SQLException { } + private List> getResults(ResultSet rs) throws SQLException { return null; } + +} diff --git a/java/ql/test/stubs/springframework-5.3.8/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java b/java/ql/test/stubs/springframework-5.3.8/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java new file mode 100644 index 000000000000..b5c37a6f86a2 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.3.8/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java @@ -0,0 +1,169 @@ +package org.springframework.jdbc.core.namedparam; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import javax.sql.DataSource; + +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementCallback; +import org.springframework.jdbc.core.PreparedStatementCreator; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.core.RowCallbackHandler; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.SqlParameter; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.jdbc.support.rowset.SqlRowSet; + +public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations { + + public static final int DEFAULT_CACHE_LIMIT = 256; + private final JdbcOperations classicJdbcTemplate; + public NamedParameterJdbcTemplate(DataSource dataSource) { + this.classicJdbcTemplate = new JdbcTemplate(dataSource); + } + public NamedParameterJdbcTemplate(JdbcOperations classicJdbcTemplate) { + this.classicJdbcTemplate = classicJdbcTemplate; + } + @Override + public JdbcOperations getJdbcOperations() { return null; } + public JdbcTemplate getJdbcTemplate() { return null; } + public void setCacheLimit(int cacheLimit) { } + public int getCacheLimit() { return 0; } + + @Override + public T execute(String sql, SqlParameterSource paramSource, PreparedStatementCallback action) + throws DataAccessException { return null; } + + @Override + public T execute(String sql, Map paramMap, PreparedStatementCallback action) + throws DataAccessException { return null; } + + @Override + public T execute(String sql, PreparedStatementCallback action) throws DataAccessException { return null; } + + @Override + public T query(String sql, SqlParameterSource paramSource, ResultSetExtractor rse) + throws DataAccessException { return null; } + + @Override + public T query(String sql, Map paramMap, ResultSetExtractor rse) + throws DataAccessException { return null; } + + @Override + public T query(String sql, ResultSetExtractor rse) throws DataAccessException { return null; } + + @Override + public void query(String sql, SqlParameterSource paramSource, RowCallbackHandler rch) + throws DataAccessException { } + + @Override + public void query(String sql, Map paramMap, RowCallbackHandler rch) + throws DataAccessException { } + + @Override + public void query(String sql, RowCallbackHandler rch) throws DataAccessException { } + + @Override + public List query(String sql, SqlParameterSource paramSource, RowMapper rowMapper) + throws DataAccessException { return null; } + + @Override + public List query(String sql, Map paramMap, RowMapper rowMapper) + throws DataAccessException { return null; } + + @Override + public List query(String sql, RowMapper rowMapper) throws DataAccessException { return null; } + + @Override + public Stream queryForStream(String sql, SqlParameterSource paramSource, RowMapper rowMapper) + throws DataAccessException { return null; } + + @Override + public Stream queryForStream(String sql, Map paramMap, RowMapper rowMapper) + throws DataAccessException { return null; } + + @Override + public T queryForObject(String sql, SqlParameterSource paramSource, RowMapper rowMapper) + throws DataAccessException { return null; } + + @Override + public T queryForObject(String sql, Map paramMap, RowMapperrowMapper) + throws DataAccessException { return null; } + + @Override + public T queryForObject(String sql, SqlParameterSource paramSource, Class requiredType) + throws DataAccessException { return null; } + + @Override + public T queryForObject(String sql, Map paramMap, Class requiredType) + throws DataAccessException { return null; } + + @Override + public Map queryForMap(String sql, SqlParameterSource paramSource) throws DataAccessException { return null; } + + @Override + public Map queryForMap(String sql, Map paramMap) throws DataAccessException { return null; } + + @Override + public List queryForList(String sql, SqlParameterSource paramSource, Class elementType) + throws DataAccessException { return null; } + + @Override + public List queryForList(String sql, Map paramMap, Class elementType) + throws DataAccessException { return null; } + + @Override + public List> queryForList(String sql, SqlParameterSource paramSource) + throws DataAccessException { return null; } + + @Override + public List> queryForList(String sql, Map paramMap) + throws DataAccessException { return null; } + + @Override + public SqlRowSet queryForRowSet(String sql, SqlParameterSource paramSource) throws DataAccessException { return null; } + + @Override + public SqlRowSet queryForRowSet(String sql, Map paramMap) throws DataAccessException { return null; } + + @Override + public int update(String sql, SqlParameterSource paramSource) throws DataAccessException { return 0; } + + @Override + public int update(String sql, Map paramMap) throws DataAccessException { return 0; } + + @Override + public int update(String sql, SqlParameterSource paramSource, KeyHolder generatedKeyHolder) + throws DataAccessException { return 0; } + + @Override + public int update( + String sql, SqlParameterSource paramSource, KeyHolder generatedKeyHolder, String[] keyColumnNames) + throws DataAccessException { return 0; } + + @Override + public int[] batchUpdate(String sql, SqlParameterSource[] batchArgs) { return new int[0]; } + + @Override + public int[] batchUpdate(String sql, Map[] batchValues) { return new int[0]; } + + public int[] batchUpdate(String sql, SqlParameterSource[] batchArgs, KeyHolder generatedKeyHolder) { return new int[0]; } + + public int[] batchUpdate(String sql, SqlParameterSource[] batchArgs, KeyHolder generatedKeyHolder, + String[] keyColumnNames) { return new int[0]; } + + protected PreparedStatementCreator getPreparedStatementCreator(String sql, SqlParameterSource paramSource) { + return null; + } + + protected ParsedSql getParsedSql(String sql) { return null; } + +} diff --git a/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/AnnotationHandler.java b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/AnnotationHandler.java new file mode 100644 index 000000000000..014e5787bd46 --- /dev/null +++ b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/AnnotationHandler.java @@ -0,0 +1,13 @@ +// Generated automatically from org.kohsuke.stapler.AnnotationHandler for testing purposes + +package org.kohsuke.stapler; + +import java.lang.annotation.Annotation; +import org.kohsuke.stapler.StaplerRequest; + +abstract public class AnnotationHandler +{ + protected final Object convert(Class p0, String p1){ return null; } + public AnnotationHandler(){} + public abstract Object parse(StaplerRequest p0, T p1, Class p2, String p3); +} diff --git a/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/QueryParameter.java b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/QueryParameter.java new file mode 100644 index 000000000000..5ffe3a8fdf5a --- /dev/null +++ b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/QueryParameter.java @@ -0,0 +1,28 @@ +// Generated automatically from org.kohsuke.stapler.QueryParameter for testing purposes + +package org.kohsuke.stapler; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.kohsuke.stapler.AnnotationHandler; +import org.kohsuke.stapler.InjectedParameter; +import org.kohsuke.stapler.StaplerRequest; + +@Documented +@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME) +@Target(value={java.lang.annotation.ElementType.PARAMETER}) +public @interface QueryParameter +{ + String value(); + boolean fixEmpty(); + boolean required(); + static public class HandlerImpl extends AnnotationHandler + { + public HandlerImpl(){} + public Object parse(StaplerRequest p0, QueryParameter p1, Class p2, String p3){ return null; } + } +} diff --git a/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/WebMethod.java b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/WebMethod.java new file mode 100644 index 000000000000..ab457f2d8244 --- /dev/null +++ b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/WebMethod.java @@ -0,0 +1,18 @@ +// Generated automatically from org.kohsuke.stapler.WebMethod for testing purposes + +package org.kohsuke.stapler; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME) +@Target(value={java.lang.annotation.ElementType.METHOD}) +public @interface WebMethod +{ + String[] name(); +} diff --git a/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/interceptor/Interceptor.java b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/interceptor/Interceptor.java new file mode 100644 index 000000000000..8148c1813fcb --- /dev/null +++ b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/interceptor/Interceptor.java @@ -0,0 +1,15 @@ +// Generated automatically from org.kohsuke.stapler.interceptor.Interceptor for testing purposes + +package org.kohsuke.stapler.interceptor; + +import org.kohsuke.stapler.Function; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; + +abstract public class Interceptor +{ + protected Function target = null; + public Interceptor(){} + public abstract Object invoke(StaplerRequest p0, StaplerResponse p1, Object p2, Object[] p3); + public void setTarget(Function p0){} +} diff --git a/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/interceptor/InterceptorAnnotation.java b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/interceptor/InterceptorAnnotation.java new file mode 100644 index 000000000000..ca2c694b4144 --- /dev/null +++ b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/interceptor/InterceptorAnnotation.java @@ -0,0 +1,21 @@ +// Generated automatically from org.kohsuke.stapler.interceptor.InterceptorAnnotation for testing purposes + +package org.kohsuke.stapler.interceptor; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.kohsuke.stapler.interceptor.Interceptor; +import org.kohsuke.stapler.interceptor.Stage; + +@Documented +@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME) +@Target(value={java.lang.annotation.ElementType.ANNOTATION_TYPE}) +public @interface InterceptorAnnotation +{ + Class value(); + Stage stage(); +} diff --git a/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/interceptor/RequirePOST.java b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/interceptor/RequirePOST.java new file mode 100644 index 000000000000..cda33abd4352 --- /dev/null +++ b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/interceptor/RequirePOST.java @@ -0,0 +1,25 @@ +// Generated automatically from org.kohsuke.stapler.interceptor.RequirePOST for testing purposes + +package org.kohsuke.stapler.interceptor; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; +import org.kohsuke.stapler.interceptor.Interceptor; +import org.kohsuke.stapler.interceptor.InterceptorAnnotation; +import org.kohsuke.stapler.interceptor.Stage; + +@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME) +@Target(value={java.lang.annotation.ElementType.METHOD,java.lang.annotation.ElementType.FIELD}) +public @interface RequirePOST +{ + static public class Processor extends Interceptor + { + public Object invoke(StaplerRequest p0, StaplerResponse p1, Object p2, Object[] p3){ return null; } + public Processor(){} + } +} diff --git a/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/interceptor/Stage.java b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/interceptor/Stage.java new file mode 100644 index 000000000000..23db10ee9b0d --- /dev/null +++ b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/interceptor/Stage.java @@ -0,0 +1,10 @@ +// Generated automatically from org.kohsuke.stapler.interceptor.Stage for testing purposes + +package org.kohsuke.stapler.interceptor; + + +public enum Stage +{ + PREINVOKE, SELECTION; + private Stage() {} +} diff --git a/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/verb/GET.java b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/verb/GET.java new file mode 100644 index 000000000000..73327d4e0f67 --- /dev/null +++ b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/verb/GET.java @@ -0,0 +1,19 @@ +// Generated automatically from org.kohsuke.stapler.verb.GET for testing purposes + +package org.kohsuke.stapler.verb; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.kohsuke.stapler.interceptor.InterceptorAnnotation; +import org.kohsuke.stapler.interceptor.Stage; + +@Documented +@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME) +@Target(value={java.lang.annotation.ElementType.METHOD}) +public @interface GET +{ +} diff --git a/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/verb/POST.java b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/verb/POST.java new file mode 100644 index 000000000000..30462d6b0fae --- /dev/null +++ b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/verb/POST.java @@ -0,0 +1,19 @@ +// Generated automatically from org.kohsuke.stapler.verb.POST for testing purposes + +package org.kohsuke.stapler.verb; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.kohsuke.stapler.interceptor.InterceptorAnnotation; +import org.kohsuke.stapler.interceptor.Stage; + +@Documented +@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME) +@Target(value={java.lang.annotation.ElementType.METHOD}) +public @interface POST +{ +} diff --git a/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/verb/PUT.java b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/verb/PUT.java new file mode 100644 index 000000000000..d4d4338772c6 --- /dev/null +++ b/java/ql/test/stubs/stapler-1.263/org/kohsuke/stapler/verb/PUT.java @@ -0,0 +1,19 @@ +// Generated automatically from org.kohsuke.stapler.verb.PUT for testing purposes + +package org.kohsuke.stapler.verb; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.kohsuke.stapler.interceptor.InterceptorAnnotation; +import org.kohsuke.stapler.interceptor.Stage; + +@Documented +@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME) +@Target(value={java.lang.annotation.ElementType.METHOD}) +public @interface PUT +{ +}