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.
Related
For a school project, unable to figure out how to debug the code, I've managed to figure out that the problem is in this line of code: Reservation_id:=: reservation_ID; but I am unsure how to fix it
Set serveroutput ON;
Create or replace procedure Reservation_Services_Report (reservation_ID IN number)
As
Service_number reservation.service_type_id%type;
People_attending reservation.numb_people_attend%type;
Begin
Reservation_id:=: reservation_ID;
Select s.service_name, s.service_type, s.service_type_food, s.service_type_entertainment, r.numb_people_attend from services s, reservation r
where services.service_type_id = reservation.service_type_id;
Exception
When no_data_found then
dbm_output.put_line(‘No services for this reservation’);
End;
Oh, it only it were your only problem!
as you declared an IN parameter, why don't you use it? It is here to be passed to the procedure, not to accept it at runtime (which is what you planned to do with preceding its name with a colon).
besides, it is a good idea to distinguish parameter name from column name, otherwise you'll have problems as Oracle won't know which is which. That's why I renamed it to par_reservation_id
select in PL/SQL requires into clause. In your case, you need to declare all those variables which match select column list. I named them all with the l_ prefix (as a l_ocal variable); some people use v_; pick whichever you want, just try to stick to some standards - it'll make your code easier to read, debug and maintain
select you wrote would probably return too_many_rows error as there's no restriction to number of rows; I presume you "forgot" to include the where clause so I put it there; I don't know whether it is correct or not as I don't have your tables. Fix it, if necessary
when joining tables, try to switch to modern ANSI syntax and separate joins from (where) conditions, as I tried
unless you're running that code in a tool that supports dbms_output to be displayed on the screen, you won't see anything. Exception is handled (kind of), but nobody will know what happened. Consider raising the error instead, e.g. raise_application_error(-20000, 'No services for this reservation')
Here's how the procedure might look like; hopefully, it is somewhat better than the original version. See if it helps.
Create or replace procedure
Reservation_Services_Report (par_reservation_ID IN number)
As
l_service_name services.service_name%type;
l_service_type services.service_type%type;
l_service_type_Food services.service_type_Food%type;
l_service_type_entertainment services.service_type_entertainment%type;
l_numb_people_attend reservation.numb_people_attend%type;
Begin
Select s.service_name,
s.service_type,
s.service_type_food,
s.service_type_entertainment,
r.numb_people_attend
into l_service_name,
l_service_type,
l_service_type_food,
l_service_type_entertainment,
l_numb_people_attend
from services s join reservation r on s.service_type_id = r.service_type_id
where r.reservation_id = par_reservation_id;
Exception
When no_data_found then
dbms_output.put_line(‘No services for this reservation’);
End;
I am very new to oracle's sql developer (since we've studied mysql) as well as in programming. I've searched in this website the answer to my question but I really can't understand the solutions provided.
What I want is to return the ID generated after inserting an object from java into the database. I'm using mybatis and oracle 10g database. I've already created the table and its columns.
Here's my code for the mapper
<insert id="addUser" parameterType="User" statementType="CALLABLE">
{ CALL addUserSP(
#{user.surname, javaType=String, jdbcType=VARCHAR, mode=IN},
#{user.firstName, javaType=String, jdbcType=VARCHAR, mode=IN},
#{userId, javaType=Integer, jdbcType=NUMBER, mode=OUT}
)}
</insert>
Here's my stored procedure (and I've already create a package named 'CREATEUSER')
PROCEDURE ADDUSERSP
( surname IN VARCHAR2,
firstName IN VARCHAR2,
userId OUT NUMBER
) AS
BEGIN
INSERT INTO users("surname", "first_name")
VALUES (surname, firstName);
RETURNING user_id INTO userId;
END ADDUSERSP;
According to what I've found here, it seems that I need to create a trigger(?) and sequence(?) to make the user_id auto increment whenever I add new data into the table. However, I have no idea how to do it.
Here are my questions:
Is my stored procedure right? Are the codes incomplete? I mean, I have not declared the package in the mapper and I've seen that it is needed (?), something like this { CALL [CreateUser].[addUserSP]( blah blah.... Should I write a sequence and trigger or there is an easy way to make the primary key user_id to be auto incremented? Kindly also check the syntax. I have a lot of problems in syntax.
Thank you so much!
To emulate MySQL AUTO_INCREMENT in Oracle, that pattern (as you found) does use a SEQUENCE object and a BEFORE INSERT trigger.
As a demonstration, something like this for the sequence object:
CREATE SEQUENCE myseq START WITH 1 INCREMENT BY 1 ;
And something like this for the before insert trigger:
CREATE TRIGGER users_bi
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
IF :NEW.id IS NULL THEN
SELECT myseq.NEXTVAL INTO :NEW.id FROM DUAL;
END IF;
END
As far as the procedure, I'm not a big fan of extra PL/SQL blocks that wrap a SQL INSERT statement.
It looks like you have an extra semicolon, before RETURNING. That clause is part of the INSERT statement, not a separate statement.
One big gotcha to be aware of is that SQL statements within a PL/SQL block can reference both columns and PL/SQL variables. When variables have the same names as columns, you will likely encounter behavior you didn't expect.
Typically PL/SQL author use a naming convention for variables that reduces the likelihood of name collisions. We frequently see variables with names like v_surname. (Personally, I use a slightly different convention, but the variable names "look like" variable names, not column references. And I don't name columns following the pattern I use for variables.)
The double quotes around the identifiers are acceptable, but this does make the identifiers case sensitive. When identifiers aren't enclosed in double quotes, Oracle treats them as if they were UPPER CASE. Just make sure that your table was defined with lower case column names.
Anyone help me with this error
declare
cursor cur1 is
select owner,table_name from dba_tables;
c1 cur1%rowtype;
str varchar2(1000);
begin
for c1 in cur1 loop
str:='analyze table '||c1.owner||'.'||c1.table_name||' list chained rows';
execute immediate str;
end loop;
end;
/
declare
*
ERROR at line 1:
ORA-00911: invalid character
ORA-06512: at line 9
You appear to have a table (or user/schema) identifier which does not conform to the usual pattern and the database object naming rules, and which must therefore have been created as a quoted identifier.
For example, a table name starting with a number, or with a space in the name - you'd need to check your data dictionary to find out exactly what it's tripping over, or add a debug before the execute immediate to do dbms_output.put_line(str);.
Whatever it is though, you can just quote the identifier in the analyze command too:
str:='analyze table "'||c1.owner||'"."'||c1.table_name||'" list chained rows';
So if your current query generates a command like:
analyze table MYSCHEMA.42 list chained rows
... where the 42 is an invalid identifier, the modified version would do:
analyze table "MYSCHEMA"."42" list chained rows
This will also take care of any mixed-case names, which would also cause the analyze to fail unless quoted.
This is why quoted identifiers are not recommended by Oracle and are usually disliked by anyone who has been forced to deal with them. The identifier has to be quoted whenever it is referenced anywhere, and be used with exactly the same case as when it was created. If you have a choice, don't use them.
It may not be your fault though. On my 11gR2 instance there's an entry in dba_tables for "SYS"."_default_auditing_options_" which breaks the naming rules by starting with an underscore, and was created quoted in lowercase. (And that seems to have been there since at least 8.0). Not sure manually analyzing SYS tables is necessarily a good idea anyway, it might be better to restrict this to your own schema(s).
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.)
I defined a role and grant it with only connect to database and execute a specific stored procedure. Users have this role can see the body of procedure when execute this query;
select * from ALL_SOURCE where NAME = 'procedureName';
Procedure takes a VARCHAR2 parameter and uses it with a select query. Is that a security issue? Should i hide it somehow or escape the parameter?
Generally, it would only be a security issue if your procedure was subject to SQL injection. The fact that you talk about escaping the parameter implies that you may be doing dynamic SQL and may be vulnerable to SQL injection attacks. If that's the case, you need to fix the procedure, not hide the source.
If your stored procedure is implementing some business logic that you consider proprietary, you could potentially wrap the code so that it is obfuscated in the data dictionary. If you do that, however, make absolutely sure that you your source code in source control because there is no way to unwrap code once you've wrapped it (strictly speaking, there are various techniques that an attacker can use to recover most of the wrapped source if they really wanted to, but it's reasonably secure).