Is insert statement like this is safe enough? Do I have to check if it's not SQL Injectionable?
#Autowired
private SessionFactory sessionFactory;
public void add(String title, String region, String def, String rangeStart,
String rangeEnd, Date extradition, Date expiration) {
Session session = null;
session = this.sessionFactory.getCurrentSession();
Query query = session
.createSQLQuery(
"INSERT INTO operators VALUES(NULL,:title,:region,:def,:rangeStart,:rangeEnd, :extradition, :expiration )")
.setString("title", title).setString("region", region)
.setString("def", def).setString("rangeStart", rangeStart)
.setString("rangeEnd", rangeEnd)
.setDate("extradition", extradition)
.setDate("expiration", expiration);
int updated = query.executeUpdate();
}
Yes, it is safe enough for SQL Injection attacks. Note that Query#executeUpdate will use a PreparedStatement behind the scenes for you to set the data for the parameters, which makes this statement safe enough.
PreparedStatement will write the Strings content directly in the specified fields, escaping any undesired value.
Still, note that if you have a bad design of your queries, you will be open to SQL Injection attacks. For example, if you create the query but append the String manually:
String sqlOpenToSqlInjection = "FROM operators WHERE stringField = " + stringVariable;
Query query = session.createSQLQuery(sqlOpenToSqlInjection);
//code to execute query...
More info:
Difference between Statement and PreparedStatement
Looks like you're using Hibernate as well, so it would be better if you save the entity. This would be similar but less code to handle/maintain.
Related
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: 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 '#gmail.com' at line 1
I don't know where is the problem
public List<UserModel> listUser(String emailParam) throws SQLException {
List<UserModel> users = new ArrayList<>();
Connection conn = null;
PreparedStatement pre = null;
ResultSet rs = null;
// Get Connection
conn = dataSource.getConnection();
// fetch query
String fetchUser = "SELECT * FROM user WHERE email = " + emailParam;
pre = conn.prepareStatement(fetchUser);
// execute query
rs = pre.executeQuery();
// fetch data using resultSet interface;
while (rs.next()) {
Integer id = rs.getInt("id");
String firstName = rs.getString("firstName");
...
String email = rs.getString("email");
Boolean isActive = rs.getBoolean("isActive");
Boolean isLibrarian = rs.getBoolean("isLibrarian");
// insert into user constructor
UserModel theUser = new UserModel(id, firstName, lastName, gender,
department, idNo, contactNo, address, email, null,
isLibrarian, isActive);
// insert into ArrayList
users.add(theUser);
}
// close connection
close(conn, pre, rs);
return users;
}
where is the problem Thanks in advance.
The error is here, in listUser():
// fetch query
String fetchUser = "SELECT * FROM user WHERE email = " + emailParam;
pre = conn.prepareStatement(fetchUser);
You managed to use a prepared statement when inserting the user, and you need to do the same here when querying:
// fetch query
String fetchUser = "SELECT * FROM user WHERE email = ?";
pre = conn.prepareStatement(fetchUser);
pre.setString(1, emailParam);
As a general thought, preparing statements has two main usages:
1) Minimise preparation time when executing a query
2) For security - among other things "query rewriting"
I also have an observation about a syntax error in your select at the bottom of this post.
If you are going to prepare statements, then it is better to do it once, then "remember" the preparedStatement that you get back. Do not prepare the same query over and over.
Most, if not all, DBMS's work as follows for prepared query processing:
1) you send the template query to the DBMS for parsing and optimisation. The output of this is known by a few different names, but for the purposes of this we can call this the "executable plan". This is the PrepareXXX call.
2) The DBMS remembers all of those details for the second stage i.e. when you send the data up as a result of the prepdQuery.executeQuery() (or similar) call. this has the effect of sending up the data and plugging it into the prepared query's executable plan.
This will always involve two network trips (one to prepare and one to execute). However ....
... If you need to run the same query again with different data (e.g. a different email), just execute the second step - this bypasses the overheads associated with parsing and planning. Which will increase your throughput significantly - especially for single row operations such as the insert (and most likely the select) shown above.
The alternative is the string concatenation method which will always require parsing etc and execution - but at least it will be only one trip over the network. This works best for longer running queries (where parse time is insignificant compared to execution times) or where the query logic is dynamic (made up at run time based upon user input).
However, if you do send the query text concatenated with user input, make sure you address point 2 above (query rewriting).
Also, finally, your concatenated SQL is missing single quotes.
The query must look like this (the text must be quoted)
select ... from ... where email = 'email#domain.com';
Thus your concatenation must look like this:
String fetchUser = "SELECT * FROM user WHERE email = '" + emailParam + "'";
What is query rewriting? Imaging if the emailParam entered by the user looked like this:
emailParam = "'; delete from user all; select 'hello"
Try plugging that into your select BUT DO NOT RUN IT unless you have a backup copy of your users table (or you get lucky).
Also, note that you never put quote marks around the ? placeholders in prepared queries - even if the parameter is a text or date value.
I'm triggering a query using HQL, normally it should return empty resultset as it doesn't have any records w.r.t it. But, it throws
org.hibernate.exception.SQLGrammarException: could not extract ResultSet
at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:106)
My code is
String hql = "FROM com.pck.Person where userId = " + userId;
Query query = session.createQuery(hql);
#SuppressWarnings("unchecked")
List<Dashboard> listUserDetails = query.list(); <-- Problem here.
I'm expecting list size is 0 because there are no records w.r.t userId passed.
What changes do I need to do?
Lets say the value of userId was "abc12"
Given your code, the value of the string called hql would become:
"FROM com.pck.Person where userId = abc12"
If you took the value of that string and tried to run it as a query on any database, most of them would fail to understand that abc12 is a string. Normally it would be interpreted as a variable.
As other users mentioned including the single quotes would produce the desired query, but the recommended way to assign parameter values is this:
String hql = "FROM com.pck.Person where userId = :id"
query.setParameter("id", userId);
Looks like you are missing single quotes around userid.
Try with "FROM com.pck.Person where userId = '" + userId + "'";
or
Use named parameters with query.setParameter("userid", userId);
Posting the full stacktrace would help if this doesn't solve.
I am forced to use createSQLQuery to insert values into tables with an Identity column (the first column and the primary key) using hibernate. Using hibernate classes are not an option since the tables are created on the fly for each customer that is added to the system. I have run the query and it successfully inserts into the table. I then execute a "select scope_identity()" and it always returns null. "select ##Identity" works but that is not guaranteed to be the correct one. I have also tried to append "select scope_identity()" to the insert query. Then I tried query.list() and query.uniqueResult() both of which throw the hibernate exception of "No Results ..."
Session session = DatabaseEngine.getSessionFactory().openSession();
String queryString = "insert into table1 (dataid) values (1)"
SQLQuery query = session.createSQLQuery(insertQueryString);
query.executeUpdate();
query = session.createSQLQuery("select scope_identity()");
BigDecimal entryID = (BigDecimal)query.uniqueResult();
The simple example table is defined as follows:
"CREATE TABLE table1 (EntryID int identity(1,1) NOT NULL," +
"DataID int default 0 NOT NULL, " +
"PRIMARY KEY (EntryID))";
Is there a way I am missing to use scope_identity() with createSQLQuery?
Actually the SQLServerDialect class used by Hibernate uses the same "scope_identity()" too.
The reason why it's not working is because you need to execute those in the same statement or stored procedure.
If you execute the scope_identity() call in a separate statement, SQL Server will not be able to give you last inserted identity value.
You cannot do it with the SQLQuery, even Hibernate uses JDBC to accomplish this task. I wrote a test on GitHub to emulate this and it works like this:
Session session = entityManager.unwrap(Session.class);
final AtomicLong resultHolder = new AtomicLong();
session.doWork(connection -> {
try(PreparedStatement statement = connection.prepareStatement("INSERT INTO post VALUES (?) select scope_identity() ") ) {
statement.setString(1, "abc");
if ( !statement.execute() ) {
while ( !statement.getMoreResults() && statement.getUpdateCount() != -1 ) {
// do nothing until we hit the resultset
}
}
try (ResultSet rs = statement.getResultSet()) {
if(rs.next()) {
resultHolder.set(rs.getLong(1));
}
}
}
});
assertNotNull(resultHolder.get());
The code uses Java 8 lambdas instead of anonymous classes, but you can easily port it to Java 1.7 too.
I want to execute a simple native query, but it does not work:
#Autowired
private EntityManager em;
Query q = em.createNativeQuery("SELECT count(*) FROM mytable where username = :username");
em.setProperty("username", "test");
(int) q.getSingleResult();
Why am I getting this exception?
org.hibernate.QueryException: Not all named parameters have been set: [username]
Named parameters are not supported by JPA in native queries, only for JPQL. You must use positional parameters.
Named parameters follow the rules for identifiers defined in Section 4.4.1. The use of named parameters applies to the Java Persistence query language, and is not defined for native queries. Only positional parameter binding may be portably used for native queries.
So, use this
Query q = em.createNativeQuery("SELECT count(*) FROM mytable where username = ?1");
q.setParameter(1, "test");
While JPA specification doesn't support named parameters in native queries, some JPA implementations (like Hibernate) may support it
Native SQL queries support positional as well as named parameters
However, this couples your application to specific JPA implementation, and thus makes it unportable.
After many tries I found that you should use createNativeQuery And you can send parameters using # replacement
In my example
String UPDATE_lOGIN_TABLE_QUERY = "UPDATE OMFX.USER_LOGIN SET LOGOUT_TIME = SYSDATE WHERE LOGIN_ID = #loginId AND USER_ID = #userId";
Query query = em.createNativeQuery(logQuery);
query.setParameter("userId", logDataDto.getUserId());
query.setParameter("loginId", logDataDto.getLoginId());
query.executeUpdate();
You are calling setProperty instead of setParameter. Change your code to
Query q = em.createNativeQuery("SELECT count(*) FROM mytable where username = :username");
em.setParameter("username", "test");
(int) q.getSingleResult();
and it should work.
I use EclipseLink. This JPA allows the following way for the native queries:
Query q = em.createNativeQuery("SELECT * FROM mytable where username = ?username");
q.setParameter("username", "test");
q.getResultList();
Use set Parameter from query.
Query q = (Query) em.createNativeQuery("SELECT count(*) FROM mytable where username = ?1");
q.setParameter(1, "test");
This was a bug fixed in version 4.3.11
https://hibernate.atlassian.net/browse/HHH-2851
EDIT:
Best way to execute a native query is still to use NamedParameterJdbcTemplate
It allows you need to retrieve a result that is not a managed entity ; you can use a RowMapper and even a Map of named parameters!
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
#Autowired
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
final List<Long> resultList = namedParameterJdbcTemplate.query(query,
mapOfNamedParamters,
new RowMapper<Long>() {
#Override
public Long mapRow(ResultSet rs, int rowNum) throws SQLException {
return rs.getLong(1);
}
});
I am developing an application using hibernate. When I try to create a Login page, The problem of Sql Injection arises.
I have the following code:
#Component
#Transactional(propagation = Propagation.SUPPORTS)
public class LoginInfoDAOImpl implements LoginInfoDAO{
#Autowired
private SessionFactory sessionFactory;
#Override
public LoginInfo getLoginInfo(String userName,String password){
List<LoginInfo> loginList = sessionFactory.getCurrentSession().createQuery("from LoginInfo where userName='"+userName+"' and password='"+password+"'").list();
if(loginList!=null )
return loginList.get(0);
else return null;
}
}
How will i prevent Sql Injection in this scenario ?The create table syntax of loginInfo table is as follows:
create table login_info
(user_name varchar(16) not null primary key,
pass_word varchar(16) not null);
Query q = sessionFactory.getCurrentSession().createQuery("from LoginInfo where userName = :name");
q.setParameter("name", userName);
List<LoginInfo> loginList = q.list();
You have other options too, see this nice article from mkyong.
You need to use named parameters to avoid sql injection. Also (nothing to do with sql injection but with security in general) do not return the first result but use getSingleResult so if there are more than one results for some reason, the query will fail with NonUniqueResultException and login will not be succesful
Query query= sessionFactory.getCurrentSession().createQuery("from LoginInfo where userName=:userName and password= :password");
query.setParameter("username", userName);
query.setParameter("password", password);
LoginInfo loginList = (LoginInfo)query.getSingleResult();
What is SQL Injection?
SQL Injection happens when a rogue attacker can manipulate the query
building process so that he can execute a different SQL statement than
what the application developer has originally intended
How to prevent the SQL injection attack
The solution is very simple and straight-forward. You just have to make sure that you always use bind parameters:
public PostComment getPostCommentByReview(String review) {
return doInJPA(entityManager -> {
return entityManager.createQuery("""
select p
from PostComment p
where p.review = :review
""", PostComment.class)
.setParameter("review", review)
.getSingleResult();
});
}
Now, if some is trying to hack this query:
getPostCommentByReview("1 AND 1 >= ALL ( SELECT 1 FROM pg_locks, pg_sleep(10) )");
the SQL Injection attack will be prevented:
Time:1, Query:["select postcommen0_.id as id1_1_, postcommen0_.post_id as post_id3_1_, postcommen0_.review as review2_1_ from post_comment postcommen0_ where postcommen0_.review=?"], Params:[(1 AND 1 >= ALL ( SELECT 1 FROM pg_locks, pg_sleep(10) ))]
JPQL Injection
SQL Injection can also happen when using JPQL or HQL queries, as demonstrated by the following example:
public List<Post> getPostsByTitle(String title) {
return doInJPA(entityManager -> {
return entityManager.createQuery(
"select p " +
"from Post p " +
"where" +
" p.title = '" + title + "'", Post.class)
.getResultList();
});
}
The JPQL query above does not use bind parameters, so it’s vulnerable to SQL injection.
Check out what happens when I execute this JPQL query like this:
List<Post> posts = getPostsByTitle(
"High-Performance Java Persistence' and " +
"FUNCTION('1 >= ALL ( SELECT 1 FROM pg_locks, pg_sleep(10) ) --',) is '"
);
Hibernate executes the following SQL query:
Time:10003, QuerySize:1, BatchSize:0, Query:["select p.id as id1_0_, p.title as title2_0_ from post p where p.title='High-Performance Java Persistence' and 1 >= ALL ( SELECT 1 FROM pg_locks, pg_sleep(10) ) --()=''"], Params:[()]
Dynamic queries
You should avoid queries that use String concatenation to build the query dynamically:
String hql = " select e.id as id,function('getActiveUser') as name from " + domainClass.getName() + " e ";
Query query=session.createQuery(hql);
return query.list();
If you want to use dynamic queries, you need to use Criteria API instead:
Class<Post> entityClass = Post.class;
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = cb.createTupleQuery();
Root<?> root = query.from(entityClass);
query.select(
cb.tuple(
root.get("id"),
cb.function("now", Date.class)
)
);
return entityManager.createQuery(query).getResultList();
I would like to add here that is a peculiar SQL Injection that is possible with the use of Like queries in searches.
Let us say we have a query string as follows:
queryString = queryString + " and c.name like :name";
While setting the name parameter, most would generally use this.
query.setParameter("name", "%" + name + "%");
Now, as mentioned above traditional parameter like "1=1" cannot be injected because of the TypedQuery and Hibernate will handle it by default.
But there is peculiar SQL Injection possible here which is because of the LIKE Query Structure which is the use of underscores
The underscore wildcard is used to match exactly one character in
MySQL meaning, for example, select * from users where user like
'abc_de'; This will produce outputs as users that start with abc, end
with de and have exactly 1 character in between.
Now, if in our scenario, if we set
name="_" produces customers whose name is at least 1 letter
name="__" produces customers whose name is at least 2 letters
name="___" produces customers whose name is at least 3 letters
and so on.
Ideal fix:
To mitigate this, we need to escape all underscores with a prefix .
___ will become \_\_\_ (equivalent to 3 raw underscores)
Likewise, the vice-versa query will also result in an injection in which %'s need to be escaped.
We should always try to use stored Procedures in general to prevent SQLInjection.. If stored procedures are not possible; we should try for Prepared Statements.