I'm looking for a way to dynamically build a SQL query for an unknown number of API parameters that may come back. As a simple example, consider the following query:
router.get("/someEndpoint", authorizationFunction, async (req, res) => {
let sql = `
SELECT *
FROM some_table
WHERE username = $1
${req.query.since !== undefined ? "AND hire_date >= $2" : ""}
`
const results = await pool.query(sql, [req.query.user, req.query.since]);
}
where pool is defined as
const Pool = require("pg").Pool;
const pool = new Pool({<connection parameters>});
The problem I'm having is that if req.query.since is not provided, then the SQL query only requires a single bound parameter ($1). This is presented as an error that says bind message supplies 2 parameters, but prepared statement "" requires 1. Since I don't know which parameters a user will provide until the time of the query, I'm under the impression that I need to provide all possible value, and let the query figure it out.
I've seen a lot of posts that point to pg-promise as a solution, but I'm wondering if that's necessary. Is there a way that I can solve this with my current setup? Perhaps I'm thinking about the problem incorrectly?
Thanks in advance!
Add a trivial expression text that contains $2 and evaluates to true instead of "", for example
SELECT * FROM some_table WHERE username = $1 AND
${req.query.since !== undefined ? " hire_date >= $2": " (true or $2 = $2)"}
The planner will remove it anyway. Added true or just in case $2 is null.
Still it would be cleaner like this
if (req.query.since !== undefined)
{
let sql = `SELECT * FROM some_table WHERE username = $1 AND hire_date >= $2`;
const results = await pool.query(sql, [req.query.user, req.query.since]);
}
else
{
let sql = `SELECT * FROM some_table WHERE username = $1`;
const results = await pool.query(sql, [req.query.user]);
}
What about SQLi risk BTW?
Related
I am writing code using nestjs and typeorm.
However, the way I write is vulnerable to SQL injection, so I am changing the code.
//Before
.where(`userId = ${userId}`)
//After
.where(`userId = :userId` , {userId:userId})
I am writing a question because I was changing the code and couldn't find a way to change it for a few cases.
//CASE1
const query1 = `select id, 'normal' as type from user where id = ${userId}`;
const query2 = `select id, 'doctor' as type from doctor where id = ${userId}`;
const finalQuery = await getConnection().query(`select id, type from (${query1} union ${query2}) as f limit ${limit} offset ${offset};`);
//CASE2
...
.addSelect(`CASE WHEN userRole = '${userRole}' THEN ...`, 'userType')
...
//CASE3 -> to find search results in order of accuracy
...
.orderBy(`((LENGTH(user.name) - LENGTH((REPLACE(user.name, '${keyword.replace( / /g, '', )}', '')))) / LENGTH('${keyword.replace(/ /g, '')}'))`,
'ASC')
...
//CASE4
let query = 'xxxx';
let whereQuery = '';
for(const i=0;i<5;i++)
whereQuery += ' or ' + `user.name like '%${keyword}%'`
query.where(whereQuery)
I cannot use parameter in the select function.
In the above case, I am wondering how to change it.
Is it ok to not have to modify the select code?
I have a search query that its parameters changes depending on the client input.
await prisma.$queryRaw(`SELECT column FROM table ${condition ? `WHERE column = '${condition}'` :' ' } `)
how can I write this query using prepared statement and avoiding duplicate queries. The only solution I came up with is the following:
const result = condition ? await prisma.$queryRaw(`SELECT column FROM table WHERE column = $1`,condition) : await prisma.$queryRaw(`SELECT column FROM table`)
The goal from this is to avoid sql injections from the first query.
EDIT
after trying the solution suggested by #Ryan I got the following error:
Raw query failed. Code: `22P03`. Message: `db error: ERROR: incorrect binary data format in bind parameter 1`
here's my implementation:
const where = Prisma.sql`WHERE ${searchConditions.join(' AND ')}`;
const fetchCount = await prisma.$queryRaw`
SELECT
COUNT(id)
FROM
table
${searchConditions.length > 0 ? where : Prisma.empty}
`;
that will translate to the following in the prisma logs:
Query:
SELECT
COUNT(id)
FROM
table
WHERE $1
["column = something"]
SOLUTION
I had to do a lot of rework to achieve what I want. Here's the idea behind it:
for every search condition you need to do the following:
let queryCondition = Prisma.empty;
if (searchFilter) {
const searchFilterCondition = Prisma.sql`column = ${searchFilter}`;
queryCondition.sql.length > 0
? (queryCondition = Prisma.sql`${queryCondition} AND ${streamingUnitCondition}`)
: (queryCondition = searchFilterCondition);
}
afterwards in the final search query you can do something of this sort:
SELECT COUNT(*) FROM table ${queryCondition.sql.length > 0 ? Prisma.sql`WHERE ${queryCondition}` : Prisma.empty}
You can do it like this:
import { Prisma } from '#prisma/client'
const where = Prisma.sql`where column = ${condition}`
const result = await prisma.$queryRaw`SELECT column FROM table ${condition ? where : Prisma.empty}`
Here is my working version, using Prima.join :
import { Prisma } from '#prisma/client'
const searchConditions: Prisma.Sql[] = []
if (q) {
searchConditions.push(Prisma.sql`column = ${q}`)
}
const where = searchConditions.length ?
Prisma.sql`where ${Prisma.join(searchConditions, ' and ')}` :
Prisma.empty
await prisma.$queryRaw(
Prisma.sql`
select *
from table
${where}
`
)
I'm using the orientjs library to perform operations in the Orient Database. I read in the documentation that it's possible to use parameter-style queries like the following:
db.query(
'SELECT name, ba FROM Player '
+ 'WHERE ba >= :ba AND team = ":team"',
{params: {
ba: targetBA,
team: targetTeam }
}, limit: 20
).then(function(hitters){
console.log(hitters)
});
My question is: Is it enough to prevent SQL injection? Because I didn't find information about that in the NodeJS API. In the case of Java, there is a 'Prepared Query' concept, I'm not sure if they are refering to the same thing.
Seems to be secure, I'm trying with this code (yours taken from the wiki is a bit buggy):
var name='admin';
db.open().then(function() {
return db.query(
"SELECT * FROM OUser "
+ "WHERE name = :name",
{params:{
name: name
}
});
}).then(function(res){
console.log(res);
db.close().then(function(){
console.log('closed');
});
});
First of all, the query is parsed as SELECT * FROM OUser WHERE name = "admin" (observed with the Studio Query Profiler).
As expected, I get the admin user record.
Since the params are evaluated directly as String, there's non need quote them (e.g. :name not ':name'). So there is no way to inject something like ' OR '1'='1 or any ; drop something;
Here are some test I did:
var name='; create class p;';
returns no records;
evaluated by orient as: SELECT * FROM OUser WHERE name = "; create class p;"
var name="' OR '1'='1";
returns no records;
evaluated as: SELECT * FROM OUser WHERE name = "' OR '1'='1"
var name='" OR "1"="1';
returns no records;
evaluated as: SELECT * FROM OUser WHERE name = "\" OR \"1\"=\"1"
quoting the param name in the query: "WHERE name = ':name'"
evaluated as: SELECT * FROM OUser WHERE name = ':name'
Feel free to try more combinations, in my opinion seems quite safe.
For an application I'm running a query on DocumentDb in .NET. For this used I wanted to use a parametrized query, like this:
var sqlString = "select p.Id, p.ActionType, p.Type, p.Region, a.TimeStamp, a.Action from History p join a in p.Actions where a.TimeStamp >= #StartTime and a.TimeStamp <= #EndTime and p.ClientId = #ClientId and p.ActionType = #ActionType";
if (actionType != "") { sqlString += actionTypeFilter; }
var queryObject = new SqlQuerySpec
{
QueryText = sqlString,
Parameters = new SqlParameterCollection()
{
new SqlParameter("#StartTime", startDate),
new SqlParameter("#EndTime", endDate),
new SqlParameter("#ClientId", clientId.ToString()),
new SqlParameter("#ActionType", actionType)
},
};
var dataListing = _documentDbClient.CreateDocumentQuery<PnrTransaction>(UriToPnrHistories, queryObject, new FeedOptions() { MaxItemCount = 1 });
When I execute this, I'm getting en empty dataset. But when I use the same query, and build it using classic string replace it works just fine.
Can anyone tell me what I'm doing wrong in my parametrized query?
If the code above is the running code, you still add the actiontypeFilter on the parameterized SQL string. Try to remove the if statement on line 2. Seems to me that may be your problem.
It would help if you could post a sample document from the server.
I usually see this syntax:
SqlParameterCollection parameters = new SqlParameterCollection();
parameters.Add(...);
parameters.Add(...);
Try that and see if you get different results. It might be that the list you use to initialize it in your answer needs to be typed differently to work.
Our code has a SqlExpression, which at its bare minimum is something like:
var q = db.From<Users>();
q.Where(u => u.Age == 25);
totalRecords = db.Scalar<int>(q.ToCountStatement());
q.ToCountStatement() generates the following query:
SELECT COUNT(*) FROM "Users" WHERE ("Age" = #0)
However, db.Scalar() throws an exception: Must declare the scalar variable "#0". This has started occurring in recent versions (tested in 4.0.54). The same code was working fine until v4.0.50. I've checked the release notes, but couldn't find a related change.
Even passing a parameter throws the same exception:
totalRecords = db.Scalar<int>(q.ToCountStatement(), 25);
Is it a bug, or my oversight?
Secondly, is it possible to get q.ToCountStatement() to generate a more optimized query with COUNT(Age) or COUNT([PrimaryKey]) instead of COUNT(*)?
Now that OrmLite defaults to parameterized queries you also need to provide the queries db parameters when executing a query (if you've specified any params), e.g:
var q = db.From<Users>().Where(u => u.Age == 25);
var count = db.Scalar<int>(q.ToCountStatement(), q.Params);
You can also use OrmLite's explicit Count() API's, e.g:
db.Count<User>(x => x.Age == 25);
Or with a typed SqlExpression:
var q = db.From<User>().Where(x => x.Age == 25);
db.Count(q);
Otherwise another way to specify db params is to use an anonymous object, e.g:
db.Scalar<int>("SELECT COUNT(*) FROM Users WHERE Age=#age", new { age = 25});