Not an SQL expert:
I am currently implementing a Searching Web Application. The user will send a GET request to a particular Endpoint.
The Endpoint will accept a set of URL Params and the request can come with optional fields. Such as 1, 2 or 3 fields.
My Database table for USER looks like this.
+--------------+---------+------+-----+---------+----------------+
| id | firstName | lastName | email | zip | phone |
+--------------+---------+------+-----+---------+----------------+
Now the web application will get a GET Request with either Phone or Zip or Email.
I have two solutions for doing this but both look bad:
Solution 1:
Have multiple SQL Queries and Execute them according to the URL Params, I receive.
Select * from User where phone=1111111111 and zip=12345 and email=test#email.com;
Select * from User where phone=1111111111 and zip=12345;
...
...
And so on........I will end up having many queries and this will be a bad implementation. Also will be bad to maintain.
Solution 2:
Other solution that I am thinking of is to have a method, which will build an SQL query based on the URL Params I receive.
Example:
buildSQLQuery(phone,zip,email){
String sql = "Select * from User where "
if(phone!=null && email!=null && zip!=null ){
sql = sql + "phone = " + phone + " and zip = " + zip + " and email = " + email;
}else if (phone!=null && email!=null && zip==null){
sql = sql + "phone = " + phone + " and email = " + email;
}
.......
......
And so on have all conditions and build that particular query.
}
I don't like both these solutions.
Is there a way to write a Single SQL query and that will handle all the above conditions.
Something like if the URL Param value is NULL then that should not affect the query and I will get my expected results.
As in my case the optional values, which don't come in are set to NULL.
Can I implement something like this?
Select * from User where (if notNull (phone))(phone = phone ) and (if notNull (email))(email = email ) and (if notNull (zip))(zip = zip )
If any of the above one value is null then don't use that part in where Condition.
Also I will always have one field present, so there will be no case where all values are null.
I am implementing this web application in Java and Spring MVC. If anyone can guide me in the correct direction.
Thank you.
I think one more possible solution like:
String sql = "Select * from User where 1=1 "
if(phone!=null){
sql += " and phone = " + phone ;}
if(email!=null){
sql += " and email = " + email ;}
if(zip!=null){
sql += " and zip = " + zip ;}
OR You can try following single query:
select * from User
where (#phone is null OR phone = #phone) AND (#email is null OR email = #email) AND (#zip is null OR zip = #zip)
You can do like this:
SELECT * FROM User where(
IF($phone != 0, IF(phone = $phone,1,0), 0) AND
IF($email != 0, IF(email = $email,1,0),0) AND
IF($zip != 0, IF(zip = $zip,1,0),0)
)
assumed for PHP you can change the syntax, may be this query not do the job completely but if you provide fiddle i will modify this to give the proper result.
Related
I have a web application that I am trying to "break".There's a login page that requires username and password input. Let's say I have a table Auser that stores username's info in MySQL.
When I hit Login after keying the credentials,it executes this line of code:
String sql = "select object(o) from Auser as o where ausername='" + username + "'";
Now, I know not using preparedStatement makes SQL query vulnerable to SQL injection and I want to perform such a stunt. I created a dummy table called test for the purpose of able to drop this table via the injection command.
I tried various ways like in my username input(root is the username):
root` DROP TABLE test;
And it didn't work. Is there a way to make my injection successful?
Update:
Just extra info, my username column is VARCHAR(255) and my method for getting the username is below:
public Auser get(String username, boolean moreInfo) {
try {
Auser u = null;
String sql = "select object(o) from Auser as o where ausername='" + username + "'";
List resList = em.createQuery(sql).getResultList();
if (resList == null) { // null check for sql query / library error
msg = CoreUtil.wrapMsg(CoreUtil.FUNC_ERROR,
this.getClass().getName(), "get[" + username + "]", "query error AUSER.");
} else if (resList.isEmpty()) {
msg = "User " + username + " not found.";
} else {
u = (Auser) resList.get(0);
}
return u;
} catch (Exception e) {
msg = CoreUtil.wrapMsg(CoreUtil.FUNC_ERROR,
this.getClass().getName(), "get[" + username + "]", e.getMessage());
return null;
}
}
Seems every solution, I tried keeps throwing IllegalArgumetnException and the table still remains.I just want to exploit the vulnerabilities of my program,it can be any kind of injection whether dropping a table, returning all users info,etc.
The EntityManager has some (very) basic protection built in that won't run more than one command in the same SQL statement.
This will protect you from Robert'); DROP TABLE Students; --, but it won't protect from attackers trying to expand/alter the one query that's being run.
For example, in your code an attacker could get the details of another user by entering the username ' OR 1 = 1 --; This would make the SQL string being executed
select object(o) from Auser as o where ausername='' OR 1 = 1 --'
which will select every user in the table (note that the -- at the end of the input will comment out everything after the injected code), and your method will return the first user in the result list This will potentially give the attacker details about another user that they should not have access to. If the first account is an administrator account then they may also have access they should not have.
An attacker can also learn the structure of the table this way - they can try strings like ' and IS_ADMIN = IS_ADMIN --, or ' OR ID = 0 --. If they try enough of these (and attacks like this can be easily automated) they will find valid column names when the query doesn't throw an error. They can potentially then make a more targeted injection attack to gain access to an admin account.
They might also learn things from the error message returned from a failed attempt, such as the DB platform, which can make attacks easier.
String sql = "select object(o) from Auser as o where ausername='" + username + "'";
If you want to delete the test table
username = "x'; DROP TABLE test AND '1'='1"
If you want to see all fields of all ausers entries
username = "x' OR '1'='1"
I have a Spring application with a update API endpoint for a Postgres database. The user can submit information and updates will be reflected in the database. The user only submits what they have to update. For example consider the following object:
class Dog {
String name;
int age;
String breed;
// Attributes and getters/setters...
}
When the user submits a update request, they only send the information they wish to update, such as only name and breed. I have the following function that updates the database with information:
public void update(String name, int age, String breed, JdbcTemplate template) {
UpdateBuilder query = new UpdateBuilder();
query.from("DogTable");
boolean updated = false;
if (name != null) {
query.set("name" + " = '" + name + "'");
updated = true;
}
if (age != null) {
query.set("age" + " = '" + age + "'");
updated = true;
}
if (breed != null) {
query.set("breed" + " = '" + breed + "'");
updated = true;
}
// And so on...
if (updated) {
query.set("UpdatedTime" + " = '" + new Date() + "'");
}
query.where("someKey" + " = '" + someId + "'");
template.update(query.toString());
}
(The query.set() stuff is just a helper class that builds a query string)
As you can see, this gets messy with all the "is the name given, is the age given?" checks. That leads to my question: Is there a data driven approach to do this? What I would like to be able to do is:
myJdbcTemplate.update(ListOfObjectsToUpdate, "TableName");
Simply, the JDBC template would see what I have provided it, and would proceed to update the provided table with that information. Is this even possible? I realize that building queries using strings is bad, but PreparedStatements don't look much better in code (not to mention, they don't solve this issue).
You can use the COALESCE function for this purpose - add user value as well and existing value and if the user value is not null (intended update) it well be set as the new value.
Similar to this -
UPDATE "user" SET alternate_contact_name = COALESCE(<user value>, alternate_contact_name)
This is a MySQL query but COALESCE works same way in Postgresql
The user value will be set and new value if it is not null. When it is null and original value of column is not null, the original value if picked. If both are null then it doesn't matter.
WIth this you can simply pass all parameters and avoid building query in an untidy way.
I have a table with four columns, this is how it looks like. I would call it T_BPR_KPI_MONTHLY_VALUES
KPI_NAME_SHORT_S | MONTH_N | YEAR_N | VALUE_N
-----------------------------------------------
MY_KPI_1 | 1 | 2015 | 99.87
MY_KPI_2 | 1 | 2015 | 97.62
... | 1 | 2015 | ...
MY_KPI_1 | 2 | 2015 | ...
... | ... | 2015 | ...
Each kpi represents a measurement and each of them has daily values which are saved in another table called T_BPR_KPI_DY. My goal is to calculate and save monthly values of each KPI.
It is possible that on the certain day, daily values for some kpis are still missing and in order to precisely calculate monthly values I must be able to replace exisisting values in the database as well as insert new etries for the future months and years.
I tought that oracle sql merge operation would be good choice for this task. The idea is to check whether an entry already exists and if so than to update its value and if not to insert new one.
This is how the query looks like
MERGE INTO T_BPR_KPI_MONTHLY_VALUE A
USING( SELECT 'MY_KPI_1' AS KPI_NAME_SHORT_S, 1 AS MONTH_N, 2014 AS YEAR_N FROM DUAL ) B
ON ( A.KPI_NAME_SHORT_S = B.KPI_NAME_SHORT_S )
WHEN MATCHED THEN
UPDATE SET VALUE_N = ( select AVG(MY_KPI_1) from T_BPR_KPI_DY where DAY_D between '01.01.2014' AND '31.01.2014')
WHEN NOT MATCHED THEN
INSERT (KPI_NAME_SHORT_S, MONTH_N, YEAR_N, VALUE_N) VALUES ('MY_KPI_1', 1, 2014, ( select AVG(MY_KPI_1) from T_BPR_KPI_DY where DAY_D between '01.01.2014' AND '31.01.2014') )
I tought that calculating avg monthly values on the fly is not a bad idea, so as you can see I have another select query which only calculates avg monthy value for the specific kpi. I am not sure if this is a best practic solution but it works fine when I execute this query in oracle sql developer tool. however when I try to execute it from the app it does not work.
This is how the method looks like
public static void storeValuesToDb(ArrayList<String> kpiNames) throws SQLException {
Connection conn = getOracleJDBCConnection_DASH();
int currentYear = cal.get(Calendar.YEAR);
int startYear = cal.get(Calendar.YEAR) - 1;
for (String kpiName : kpiNames) {
for (int i = startYear; i <= currentYear; i++) {
for (int j = 0; j < 12; j++) {
try {
String myMergeSQL = ""
+ "MERGE INTO T_BPR_KPI_MONTHLY_VALUE A "
+ "USING( SELECT '" + kpiName + "' AS KPI_NAME_SHORT_S, " + (j + 1) + " AS MONTH_N, " + i + " AS YEAR_N FROM DUAL ) B ON ( A.KPI_NAME_SHORT_S = B.KPI_NAME_SHORT_S ) "
+ "WHEN MATCHED THEN "
+ "UPDATE SET VALUE_N = ( select AVG(" + kpiName + ") from T_BPR_KPI_DY where DAY_D between '" + getFirstDateOfMonth(j, i) + "' AND '" + getLastDateOfMonth(j, i) + "') "
+ "WHEN NOT MATCHED THEN "
+ "INSERT (KPI_NAME_SHORT_S, MONTH_N, YEAR_N, VALUE_N) VALUES ('" + kpiName + "', " + (j + 1) + ", " + i + ", ( select AVG(" + kpiName + ") from T_BPR_KPI_DY where DAY_D between '" + getFirstDateOfMonth(j, i) + "' AND '" + getLastDateOfMonth(j, i) + "') )";
System.out.println(myMergeSQL);
Statement stmt_dash = conn.createStatement();
stmt_dash.executeUpdate(myMergeSQL);
conn.commit();
stmt_dash.close();
} catch (SQLException ex) {
conn.close();
}
}
}
}
conn.close();
}
In terminal it prints out only the first merge sql. It neither finishs the operation nor throws an exception. It blocks somehow and in the db happens also nothing. It could be possible that my merge query is not correct or that it is not possible to execute this kind of operation with statement object. If someone is able to see what cases this issue, please help.
Thx in advance
I would start by reformulating your merge query and solve some issues:
the USING part of a MERGE actually means your "source of raw data". You are using a select from dual with hardcoded values. Here you should select all KPIs and also calculate the Average by KPI. Compose your query that selects all KPIs with their coresponding VALUE_N and use it in the USING part
when matched then UPDATE SET use the values from "source of raw data" which is alias B in your code, not compute on inside the UPDATE clause.
when not matched then INSERT VALUES - again, use values from "source of raw data" which is alias B in your code, do not try to compute the VALUE_N inside the insert - well at least not in that manner I think this is your querys main issue.
MERGE INTO xxx A using () B you gave 2 aliases to your tables but down the line inside the WHEN MATCHED or NOT you are not using the alias. This can raise problems if A and B have similar named columns.
An example of how I use merge in production:
Merge into Destination, using a select from a table Source (inside the select from source you can also add other computations obviously, in your case the average)
T_REPORT_DAILY_SNAPSHOT_2G should be in your code the select kpis name, value and average or whatever you need on INSERT and UPDATE
MERGE INTO T_CELLS_2G dest
USING (SELECT DISTINCT *
FROM T_REPORT_DAILY_SNAPSHOT_2G) src
ON (dest.lac = src.lac and dest.cell_id = src.cell_id)
WHEN MATCHED THEN
UPDATE SET
dest.cell_name = src.cell_name,
dest.loc_code = src.loc_code,
dest.site_code = src.site_code,
dest.rac = src.rac
WHEN NOT MATCHED THEN
INSERT (dest.cell_name,
dest.loc_code,
dest.site_code,
dest.lac,
dest.cell_id,
dest.rac)
VALUES (src.cell_name,
src.loc_code,
src.site_code,
src.lac,
src.cell_id,
src.rac);
Hope this helps in some way.
I'm having an sql problem. I writing a java application on top of a Access database.
It's a search query for several fields and I know the error is in the part where I need to calculate the age of a person when he or she went missing. I'm returning a tablemodel in my method so i need to do the calculations in my query. My latest atempt to make it work is this:
public TableModel UpdateTable(String dossiernr, String naam, String voornaam,
String startleeftijd, String eindleeftijd, String dossierjaar, String geslacht)
{
TableModel tb = null;
String sql= "SELECT [Nr dossier],[Annee],[Nom],[Prenom],[Disparu le],[Ne le],[Sexe], DATEDIFF('yyyy',[Ne le],[Disparu le]) - iif(DATEADD('yyyy', DATEDIFF('yyyy',[Ne le],[Disparu le]),"
+ "[Ne le])>[Disparu le],1,0) AS Age FROM TotalTable "
+ "WHERE [Nr dossier] LIKE ? AND [Nom] LIKE ? AND [Prenom] LIKE ? AND [Annee] LIKE ? AND Age >= ? AND Age <= ? AND [Sexe] LIKE ?;";
try
{
PreparedStatement pstatement;
Connection connection = PersistentieController.getInstance().getConnection();
pstatement = initStatement(connection,sql);
pstatement.setString(1, "%" + dossiernr + "%");
pstatement.setString(2, "%" + naam + "%");
pstatement.setString(3, "%" + voornaam + "%");
pstatement.setString(4, "%" + dossierjaar + "%");
pstatement.setString(5, startleeftijd);
pstatement.setString(6, eindleeftijd);
pstatement.setString(7, "%" + geslacht + "%");
rs=pstatement.executeQuery();
tb = DbUtils.resultSetToTableModel(rs);
pstatement.close();
}//einde try
catch (SQLException e)
{
e.printStackTrace();
} //einde catch
return tb;
}
When i run it, i get following error:
java.sql.SQLException: [Microsoft][ODBC Microsoft Access Driver] Too few parameters. Expected 8.
I only work with 7 parameters and don't get why he's asking for 8.
Thanks
You count 7 parameters in your WHERE clause. Unfortunately, the Access db engine treats Age as another parameter in that situation, so it thinks you have 8 parameters instead of only 7.
To understand why, start with this query which runs without error with my Access database:
SELECT some_text AS foo
FROM tblFoo
WHERE some_text Is Not Null;
However, when attempting to use the alias instead of the field name in the WHERE clause, Access prompts me to supply a value for foo because it treats it as a parameter:
SELECT some_text AS foo
FROM tblFoo
WHERE foo Is Not Null;
Access limits your ability to re-use alias names later in a query. In certain cases, it will accept the alias, but yours is not one of those cases.
You could define the alias in a subquery. Then the db engine will recognize it correctly when you reference the subquery's alias in the parent query.
If possible, test your SQL statements directly in Access. If they fail, that effort will give you the best chance to determine why.
I have a form where user can select search criteria.
The criterias are say:
Product Name: Input field
Name Option: Radio button group - begins with (default selected)/ is/ contains
Country: dropdown of country
Status: All, Active, Blocked
Type: All, One, Two, Three
Only Product Name is mandatory. Other dropdowns are optional.
So if country is not given, I should find products for all countries.
If active is not given, I should find both active and blocked products.
If Type is not given, I should return all the three types products.
I am building hibernate query as below:
String productName = searchCriteria.getValue("productName");
String productNameCriteria = searchCriteria.getValue("productNameCriteria");
String country = searchCriteria.getValue("country");
String status = searchCriteria.getValue("status");
String type = searchCriteria.getValue("type");
Query prodQuery = null;
String prodSql = "select count(*) from Product p where";
// is
if (productNameCriteria.equalsIgnoreCase("IS")){
prodSql += "p.productName = '"+productName+"'";
}
// begins with
else if (productNameCriteria.equalsIgnoreCase("BEGINS WITH")){
prodSql += "p.productName = '"+productName+"%'";
}
// contains
else (productNameCriteria.equalsIgnoreCase("BEGINS WITH")){
prodSql += "p.productName = '%"+productName+"%'";
}
if(!country.equalsIgnoreCase("0")){
prodSql += " and p.country = '"+country+"'";
}
if(!status.equalsIgnoreCase("ALL")){
if(status.equalsIgnoreCase("active"))
prodSql += " and p.status = 'active'";
else
prodSql += " and p.status = 'blocked'";
}
if(!type.equalsIgnoreCase("ALL")){
if(type.equalsIgnoreCase("one"))
prodSql += " and p.type = 'one'";
else if(type.equalsIgnoreCase("two"))
prodSql += " and p.type = 'two'";
else
prodSql += " and p.type = 'three'";
}
prodQuery = this.em.createQuery(prodSql);
List<Object[]> results = prodQuery.getResultList();
Am I doing query building the right way ? Or is there any other efficient method ???
Thanks for reading!!
Try looking at Criteria Query
Criteria crit = sess.createCriteria(Product.class);
if (productNameCriteria.equalsIgnoreCase("IS"))
crit.add( Restrictions.eq("productName", productName);
else if (productNameCriteria.equalsIgnoreCase("BEGINS WITH"))
crit.add( Restrictions.like("productName", productName + "%")
// etc
If you absolutely must build a string query then you should be using a StringBuilder
StringBuilder sb = new StringBuilder();
sb.append("select count(*) from Product p where ");
if (productNameCriteria.equalsIgnoreCase("IS"))
sb.append("p.productName = '").append(productName).append("'");
// etc
String query = sb.toString();
Using a StringBuilder reduces the number of instances created at runtime.
You could also look into using query parameters, which would reduce some of the query complexity, though I don't know what the runtime query performance effects are.
"select count(*) from Product p where p.productName = :productName"
"select count(*) from Product p where p.productName = ?"
You can then use Query#setParameter (or one of the other variants like setString) to define the values in the query. This is also a much, much better way of building the query because it's going to automatically manage quoting and escaping of values you're receiving from the UI. Use query parameters and not string concatenation, regardless of how you build the query string.
Yes .It will work if you build the query dynamically in this way .But the code will become tedious and noisy as it involves string manipulating of the where-condition clause .
For this kind of query 's use case , which is a search that allows users to specify a range of different property values to be matched by the returned result set , using Query By Example(QBE) is more efficient and elegant.
The idea of QBE is that you provide an instance of the queried class with some properties initialized, and the query will returns the records with matching property values.
Reference
Example JavaDocs
YouTube Hibernate Tutorial - Projections and Query By Example