How correctly pass arguments to the SQL request via the sqlx package in Golang? - sql

In my Golang (1.15) application I use sqlx package to work with the PostgreSQL database (PostgreSQL 12.5).
When I try to execute SQL statement with arguments PostgreSQL database it raises an error:
ERROR: could not determine data type of parameter $1 (SQLSTATE 42P18):
PgError null
According to the official documentation, this error means that an INDETERMINATE DATATYPE was passed.
The organizationId has value. It's not null/nil or empty. Also, its data type is a simple built-in data type *string.
Code snippet with Query method:
rows, err := cr.db.Query(`
select
channels.channel_id::text,
channels.channel_name::text
from
channels
left join organizations on
channels.organization_id = organizations.organization_id
where
organizations.tree_organization_id like concat( '%', '\', $1, '%' );`, *organizationId)
if err != nil {
fmt.Println(err)
}
I also tried to use NamedQuery but it also raise error:
ERROR: syntax error at or near ":" (SQLSTATE 42601): PgError null
Code snippet with NamedQuery method:
args := map[string]interface{}{"organization_id": *organizationId}
rows, err := cr.db.NamedQuery(`
select
channels.channel_id::text,
channels.channel_name::text
from
channels
left join organizations on
channels.organization_id = organizations.organization_id
where
organizations.tree_organization_id like concat( '%', '\', :organization_id, '%' );`, args)
if err != nil {
fmt.Println(err)
}
In all likelihood, the arguments is not passed correctly to my request. Can someone explain how to fix this strange behavior?
P.S. I must say right away that I do not want to form an sql query through concatenation, or through the fmt.Sprintf method. It's not safe.

Well, I found the solution of this problem.
I found the discussion in github repository of the sqlx package.
In the first option, we can make concatenation of our search string outside of the query. This should still be safe from injection attacks.
The second choice to try this: concat( '%', '\', $1::text, '%' ). As Bjarni Ragnarsson said in the comment, PostgreSQL cannot deduce the type of $1.

Related

SQL table name as variable to query

I am using the pgx library to populate a Postgres database in Go.
Following e.g. the tutorial here and this question, I construct my query like so:
// this works
tblinsert = `INSERT into tablename (id, body) VALUES ($1, $2) RETURNING id`
var id string
err := Connector.QueryRow(context.Background(), tblinsert, "value1", "value2").Scan(&id)
Question: I would like to supply the tablename as a variable to the query as well, e.g. change tblinsert to INSERT into $1 (id, body) VALUES ($2, $3)
Issue: the above code errors out and returns a "syntax error at or near "$1" (SQLSTATE 42601)" when I run:
//this errors out
err := Connector.QueryRow(context.Background(), tblinsert, "tablename", "value1", "value2").Scan(&id)`.
I do not fully understand why the error message even references the $ placeholder - I expected the query to do the string substitution here, just like for the VALUES.
I found similar questions in pure SQL here and here, so not sure if this is even possible. .
Any pointers on where I am going wrong, or where I can learn more about the $x syntax (I got the above to work using Sprintf, but this appears discouraged) are much appreciated - I am pretty new to both SQL and Go.

How to prevent SQL Injection in PL/SQL

We have some few packages where we need to resolve some SQL Injection issues. I need some help to rewrite sql statement or sanitize the inputs. Below is the line number where veracode throw the error.
open c_ccl (p_part_nr,p_ctry_cd);
// Source code
CREATE OR REPLACE EDITIONABLE PACKAGE BODY "schema"."Test_PKG" AS
v_data t_cla_class_data;
FUNCTION nat_eccn_cd( p_part_nr IN t_part_nr, p_ctry_cd IN t_ctry_cd )
RETURN t_us_eccn_cd IS
CURSOR c_ccl(p_part_nr CHAR, p_ctry_cd CHAR) IS
SELECT NAT_CCL_CD FROM CLSDBA.CLA_EXP_PART_CTRY e
WHERE e.PART_NR = p_part_nr AND e.CTRY_CD = p_ctry_cd
ORDER BY e.VAL_FROM_DT DESC;
v_ctry_cd char(4) := p_ctry_cd;
v_trf_cd char(4);
BEGIN
v_data.nat_eccn_cd := NULL;
open c_ccl (p_part_nr,p_ctry_cd);
fetch c_ccl INTO v_data.nat_eccn_cd;
close c_ccl;
return (trim(v_data.nat_eccn_cd));
exception when others then return NULL;
end;
I don't see any SQL injection issues with your code - there is no dynamic code where the user inputs could be evaluated and escape out of the expected code flow. Unless your code snippet is generated somewhere else, or one of the column names is really a function that calls dynamic SQL, your code looks safe.
You used the phrase "sanitize the inputs", which is terrible advice for database programming. As much as I love the comic strip XKCD, Randall got this one wrong.
Bind variables are the best solution to avoiding SQL injection. I'll take this opportunity to (poorly) change his comic:

Postgres could not determine data type of parameter $1 in Golang application

I am creating an application in Golang that uses Postgres using the pq driver. I want to make a function that can select a user-determined field from my database, but I get an error:
pq: could not determine data type of parameter $1
Below is the code that generated this error:
var ifc interface{}
if err := conn.QueryRow("SELECT $1 FROM "+db+" WHERE uuid=$3 OR uri=$4 LIMIT 1", field, UUIDOrURI, UUIDOrURI).Scan(&ifc); err != nil {
if err == sql.ErrNoRows {
return http.StatusNotFound
}
log.Println(err)
return http.StatusInternalServerError
}
Why can I not insert the field that I want to SELECT using $1? Is there another way to do this?
You cannot use placeholders for field names. You'll have to build the query directly, as in:
"SELECT `" + field + "` FROM "
To avoid SQL injections, make sure that the field is part of a list of allowed fields beforehand.
IMHO an easier way, but not safe, to create SQL queries is to use fmt.Sprintf:
query := fmt.Sprintf("SELECT %s FROM %s WHERE uuid=%s", field, db, UUIDOrURI)
if err := conn.QueryRow(query).scan(&ifc); err != nil {
}
You can even specify an argument index:
query := fmt.Sprintf("SELECT %[2]s FROM %[1]s", db, field)
In order to ease the development, I recommend to use a package for the postgresql communication, I tried this one and worked great.

How use the insert query using parameters?

When i try with this query i get an error says that Perameter email doesn't exist, i am sure that the variables : email, login_pass, payment_method,operateur are valid and exists.
SQLQuery2.sql.Text := 'INSERT INTO registered (email,login_pass,payment_method,operateur) VALUES (":email",":login_pass",":payment_method",":avecpuce")';
SQLQuery2.ParamByName('email').AsString := email;
SQLQuery2.ParamByName('login_pass').AsString := login_pass;
SQLQuery2.ParamByName('payment_method').AsString := payment_method;
SQLQuery2.ParamByName('avecpuce').AsString := avecpuce;
SQLQuery2.ExecSQL(true);
I tried removing the quotation, but i get
You have an error in your Sql syntax, check the manual that corresponds to your SQL server for the right syntax to use near
':email,:login_pass,:payment_method,:avecpuce)' at line 1
How to use the insert query above using parameters?
From the TSQLQuery.ExecSQL documentation:
ExecDirect indicates that the query does not need to be prepared
before it is executed. This parameter can be set to true if the query
does not include any parameters.
So if the code uses
SQLQuery2.ExecSQL(true);
this means that there will be no support for parameters.
But because you use parameters, just use
SQLQuery2.ExecSQL;
and also remove the quotes around parameters.
Remove quotation marks:
SQLQuery2.sql.Text := 'INSERT INTO registered (email,login_pass,payment_method,operateur)
VALUES (:email, :login_pass, :payment_method, :avecpuce)';
Found the answer !
MySQLQuery2.SQL.Clear;
MySQLQuery2.SQL.Add('INSERT INTO COUNTRY (NAME, CAPITAL, POPULATION)');
MySQLQuery2.SQL.Add('VALUES (:Name, :Capital, :Population)');
MySQLQuery2.Params[0].AsString := 'Lichtenstein';
MySQLQuery2.Params[1].AsString := 'Vaduz';
MySQLQuery2.Params[2].AsInteger := 420000;
MySQLQuery2.ExecSQL;
Thankyou All !!
You don't usually quote parameters, only literals. So instead of:
VALUES (":email",":login_pass",":payment_method",":avecpuce")
Try:
VALUES (:email,:login_pass,:payment_method,:avecpuce)
You should not use quotes around the parameter name.
Parameters are automatically generated for you if your TSQLQuery has a connection assigned and ParamCheck is true and you assign TSQLQuery.CommandText.
It will not generate the parameters when you assign the query to TSQLQuery.SQL.Text.
You can have the parameters generated for you by calling TSQLQuery.Params.ParseSQL:
SQLQuery2.Params.ParseSQL(SQLQuery2.SQL.Text, True);
Or you can add them yourself by calling TSQLQuery.Params.AddParameter.

SQL Injection: is this secure?

I have this site with the following parameters:
http://www.example.com.com/pagination.php?page=4&order=comment_time&sc=desc
I use the values of each of the parameters as a value in a SQL query.
I am trying to test my application and ultimately hack my own application for learning purposes.
I'm trying to inject this statement:
http://www.example.com.com/pagination.php?page=4&order=comment_time&sc=desc' or 1=1 --
But It fails, and MySQL says this:
Warning: mysql_fetch_assoc() expects parameter 1 to be resource,
boolean given in /home/dir/public_html/pagination.php on line 132
Is my application completely free from SQL injection, or is it still possible?
EDIT: Is it possible for me to find a valid sql injection statement to input into one of the parameters of the URL?
The application secured from sql injection never produces invalid queries.
So obviously you still have some issues.
Well-written application for any input produces valid and expected output.
That's completely vulnerable, and the fact that you can cause a syntax error proves it.
There is no function to escape column names or order by directions. Those functions do not exist because it is bad style to expose the DB logic directly in the URL, because it makes the URLs dependent on changes to your database logic.
I'd suggest something like an array mapping the "order" parameter values to column names:
$order_cols = array(
'time' => 'comment_time',
'popular' => 'comment_score',
... and so on ...
);
if (!isset($order_cols[$_GET['order'])) {
$_GET['order'] = 'time';
}
$order = $order_cols[$_GET['order']];
Restrict "sc" manually:
if ($_GET['sc'] == 'asc' || $_GET['sc'] == 'desc') {
$order .= ' ' . $_GET['sc'];
} else {
$order .= ' desc';
}
Then you're guaranteed safe to append that to the query, and the URL is not tied to the DB implementation.
I'm not 100% certain, but I'd say it still seems vulnerable to me -- the fact that it's accepting the single-quote (') as a delimiter and then generating an error off the subsequent injected code says to me that it's passing things it shouldn't on to MySQL.
Any data that could possibly be taken from somewhere other than your application itself should go through mysql_real_escape_string() first. This way the whole ' or 1=1 part gets passed as a value to MySQL... unless you're passing "sc" straight through for the sort order, such as
$sql = "SELECT * FROM foo WHERE page='{$_REQUEST['page']}' ORDER BY data {$_REQUEST['sc']}";
... which you also shouldn't be doing. Try something along these lines:
$page = mysql_real_escape_string($_REQUEST['page']);
if ($_REQUEST['sc'] == "desc")
$sortorder = "DESC";
else
$sortorder = "ASC";
$sql = "SELECT * FROM foo WHERE page='{$page}' ORDER BY data {$sortorder}";
I still couldn't say it's TOTALLY injection-proof, but it's definitely more robust.
I am assuming that your generated query does something like
select <some number of fields>
from <some table>
where sc=desc
order by comment_time
Now, if I were to attack the order by statement instead of the WHERE, I might be able to get some results... Imagine I added the following
comment_time; select top 5 * from sysobjects
the query being returned to your front end would be the top 5 rows from sysobjects, rather than the query you try to generated (depending a lot on the front end)...
It really depends on how PHP validates those arguments. If MySQL is giving you a warning, it means that a hacker already passes through your first line of defence, which is your PHP script.
Use if(!preg_match('/^regex_pattern$/', $your_input)) to filter all your inputs before passing them to MySQL.