sql injection - risk with SELECT - sql

I would like to read user query from variable as (:user_query:) and execute it - in Oracle it will be like:
DECLARE
ddl_qry CLOB;
user_query VARCHAR2(20) := 'SELECT 100 FROM dual';
BEGIN
ddl_qry := 'INSERT INTO a (v) VALUES ((SELECT CAST(( ' || user_query || ') AS NUMBER) as COUNTER FROM dual))';
EXECUTE IMMEDIATE ddl_qry;
END;
I need to insert result of user_query to table 'a'.
I don't care if it will fail or sth, but is this safe? :) Is there any option if string user_query with SQL will drop my database or do sth else?

Or sb can construct a query that will drop my database?
If we fix the (many) syntax errors in your PL/SQL block we come to:
DECLARE
ddl_qry CLOB;
user_query VARCHAR2(20) := '1024';
BEGIN
ddl_qry := 'INSERT INTO a (v) VALUES ((SELECT CAST(( :user_query ) AS NUMBER) as COUNTER FROM dual))';
EXECUTE IMMEDIATE ddl_qry USING user_query;
END;
/
It will work and there is not a SQL injection vulnerability as it uses a bind variable :user_query to input the value.
However, that does not mean that it is particularly good as you:
Do not need to use dynamic SQL;
Do not need to select from the DUAL table; and
Do not need to explicitly CAST the input to a NUMBER as, if the column you are inserting into is a NUMBER then, there will be an implicit cast.
So the above code can be simplified to:
DECLARE
user_query VARCHAR2(20) := '1024';
BEGIN
INSERT INTO a (v) VALUES ( user_query );
END;
/
There is still no SQL injection vulnerability and the query is much simpler.
db<>fiddle here
Update
in Oracle it will be like:
DECLARE
ddl_qry CLOB;
user_query VARCHAR2(20) := 'SELECT 100 FROM dual';
BEGIN
ddl_qry := 'INSERT INTO a (v) VALUES ((SELECT CAST(( ' || user_query || ') AS NUMBER) as COUNTER FROM dual))';
EXECUTE IMMEDIATE ddl_qry;
END;
That has huge SQL injection vulnerabilities.
If user_query is, instead, set to:
SELECT CASE
WHEN EXISTS(
SELECT 1
FROM users
WHERE username = 'Admin'
AND password_hash = STANDARD_HASH( 'my$ecretPassw0rd', 'SHA256' )
)
THEN 100
ELSE 0
END
FROM DUAL
If you get the value 100 then you know that:
There is a table called users;
It has columns username and password_hash;
There is an Admin user; and
You've verified their password.
Please don't use dynamic SQL and string concatenation if you do not need to.

Related

How to select all rows from the oracle PL/SQL collection into SYS_REFCURSOR

Note: I have seen many solution and all says I can not use SQL with a PL/SQL type. I must have to use CREATE or REPLACE, but my restriction is I can not use system object for this task.
What I have tried the below example returns only last row.
create or replace PROCEDURE SP_TEST (TEST_cursor OUT SYS_REFCURSOR)IS
TYPE TEMP_RECORD IS RECORD(
entries NUMBER,
name VARCHAR2(50),
update VARCHAR2(200)
);
TYPE TEMP_TABLE IS TABLE OF TEMP_RECORD INDEX BY PLS_INTEGER;
VAR_TEMP TEMP_TABLE;
IDX PLS_INTEGER := 0;
BEGIN
VAR_TEMP(IDX).cur_entries := 1;
VAR_TEMP(IDX).cur_entries := 2;
OPEN TEST_cursor FOR
SELECT VAR_TEMP(idx).cur_entries from dual;
END SP_TEST;
Another way tried.
OPEN TEST_cursor FOR
SELECT * FROM TABLE(VAR_TEMP)
--- It gives compilation error ora-
Given that you can't create an object in the database, the only solution I can think of is to use dynamic SQL:
CREATE TYPE temp_record AS OBJECT
(
entries NUMBER,
entry_name VARCHAR2 (50),
update_value VARCHAR2 (200)
);
CREATE TYPE temp_table IS TABLE OF temp_record;
CREATE OR REPLACE PROCEDURE sp_test (test_cursor OUT SYS_REFCURSOR) IS
var_temp temp_table := temp_table ();
strSql VARCHAR2(32767);
BEGIN
-- Populate the temp table, or pass it in from elsewhere
var_temp.EXTEND();
var_temp (var_temp.LAST).entries := 1;
var_temp (var_temp.LAST).entry_name := 'test';
FOR i IN 1..var_temp.COUNT LOOP
strSql := strSql ||
CASE
WHEN LENGTH(strSql) > 0 THEN ' UNION ALL '
ELSE NULL
END ||
'SELECT ' || var_temp.ENTRIES || ' ENTRIES,' ||
'''' || var_temp.ENTRY_NAME || ''' ENTRY_NAME FROM DUAL';
END LOOP;
OPEN test_cursor FOR strSql;
END sp_test;
Now, I may have messed up the string concatenation logic there a bit, but the objective is to end up with an SQL string which looks something like
SELECT 1 ENTRIES,'test' ENTRY_NAME FROM DUAL
UNION ALL
SELECT 2 ENTRIES,'test 2' ENTRY_NAME FROM DUAL
UNION ALL
SELECT 3 ENTRIES,'test_3' ENTRY_NAME FROM DUAL
but, of course, without the nice white space and etc.
The 32K limit on dynamic SQL may bite you eventually, but if push comes to shove you can the DBMS_SQL package to handle arbitrarily large SQL text, although that presents its own challenges.
Best of luck.
In order to reference types in SQL (as opposed to PL/SQL), they must be created as objects in the database. This is effectively a scope issue: when you run SQL you are shifting to a different context. Any structures that you have created locally are not available there.
CREATE TYPE temp_record AS OBJECT
(
entries NUMBER,
entry_name VARCHAR2 (50),
update_value VARCHAR2 (200)
);
CREATE TYPE temp_table IS TABLE OF temp_record;
CREATE OR REPLACE PROCEDURE sp_test (test_cursor OUT SYS_REFCURSOR) IS
var_temp temp_table := temp_table ();
BEGIN
var_temp.EXTEND ();
var_temp (var_temp.LAST).entries := 1;
var_temp (var_temp.LAST).entry_name := 'test';
OPEN test_cursor FOR SELECT * FROM TABLE (var_temp);
END sp_test;

Can i somehow declare variable as query result?

I have stored procedure which should use count of records in table, lets name it table1.
I know i can:
select count(*)
into variable_name
from table1
but i really want to keep procedure body as clean and simple as possible.
Can i put this query into declaration and set value of my variable as result of the query in the declaration section?
As Vivek and default Locale mentioned, a Select within a pl/sql-block (like funcitons or procedures) is totally ok.
But there is one place where i do it: We store create-scripts for automated integration-tests in plsql-scripts.
To make them readable they look like this:
DECLARE
sqlCreateTable VARCHAR2 (2000) :=
'CREATE TABLE TMyTableName
(
COL1 VARCHAR2(30 BYTE) NOT NULL,
COL2 VARCHAR2(30 BYTE) NOT NULL
)
TABLESPACE MYTABLESPACE
PCTUSED 0
PCTFREE 0
INITRANS 1
MAXTRANS 255
STORAGE (
INITIAL 1M
NEXT 1M
MINEXTENTS 1
MAXEXTENTS UNLIMITED
PCTINCREASE 0
BUFFER_POOL DEFAULT
)
LOGGING
NOCOMPRESS
NOCACHE
MONITORING';
sqlCreateIndex VARCHAR2 (2000) :=
'CREATE UNIQUE
[...]
)';
sqlCreateView VARCHAR2 (2000) :=
'CREATE OR REPLACE FORCE VIEW VMyTable
(
COL1,
COL2
)
BEQUEATH DEFINER
AS
SELECT COL1, COL2 FROM
FROM MyTableName';
BEGIN
-- |---------------------------------------------------------------------------|
-- | CREATE TABLE |
-- |---------------------------------------------------------------------------|
dbms_output.put_line('- - - - - - START - - - - - -');
dbms_output.put_line('CREATE TABLE:' || chr(10) || sqlCreateTable);
EXECUTE IMMEDIATE sqlCreateTable;
dbms_output.put_line('CREATE INDEX:' || chr(10) || sqlCreateIndex);
EXECUTE IMMEDIATE sqlCreateIndex;
dbms_output.put_line('CREATE VIEW:' || chr(10) || sqlCreateView);
EXECUTE IMMEDIATE sqlCreateView;
dbms_output.put_line('- - - - - - DONE - - - - - -');
END;
Short example to run a query and populate local variables:
DECLARE
myQuery VARCHAR2 (2000) := 'SELECT 1 from DUAL';
myVar NUMBER;
BEGIN
EXECUTE IMMEDIATE myQuery INTO myVar;
DBMS_OUTPUT.put_line ('MyVar: ' || myVar);
END;
The answer is there is no syntax to embed an implicit cursor within a declaration. The general syntax for declaring a variable or constant is:
identifier [constant] datatype := expression;
expression can be a function, for example:
k_somecount constant integer := some_lookup('whatever');
That might be a neat approach if the function is used in multiple places or has multiple steps etc.
I wondered if there was a way to use an XML expression, for example:
k_somecount constant integer := cast(xmlwhatever(mystic xquery incantation) as integer);
but it seems not (and if there was, I don't think you would like it).
There is no implicit cursor expression syntax such as the following:
-- Made-up syntax purely to illustrate what you might have been hoping existed:
k_somecount integer := (select count(*) from employees);
You can do it like this,
DECLARE
CURSOR c
IS SELECT last_name, first_name
FROM employee;
TYPE emp_tab IS TABLE OF c%ROWTYPE INDEX BY BINARY_INTEGER;
v_emp_tab emp_tab;
v_variable_count NUMBER;
BEGIN
OPEN c;
FETCH c BULK COLLECT INTO v_emp_tab;
v_variable_count := v_emp_tab.COUNT;
DBMS_OUTPUT.PUT_LINE(v_variable_count);
CLOSE c;
END;
/

PLSQL SELECT INTO FROM parameter

I created a function that should return the max id from a table(parameter)
CREATE OR REPLACE FUNCTION getmaxid
(
P_TABLE IN VARCHAR2
)
RETURN NUMBER IS
v_maxId NUMBER(38);
BEGIN
SELECT MAX(id) INTO v_maxId FROM P_TABLE;
RETURN v_maxId;
END getmaxid
However, i keep getting the error message "ORA-00942: table or view does not exist" on this line:
SELECT MAX(id) INTO v_maxId FROM P_TABLE;
Like explained earlier, you need to use dynamic SQL to perform the operation. In this case, p_table is a variable. The solution to this is to build a string that will contain the SQL and dynamically execute it one you've build the query.
The example below uses, DUAL, but the table name is arbitrary.
Here is what you're looking for, take the function outside of the block, I left it like this so that you can test it..
DECLARE
FUNCTION getmaxid (p_table IN VARCHAR2)
RETURN NUMBER
IS
v_maxid NUMBER (38);
v_select VARCHAR2 (200);
cnt SYS_REFCURSOR;
BEGIN
v_select := 'SELECT COUNT(*) FROM ' || p_table;
DBMS_OUTPUT.put_line (v_select);
EXECUTE IMMEDIATE v_select INTO v_maxid;
RETURN v_maxid;
END getmaxid;
BEGIN
DBMS_OUTPUT.put_line (getmaxid ('DUAL'));
END;

PL/SQL Dynamic Loop Value

My goal is to keep a table which contains bind values and arguments, which will later be used by dbms_sql. The below pl/sql example is basic, it's purpose is to illustrate the issue I am having with recalling values from prior loop objects.
The table account_table holds acccount information
CREATE TABLE account_table (account number, name varchar2(100)));
INSERT INTO mytest
(account, name)
VALUES
(1 ,'Test');
COMMIT;
The table MYTEST holds bind information
CREATE TABLE mytest (bind_value varchar2(100));
INSERT INTO mytest (bind_value) VALUES ('i.account');
COMMIT;
DECLARE
v_sql VARCHAR2(4000) := NULL;
v_ret VARCHAR2(4000) := NULL;
BEGIN
FOR I IN (
SELECT account
FROM account_table
WHERE ROWNUM = 1
) LOOP
FOR REC IN (
SELECT *
FROM mytest
) LOOP
v_sql := 'SELECT ' || rec.bind_value || ' FROM dual';
EXECUTE IMMEDIATE v_sql INTO v_ret;
dbms_output.put_line ('Account: ' || v_ret);
END LOOP;
END LOOP;
END;
/
I cannot store the name i.account and later use the value that object. My idea was to use NDS; but, while the value of v_sql looks ok (it will read "Select i.account from dual"), an exception of invalid identifier would be raised on i.account. Is there a way to get the value of the object? We are using Oracle 11g2.
Thanks!
Dynamic SQL will not have the context of your PL/SQL block. You cannot use identifiers that are defined in the PL/SQL block in your dynamic SQL statement. So you cannot reference i.account and reference the loop that you have defined outside of the dynamic SQL statement. You could, of course, dynamically generate the entire PL/SQL block but dynamic PL/SQL is generally a pretty terrible approach-- it is very hard to get that sort of thing right.
If you are trying to use the value of i.account, however, you can do something like
v_sql := 'SELECT :1 FROM dual';
EXECUTE IMMEDIATE v_sql
INTO v_ret
USING i.account;
That doesn't appear to help you, though, if you want to get the string i.account from a table.
Are you always trying to access the same table with the same where clause, but just looking for a different column each time? If so, you could do this:
p_what_I_want := 'ACCOUNT';
--
SELECT decode(p_what_I_want
,'ACCOUNT', i.account
, 'OTHER_THING_1', i.other_thing_1
, 'OTHER_THING_2', i.other_thing_2
, 'OTHER_THING_1', i.default_thing) out_thing
INTO l_thing
FROM table;
The above statement is not dynamic but returns a different column on demand...

writing a generic procedure in oracle

i want to write procedure which accents name of 2 tables as arguments and then compare the number or rows of the 2.
Also i want to each field of the 2 columns.The row which has a missmatch shold be
moved to another error table.
Can anyone give a PL/SQL procedure for doing this.
I want to achive this in oracle 9
Pablos example wont work, the idea is right though.
Something like this do it.
create or replace PROCEDURE COMPARE_ROW_COUNT(T1 IN VARCHAR2, T2 IN VARCHAR2) AS
v_r1 number;
v_r2 number;
v_sql1 varchar2(200);
v_sql2 varchar2(200);
BEGIN
v_sql1 := 'select count(1) from ' || T1;
v_sql2 := 'select count(1) from ' || T2;
EXECUTE IMMEDIATE v_sql1 into v_r1;
EXECUTE IMMEDIATE v_sql2 into v_r2;
dbms_output.put_line(T1 || ' count = ' || v_r1 || ', ' || T2 || ' count = ' || v_r2);
END;
DBMS_SQL is your friend for such operations.
You can use dynamic sql in PL/SQL. EXECUTE IMMEDIATE is your friend.
So, if you take two table names and trying to compare their row counts, you would do something like:
CREATE OR REPLACE PROCEDURE COMPARE_ROW_COUNT(T1 IN VARCHAR2(200), T2 IN VARCHAR2(200)) AS
v_cursor integer;
v_r1 integer;
v_r2 integer;
v_sql varchar2(200);
BEGIN
v_sql := "select count(1) into :1 from " || T1;
EXECUTE IMMEDIATE v_sql USING v_r1;
v_sql := "select count(1) into :1 from " || T2;
EXECUTE IMMEDIATE v_sql USING v_r2;
-- compare v_r1 and v_r2
END;
Not 100% sure about PL/SQL syntax. It's been a while since the last time I coded in great PL/SQL!
You can achieve same results with similar approach using DBMS_SQL. Syntax is a little bit more complicated though.
I am just posting here to note that all answers gravitate around dynamic SQL, and do not turn the attention to the implied problems using it.
Consider passing the following string as first or second parameter:
dual where rownum = 0 intersect
SELECT 0 FROM dual WHERE exists (select 1 from user_sys_privs where UPPER(privilege) = 'DROP USER')
I'll leave it to that.
To answer your question - Oracle actually stores these values in the data dictionary, so if you have access to it:
CREATE OR REPLACE PROCEDURE COMPARE_ROW_COUNT(T1 IN VARCHAR2, T2 IN VARCHAR2) AS
v_text varchar2(1000);
BEGIN
select listagg(owner || ' ' || table_name || ' count = ' || num_rows, ',')
into v_text
from all_tables --user, all or dba tables depends on requirements
where table_name in (T1, T2);
dbms_output.put_line(v_text);
exception
when others then raise; -- Put anything here, as long as you have an exception block
END COMPARE_ROW_COUNT;