Procedure to delete table rows - oracle

I am testing this perl script which basically call procedure and run DELETE on 2 tables.
Questions:
Is there any issue with the procedure or calling procedure in perl?
Can I use 2 deletes in single procedure?
Procedure delete (v_db_id in number)
IS BEGIN
DELETE from TAB1
where db_id = v_db_id;
DELETE from TAB2
where db_id = v_db_id;
END delete;
PERL Script:
sub getdelete {
my $dbID = shift
my $rs;
my $SQL;
$SQL = q{delete (?)};
$rs = executeQuery($SQL,$dbID);
$rs -> fetchrow();
$rs -> finish();
}
PERL Script calling subroutine getdelete as below:
&getdelete ($dbID);
Error:
DBD::Oracle::st execute failed: ORA-00900: invalid SQL statement (DBD Error: OCIStmtExecute)[for statement "delete"]

DELETE doesn't takes an expression that results in a table id; it takes a table id literal. As such, you can't use replaceable parameters. You need to construct the table id literal, which $dbh->quote_identifier can do.
my $sql = 'DELETE '.$dbh->quote_identifier($dbID);
$dbh->do($sql);
You are using a module that wraps DBI in a very poor way1. I have no way of knowing if it'll give access to the database handle or to the handle's quote_identifier method, but at least now you know what to look for.
Notes:
There are three ways to wrap DBI that make sense:
To add functions or override some minor aspect of existing functions.
For example, DBI database statements don't have the selectrow_* methods founds on database handles. Adding these without restricting access to the rest of DBI is perfectly fine.
To provide a higher level abstraction of a database, such as an ORM like DBIx::Class. These are massive systems with thousands if not tens of thousands of lines.
If your wrapper provide a new database interface and its code fits on two screens, it's doing something wrong.
To centralise all DB code by providing application-specific functions like create_user, fetch_daily_report_data, etc. SQL isn't passed
If your wrapper attempts to this but provides functions that expect SQL, it's doing something wrong.
What doesn't make sense is to attempt to simplify DBI, and this appears to be what your wrapper does. DBI actually provides a very simple interface. Any attempts to simplify it is bound to leave out something critical.

Related

Using regexp_replace to prevent SQL injection

We have thousands of oracle packages that contain a map_products procedure.
We have a table that stores the list of oracle packages a customer would like that map_products run for.
The process that runs them uses dynamic SQL like this:
select sanitize(package_name)
into v_package_name
from custom_plugins
where id = p_id;
execute immediate '
begin
'||v_package_name||'.map_products;
end;
';
The sanitize function above is meant to prevent SQL injection.
Here is the function definition:
function sanitize(p_string in varchar2) return varchar2
is
begin
return regexp_replace(upper(p_string),
'(ALTER|MERGE|CREATE|SELECT|INSERT|UPDATE|DELETE|MODIFY|DROP|ENABLE|DISABLE|;)');
end;
Now we realize this is a dangerous approach in the long run and are planning to redo the entire process. However, for the time being, is there any easy way that this regexp_replace can be circumvented allowing SQL injection?
More specifically, we want to make sure that a semicolon cannot be passed in. Does the above ensure that?
Use the string:
DRDROPOP your_package_name
The replace will only replace DROP once leaving you with:
DROP your_package_name
I suggest whitelisting instead of regular expressions.
Check the input against the system tables.
select object_name
from dba_objects
where owner = 'SYS'
and object_type = 'PACKAGE'
and object_name = :p_string;
If you can't find a match, then it isn't a known package, so don't use it.
I think removing the ; will be a first approach to avoid the sql injection. But please consider this possible strategy:
Extract with regexp a valid package name from the input(for example, checking that you have alphanumeric characters, or "_", or any other character allowed in a package name but nothing else).
With the output from previous step, check that the package is actually an object in the database (querying user_object).
At this point you will have a valid package name (if any) and you could use it in the dynamic statement.
You already said that your approach was dangerous. I just want to make sure that you're aware that it'd be problematic with a package with a name containing any of the words in your regexp (salaryUPDATEr, for example)
As the other posters above have commented blacklisting is going to be quite easy to bypass.
Using whitelist validation is always ideal. If that's not feasible, and since this is Oracle, the best option is to use the built in dbms_assert.enquote_name - this safely enquotes the value (and checks for embedded quotes).
For more info on DBMS_ASSERT see:
https://oracle-base.com/articles/10g/dbms_assert_10gR2#ENQUOTE_NAME
Or for much more in depth on preventing SQL injection see:
http://www.oracle.com/technetwork/database/features/plsql/overview/how-to-write-injection-proof-plsql-1-129572.pdf
You can use the DBMS_ASSERT package.
Since the 10g version, Oracle provides the DBMS_ASSERT package. It contains functions that can be used to validate user input.
Example (SQL Injection attempt)
SELECT description FROM products WHERE name=DBMS_ASSERT.ENQUOTE_LITERAL('abc'' OR 1=1--');
ERROR RETURNED.
ORA-06502: PL/SQL: numeric or value error
You can find more information at link.

Dynamic Query Re-write or Evaluating the Query Before Executing Table Function

First, I want to make it clear that the question is not about the materialized views feature.
Suppose, I have a table function that returns a pre-defined set of columns.
When a function call is submitted as
SELECT col1, col2, col3
FROM TABLE(my_tfn(:p1))
WHERE col4 = 'X';
I can evaluate the parameter and choose what queries to execute.
I can either open one of the pre-defined cursors, or I can assemble my query dynamically.
What if instead of evaluating the parameter I want to evaluate the text of the requesting query?
For example, if my function returns 20 columns but the query is only requesting 4,
I can assign NULLs to remaining 16 clumns of the return type, and execute fewer joins.
Or I can push the filter down to my dynamic query.
Is there a way to make this happen?
More generally, is there a way to look at the requesting query before exuting the function?
There is no robust way to identify the SQL that called a PL/SQL object.
Below is a not-so-robust way to identify the calling SQL. I've used code like this before, but only in special circumstances where I knew that the PL/SQL would never run concurrently.
This seems like it should be so simple. The data dictionary tracks all sessions and running SQL. You can find the current session with sys_context('userenv', 'sid'), match that to GV$SESSION, and then get either SQL_ID and PREV_SQL_ID. But neither of those contain the calling SQL. There's even a CURRENT_SQL in SYS_CONTEXT, but it's only for fine-grained auditing.
Instead, the calling SQL must be found by a string search. Using a unique name for the PL/SQL object will help filter out unrelated statements. To prevent re-running for old statements, the SQL must be individually purged from the shared pool as soon as it is found. This could lead to race conditions so this approach will only work if it's never called concurrently.
--Create simple test type for function.
create or replace type clob_table is table of clob;
--Table function that returns the SQL that called it.
--This requires elevated privileges to run.
--To simplify the code, run this as SYS:
-- "grant execute on sys.dbms_shared_pool to your_user;"
--(If you don't want to do that, convert this to invoker's rights and use dynamic SQL.)
create or replace function my_tfn return clob_table is
v_my_type clob_table;
type string_table is table of varchar2(4000);
v_addresses string_table;
v_hash_values string_table;
begin
--Get calling SQL based on the SQL text.
select sql_fulltext, address, hash_value
bulk collect into v_my_type, v_addresses, v_hash_values
from gv$sql
--Make sure there is something unique in the query.
where sql_fulltext like '%my_tfn%'
--But don't include this query!
--(Normally creating a quine is a challenge, but in V$SQL it's more of
-- a challenge to avoid quines.)
and sql_fulltext not like '%quine%';
--Flush the SQL statements immediately, so they won't show up in next run.
for i in 1 .. v_addresses.count loop
sys.dbms_shared_pool.purge(v_addresses(i)||', '||v_hash_values(i), 'C');
end loop;
--Return the SQL statement(s).
return v_my_type;
end;
/
Now queries like these will return themselves, demonstrating that the PL/SQL code was reading the SQL that called it:
SELECT * FROM TABLE(my_tfn) where 1=1;
SELECT * FROM TABLE(my_tfn) where 2=2;
But even if you go through all this trouble - what are you going to do with the results? Parsing SQL is insanely difficult unless you can ensure that everyone always follows strict syntax rules.

load SQL statements from a file using clojure.java.jdbc

The REST call is sending the branchId and emplId to this exec-sql-file method. I am passing these as a parameter. I am not able to execute the SQL statement when I pass branch_id = #branchid and empl_id = #emplid. But when I hardcode the branch_id = 'BR101' and empl_id = 123456 then it is working. Any suggestion how to get the branch_Id and empl_Id in my some-statements.sql?
(defn exec-sql-file
[branchid emplid]
(sql/with-db-connection (db-conn)
(sql/db-do-prepared conn
[branchid emplid (slurp (resource "sql/some-statements.sql"))])))
some-statements.sql have this query
DELETE from customer where branch_id = #branchid and empl_id = #emplid;
I am executing this from REPL as
(exec-sql-file "BR101" 123456)
I grab the code snippet from the below post.
Is it possible to patch load SQL statements from a file using clojure.java.jdbc?
There is no simple way to do this as your approach requires that you have to provide parameters to multiple SQL statements in one run. Another issue is that Java's PreparedStatement (used under the hood by clojure.java.jdbc) doesn't support named parameters, so even if parameters to multiple SQL statements done using a single prepared statement would have to be provided for every placeholder (?).
I would suggest following solutions:
use multiple prepared statements (so separate clojure.java.jdbc/execute! calls) for each of the SQL statement you want to execute wrapped in a single transaction (each SQL could be read from a separate file). You could also use some helper library like YeSQL to make loading your SQL statements from external files and exposing them as functions you could call as ordinary Clojure functions. It would be simple but if you change the number of statements you would like to execute, then you need to change your code
create a stored procedure and call them from Clojure providing the parameters - this will define an interface for some DB logic which will be defined on the DB side. Unless you change the interface of your stored procedure you can modify its implementation without changing your Clojure code or redeployment
implement your own logic of interpolating named parameters into your "multistatement" SQL file. The issue is to appropriately escape parameters' values so your code is not vulnerable to SQL injection. I would discourage this solution.

Oracle: Using Procedure or Cursor to store a Select-Statement?

I have a PL/SQL package where i want to declare a select-statment which are used by different other Packages. So i see to ways. First way i define a cursor which can be called from other packages and store the select. Second way would be a procedure which stored the select.
Can someone tell me the advantages and disadvantages of each way? My Prof. say Cursor are old and statefull and noone use this today. My Chef tell me Cursor is faster to iterate and you can make Types of it.
Can someone tell me what's the best practice here?
For example:
CURSOR crs_active_customer IS
SELECT * FROM customer where status = 'active'
OR
PROCEDURE prc_getActiveCustomer IS
BEGIN
SELECT * FROM customer where status = 'active';
END prc_getActiveCustomer;
What is better way to store select-statements.
I would write a function that returns a new cursor instance every time you call it. A cursor variable in a package is actually a GLOBAL variable: you can have only one procedure at a time using it. This is probably the problem your professor is referring to.
Having a global cursor means that you will run into "cursor already open" errors if you write a a procedure that, while scanning the results of such cursor calls another function that internally needs to use the same cursor.
PL/SQL 101 to the rescue ! From Working with Cursors:
The central purpose of the Oracle PL/SQL language is to make it as easy and efficient as possible to query and change the contents of tables in a database. You must, of course, use the SQL language to access tables, and each time you do so, you use a cursor to get the job done.
So every time you have SQL in PL/SQL there will be a cursor. The next question is what kinds of cursors there is and when to use them. The above mentioned article touches also this topic.
You can also read the fine manual: Cursors
A cursor is a pointer to a private SQL area that stores information about processing a specific SELECT or DML statement.
And then carry on reading about implicit and explicit cursors.
Next find a better professor.

Restricting dbms_sql to SELECT queries

I am writing a function similar to Tom Kyte's print_table, but with more output options and better formatting, etc., etc. It struck me that writing a function where you could enter arbitrary SQL with authid currentuser -- rather than passing a ref cursor -- was pretty dangerous! However, I need to know the column attributes and, AFAIK, there's no other way of getting this.
Is there a way, therefore, of restricting the dbms_sql.execute function so it only operates on select queries? Or, put another way, is there a way to check the type of DML being parsed through the cursor and then, for example, raise an exception if it's anything other than a select?
Textual analysis of the query (e.g., allowing select or with, but nothing else) wouldn't work, because you could just do print_table('select * from dual; drop someTable'); etc...
(P.S., Oracle 10gR2, if it makes a difference.)

Resources