SQL injection with function call - sql

Following query gets executed in my program, where 'a' is the parameter value which I am taking as input & passing it in query.
select * from emp where name LIKE LOWER('%a%')
Can anybody tell me whether I can do SQL injection attack on above query or is it safe?
I have seen SQL injection with where clause & like operator but can we do it with function call as well. What can I pass as the instead of 'a' for SQL injection.
I am using PL/SQL editor & Oracle DB.

Risk of SQL-injection appears when your application interacts with environment (other program or user) and assembles SQL query from parts using string concatenation. For example, you can write PL/SQL procedure:
create or replace procedure myproc(a varchar2) is
sql_str varchar2(4000);
sql_result number;
begin
execute immediate 'select count(*) from mytable where mycolumn = ' || a into sql_result;
end;
This procedure is vulnerable. You can pass there a string ''abc''' or 1 = 1 or anything like this and it distorts result (or made something worse).
Or you can write it like this:
create or replace procedure myproc(a varchar2) is
sql_str varchar2(4000);
sql_result number;
begin
execute immediate 'select count(*) from mytable where mycolumn = :A' using a into sql_result;
end;
And this procedure is not vulnerable.
Also you can write
create or replace procedure myproc(a varchar2) is
sql_str varchar2(4000);
sql_result number;
begin
select count(*)
into sql_result
from mytable
where mycolumn = a;
end;
Here is no problems at all, it is the most secure way (it is "static SQL"), but sometimes we need dynamic SQL (like in first two examples).
Why first way is bad and second is good? It is because SQL engine compiles queries almost like other compilers compile their code, like C++ for example. SQL engine compiles query as a "program" and defines possible "variables" in this "program". "Variable" in second procedure is the parameter :A. If query contains "variables", engine asks their values (USING clause) and passes them into compiled query. In first case engine gets concatenated string:
select count(*) from mytable where mycolumn = 'abc' or 1 = 1
considers it like a whole "program" and executes it "as is". In second case engine gets string
select count(*) from mytable where mycolumn = :A
compiles it, defines 1 "variable" A and asks it value, and then passes it to a "program", and that "program" just searches value 'abc' or 1 = 1 in column mycolumn. This works not only with dynamic SQL in PL/SQL code. It works the same way in any language, and all popular frameworks (for java, c#, delphi, etc.) and all popular DBMS provide instruments for safe work as in second example.
Of course, it was simplified example, sometimes consequences could be much more worse.

a can be
1') or 1 = 1 or LIKE LOWER('1
You should make sure the values passed as a are clean maybe by stripping the slashes in a.

You can exploit SQL Injection almost anywhere.
Checkout my white paper, Google "SQL Injection Anywhere" that demonstrates injections in weird places - including function calls.

Related

If I pass a where clause as a parameter will that prevent SQL Injection?

I created an Oracle proc where I create a dynamic sql statement based on the parameters supplied to the proc.
I've done some testing and it appears that I can't perform sql injection.
Is there anything additional I should be safe guarding against?
SELECT 'UPDATE ' || p_table || ' SET MY_FIELD = ''' || p_Value || ''' ' || p_Where
INTO query_string
FROM DUAL;
EDIT:
Scenarios that I've tried.
1. WHERE SOME_VAL IN ('AAA','BBB') - This works
2. WHERE SOME_VAL IN ('AAA','BBB') OR SOME_VAL2 = '123' - This works.
3. WHERE SOME_VAL IN ('AAA','BBB'); DROP TABLE TEST_TABLE; - This errors out.
4. WHERE SOME_VAL IN ('AAA','BBB') OR (DELETE FROM TEST_TABLE) - This errors out.
It depends on how and by whom your procedure is being invoked. Usually you need to worry about SQL injection for something that is open to large number of users in production. And that should not be the case for any database procedure. If your database procedure is accessible by large number of users, then you have potential for malicious use by someone.
In your case, you can mitigate this risk by creating mapping of parameters to hide actual schema object names and some validation.
For example change parameter p_table to table_name as input parameter. Then using case statement map to actual table name. I am giving you example of table name here because you should really restrict who can access which table from db.
CREATE OR REPLACE PROCEDURE test_proc(table_name IN VARCHAR)
IS
p_table varchar2(100);
BEGIN
CASE table_name
WHEN 'A' THEN p_table:='db_table_a';
WHEN 'B' THEN p_table:='db_table_b';
ELSE RAISE 'Invalid table name parameter';
END CASE;
SELECT 'UPDATE ' || p_table || ' SET MY_FIELD = ''' || p_Value || ''' '
|| p_Where
INTO query_string
FROM DUAL;
END;
You should do similar mapping and validation for other parameters too.
SQL injection always opens Pandora's box.
You should always assume a user can break out of a dynamic SQL statement. With full SQL access you should then assume a user can find a way to escalate privileges and own your database. (Depending on how paranoid you are, it might be safe to assume privilege escalation is impossible as long as your database and schemas are constantly patched and thoroughly hardened. In practice the vast majority of Oracle databases are not sufficiently patched and hardened.)
Below are a few simple examples that should scare you. And you should also assume that there are many hackers who are more clever than I am and have better attacks.
Sample Schema
First let's create a simple table with some data for a realistic test.
drop table test1;
create table test1(my_field varchar2(100), some_val varchar2(100));
insert into test1 values('A', 'AAA');
commit;
Obviously Dangerous Function
Are all of the existing functions safe?
create or replace function dangerous_function return number is
pragma autonomous_transaction;
begin
delete from test1;
commit;
return 1;
end;
/
If not, what is stopping the user from calling it like this?
--Safe static part:
update test1
set my_field = 'b'
--Dangerous dynamic part:
where some_val IN ('AAA')
and 1 = (select dangerous_function from dual)
Luckily creating an autonomous function is unusual and you can probably check the code. But can you guarantee the application will not create one in the future?
Custom Function in SQL
Even if there are no objects a clever user can turn your UPDATE into other DML:
--Safe static part:
update --+ WITH_PLSQL
test1
set my_field = 'b'
--Dangerous dynamic part:
where some_val IN ('AAA')
and 1 = (
with function dangerous_function return number is
pragma autonomous_transaction;
begin
delete from test1;
commit;
return 1;
end;
select dangerous_function from dual
);
I did cheat a little, the above code only works for me with the --+ WITH_PLSQL hint. Without that hint the code throws the error ORA-32034: unsupported use of WITH clause. But that's only a version limitation that might be lifted in the future. Or there might be some clever way to work around it, sometimes hints can break out of their part of the query and reference other sections.
Why Risk It?
Maybe there is a safe way to do it. But why risk it? Everybody in the IT world understands SQL injection bugs now. If you mess up and cause an exploit there will be no sympathy for you.

If Else with Select statement in Oracle sql developer

I would like to know if it's possible to write a sql query that returns a set of columns based on a condition.
Like for example:
If (id=='A')
{
Select id,name
From Table A
}
Else If(Condition=B)
{
Select Column1, Column3
From Table A
}
If yes please help me write it
You can do switch-like statements with CASE expressions in plain SQL. See the following example:
SELECT some_other_field,
id,
CASE ID
WHEN A THEN columnA
WHEN B THEN columnB
ELSE 'Unknown'
END genericvalue
FROM customers;
There are some limitations of course. For example the type of the return values in the THEN clause need to match, so you may need to convert for example all to char, or to int, etc.
Syntax (IF-THEN-ELSE)
The syntax is for IF-THEN-ELSE in Oracle/PLSQL is:
IF condition THEN
{...statements to execute when condition is TRUE...}
ELSE
{...statements to execute when condition is FALSE...}
END IF;
http://www.techonthenet.com/oracle/loops/if_then.php
SQL itself isn't turing-complete and it doesn't have syntax for loops and conditions: you can perform a query with it, no matter how complex it is, but you can't decide which query to execute depending on a condition or perform a query a number of times, which is what you are trying to do here.
In order to provide such functionality each database developer typically provides an additional language that includes variable declaration, loops, conditionals, etc. For Oracle this language is PL/SQL.
What you need to do in SQL Developer to solve your issue and see how PL/SQL works is create an empty script, then write something like this:
--Enabling output to the console
SET SERVEROUTPUT ON;
DECLARE
--Variable declaration block; can initialize variables here too
test_var varchar2(10);
test_result varchar2(10);
BEGIN
--Initializing variables, the first one we will check in the IF statement, the second one is just for transparency
test_var := 'test';
test_result := '';
--IF block: check some condition, perform a select based on the value, save result into a variable
IF test_var = 'test' THEN
SELECT '1' INTO test_result FROM dual;
ELSE
SELECT '2' INTO test_result FROM dual;
END IF;
--Output the result to console
DBMS_OUTPUT.PUT_LINE(test_result);
END;
Then run it with 'Run script'/F5. You will get '1' as output as you would expect. Change test_var to something else and run it again, you will get '2'.
If you have questions of this kind it might be useful to read about what exactly SQL and PL/SQL are. PL/SQL is quite efficient and versatile and can be used for anything from automating SQL scripts to implementing complex optimisation algorithms.
Of course, PL/SQL has similar constructs for FOR and WHILE loops, CASE checks, etc.
I guess this is what you are looking at.
It is not possible to do such a selection in SQL even by using CASE and DECODE.
But the best we can do is we can create a function which returns a ref_cursor and use the function in the SQL stetement to fit your requirement.
Below is an example for it:
CREATE OR REPLACE
FUNCTION test1(
id1 VARCHAR)
RETURN sys_refcursor
AS
v_ref sys_refcursor;
BEGIN
IF(id1='A') THEN
OPEN v_ref FOR SELECT id,name FROM TABLE A;
ElsIf(id1='B') THEN
OPEN v_ref FOR SELECT Column1, Column3 FROM TABLE A;
END IF;
RETURN v_ref;
END;
select test1(A) from dual;
Above will display only the columns id and name.

ORACLE PL/SQL Variable Function Scope - Need Explanation

I just tripped over an answer to a problem I was having with a PL/SQL variable not being recognized by a function and I was hoping someone could explain to me why my solution worked and what is happening "underneath the hood".
Background
As part of an optimization project, I am trying to collect metrics on individual SQL scripts within a Stored Procedure. The Stored Proc that I am dissecting has an In-type date parameter that I need to define in order to run each individual SQL Script:
CREATE OR REPLACE myStoredProc (DATE_IN DATE, ERROR_OUT OUT VARCHAR2)
IS
BEGIN
--Truncate Temp Tables
--6 Individual SQL Scripts
EXCEPTION
--Error Handling
END;
To run each script individually, I decided to just drop each SQL statement into a PL/SQL block and feed the DATE_IN parameter in as a variable:
DECLARE
DATE_IN DATE := TO_DATE('16-JUL-2014','DD-MON-RR');
BEGIN
--Place individual script here
END;
The Problem
This approach worked fine for a couple of the queries that referred to this DATE_IN variable but one query with a reference to an outside function which takes DATE_IN as a parameter started throwing an ORA-00904 error:
DECLARE
DATE_IN DATE := TO_DATE('16-JUL-2014','DD-MON-RR');
BEGIN
insert into temp_table
SELECT table1.field1,
table1.field2,
table2.fieldA,
MyFunction(table1.field1, DATE_IN) --This was the problem line
FROM
table1,
table2
WHERE EXISTS (inner query)
AND table1.keys = table2.keys
AND table2.date <= DATE_IN
END;
Solution
At the advice of another Developer, I was able to get around this error by adding a colon (:) in front of the DATE_IN variable that I was passing into the function so that the problem line read MyFunction(table1.field1, :DATE_IN). As soon as I did that, my error disappeared and I was able to run the query without issue.
I was happy for the result but the other Developer wasn't able to explain why it was needed, only that it was necessary to call any functions or other stored procs from a PL/SQL statement. I assume this has something to do with scope but I would like to get a better explanation as to why this colon was necessary for the function to see the variable.
Questions
I've tried to do a little research looking over Oracle documentation on parameters, variables, binding/declaring and constants but my research has only given me more questions:
After reading up on variables, I now question if that is the correct term for what I have been using (since I didn't actually use the VARIABLE command and I'm passing in a date - which is not an allowable data type). If my DATE_IN DATE := statement is not a variable, then what is it?
Why were the rest of my references to DATE_IN recognized by the compiler but passing the value to the function was out of scope?
What exactly is the colon (:) doing here? Is this turning that into a bind variable?
Thanks in advance. I appreciate any guidance you can provide!
----------------------------------EDIT--------------------------------------
I was asked to provide additional information. My Db version is 11G, 11.2.0.2.0. The query that I was able to reproduce this error is below.
DECLARE
EXTRACT_DT_IN DATE := TO_DATE('16-JUL-2014','DD-MON-RR');
BEGIN
--This begins the pre-optimized query that I'm testing
insert into AELI_COV_TMP_2_OPT
SELECT /*+ ordered use_nl(CM MAMT) INDEX (CM CSMB_CSMB2_UK) INDEX (MAMT (MBAM_CSMB_FK_I) */
CM.CASE_MBR_KEY
,CM.pyrl_no
,MAMT.AMT
,MAMT.FREQ_CD
,MAMT.HOURS
,aeli$cov_pdtodt(CM.CASE_MBR_KEY, EXTRACT_DT_IN)
FROM
CASE_MEMBERS CM
,MEMBER_AMOUNTS MAMT
WHERE EXISTS (select /*+ INDEX(SDEF SLRY_BCAT_FK_I) */
'x'
from SALARY_DEF SDEF
where SDEF.CASE_KEY = CM.CASE_KEY
AND SDEF.TYP_CD = '04'
AND SDEF.SLRY_KEY = MAMT.SLRY_KEY)
AND CM.CASE_MBR_KEY = MAMT.CASE_MBR_KEY
AND MAMT.STAT_CD = '00'
AND (MAMT.xpir_dt is null or MAMT.xpir_dt > EXTRACT_DT_IN)
AND MAMT.eff_dt <= EXTRACT_DT_IN;
--This ends the pre-optimized query that I'm testing
END;
Here is the error I'm encountering when trying to run an Explain Plan on this statement. I am able to get past this error if I remove reference to line 13 or I add a colon (:) to the EXTRACT_DT_IN on that line.
----------------------EDIT 2-------------------
Here is the function signature of aeli$.cov_pdtodt. (I've replaced the owner for security reasons).
CREATE OR REPLACE function __owner__.aeli$cov_pdtodt
(CASE_MBR_KEY_IN IN NUMBER, EXTRACT_EFF_DT_IN DATE)
RETURN DATE IS
PDTODT DATE;
Your anonymous block is fine as it is, as long as you execute the whole block. If you try to execute just the insert, or its select, as a standalone command then it will indeed fail with ORA-00904.
That isn't quite a scope problem, it's a context problem. You're trying to refer to a PL/SQL variable in an SQL context, and that is never going to work.
In a PL/SQL context this would work:
declare
some_var dual.dummy%type := 'X';
begin
insert into some_table
select dummy from dual where dummy = some_var;
end;
/
... because the insert has access to the PL/SQL some_var.
In an SQL context this will error:
select * from dual where dummy = some_var;
... because it's looking for a column called SOME_VAR, and there isn't one.
If you do this instead:
select * from dual where dummy = :some_var;
... the some_var is now a client-managed bind variable. If you execute that you'll either be prompted for the bind value, or given a not-all-variables-bound error, or bind-variable-not-declared, or similar, depending on your client.
If you only do an explain plan of that though, e.g. with
set auto trace traceonly explain
select * from dual where dummy = :some_var;
... then the bind variable doesn't necessarily have to be populated for the plan to be calculated. Some clients may still complain and want a bind value, but the parser would be OK with it - enough to produce a plan anyway. Though not able to take advantage of bind variable peeking or histograms etc.
For example, SQL Developer happily produces a plan for your original sample query if both references are turned into bind variables, just the insert ... part of the block is selected, and you press Explain Plan (F10).
I'm not sure what you read, but you're mixed up on a few things here.
Your DATE_IN is a variable. You don't need to type 'VARIABLE' anywhere to declare a variable, all you need is the name of the variable and the datatype.
All of the below are legitimate variables in PL/SQL (although poorly named).
variable_1 NUMBER;
variable_2 VARCHAR2(100);
variable_3 DATE;
It's hard to tell what you're doing in your code without seeing it all. Do you have two DATE_IN variables declared within the same block? Is DATE_IN the name of a column in your table?
If you have a column named DATE_IN in table1 or table2, that's likely your problem. Oracle doesn't know if you want to use your variable or your column, and it will always default to the column name. Your function would be expecting a DATE and receiving a column, hence the error.

Viewing query results with a parameters in Oracle

I need to run big queries (that was a part of SP) and look at their results (just trying to find a bug in a big SP with many unions. I want to break it into parts and run them separately).
How can I do that if this SP have few parameters? I don't want to replace them in code, it would be great just to add declare in a header with a hardcode for this parameter.
I've tried something like this:
DECLARE
p_asOfDate DATE := '22-Feb-2011';
BEGIN
SELECT * from myTable where dateInTable < p_asOfDate;
END
But it says that I should use INTO keyword. How can I view this results in my IDE? (I'm using Aqua data studio)
I need to do that very often, so will be very happy if will find a simple solution
You are using an anonymous block of pl/sql code.
In pl/sql procedures you need to specify a target variable for the result.
So you first need to define a variable to hold the result in the declare section
and then insert the result data into it.
DECLARE
p_asOfDate DATE := '22-Feb-2011';
p_result myTable%ROWTYPE;
BEGIN
select * into p_result from myTable where dateInTable < p_asOfDate;
END
That said you will probaply get more than one row returned, so I would use
a cursor to get the rows separately.
DECLARE
CURSOR c_cursor (asOfDate IN DATE) is
select * from myTable where dateInTable < asOfDate;
p_asOfDate DATE := '22-Feb-2011';
p_result myTable%ROWTYPE;
BEGIN
OPEN c_cursor(p_asOfDate);
loop
FETCH c_cursor into p_result;
exit when c_cursor%NOTFOUND;
/* do something with the result row here */
end loop;
CLOSE c_cursor;
END
To output the results you can use something like this for example:
dbms_output.put_line('some text' || p_result.someColumn);
Alternatively you can execute the query on an sql command-line (like sqlplus)
and get the result as a table immediately.
I hope I understood your question correctly...
update
Here is a different way to inject your test data:
Use your tools sql execution environemnt to submit your sql statement directly without a pl/sql block.
Use a "&" in front of the variable part to trigger a prompt for the variable.
select * from myTable where dateInTable < &p_asOfDate;
The Result should be displayed in a formatted way by your tool this way.
I do not know about Aqua, but some tools have functions to define those parameters outside the sql code.

PL/SQL Query with Variables

I have a fairly complex query that will be referencing a single date as a start or stop date multiple times throughout. I'm going to have to run this query for 3 different fiscal years, and don't want to have to hunt down the date 17 times in order to change it throughout my query.
Is there a way to set a variable at the beginning of my query and reference it throughout? I'm not looking to write a whole function, just reference a variable throughout my query.
Yes, depends how you want to do it.
You could use an anonymous procedure IE:
BEGIN
v_date DATE := TO_DATE(your_date, your_date_mask);
[your query referencing v_date where ever you need];
END;
Or if you run the query in SQLPlus, you use & to note variables (IE: &your_date), and will be prompted for the value when you run the script.
As OMG Ponies says, inside PL/SQL you can always refer to any PL/SQL variable (including parameters) right in the SQL as long as it's static SQL. Outside PL/SQL, or if your SQL is dynamic (because native dynamic SQL doesn't support reusable named parameters at least as of 10g) you can use the following trick. Add the following before the WHERE clause in your query:
CROSS JOIN (SELECT :dateparam Mydate FROM dual) Dateview
And everywhere you want to refer to that value in your main query, call it Dateview.Mydate Then when you execute the query, you need only pass in the one bind parameter.
You're not really saying how you reference this so I'll just show from SQL*Plus point of view.
Two ways
Have it prompt you for the value. Since you use the same variable many times you'll want to use the && operator.
SQL> SELECT &&var, &&var FROM Dual;
Enter value for var: 'PUMPKIN'
old 1: SELECT &&var, &&var FROM Dual
new 1: SELECT 'PUMPKIN', 'PUMPKIN' FROM Dual
'PUMPKI 'PUMPKI
------- -------
PUMPKIN PUMPKIN
Alternatively you could set it before you ran your SQL.
SQL> VARIABLE new_var VARCHAR2(20);
SQL> EXECUTE :new_var := 'PUMPKIN PIE';
PL/SQL procedure successfully completed.
SQL> SELECT :new_var, :new_var FROM DUAL;
:NEW_VAR :NEW_VAR
-------------------------------- --------------------------------
PUMPKIN PIE PUMPKIN PIE
If you use Toad with second mouse button -> Execute as Script, so not prompt you for values:
var myVar varchar2(20);
exec :req := 'x';
delete from MYTable where Field =
:myVar;