Safe way to prevent sql injection with mybatis - mybatis

I am using '$' notation in mybatis query:
<select id="queryOrgs" resultMap="orgLiteMap" parameterType="gov.cbrc.gzbanking.data.QueryRequest" >
select id, name from sys_orgs
<if test="filter != null">where id like #{filter, jdbcType=INTEGER} or name like #{filter, jdbcType=INTEGER}</if>
<if test="order != null">order by ${order}</if>
limit #{offset, jdbcType=INTEGER},#{fetch, jdbcType=INTEGER}
</select>
the order parameter can be something like "id desc", do I need to worry about sql injection here? We know that mybatis uses PreparedStatement, if mybatis call PreparedStatement#executeQuery() against "select" statement, or the jdbc driver implementation does't allow multiple statements in one call, then sql injection is impossible, am I right?
If that is possible for sql injection in my case, what's the verified way to prevent it ?
------------------------ edit -----------------------
Is that enough to check order parameter has a sql delimiter?

If order comes directly from the user, you can get into trouble. You should never trust user input.
To respond to your questions:
If you don't allow multiple queries you will get an exception if you try running more than one, yes;
checking for an SQL delimiter might be enough, might not;
Using ${} exposes you to SQL injection if you send the user input unchanged to the SQL. The above two methods are fragile:
you can easily forget to configure the connection or a colleague of yours might turn it on because he/she needs to execute multiple queries in one statement and they were not aware about this particular statement
hackers might be smarter than checks you have in place (e.g have you considered testing for \u003B too? What else might an attacker attempt?).
Always perform your own escapes and checks and send a value you control, not something received from the user. Have all the correct values in your code and send that instead. It's then just a matter of determining which one the user wanted and fallback to a default if you can't determine it, something like:
String userChoice = order.toLowerCase();
String safeOrder = null;
if ("id desc".equals(userChoice)) {
safeOrder = "id desc";
} else if ("id asc".equals(userChoice)) {
safeOrder = "id asc";
....
....
} else if ("name desc".equals(userChoice)) {
safeOrder = "name desc";
} else {
// if no clean match then maybe user tried something fishy...
// ... go with some default
safeOrder = "id desc"; // or null or whatever you like...
}
then in your mapping you do:
<if test="safeOrder != null">order by ${safeOrder}</if>

See https://mybatis.github.io/mybatis-3/sqlmap-xml.html. section 'String Substitution' for offical comment on that.
It's not safe to accept input from a user and supply it to a statement
unmodified in this way. This leads to potential SQL Injection attacks
and therefore you should either disallow user input in these fields,
or always perform your own escapes and checks.

My answer, mybatis sql template may be a good choose. FYI:
<sql id="orderTypeSql">
<trim prefix=" ">
<if test="'desc'.equalsIgnoreCase(orderType)">desc</if>
</trim>
</sql>
<sql id="orderColumnSql">
<trim prefix="order by " suffix="" >
<choose>
<when test="orderColumn==null or orderColumn==''"></when>
<when test="orderColumn=='id'">
id<include refid="orderTypeSql"/>
</when>
<when test="orderColumn=='name'">
`name`<include refid="orderTypeSql"/>
</when>
</choose>
</trim>
</sql>
<select id="testOrderBy" resultType="User">
select
id,
`name`
from t_user
<include refid="orderColumnSql"/>
limit 0, 10
</select>

Related

hooks causing issues with insert using subqueries

I'm not sure why this is causing me an issue, but I'm using Orient 2.1.19, found this in 2.1.12 as well. We are building some hooks to implement a method of encryption. I know 2.2 implements some encryption, but we had some further requirements.
Anyway, we have hooks for onRecordAfterRead, onRecordBeforeCreate and onRecordBeforeUpdate. It works for most statements fine, but with the hook in place, running a query that sets a link property using a subquery in an insert fails. Here's an example query:
create EDGE eThisEdge from (select from vVertex where thisproperty = 'this') to (select from vVertex where thatProperty = 'that' ) set current = (select from lookupCurrent where displayCurrentPast = 'Current');
Runnning this query gives me the error:
com.orientechnologies.orient.core.exception.OValidationException: The field 'eThisEdge.current' has been declared as LINK but the value is not a record or a record-id.
It's some issue with the way a subquery is ran during just an insert though, because if I run the insert without setting any properties, then run an update to set the properties, that works. I'd hate to have to rewrite all of our inserts for our base data and our coding just as a work around for this, and it seems like I'm just missing something here.
Has anyone seen this kind of issue with hooks as well?
The biggest issue seems to be surrounding the onRecordBeforeCreate code. We are trying to have a generic hook that encrypts strings in our database. Here's the basics of the onRecordBeforeCreate method:
public RESULT onRecordBeforeCreate( ODocument oDocument) {
RESULT changed = RESULT.RECORD_NOT_CHANGED;
try {
if(classIsCipherable(oDocument)) {
for (String field : oDocument.fieldNames()) {
if (oDocument.fieldType(field) != null && oDocument.fieldType(field) == OType.STRING && oDocument.field(field) != null) {
oDocument.field(field, crypto.encrypt(oDocument.field(field).toString()));
changed = RESULT.RECORD_CHANGED;
}
}
}
return changed;
} catch (Exception e) {
throw new RuntimeException( e );
}
Is there anything there that looks obvious that I'd have issues with running a create edge statement that sets properties with a property that is a link?
The query select from lookupCurrent where displayCurrentPast = "Current" return more than one element, you must use a LinkList or a LinkSet

Using unnest as a field rather than a table in jOOQ

This is the query I am trying to run in PostgreSQL:
SELECT * FROM message WHERE id IN (
SELECT unnest(message_ids) "mid"
FROM session_messages WHERE session_id = '?' ORDER BY "mid" ASC
);
However, I am not able do something:
create.selectFrom(Tables.MESSAGE).where(Tables.MESSAGE.ID.in(
create.select(DSL.unnest(..))
Because DSL.unnest is a Table<?>, which makes sense since it is trying to take a List-like object (mostly a literal) and convert it to table.
I have a feeling that I need to find a way to wrap the function around my field name, but I have no clue as to how to proceed.
NOTE. The field message_ids is of type bigint[].
EDIT
So, this is how I am doing it now, and it works exactly as expected, but I am not sure if this is the best way to do it:
Field<Long> unnestMessageIdField = DSL.field(
"unnest(" + SESSION_MESSAGES.MESSAGE_IDS.getName() + ")",
Long.class)
.as("mid");
Field<Long> messageIdField = DSL.field("mid", Long.class);
MESSAGE.ID.in(
ctx.select(messageIdField).from(
ctx.select(unnestMessageIdField)
.from(Tables.CHAT_SESSION_MESSAGES)
.where(Tables.CHAT_SESSION_MESSAGES.SESSION_ID.eq(sessionId))
)
.where(condition)
)
EDIT2
After going through the code on https://github.com/jOOQ/jOOQ/blob/master/jOOQ/src/main/java/org/jooq/impl/DSL.java I guess the right way to do this would be:
DSL.function("unnest", SQLDataTypes.BIGINT.getArrayType(), SESSION_MESSAGES.MESSAGE_IDS)
EDIT3
Since as always lukas is here for my jOOQ woes, I am going to capitalize on this :)
Trying to generalize this function, in a signature of sort
public <T> Field<T> unnest(Field<T[]> arrayField) {
return DSL.function("unnest", <??>, arrayField);
}
I don't know how I can fetch the datatype. There seems to be a way to get DataType<T[]> from DataType<T> using DataType::getArrayDataType(), but the reverse is not possible. There is this class I found ArrayDataType, but it seems to be package-private, so I cannot use it (and even if I could, it does not expose the field elementType).
Old PostgreSQL versions had this funky idea that it is OK to produce a table from within the SELECT clause, and expand it into the "outer" table, as if it were declared in the FROM clause. That is a very obscure PostgreSQL legacy, and this example is a good chance to get rid of it, and use LATERAL instead. Your query is equivalent to this one:
SELECT *
FROM message
WHERE id IN (
SELECT "mid"
FROM session_messages
CROSS JOIN LATERAL unnest(message_ids) AS t("mid")
WHERE session_id = '?'
);
This can be translated to jOOQ much more easily as:
DSL.using(configuration)
.select()
.from(MESSAGE)
.where(MESSAGE.ID).in(
select(field(name("mid"), MESSAGE.ID.getDataType()))
.from(SESSION_MESSAGES)
.crossJoin(lateral(unnest(SESSION_MESSAGES.MESSAGE_IDS)).as("t", "mid"))
.where(SESSION_MESSAGES.SESSION_ID.eq("'?'"))
)
The Edit3 in the question is quite close to a decent solution for this problem.
We can create a custom generic unnest method for jOOQ which accepts Field and use it in jOOQ query normally.
Helper method:
public static <T> Field<T> unnest(Field<T[]> field) {
var type = (Class<T>) field.getType().getComponentType();
return DSL.function("unnest", type, field);
}
Usage:
public void query(SessionId sessionId) {
var field = unnest(SESSION_MESSAGES.MESSAGE_IDS, UUID.class);
dsl.select().from(MESSAGE).where(
MESSAGE.ID.in(
dsl.select(field).from(SESSION_MESSAGES)
.where(SESSION_MESSAGES.SESSION_ID.eq(sessionId.id))
.orderBy(field)
)
);
}

Reusing dynamic sql fragments

Hei there, I'm working on a Primefaces app and as a persistence layer I chose Mybatis.
This is how a regular sql would look in my mapper:
<select id="getAllTransportUnit" resultMap="TransportUnitMap">
SELECT * FROM SSLS_GUI.VW_TU
<if test="( hasFilters == 'yes' ) and ( parameters != null )">
<where>
<foreach item="clause" collection="parameters" separator=" AND "
open="(" close=")">
UPPER(${clause.column}) ${clause.operator} #{clause.value}
</foreach>
</where>
</if>
<if test="sort == 'true'">
ORDER BY ${sortField}
<if test="sortOrder == 'DESC'"> DESC</if>
<if test="sortOder == 'ASC'"> ASC</if>
</if>
</select>
Almost all my queries use the dynamic sql part starting from the <if test...>. Is it possible to put it in a separate file and then reuse it all over my queries?
There are several options how to reuse sql snippets.
SQL snippets and include
The first one is using include. Create separate mapper Common.xml:
<mapper namespace="com.company.project.common">
<sql id="orderBy>
<if test="sort == 'true'">
ORDER BY ${sortField}
<if test="sortOrder == 'DESC'"> DESC</if>
<if test="sortOder == 'ASC'"> ASC</if>
</if>
</sql>
<sql id="filters">
<if test="( hasFilters == 'yes' ) and ( parameters != null )">
<where>
<foreach item="clause" collection="parameters" separator=" AND "
open="(" close=")">
UPPER(${clause.column}) ${clause.operator} #{clause.value}
</foreach>
</where>
</if>
</sql>
</mapper>
And the use it in other mappers MyMapper.xml:
<select id="getAllTransportUnit" resultMap="TransportUnitMap">
SELECT * FROM SSLS_GUI.VW_TU
<include refid="com.company.project.common.filters"/>
<include refid="com.company.project.common.orderBy"/>
</select>
To avoid duplicating namespace in every include you can create shortcut snippets in MyMapper.xml:
<sql id="orderBy">
<include refid="com.company.project.common.orderBy"/>
</sql>
<select id="getAllTransportUnit" resultMap="TransportUnitMap">
SELECT * FROM SSLS_GUI.VW_TU
<include refid="orderBy"/>
</select>
Mybatis-velocity macro
Another possible option is to use mybatis scripting. Using mybatis-velocity scripting engine you can define velocity macro and use it like this.
In Commons.xml:
<sql id="macros"
#macro(filters)
#if ( $_parameter.hasFilters )
#repeat( $_parameter.parameters $clause "AND" " (" ")" )
${clause.column} ${clause.operator} #{clause.value}
#end
#end
#end
#macro(order_by)
..
#end
</sql>
In MyMapper.xml:
<select id="getAllTransportUnit" resultMap="TransportUnitMap">
<include refid="macros"/>
SELECT * FROM SSLS_GUI.VW_TU
#filters()
#order_by()
</select>
Including macros via sql snippet is not the most clean way to reuse macros. It is just an idea how this is used.
Much better option is to configure mybatis-velocity and specify what global macros are available. In this case there will be no need to include macros snippet and result query will be like this:
<select id="getAllTransportUnit" resultMap="TransportUnitMap">
SELECT * FROM SSLS_GUI.VW_TU
#filters()
#order_by()
</select>
See #Roman Konoval's answer for how to do this in XML.
For another option on the pure Java side (in OP's case, the XML option above is more applicable; I leave this here for those who may be using pure Java), one can use Mybatis' Statement Builders, which allow for the construction of dynamic SQL inline with Java code, you can factor out the common code there similar to the way you would factor out any common code.
The example they give in the Mybatis doc is as follows:
private String selectPersonSql() {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
FROM("PERSON P");
FROM("ACCOUNT A");
INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
WHERE("P.ID = A.ID");
WHERE("P.FIRST_NAME like ?");
OR();
WHERE("P.LAST_NAME like ?");
GROUP_BY("P.ID");
HAVING("P.LAST_NAME like ?");
OR();
HAVING("P.FIRST_NAME like ?");
ORDER_BY("P.ID");
ORDER_BY("P.FULL_NAME");
}}.toString();
}
So, you could define a function that factors out your commmon dynamic SQL from your XML, and perhaps takes arguments representing the SELECT columns and FROM table portions of the statement (and anything else that might vary), which can then be passed in to the factored-out dynamic SQL methods inside of the function.

Zend_Framework 1.12 SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax

Before anything I am aware of the multiple questions asked concerning this exception. I have looked through them but have not found an answer for my particular problem. Most of the questions use the Zend_Db_Table::getDefaultAdapter(); , and I am not using that. Instead I am using an Application_Model_DbTable_Name and am left wondering if it´s possible to do so.
Also, I do have access since that´s the first thing I checked when I saw the error. The database is local and I access it with the same user/password through MySqlWorkBench.
My goal is to delete a row when two columns meet the criteria set in the controller action, like so:
public function deleteAction(){
$request = $this->getRequest();
$this->_validateDigit($request->getParam('id'));
// _validateDigit(..) checks that the variable is numeric and if it´s not it redirects to
// an error page
$db = new Application_Model_DbTable_BecasPerfiles();
$select = $db->select();
$select->where('unidad=?', $this->_getUnit())
->where('id=?', (int) $request->getParam('id'));
$db->delete($select->getPart(Zend_Db_Select::WHERE));
$this->_redirect('/profile/view-profiles/unit/'.$this->_getUnit());
}
private function _getUnit()
{
return Zend_Registry::getInstance()['session']->unit;
//unit is nothing but a string
}
Here is my DbTable class (real simple):
class Application_Model_DbTable_BecasPerfiles extends Zend_Db_Table_Abstract
{
protected $_name = 'becas_perfiles';
}
Here is the error that spits out:
Message: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in
your SQL syntax; check the manual that corresponds to your MySQL server version for
the right syntax to use near 'AND (id=7))' at line 1
Here is what calls my attention AND (id=7)), see the extra parenthesis? where is that coming from?
Here is the result of var_dump($select->getPart(Zend_Db_Select::WHERE));
array(2) { [0]=> string(33) "(unidad='Galería de Arte ULPGC')" [1]=> string(10) "AND (id=7)" }
Just for the fun of it, I tried switching the order of the where clause:
$select->where('id=?', (int) $request->getParam('id'))
->where('unidad=?', $this->_getUnit());
Here is the output:
Message: SQLSTATE[42000]: ...
syntax to use near 'AND (unidad='Galería de Arte ULPGC'))' at line 1
There it is again, AND (unidad='Galería de Arte ULPGC')) that second parenthesis. I don´t really know if that´s the problem (but I figure it is because otherwise I don´t know what could posssibly be wrong).
I tried just using one where condition (like id), and it deleted just fine. I´d really appreciate your help, thank you!
The problem here is that there is a mismatch between what getPart() returns and what delete() expects.
As you already pointed out, var_dump($select->getPart(Zend_Db_Select::WHERE)); also returns the logical operators in the where statement, but the delete() operator either expects an array in the form "field => value" or a string containing the full where clause.
So the simplest (untested) approach to fix your problem would be to pass $db->delete(implode(' ', $select->getPart(Zend_Db_Select::WHERE))); so that delete() receives a fully qualified where clause as string instead of an array it cannot handle.

Linq to Entities and Xml Fields

I have this scenario:
A SQL Server table myTable with field1, xmlField (nvarchar(50) and xml sql server data type)
Linq to entities
Now I'd like to get a query like this:
SELECT Field1, XmlField
FROM MyTable
WHERE CAST(XmlField AS nvarchar(4000)) = '<myXml />'
Obviously this is a correct query in SQL Server but I can't find a solution to write this in L2E.
Please notify that this code doesn't work:
var query = from row in context.MyTables
where (string)row.XmlField == "<myXml />"
select row
and other cast methods too.
This just because in L2E the "ToString" does't work correctly.
Now my idea is this one: an extension method:
var query = from row in context.MyTables
select row
query = query.CompareXml("XmlField", "<myXml />")
and this is the extended method:
public static IQueryable<TSource> CompareXml<TSource>(this IQueryable<TSource> source, string xmlFieldName, string xmlToCompare)
{
ConstantExpression xmlValue = Expression.Constant(xmlToCompare);
ParameterExpression parameter = Expression.Parameter(typeof(TSource), source.ElementType.Name);
PropertyInfo propertyInfo = typeof(TSource).GetProperty(xmlFieldName);
MemberExpression memberAccess = Expression.MakeMemberAccess(parameter, propertyInfo);
var stringMember = Expression.Convert(memberAccess, typeof(string));
BinaryExpression clauseExpression = Expression.Equal(xmlValue, stringMember);
return source.Where(Expression.Lambda<Func<TSource, bool>>(clauseExpression, parameter));
}
and again this doesn't work too.
Now I'd like to understand how I can force a "Convert" using Cast so I can compare Xml and nvarchar.
Thanks in advance
Massimiliano
Unfortunately EF still doesn't properly support XML columns. I'm afraid pretty much the only choice I know of is to create a view that does the cast and map that to a different entity. This will probably make the code awkward but also offers additional possible scenarios; for example, with a lot of SQL code, you could map elements in the XML columns to actual columns in the view, allowing you to make queries on specific parts of the XML.
On the bright side, at least inserting values in an XML column works pretty much as expected.