Securing a raw sql query in Laravel to prevent injections - sql

The query when not trying any SQL injection fix works perfectly there is no any connection issue. It is only when trying to change the query to protect it from injections, when the syntax breaks.
I am trying to secure this raw query preventing sql injection
I have seen in the docs (and I know the prepared statements from goo'ol php)
and that Laravel shows a simple example such as this one:
$results = DB::select('select * from users where id = :id', ['id' => 1]);
or this one
$users = DB::select('select * from users where active = ?', [1]);
I am trying to do that where it corresponds, at the WHERE clause equaling the variable provided from the form $a, but all attempts break the syntax.
so anything like:
where author = ?, [$ba], or '$ba' or where author = :ba, ['author' => '$ba']
gives syntax errors.
$ba = 'whatever';
$results =
DB::select(DB::raw("SELECT
t.id, t.AvgStyle, r.RateDesc
FROM (
SELECT
p.id, ROUND(AVG(s.Value)) AS AvgStyle
FROM posts p
INNER JOIN styles s
ON s.post_id = p.id
WHERE author = '$ba'
GROUP BY p.id
) t
INNER JOIN rates r
ON r.digit = t.AvgStyle"
));
Thank you.
Note> question is not eligible for bounties. It is someone always putting it on every question of mine without my permission.

The answer was extremely close to what Everton said: It just needed to go one parentheses to the right.
That is:
, )[$ba]);

You can try this way:
$ba = 'whatever';
$results =
DB::select(DB::raw("SELECT
t.id, t.AvgStyle, r.RateDesc
FROM (
SELECT
p.id, ROUND(AVG(s.Value)) AS AvgStyle
FROM posts p
INNER JOIN styles s
ON s.post_id = p.id
WHERE author = ?
GROUP BY p.id
) t
INNER JOIN rates r
ON r.digit = t.AvgStyle"
, )[$ba]); // #everton We just needed to move it one parentheses
The important thing here is that you need to respect the order if you have others parameters.

Related

How to Join two Queries in Sequelize

I tried to join two queries and based on it find the result. I am able to write the code in SQL. My SQL Code is
SELECT a.awbid,
m.mpscount
FROM ( select a.awbid
FROM awbmaster a
where a.batchid ='B/117/15022022'
and a.hubid ='117'
) as a
left join ( select count('mpsid') as "mpscount" ,
awbid from
mpsmaster m
where m.batchid = 'B/117/15022022'
group by "awbid"
) as m on a.awbid = m.awbid
But, I am not yet found any solution regarding the sequelize. How can I write the above SQL code in sequelize?
First, we can simplify this SQL query simply using a subquery to find out mpscount:
SELECT a.awbid,
(SELECT count(*)
FROM mpsmaster m
WHERE m.batchid = 'B/117/15022022'
AND m.awbid=a.awbid ) AS mpscount
FROM awbmaster a
WHERE a.batchid ='B/117/15022022'
AND a.hubid ='117'
Now if we already have both models MpsMaster and AwbMaster and a correct association between them then we can make a request something like this:
const records = AwbMaster.findAll(}
attributes: [
'awbid',
[Sequelize.literal('(SELECT count(*) FROM mpsmaster m WHERE m.batchid = \'B/117/15022022\' AND m.awbid=AwbMaster.awbid)'), 'mpscount']
]
})

Query with a sub query that requires multiple values

I can't really think of a title so let me explain the problem:
Problem: I want to return an array of Posts with each Post containing a Like Count. The Like Count is for a specific post but for all users who have liked it
For example:
const posts = [
{
post_id: 1,
like_count: 100
},
{
post_id: 2,
like_count: 50
}
]
Now with my current solution, I don't think it's possible but here is what I have so far.
My query currently looks like this (produced by TypeORM):
SELECT
"p"."uid" AS "p_uid",
"p"."created_at" AS "post_created_at",
"l"."uid" AS "like_uid",
"l"."post_liked" AS "post_liked",
"ph"."path" AS "path",
"ph"."title" AS "photo_title",
"u"."name" AS "post_author",
(
SELECT
COUNT(like_id) AS "like_count"
FROM
"likes" "l"
INNER JOIN
"posts" "p"
ON "p"."post_id" = "l"."post_id"
WHERE
"l"."post_liked" = true
AND l.post_id = $1
)
AS "like_count"
FROM
"posts" "p"
LEFT JOIN
"likes" "l"
ON "l"."post_id" = "p"."post_id"
INNER JOIN
"photos" "ph"
ON "ph"."photo_id" = "p"."photo_id"
INNER JOIN
"users" "u"
ON "u"."user_id" = "p"."user_id"
At $1 is where the post.post_id should go (but for the sake of testing I stuck the first post's id in there), assuming I have an array of post_ids ready to put in there.
My TypeORM query looks like this
async findAll(): Promise<Post[]> {
return await getRepository(Post)
.createQueryBuilder('p')
.select(['p.uid'])
.addSelect(subQuery =>
subQuery
.select('COUNT(like_id)', 'like_count')
.from(Like, 'l')
.innerJoin('l.post', 'p')
.where('l.post_liked = true AND l.post_id = :post_id', {post_id: 'a16f0c3e-5aa0-4cf8-82da-dfe27d3f991a'}), 'like_count'
)
.addSelect('p.created_at', 'post_created_at')
.addSelect('u.name', 'post_author')
.addSelect('l.uid', 'like_uid')
.addSelect('l.post_liked', 'post_liked')
.addSelect('ph.title', 'photo_title')
.addSelect('ph.path', 'path')
.leftJoin('p.likes', 'l')
.innerJoin('p.photo', 'ph')
.innerJoin('p.user', 'u')
.getRawMany()
}
Why am I doing this? What I am trying to avoid is calling count for every single post on my page to return the number of likes for each post. I thought I could somehow do this in a subquery but now I am not sure if it's possible.
Can someone suggest a more efficient way of doing something like this? Or is this approach completely wrong?
I find working with ORMs terrible and cannot help you with this. But the query itself has flaws:
You want one row per post, but you are joining likes, thus getting one row per post and like.
Your subquery is not related to your main query. It should instead relate to the main query's post.
The corrected query:
SELECT
p.uid,
p.created_at,
ph.path AS photo_path,
ph.title AS photo_title,
u.name AS post_author,
(
SELECT COUNT(*)
FROM likes l
WHERE l.post_id = p.post_id
AND l.post_liked = true
) AS like_count
FROM posts p
JOIN photos ph ON ph.photo_id = p.photo_id
JOIN users u ON u.user_id = p.user_id
ORDER BY p.uid;
I suppose it's quite easy for you to convert this to TypeORM. There is nothing wrong with counting for every single post, by the way. It is even necessary to get the result you are after.
The subquery could also be moved to the FROM clause using GROUP BY l.post_id within. As is, you are getting all posts, regardless of them having likes or not. By moving the subquery to the FROM clause, you could instead decide between INNER JOIN and LEFT OUTER JOIN.
The query would benefit from the following index:
CREATE INDEX idx ON likes (post_id, post_liked);
Provide this index, if the query seems too slow.

Query on other query linq

Just assume a simple example:I have two table and use this query:
select *
from People
where PersonGuid = (select PersonGuid from Sellers where Guid = '')
How can I write this query with linq?
I tried this:
var person = from p in loginContext.Person where p.PersonGuid =
(loginContext.Seller.Where(s => s.Guid == sellerGuid).FirstOrDefaultAsync());
but that's wrong.
What is the right way to write it?
If you were to rewrite your SQL query as
select *
from People AS PE
JOIN
Sellers AS Sellers
ON PE.PersonGuid = SE.PersonGuid
WHERE
SE.Guid = ''
then the LINQ becomes a little more obvious:
var person = (from p in loginContext.Person
join s in loginContext.Seller on p.PersonGuid equals s.PersonGuid
where s.guid == ""
select p)
.FirstOrDefault();
Although your SQL would suggest FirstOrDefault is not required.
Warning - not tested.
You could probably try using LINQ JOIN clause.
It should be something like:
from p in loginContext.Person
join s in loginContext.Seller on new { p.PersonGuid = s.Guid }
where s.Guid = sellerGuid
You can know more about LINQ JOIN here -> Microsoft C# Reference
Hope it helps!
I will assume that select PersonGuid from Sellers where Guid = '' will provide a single value no matter what, otherwise your SQL statement would fail.
Accepted answer works, but if you want a solution that keeps the spirit of the SQL query, you can rely on Any function. Something along the following:
var person = await loginContext.Person
.Where(p => loginContext.Sellers
.Any(s => s.PersonGuid == p.PersonGuid && s.Guid = ""))
.FirstOrDefaultAsync();
I expect this to be transformed into an SELECT ... WHERE EXISTS which is similar to your initial query.

I need to convert to codeigniter3 active record syntax

I need to join main (4-5) tables and get the latest from the inner join table to get the project current status.
investor have many investments
investments have many investment_details
investment_details has many status through project status
Select
siv.company_name
, siv.full_name
, si.permit_number
, si.project_name
, sid.investment_detailed_id
, sis.project_status_id
, sps.project_status_name
From sma_investors siv
Join sma_investments si
On siv.investor_id = si.investment_id
Join sma_investment_details sid
On si.investment_id = sid.investment_id
Inner Join sma_investment_status sis
On sis.investment_status_id = (
Select investment_status_id
From sma_investment_status s
Where s.investment_detailed_id = sid.investment_detailed_id
Order BY investment_status_id DESC LIMIT 1)
Join sma_project_status sps
On sis.project_status_id = sps.project_status_id
This works fine but I can't convert it the CI3.
There is no advantage to using Query Builder (QB) unless you need parts of the query to be written differently due to some condition. QB is also useful if you want user inputs to be automatically escaped. Otherwise, you simply run a whole lot of extra code that leads to the exact same SQL statement string you already have.
In your case, the sub-select will make the conversion even more difficult to accomplish and add a lot of extra code to be executed.
My advice is to keep it simple and use db->query(), e.g.
$sql = "Select siv.company_name, siv.full_name, si.permit_number, si.project_name
, sid.investment_detailed_id, sis.project_status_id, sps.project_status_name
From sma_investors siv
Join sma_investments si On siv.investor_id = si.investment_id
Join sma_investment_details sid On si.investment_id = sid.investment_id
Inner Join sma_investment_status sis On sis.investment_status_id = (
Select investment_status_id From sma_investment_status s
Where s.investment_detailed_id = sid.investment_detailed_id
Order BY investment_status_id DESC LIMIT 1)
Join sma_project_status sps On sis.project_status_id = sps.project_status_id";
$query = $this->db->query($sql);
if(! $query) // might be false if query fails
{
return null;
}
return $query->row(); // one row of data
I finally come up with my own solution and stick to the QB codding standard and it works as i expected
$q = $this->db->select('*')
->from('investors')
->join('investments', `enter code here`'investors.investor_id=investments.investment_id')
->join('investment_details as sid', 'investments.investment_id=sid.investment_id')
->join('investment_status', 'investment_status.investment_status_id=(select '.$this->db->dbprefix('investment_status').'.investment_status_id from '.$this->db->dbprefix('investment_status').' where '.$this->db->dbprefix('investment_status').'.investment_detailed_id=sid.investment_detailed_id order by '.$this->db->dbprefix('investment_status').'.investment_status_id Desc limit 1)', 'inner')
->join('project_status', 'investment_status.project_status_id=project_status.project_status_id')
->where('sid.company_type_id', 1)
->order_by('investments.investment_id', 'desc')
->limit(5)
->get();

Problem with adding custom sql to finder condition

I am trying to add the following custom sql to a finder condition and there is something not quite right.. I am not an sql expert but had this worked out with a friend who is..(yet they are not familiar with rubyonrails or activerecord or finder)
status_search = "select p.*
from policies p
where exists
(select 0 from status_changes sc
where sc.policy_id = p.id
and sc.status_id = '"+search[:status_id].to_s+"'
and sc.created_at between "+status_date_start.to_s+" and "+status_date_end.to_s+")
or exists
(select 0 from status_changes sc
where sc.created_at =
(select max(sc2.created_at)
from status_changes sc2
where sc2.policy_id = p.id
and sc2.created_at < "+status_date_start.to_s+")
and sc.status_id = '"+search[:status_id].to_s+"'
and sc.policy_id = p.id)" unless search[:status_id].blank?
My find statement:
Policy.find(:all,:include=>[{:client=>[:agent,:source_id,:source_code]},{:status_changes=>:status}],
:conditions=>[status_search])
and I am getting this error message in my log:
ActiveRecord::StatementInvalid (Mysql::Error: Operand should contain 1 column(s): SELECT DISTINCT `policies`.id FROM `policies` LEFT OUTER JOIN `clients` ON `clients`.id = `policies`.client_id WHERE ((((policies.created_at BETWEEN '2009-01-01' AND '2009-03-10' OR policies.created_at = '2009-01-01' OR policies.created_at = '2009-03-10')))) AND (select p.*
from policies p
where exists
(select 0 from status_changes sc
where sc.policy_id = p.id
and sc.status_id = '2'
and sc.created_at between 2009-03-10 and 2009-03-10)
or exists
(select 0 from status_changes sc
where sc.created_at =
(select max(sc2.created_at)
from status_changes sc2
where sc2.policy_id = p.id
and sc2.created_at < 2009-03-10)
and sc.status_id = '2'
and sc.policy_id = p.id)) ORDER BY clients.created_at DESC LIMIT 0, 25):
what is the major malfunction here - why is it complaining about the columns?
The conditions modifier is expecting a condition (e.g. a boolean expression that could go in a where clause) and you are passing it an entire query (a select statement).
It looks as if you are trying to do too much in one go here, and should break it down into smaller steps. A few suggestions:
use the query with find_by_sql and don't mess with the conditions.
use the rails finders and filter the records in the rails code
Also, note that constructing a query this way isn't secure if the values like status_date_start can come from users. Look up "sql injection attacks" to see what the problem is, and read the rails documentation & examples for find_by_sql to see how to avoid them.
Ok, I've managed to retool this so it is more friendly to a conditions modifier and I think it is doing the sql query correctly.. however, it is returning policies that when I try to list the current status (the policy.status_change.last.status) it is set to the same status used in the query - which is not correct
here is my updated condition string..
status_search = "status_changes.created_at between ? and ? and status_changes.status_id = ?) or
(status_changes.created_at = (SELECT MAX(sc2.created_at) FROM status_changes sc2
WHERE sc2.policy_id = policies.id and sc2.created_at < ?) and status_changes.status_id = ?"
is there something obvious to this that is not returning all of the remaining associated status changes once it finds the one in the query?
here is the updated find..
Policy.find(:all,:include=>[{:client=>[:agent,:source_id,:source_code]},:status_changes],
:conditions=>[status_search,status_date_start,status_date_end,search[:status_id].to_s,status_date_start,search[:status_id].to_s])