Merge pull request #6319 from haby0/java/MyBatisSqlInjection

[Java] CWE-089 MyBatis Mapper Sql Injection
This commit is contained in:
Chris Smowton
2021-12-09 19:57:18 +00:00
committed by GitHub
27 changed files with 1160 additions and 0 deletions

View File

@@ -24,3 +24,81 @@ private class SqlSinkCsv extends SinkModelCsv {
]
}
}
/** The class `org.apache.ibatis.session.Configuration`. */
class IbatisConfiguration extends RefType {
IbatisConfiguration() { this.hasQualifiedName("org.apache.ibatis.session", "Configuration") }
}
/**
* The method `getVariables()` declared in `org.apache.ibatis.session.Configuration`.
*/
class IbatisConfigurationGetVariablesMethod extends Method {
IbatisConfigurationGetVariablesMethod() {
this.getDeclaringType() instanceof IbatisConfiguration and
this.hasName("getVariables") and
this.getNumberOfParameters() = 0
}
}
/**
* An annotation type that identifies Ibatis select.
*/
private class IbatisSelectAnnotationType extends AnnotationType {
IbatisSelectAnnotationType() { this.hasQualifiedName("org.apache.ibatis.annotations", "Select") }
}
/**
* An annotation type that identifies Ibatis delete.
*/
private class IbatisDeleteAnnotationType extends AnnotationType {
IbatisDeleteAnnotationType() { this.hasQualifiedName("org.apache.ibatis.annotations", "Delete") }
}
/**
* An annotation type that identifies Ibatis insert.
*/
private class IbatisInsertAnnotationType extends AnnotationType {
IbatisInsertAnnotationType() { this.hasQualifiedName("org.apache.ibatis.annotations", "Insert") }
}
/**
* An annotation type that identifies Ibatis update.
*/
private class IbatisUpdateAnnotationType extends AnnotationType {
IbatisUpdateAnnotationType() { this.hasQualifiedName("org.apache.ibatis.annotations", "Update") }
}
/**
* An Ibatis SQL operation annotation.
*/
class IbatisSqlOperationAnnotation extends Annotation {
IbatisSqlOperationAnnotation() {
this.getType() instanceof IbatisSelectAnnotationType or
this.getType() instanceof IbatisDeleteAnnotationType or
this.getType() instanceof IbatisInsertAnnotationType or
this.getType() instanceof IbatisUpdateAnnotationType
}
/**
* Gets this annotation's SQL statement string.
*/
string getSqlValue() {
result = this.getAValue("value").(CompileTimeConstantExpr).getStringValue()
}
}
/**
* Methods annotated with `@org.apache.ibatis.annotations.Select` or `@org.apache.ibatis.annotations.Delete`
* or `@org.apache.ibatis.annotations.Update` or `@org.apache.ibatis.annotations.Insert`.
*/
class MyBatisSqlOperationAnnotationMethod extends Method {
MyBatisSqlOperationAnnotationMethod() {
this.getAnAnnotation() instanceof IbatisSqlOperationAnnotation
}
}
/** The interface `org.apache.ibatis.annotations.Param`. */
class TypeParam extends Interface {
TypeParam() { this.hasQualifiedName("org.apache.ibatis.annotations", "Param") }
}

View File

@@ -0,0 +1,10 @@
import org.apache.ibatis.annotations.Select;
public interface MyBatisAnnotationSqlInjection {
@Select("select * from test where name = ${name}")
public Test bad1(String name);
@Select("select * from test where name = #{name}")
public Test good1(String name);
}

View File

@@ -0,0 +1,31 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>MyBatis uses methods with the annotations <code>@Select</code>, <code>@Insert</code>, etc. to construct dynamic SQL statements.
If the syntax <code>${param}</code> is used in those statements, and <code>param</code> is a parameter of the annotated method, attackers can exploit this to tamper with the SQL statements or execute arbitrary SQL commands.</p>
</overview>
<recommendation>
<p>
When writing MyBatis mapping statements, use the syntax <code>#{xxx}</code> whenever possible. If the syntax <code>${xxx}</code> must be used, any parameters included in it should be sanitized to prevent SQL injection attacks.
</p>
</recommendation>
<example>
<p>
The following sample shows a bad and a good example of MyBatis annotations usage. The <code>bad1</code> method uses <code>$(name)</code>
in the <code>@Select</code> annotation to dynamically build a SQL statement, which causes a SQL injection vulnerability.
The <code>good1</code> method uses <code>#{name}</code> in the <code>@Select</code> annotation to dynamically include the parameter in a SQL statement, which causes the MyBatis framework to sanitize the input provided, preventing the vulnerability.
</p>
<sample src="MyBatisAnnotationSqlInjection.java" />
</example>
<references>
<li>
Fortify:
<a href="https://vulncat.fortify.com/en/detail?id=desc.config.java.sql_injection_mybatis_mapper">SQL Injection: MyBatis Mapper</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,60 @@
/**
* @name SQL injection in MyBatis annotation
* @description Constructing a dynamic SQL statement with input that comes from an
* untrusted source could allow an attacker to modify the statement's
* meaning or to execute arbitrary SQL commands.
* @kind path-problem
* @problem.severity error
* @precision high
* @id java/mybatis-annotation-sql-injection
* @tags security
* external/cwe/cwe-089
*/
import java
import DataFlow::PathGraph
import MyBatisCommonLib
import MyBatisAnnotationSqlInjectionLib
import semmle.code.java.dataflow.FlowSources
private class MyBatisAnnotationSqlInjectionConfiguration extends TaintTracking::Configuration {
MyBatisAnnotationSqlInjectionConfiguration() { this = "MyBatis annotation sql injection" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
sink instanceof MyBatisAnnotatedMethodCallArgument
}
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or
node.getType() instanceof BoxedType or
node.getType() instanceof NumberType
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(MethodAccess ma |
ma.getMethod().getDeclaringType() instanceof TypeObject and
ma.getMethod().getName() = "toString" and
ma.getQualifier() = node1.asExpr() and
ma = node2.asExpr()
)
}
}
from
MyBatisAnnotationSqlInjectionConfiguration cfg, DataFlow::PathNode source,
DataFlow::PathNode sink, IbatisSqlOperationAnnotation isoa, MethodAccess ma,
string unsafeExpression
where
cfg.hasFlowPath(source, sink) and
ma.getAnArgument() = sink.getNode().asExpr() and
myBatisSqlOperationAnnotationFromMethod(ma.getMethod(), isoa) and
unsafeExpression = getAMybatisAnnotationSqlValue(isoa) and
(
isMybatisXmlOrAnnotationSqlInjection(sink.getNode(), ma, unsafeExpression) or
isMybatisCollectionTypeSqlInjection(sink.getNode(), ma, unsafeExpression)
)
select sink.getNode(), source, sink,
"MyBatis annotation SQL injection might include code from $@ to $@.", source.getNode(),
"this user input", isoa, "this SQL operation"

View File

@@ -0,0 +1,17 @@
/**
* Provides classes for SQL injection detection regarding MyBatis annotated methods.
*/
import java
import MyBatisCommonLib
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.frameworks.Properties
/** An argument of a MyBatis annotated method. */
class MyBatisAnnotatedMethodCallArgument extends DataFlow::Node {
MyBatisAnnotatedMethodCallArgument() {
exists(MyBatisSqlOperationAnnotationMethod msoam, MethodAccess ma | ma.getMethod() = msoam |
ma.getAnArgument() = this.asExpr()
)
}
}

View File

@@ -0,0 +1,184 @@
/**
* Provides public classes for MyBatis SQL injection detection.
*/
import java
import semmle.code.xml.MyBatisMapperXML
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.frameworks.MyBatis
import semmle.code.java.frameworks.Properties
private predicate propertiesKey(DataFlow::Node prop, string key) {
exists(MethodAccess m |
m.getMethod() instanceof PropertiesSetPropertyMethod and
key = m.getArgument(0).(CompileTimeConstantExpr).getStringValue() and
prop.asExpr() = m.getQualifier()
)
}
/** A data flow configuration tracing flow from ibatis `Configuration.getVariables()` to a store into a `Properties` object. */
private class PropertiesFlowConfig extends DataFlow2::Configuration {
PropertiesFlowConfig() { this = "PropertiesFlowConfig" }
override predicate isSource(DataFlow::Node src) {
exists(MethodAccess ma | ma.getMethod() instanceof IbatisConfigurationGetVariablesMethod |
src.asExpr() = ma
)
}
override predicate isSink(DataFlow::Node sink) { propertiesKey(sink, _) }
}
/** Gets a `Properties` key that may map onto a Mybatis `Configuration` variable. */
string getAMybatisConfigurationVariableKey() {
exists(PropertiesFlowConfig conf, DataFlow::Node n |
propertiesKey(n, result) and
conf.hasFlowTo(n)
)
}
/** A reference type that extends a parameterization of `java.util.List`. */
class ListType extends RefType {
ListType() {
this.getSourceDeclaration().getASourceSupertype*().hasQualifiedName("java.util", "List")
}
}
/** Holds if the specified `method` uses MyBatis Mapper XMLElement `mmxx`. */
predicate myBatisMapperXMLElementFromMethod(Method method, MyBatisMapperXMLElement mmxx) {
exists(MyBatisMapperSqlOperation mbmxe | mbmxe.getMapperMethod() = method |
mbmxe.getAChild*() = mmxx
or
exists(MyBatisMapperSql mbms |
mbmxe.getInclude().getRefid() = mbms.getId() and
mbms.getAChild*() = mmxx
)
)
}
/** Holds if the specified `method` has Ibatis Sql operation annotation `isoa`. */
predicate myBatisSqlOperationAnnotationFromMethod(Method method, IbatisSqlOperationAnnotation isoa) {
exists(MyBatisSqlOperationAnnotationMethod msoam |
msoam = method and
msoam.getAnAnnotation() = isoa
)
}
/** Gets a `#{...}` or `${...}` expression argument in XML element `xmle`. */
string getAMybatisXmlSetValue(XMLElement xmle) {
result = xmle.getTextValue().regexpFind("(#|\\$)\\{[^\\}]*\\}", _, _)
}
/** Gets a `#{...}` or `${...}` expression argument in annotation `isoa`. */
string getAMybatisAnnotationSqlValue(IbatisSqlOperationAnnotation isoa) {
result = isoa.getSqlValue().regexpFind("(#|\\$)\\{[^\\}]*\\}", _, _)
}
/**
* Holds if `node` is an argument to `ma` that is vulnerable to SQL injection attacks if `unsafeExpression` occurs in a MyBatis SQL expression.
*
* This case currently assumes all `${...}` expressions are potentially dangerous when there is a non-`@Param` annotated, collection-typed parameter to `ma`.
*/
bindingset[unsafeExpression]
predicate isMybatisCollectionTypeSqlInjection(
DataFlow::Node node, MethodAccess ma, string unsafeExpression
) {
not unsafeExpression.regexpMatch("\\$\\{" + getAMybatisConfigurationVariableKey() + "\\}") and
// The parameter type of the MyBatis method parameter is Map or List or Array.
// SQL injection vulnerability caused by improper use of this parameter.
// e.g.
//
// ```java
// @Select(select id,name from test where name like '%${value}%')
// Test test(Map map);
// ```
exists(int i |
not ma.getMethod().getParameter(i).getAnAnnotation().getType() instanceof TypeParam and
(
ma.getMethod().getParameterType(i) instanceof MapType or
ma.getMethod().getParameterType(i) instanceof ListType or
ma.getMethod().getParameterType(i) instanceof Array
) and
unsafeExpression.matches("${%}") and
ma.getArgument(i) = node.asExpr()
)
}
/**
* Holds if `node` is an argument to `ma` that is vulnerable to SQL injection attacks if `unsafeExpression` occurs in a MyBatis SQL expression.
*
* This accounts for:
* - arguments referred to by a name given in a `@Param` annotation,
* - arguments referred to by ordinal position, like `${param1}`
* - references to class instance fields
* - any `${}` expression where there is a single, non-`@Param`-annotated argument to `ma`.
*/
bindingset[unsafeExpression]
predicate isMybatisXmlOrAnnotationSqlInjection(
DataFlow::Node node, MethodAccess ma, string unsafeExpression
) {
not unsafeExpression.regexpMatch("\\$\\{" + getAMybatisConfigurationVariableKey() + "\\}") and
(
// The method parameters use `@Param` annotation. Due to improper use of this parameter, SQL injection vulnerabilities are caused.
// e.g.
//
// ```java
// @Select(select id,name from test order by ${orderby,jdbcType=VARCHAR})
// void test(@Param("orderby") String name);
// ```
exists(Annotation annotation |
unsafeExpression
.matches("${" + annotation.getValue("value").(CompileTimeConstantExpr).getStringValue() +
"%}") and
annotation.getType() instanceof TypeParam and
ma.getAnArgument() = node.asExpr()
)
or
// MyBatis default parameter sql injection vulnerabilities.the default parameter form of the method is arg[0...n] or param[1...n].
// e.g.
//
// ```java
// @Select(select id,name from test order by ${arg0,jdbcType=VARCHAR})
// void test(String name);
// ```
exists(int i |
not ma.getMethod().getParameter(i).getAnAnnotation().getType() instanceof TypeParam and
(
unsafeExpression.matches("${param" + (i + 1) + "%}")
or
unsafeExpression.matches("${arg" + i + "%}")
) and
ma.getArgument(i) = node.asExpr()
)
or
// SQL injection vulnerability caused by improper use of MyBatis instance class fields.
// e.g.
//
// ```java
// @Select(select id,name from test order by ${name,jdbcType=VARCHAR})
// void test(Test test);
// ```
exists(int i, RefType t |
not ma.getMethod().getParameter(i).getAnAnnotation().getType() instanceof TypeParam and
ma.getMethod().getParameterType(i).getName() = t.getName() and
unsafeExpression.matches("${" + t.getAField().getName() + "%}") and
ma.getArgument(i) = node.asExpr()
)
or
// This method has only one parameter and the parameter is not annotated with `@Param`. The parameter can be named arbitrarily in the SQL statement.
// If the number of method variables is greater than one, they cannot be named arbitrarily.
// Improper use of this parameter has a SQL injection vulnerability.
// e.g.
//
// ```java
// @Select(select id,name from test where name like '%${value}%')
// Test test(String name);
// ```
exists(int i | i = 1 |
ma.getMethod().getNumberOfParameters() = i and
not ma.getMethod().getAParameter().getAnAnnotation().getType() instanceof TypeParam and
unsafeExpression.matches("${%}") and
ma.getAnArgument() = node.asExpr()
)
)
}

View File

@@ -0,0 +1,33 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>MyBatis allows operating the database by creating XML files to construct dynamic SQL statements.
If the syntax <code>${param}</code> is used in those statements, and <code>param</code> is under the user's control, attackers can exploit this to tamper with the SQL statements or execute arbitrary SQL commands.</p>
</overview>
<recommendation>
<p>
When writing MyBatis mapping statements, try to use the syntax <code>#{xxx}</code>. If the syntax <code>${xxx}</code> must be used, any parameters included in it should be sanitized to prevent SQL injection attacks.
</p>
</recommendation>
<example>
<p>
The following sample shows several bad and good examples of MyBatis XML files usage. In <code>bad1</code>,
<code>bad2</code>, <code>bad3</code>, <code>bad4</code>, and <code >bad5</code> the syntax
<code>${xxx}</code> is used to build dynamic SQL statements, which causes a SQL injection vulnerability. In <code>good1</code>,
the program uses the <code>${xxx}</code> syntax, but there are subtle restrictions on the data,
while in <code>good2</code> the syntax <code>#{xxx}</code> is used. In both cases the SQL injection vulnerability is prevented.
</p>
<sample src="MyBatisMapperXmlSqlInjection.xml" />
</example>
<references>
<li>
Fortify:
<a href="https://vulncat.fortify.com/en/detail?id=desc.config.java.sql_injection_mybatis_mapper">SQL Injection: MyBatis Mapper</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,62 @@
/**
* @name SQL injection in MyBatis Mapper XML
* @description Constructing a dynamic SQL statement with input that comes from an
* untrusted source could allow an attacker to modify the statement's
* meaning or to execute arbitrary SQL commands.
* @kind path-problem
* @problem.severity error
* @precision high
* @id java/mybatis-xml-sql-injection
* @tags security
* external/cwe/cwe-089
*/
import java
import DataFlow::PathGraph
import MyBatisCommonLib
import MyBatisMapperXmlSqlInjectionLib
import semmle.code.xml.MyBatisMapperXML
import semmle.code.java.dataflow.FlowSources
private class MyBatisMapperXmlSqlInjectionConfiguration extends TaintTracking::Configuration {
MyBatisMapperXmlSqlInjectionConfiguration() { this = "MyBatis mapper xml sql injection" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
sink instanceof MyBatisMapperMethodCallAnArgument
}
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or
node.getType() instanceof BoxedType or
node.getType() instanceof NumberType
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(MethodAccess ma |
ma.getMethod().getDeclaringType() instanceof TypeObject and
ma.getMethod().getName() = "toString" and
ma.getQualifier() = node1.asExpr() and
ma = node2.asExpr()
)
}
}
from
MyBatisMapperXmlSqlInjectionConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink,
MyBatisMapperXMLElement mmxe, MethodAccess ma, string unsafeExpression
where
cfg.hasFlowPath(source, sink) and
ma.getAnArgument() = sink.getNode().asExpr() and
myBatisMapperXMLElementFromMethod(ma.getMethod(), mmxe) and
unsafeExpression = getAMybatisXmlSetValue(mmxe) and
(
isMybatisXmlOrAnnotationSqlInjection(sink.getNode(), ma, unsafeExpression)
or
mmxe instanceof MyBatisMapperForeach and
isMybatisCollectionTypeSqlInjection(sink.getNode(), ma, unsafeExpression)
)
select sink.getNode(), source, sink,
"MyBatis Mapper XML SQL injection might include code from $@ to $@.", source.getNode(),
"this user input", mmxe, "this SQL operation"

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="SqlInjectionMapper">
<resultMap id="BaseResultMap" type="Test">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="pass" jdbcType="VARCHAR" property="pass"/>
</resultMap>
<sql id="Update_By_Example_Where_Clause">
<where>
<if test="name != null">
-- bad
and name = ${name}
</if>
<if test="id != null">
and id = #{id}
</if>
</where>
</sql>
<select id="bad1" parameterType="java.lang.String" resultMap="BaseResultMap">
-- bad
select id,name from test where name like '%${name}%'
</select>
<select id="bad2" parameterType="java.lang.String" resultMap="BaseResultMap">
-- bad
select id,name from test order by ${name} desc
</select>
<select id="bad3" parameterType="java.lang.String" resultMap="BaseResultMap">
-- bad
select id,name from test where name in ${name}
</select>
<update id="bad4" parameterType="Test">
update test
<set>
<if test="pass != null">
pass = #{pass},
</if>
</set>
<if test="_parameter != null">
-- bad
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<insert id="bad5" parameterType="Test">
insert into test (name, pass)
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="name != null">
-- bad
name = ${name},
</if>
<if test="pass != null">
-- bad
pass = ${pass},
</if>
</trim>
</insert>
<select id="good1" parameterType="java.lang.Integer" resultMap="BaseResultMap">
-- good
select id,name from test where id = ${id}
</select>
<select id="good2" parameterType="java.lang.String" resultMap="BaseResultMap">
-- good
select id,name from test where name = #{name}
</select>
</mapper>

View File

@@ -0,0 +1,19 @@
/**
* Provide classes for SQL injection detection in MyBatis Mapper XML.
*/
import java
import semmle.code.xml.MyBatisMapperXML
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.frameworks.Properties
/** A sink for MyBatis Mapper method call an argument. */
class MyBatisMapperMethodCallAnArgument extends DataFlow::Node {
MyBatisMapperMethodCallAnArgument() {
exists(MyBatisMapperSqlOperation mbmxe, MethodAccess ma |
mbmxe.getMapperMethod() = ma.getMethod()
|
ma.getAnArgument() = this.asExpr()
)
}
}

View File

@@ -0,0 +1,116 @@
/**
* Provides classes for working with MyBatis mapper xml files and their content.
*/
import java
/**
* 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 `<include>` 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 `<insert>` element in a `MyBatisMapperSqlOperation`.
*/
class MyBatisMapperInsert extends MyBatisMapperSqlOperation {
MyBatisMapperInsert() { this.getName() = "insert" }
}
/**
* A `<update>` element in a `MyBatisMapperSqlOperation`.
*/
class MyBatisMapperUpdate extends MyBatisMapperSqlOperation {
MyBatisMapperUpdate() { this.getName() = "update" }
}
/**
* A `<delete>` element in a `MyBatisMapperSqlOperation`.
*/
class MyBatisMapperDelete extends MyBatisMapperSqlOperation {
MyBatisMapperDelete() { this.getName() = "delete" }
}
/**
* A `<select>` element in a `MyBatisMapperSqlOperation`.
*/
class MyBatisMapperSelect extends MyBatisMapperSqlOperation {
MyBatisMapperSelect() { this.getName() = "select" }
}
/**
* A `<sql>` element in a `MyBatisMapperXMLElement`.
*/
class MyBatisMapperSql extends MyBatisMapperXMLElement {
MyBatisMapperSql() { this.getName() = "sql" }
/**
* Gets the value of the `id` attribute of this `<sql>`.
*/
string getId() { result = this.getAttribute("id").getValue() }
}
/**
* A `<include>` element in a `MyBatisMapperXMLElement`.
*/
class MyBatisMapperInclude extends MyBatisMapperXMLElement {
MyBatisMapperInclude() { this.getName() = "include" }
/**
* Gets the value of the `refid` attribute of this `<include>`.
*/
string getRefid() { result = this.getAttribute("refid").getValue() }
}
/**
* A `<foreach>` element in a `MyBatisMapperXMLElement`.
*/
class MyBatisMapperForeach extends MyBatisMapperXMLElement {
MyBatisMapperForeach() { getName() = "foreach" }
}

View File

@@ -0,0 +1,16 @@
edges
| MybatisSqlInjection.java:62:19:62:43 | name : String | MybatisSqlInjection.java:63:35:63:38 | name : String |
| MybatisSqlInjection.java:63:35:63:38 | name : String | MybatisSqlInjectionService.java:48:19:48:29 | name : String |
| MybatisSqlInjectionService.java:48:19:48:29 | name : String | MybatisSqlInjectionService.java:50:23:50:26 | name : String |
| MybatisSqlInjectionService.java:50:3:50:9 | hashMap [post update] [<map.value>] : String | MybatisSqlInjectionService.java:51:27:51:33 | hashMap |
| MybatisSqlInjectionService.java:50:23:50:26 | name : String | MybatisSqlInjectionService.java:50:3:50:9 | hashMap [post update] [<map.value>] : String |
nodes
| MybatisSqlInjection.java:62:19:62:43 | name : String | semmle.label | name : String |
| MybatisSqlInjection.java:63:35:63:38 | name : String | semmle.label | name : String |
| MybatisSqlInjectionService.java:48:19:48:29 | name : String | semmle.label | name : String |
| MybatisSqlInjectionService.java:50:3:50:9 | hashMap [post update] [<map.value>] : String | semmle.label | hashMap [post update] [<map.value>] : String |
| MybatisSqlInjectionService.java:50:23:50:26 | name : String | semmle.label | name : String |
| MybatisSqlInjectionService.java:51:27:51:33 | hashMap | semmle.label | hashMap |
subpaths
#select
| MybatisSqlInjectionService.java:51:27:51:33 | hashMap | MybatisSqlInjection.java:62:19:62:43 | name : String | MybatisSqlInjectionService.java:51:27:51:33 | hashMap | MyBatis annotation SQL injection might include code from $@ to $@. | MybatisSqlInjection.java:62:19:62:43 | name | this user input | SqlInjectionMapper.java:29:2:29:54 | Select | this SQL operation |

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-089/MyBatisAnnotationSqlInjection.ql

View File

@@ -0,0 +1,69 @@
edges
| MybatisSqlInjection.java:19:25:19:49 | name : String | MybatisSqlInjection.java:20:55:20:58 | name : String |
| MybatisSqlInjection.java:20:55:20:58 | name : String | MybatisSqlInjectionService.java:13:25:13:35 | name : String |
| MybatisSqlInjection.java:25:25:25:49 | name : String | MybatisSqlInjection.java:26:55:26:58 | name : String |
| MybatisSqlInjection.java:26:55:26:58 | name : String | MybatisSqlInjectionService.java:18:25:18:35 | name : String |
| MybatisSqlInjection.java:31:25:31:49 | test : Test | MybatisSqlInjection.java:32:55:32:58 | test : Test |
| MybatisSqlInjection.java:32:55:32:58 | test : Test | MybatisSqlInjectionService.java:23:25:23:33 | test : Test |
| MybatisSqlInjection.java:37:19:37:40 | test : Test | MybatisSqlInjection.java:38:35:38:38 | test : Test |
| MybatisSqlInjection.java:38:35:38:38 | test : Test | MybatisSqlInjectionService.java:28:19:28:27 | test : Test |
| MybatisSqlInjection.java:42:19:42:40 | test : Test | MybatisSqlInjection.java:43:35:43:38 | test : Test |
| MybatisSqlInjection.java:43:35:43:38 | test : Test | MybatisSqlInjectionService.java:32:19:32:27 | test : Test |
| MybatisSqlInjection.java:47:19:47:57 | params : Map | MybatisSqlInjection.java:48:35:48:40 | params : Map |
| MybatisSqlInjection.java:48:35:48:40 | params : Map | MybatisSqlInjectionService.java:36:19:36:44 | params : Map |
| MybatisSqlInjection.java:52:19:52:50 | params : List | MybatisSqlInjection.java:53:35:53:40 | params : List |
| MybatisSqlInjection.java:53:35:53:40 | params : List | MybatisSqlInjectionService.java:40:19:40:37 | params : List |
| MybatisSqlInjection.java:57:19:57:46 | params : String[] | MybatisSqlInjection.java:58:35:58:40 | params : String[] |
| MybatisSqlInjection.java:58:35:58:40 | params : String[] | MybatisSqlInjectionService.java:44:19:44:33 | params : String[] |
| MybatisSqlInjectionService.java:13:25:13:35 | name : String | MybatisSqlInjectionService.java:14:47:14:50 | name |
| MybatisSqlInjectionService.java:18:25:18:35 | name : String | MybatisSqlInjectionService.java:19:47:19:50 | name |
| MybatisSqlInjectionService.java:23:25:23:33 | test : Test | MybatisSqlInjectionService.java:24:47:24:50 | test |
| MybatisSqlInjectionService.java:28:19:28:27 | test : Test | MybatisSqlInjectionService.java:29:27:29:30 | test |
| MybatisSqlInjectionService.java:32:19:32:27 | test : Test | MybatisSqlInjectionService.java:33:27:33:30 | test |
| MybatisSqlInjectionService.java:36:19:36:44 | params : Map | MybatisSqlInjectionService.java:37:27:37:32 | params |
| MybatisSqlInjectionService.java:40:19:40:37 | params : List | MybatisSqlInjectionService.java:41:27:41:32 | params |
| MybatisSqlInjectionService.java:44:19:44:33 | params : String[] | MybatisSqlInjectionService.java:45:27:45:32 | params |
nodes
| MybatisSqlInjection.java:19:25:19:49 | name : String | semmle.label | name : String |
| MybatisSqlInjection.java:20:55:20:58 | name : String | semmle.label | name : String |
| MybatisSqlInjection.java:25:25:25:49 | name : String | semmle.label | name : String |
| MybatisSqlInjection.java:26:55:26:58 | name : String | semmle.label | name : String |
| MybatisSqlInjection.java:31:25:31:49 | test : Test | semmle.label | test : Test |
| MybatisSqlInjection.java:32:55:32:58 | test : Test | semmle.label | test : Test |
| MybatisSqlInjection.java:37:19:37:40 | test : Test | semmle.label | test : Test |
| MybatisSqlInjection.java:38:35:38:38 | test : Test | semmle.label | test : Test |
| MybatisSqlInjection.java:42:19:42:40 | test : Test | semmle.label | test : Test |
| MybatisSqlInjection.java:43:35:43:38 | test : Test | semmle.label | test : Test |
| MybatisSqlInjection.java:47:19:47:57 | params : Map | semmle.label | params : Map |
| MybatisSqlInjection.java:48:35:48:40 | params : Map | semmle.label | params : Map |
| MybatisSqlInjection.java:52:19:52:50 | params : List | semmle.label | params : List |
| MybatisSqlInjection.java:53:35:53:40 | params : List | semmle.label | params : List |
| MybatisSqlInjection.java:57:19:57:46 | params : String[] | semmle.label | params : String[] |
| MybatisSqlInjection.java:58:35:58:40 | params : String[] | semmle.label | params : String[] |
| MybatisSqlInjectionService.java:13:25:13:35 | name : String | semmle.label | name : String |
| MybatisSqlInjectionService.java:14:47:14:50 | name | semmle.label | name |
| MybatisSqlInjectionService.java:18:25:18:35 | name : String | semmle.label | name : String |
| MybatisSqlInjectionService.java:19:47:19:50 | name | semmle.label | name |
| MybatisSqlInjectionService.java:23:25:23:33 | test : Test | semmle.label | test : Test |
| MybatisSqlInjectionService.java:24:47:24:50 | test | semmle.label | test |
| MybatisSqlInjectionService.java:28:19:28:27 | test : Test | semmle.label | test : Test |
| MybatisSqlInjectionService.java:29:27:29:30 | test | semmle.label | test |
| MybatisSqlInjectionService.java:32:19:32:27 | test : Test | semmle.label | test : Test |
| MybatisSqlInjectionService.java:33:27:33:30 | test | semmle.label | test |
| MybatisSqlInjectionService.java:36:19:36:44 | params : Map | semmle.label | params : Map |
| MybatisSqlInjectionService.java:37:27:37:32 | params | semmle.label | params |
| MybatisSqlInjectionService.java:40:19:40:37 | params : List | semmle.label | params : List |
| MybatisSqlInjectionService.java:41:27:41:32 | params | semmle.label | params |
| MybatisSqlInjectionService.java:44:19:44:33 | params : String[] | semmle.label | params : String[] |
| MybatisSqlInjectionService.java:45:27:45:32 | params | semmle.label | params |
subpaths
#select
| MybatisSqlInjectionService.java:14:47:14:50 | name | MybatisSqlInjection.java:19:25:19:49 | name : String | MybatisSqlInjectionService.java:14:47:14:50 | name | MyBatis Mapper XML SQL injection might include code from $@ to $@. | MybatisSqlInjection.java:19:25:19:49 | name | this user input | SqlInjectionMapper.xml:23:3:25:12 | select | this SQL operation |
| MybatisSqlInjectionService.java:19:47:19:50 | name | MybatisSqlInjection.java:25:25:25:49 | name : String | MybatisSqlInjectionService.java:19:47:19:50 | name | MyBatis Mapper XML SQL injection might include code from $@ to $@. | MybatisSqlInjection.java:25:25:25:49 | name | this user input | SqlInjectionMapper.xml:27:3:29:12 | select | this SQL operation |
| MybatisSqlInjectionService.java:24:47:24:50 | test | MybatisSqlInjection.java:31:25:31:49 | test : Test | MybatisSqlInjectionService.java:24:47:24:50 | test | MyBatis Mapper XML SQL injection might include code from $@ to $@. | MybatisSqlInjection.java:31:25:31:49 | test | this user input | SqlInjectionMapper.xml:31:3:33:12 | select | this SQL operation |
| MybatisSqlInjectionService.java:29:27:29:30 | test | MybatisSqlInjection.java:37:19:37:40 | test : Test | MybatisSqlInjectionService.java:29:27:29:30 | test | MyBatis Mapper XML SQL injection might include code from $@ to $@. | MybatisSqlInjection.java:37:19:37:40 | test | this user input | SqlInjectionMapper.xml:14:7:16:12 | if | this SQL operation |
| MybatisSqlInjectionService.java:33:27:33:30 | test | MybatisSqlInjection.java:42:19:42:40 | test : Test | MybatisSqlInjectionService.java:33:27:33:30 | test | MyBatis Mapper XML SQL injection might include code from $@ to $@. | MybatisSqlInjection.java:42:19:42:40 | test | this user input | SqlInjectionMapper.xml:50:7:52:12 | if | this SQL operation |
| MybatisSqlInjectionService.java:33:27:33:30 | test | MybatisSqlInjection.java:42:19:42:40 | test : Test | MybatisSqlInjectionService.java:33:27:33:30 | test | MyBatis Mapper XML SQL injection might include code from $@ to $@. | MybatisSqlInjection.java:42:19:42:40 | test | this user input | SqlInjectionMapper.xml:53:7:55:12 | if | this SQL operation |
| MybatisSqlInjectionService.java:37:27:37:32 | params | MybatisSqlInjection.java:47:19:47:57 | params : Map | MybatisSqlInjectionService.java:37:27:37:32 | params | MyBatis Mapper XML SQL injection might include code from $@ to $@. | MybatisSqlInjection.java:47:19:47:57 | params | this user input | SqlInjectionMapper.xml:59:3:61:12 | select | this SQL operation |
| MybatisSqlInjectionService.java:41:27:41:32 | params | MybatisSqlInjection.java:52:19:52:50 | params : List | MybatisSqlInjectionService.java:41:27:41:32 | params | MyBatis Mapper XML SQL injection might include code from $@ to $@. | MybatisSqlInjection.java:52:19:52:50 | params | this user input | SqlInjectionMapper.xml:65:5:67:15 | foreach | this SQL operation |
| MybatisSqlInjectionService.java:45:27:45:32 | params | MybatisSqlInjection.java:57:19:57:46 | params : String[] | MybatisSqlInjectionService.java:45:27:45:32 | params | MyBatis Mapper XML SQL injection might include code from $@ to $@. | MybatisSqlInjection.java:57:19:57:46 | params | this user input | SqlInjectionMapper.xml:72:5:74:15 | foreach | this SQL operation |

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-089/MyBatisMapperXmlSqlInjection.ql

View File

@@ -0,0 +1,71 @@
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class MybatisSqlInjection {
@Autowired
private MybatisSqlInjectionService mybatisSqlInjectionService;
@GetMapping(value = "msi1")
public List<Test> bad1(@RequestParam String name) {
List<Test> result = mybatisSqlInjectionService.bad1(name);
return result;
}
@GetMapping(value = "msi2")
public List<Test> bad2(@RequestParam String name) {
List<Test> result = mybatisSqlInjectionService.bad2(name);
return result;
}
@GetMapping(value = "msi3")
public List<Test> bad3(@ModelAttribute Test test) {
List<Test> result = mybatisSqlInjectionService.bad3(test);
return result;
}
@RequestMapping(value = "msi4", method = RequestMethod.POST, produces = "application/json")
public void bad4(@RequestBody Test test) {
mybatisSqlInjectionService.bad4(test);
}
@RequestMapping(value = "msi5", method = RequestMethod.PUT, produces = "application/json")
public void bad5(@RequestBody Test test) {
mybatisSqlInjectionService.bad5(test);
}
@RequestMapping(value = "msi6", method = RequestMethod.POST, produces = "application/json")
public void bad6(@RequestBody Map<String, String> params) {
mybatisSqlInjectionService.bad6(params);
}
@RequestMapping(value = "msi7", method = RequestMethod.POST, produces = "application/json")
public void bad7(@RequestBody List<String> params) {
mybatisSqlInjectionService.bad7(params);
}
@RequestMapping(value = "msi8", method = RequestMethod.POST, produces = "application/json")
public void bad8(@RequestBody String[] params) {
mybatisSqlInjectionService.bad8(params);
}
@GetMapping(value = "msi9")
public void bad9(@RequestParam String name) {
mybatisSqlInjectionService.bad9(name);
}
@GetMapping(value = "good1")
public List<Test> good1(Integer id) {
List<Test> result = mybatisSqlInjectionService.good1(id);
return result;
}
}

View File

@@ -0,0 +1,58 @@
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MybatisSqlInjectionService {
@Autowired
private SqlInjectionMapper sqlInjectionMapper;
public List<Test> bad1(String name) {
List<Test> result = sqlInjectionMapper.bad1(name);
return result;
}
public List<Test> bad2(String name) {
List<Test> result = sqlInjectionMapper.bad2(name);
return result;
}
public List<Test> bad3(Test test) {
List<Test> result = sqlInjectionMapper.bad3(test);
return result;
}
public void bad4(Test test) {
sqlInjectionMapper.bad4(test);
}
public void bad5(Test test) {
sqlInjectionMapper.bad5(test);
}
public void bad6(Map<String, String> params) {
sqlInjectionMapper.bad6(params);
}
public void bad7(List<String> params) {
sqlInjectionMapper.bad7(params);
}
public void bad8(String[] params) {
sqlInjectionMapper.bad8(params);
}
public void bad9(String name) {
HashMap hashMap = new HashMap();
hashMap.put("name", name);
sqlInjectionMapper.bad9(hashMap);
}
public List<Test> good1(Integer id) {
List<Test> result = sqlInjectionMapper.good1(id);
return result;
}
}

View File

@@ -0,0 +1,33 @@
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import org.apache.ibatis.annotations.Select;
@Mapper
@Repository
public interface SqlInjectionMapper {
List<Test> bad1(String name);
List<Test> bad2(@Param("orderby") String name);
List<Test> bad3(Test test);
void bad4(@Param("test") Test test);
void bad5(Test test);
void bad6(Map<String, String> params);
void bad7(List<String> params);
void bad8(String[] params);
@Select({"select * from test", "where id = ${name}"})
public Test bad9(HashMap<String, Object> map);
List<Test> good1(Integer id);
}

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="SqlInjectionMapper">
<resultMap id="BaseResultMap" type="Test">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="pass" jdbcType="VARCHAR" property="pass"/>
</resultMap>
<sql id="Update_By_Example_Where_Clause">
<where>
<if test="test.name != null">
and name = ${test.name,jdbcType=VARCHAR}
</if>
<if test="test.id != null">
and id = #{test.id}
</if>
</where>
</sql>
<select id="bad1" parameterType="java.lang.String" resultMap="BaseResultMap">
select id,name from test where name like '%${name}%'
</select>
<select id="bad2" resultMap="BaseResultMap">
select id,name from test order by ${orderby,jdbcType=VARCHAR} desc
</select>
<select id="bad3" parameterType="Test" resultMap="BaseResultMap">
select id,name from test where name in ${name}
</select>
<update id="bad4" parameterType="Test">
update test
<set>
<if test="test.pass != null">
pass = #{test.pass},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<insert id="bad5" parameterType="Test">
insert into test (name, pass)
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="name != null">
name = ${name,jdbcType=VARCHAR},
</if>
<if test="pass != null">
pass = ${pass},
</if>
</trim>
</insert>
<select id="bad6" resultMap="BaseResultMap">
select id,name from test where name like '%${name}%'
</select>
<select id="bad7" resultMap="BaseResultMap">
select id,name from test where name in
<foreach collection="list" item="value" open="(" close=")" separator=",">
${value}
</foreach>
</select>
<select id="bad8" resultMap="BaseResultMap">
select id,name from test where name in
<foreach collection="array" item="value" open="(" close=")" separator=",">
${value}
</foreach>
</select>
<select id="good1" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select id,name from test where id = ${id}
</select>
</mapper>

View File

@@ -0,0 +1,43 @@
import java.io.Serializable;
public class Test implements Serializable {
private Integer id;
private String name;
private String pass;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPass() {
return pass;
}
public void setPass(String pass) {
this.pass = pass;
}
@Override
public String toString() {
return "Test{" +
"id=" + id +
", name='" + name + '\'' +
", pass='" + pass + '\'' +
'}';
}
}

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../../stubs/springframework-5.3.8/:${testdir}/../../../../../../stubs/org.mybatis-3.5.4/

View File

@@ -0,0 +1,15 @@
package org.apache.ibatis.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface Mapper {
}

View File

@@ -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.PARAMETER})
public @interface Param {
String value();
}

View File

@@ -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 Select {
String[] value();
}

View File

@@ -0,0 +1,19 @@
package org.springframework.stereotype;
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.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(
annotation = Component.class
)
String value() default "";
}

View File

@@ -0,0 +1,19 @@
package org.springframework.stereotype;
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.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(
annotation = Component.class
)
String value() default "";
}

View File

@@ -0,0 +1,21 @@
package org.springframework.web.bind.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.springframework.core.annotation.AliasFor;
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModelAttribute {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean binding() default true;
}