PL/SQL: bind variable does not exist - sql

How to modify this procedure to let it use bind variables
PROCEDURE KILL(user IN VARCHAR2) AS
BEGIN
FOR REC IN (SELECT sid,serial# serial FROM V$SESSION WHERE username = user)
LOOP
execute immediate 'alter system kill session '' :1 , :2 '' immediate'
using rec.sid, rec.serial;
END LOOP;
END;
It gives:
bind variable does not exist

The bind variables in your statement are being treated as literal strings rather than place holders. If you output the statement you're generating:
BEGIN
FOR REC IN (SELECT sid,serial# serial FROM V$SESSION WHERE username = user)
LOOP
dbms_output.put_line('alter system kill session '':1,:2'' immediate');
END LOOP;
END;
/
... you see lines like:
alter system kill session ':1,:2' immediate
The ':1,:2' is treated as a static value and not as two bind variables. You can't use bind variables in dynamic DDL, and I'm not sure if that applies to alter commands, so this may be impossible anyway.
The simplest way to achieve this may be to generate the whole statement in the cursor:
BEGIN
FOR REC IN (
SELECT 'alter system kill session ''' || sid ||','|| serial#
||''' immediate' stmt
FROM V$SESSION WHERE username = user)
LOOP
dbms_output.put_line(rec.stmt);
--execute immediate rec.stmt;
END LOOP;
END;
/
With the execute commented out (I don't really want to kill my sessions just now) you can just see the commands it will run, like:
alter system kill session '58,47157' immediate
Your approach may still be flawed though as it will kill the session that is executing the block, and it may or may not kill it last. I think this is in the realms of undefined behaviour, and I don't really want to try it to find out what happens... I doubt that's what you actually want anyway.
Edit: 'flawed' comment was based on using user, which in my anonymous block would be the executing user; in your proc it would be the user from the parameter. Using a keyword as a parameter name is confusing though, so I'd recommend changing the name to something like p_user, in the args and the statement.

I believe this might work
PROCEDURE KILL(user IN VARCHAR2) AS
BEGIN
FOR REC IN (SELECT sid,serial# serial FROM V$SESSION WHERE username = user)
LOOP
execute immediate 'alter system kill session :1 '||','|| ':2 immediate'
using rec.sid, rec.serial;
END LOOP;
END;

Related

Oracle: grant to role if role exists

How to execute
GRANT SELECT ON <ownschema>.<sometable> TO <somerole>;
but backing off gracefully if somerole does not exist. The user executing the statement is a standard user (think SCOTT) without any special privileges.
Version: Oracle Database 19 or later
I don't think you can.
If you're running it at SQL level, then Oracle will raise an error if role doesn't exist.
If you want to check whether it exists, you'll need some kind of a PL/SQL procedure (which is not a problem), but - DBA should grant you select privilege on dba_roles so that you could check it. Then, if it exists, you'd grant that privilege; otherwise, return some kind of an information (dbms_output.put_line in my example is pretty much stupid; it wouldn't be visible out of SQL*Plus, SQL Developer, TOAD and similar), but you got the idea, I hope.
Something like this:
create or replace procedure p_grant_to_role (par_role in varchar2) is
l_cnt number;
begin
select count(*)
into l_cnt
from dba_roles
where role_name = par_role;
if l_cnt > 0 then
execute immediate 'grant select on emp to ' || par_role;
else
dbms_output.put_line('Role does not exist');
end if;
end;
/
It all depends on the tool, but you can do something like this (very crude as usually you should have better exception handling):
begin
execute immediate 'grant .....';
exception
when others then null;
end;

Oracle - Why is EXECUTE IMMEDIATE allowed in stored procedures?

Why is EXECUTE IMMEDIATE allowed in stored procedures, if stored procedures are meant to mitigate SQL injection attacks? The accepted answer to the following question refers to them as a step against such attacks:
What is a stored procedure? https://stackoverflow.com/a/459531/3163495
"Stored procedures also have a security benefit in that you can grant
execute rights to a stored procedure but the user will not need to
have read/write permissions on the underlying tables. This is a good
first step against SQL injection."
...unless the stored procedure is using EXECUTE IMMEDIATE.
This PL/SQL code returns a product's description (second parameter).
CREATE OR REPLACE PROCEDURE prodDescr(vname IN VARCHAR2, vresult OUT VARCHAR2) AS
vsql VARCHAR2(4000);
BEGIN
vsql := 'SELECT description FROM products WHERE name=''' || vname || '''';
EXECUTE IMMEDIATE vsql INTO vresult;
END;
Malicious user input.
A' AND 1=2 UNION SELECT password FROM members WHERE username='admin
Generated Query.
SELECT description FROM products WHERE name='A' OR 1=2 UNION SELECT password FROM members WHERE username='admin'
When the query is executed, the attacker gets the administrator’s password.
As you can see, although we used a stored procedure, an attacker can still exploit a vulnerability just as easily as if we were an amateur developer concatenating some SELECT statement in PHP without sanitizing input. To me, it seems it can be very misleading to say to developers that stored procedures will help keep your database safe.
Execute Immediate can still be used in a safe way. It all comes down to the logic of the stored proc. The concat is making the code unsafe not the execute immediate.
vsql := 'SELECT description FROM products WHERE name=''' || vname || '''';
Should be using bind variables or a dbms_assert call.
vsql := 'select count(1) from all_objects where owner = :1'
EXECUTE IMMEDIATE vsql into vresult using vname ;
OR
vsql := 'select count(1) from all_objects where owner ='||DBMS_ASSERT.ENQUOTE_LITERAL(vname);
EXECUTE IMMEDIATE vsql into vresult ;
In a full example below using both methods. The first has bind(s) and the second is wrappered with DBMS_ASSERT.
SQL>declare
v_in varchar2(2000);
ret varchar2(2000);
begin
v_in := 'KLRICE';
EXECUTE IMMEDIATE 'select count(1) from all_objects where owner = :1' into ret using v_in ;
dbms_output.put_line('First Object Count : ' || ret);
EXECUTE IMMEDIATE 'select count(1) from all_objects where owner ='||DBMS_ASSERT.ENQUOTE_LITERAL(v_in) into ret ;
dbms_output.put_line('Second Object Count : ' || ret);
end
SQL> /
First Object Count : 74
Second Object Count : 74
PL/SQL procedure successfully completed.
SQL>
Stored procedures do not keep your database safe. That has never been true.
No language, framework, or API can keep your database safe.
It is the developer's responsibility to write safe code.
If you use EXECUTE IMMEDIATE in an unsafe way, then you have a vulnerability.
The same is true when you don't use stored procedures — if you write dynamic SQL using any application language, you have the same risk of creating SQL injection vulnerabilities.

Trigger pl/sql insert data on table

i'm new in pl/sql. I'm trying to create a trigger that insert datas in specific tables.
I have datas that arrives in real-time on my table EV_48h. To know on which table I have to insert the data i have to know it Ref_equip (Ref_equip is on an other table named C_Equip).
I've made quickly this littre merise to be more understandable:
merise
As I said I have data that arrives on real-time on the table EV_48H and I have to put them automatically on the tables that are named 'EVV_'+Ref_equip.
So, here is my code. I don't have any error but it don't work. I know i missed of forget something but i don't know what.
TRIGGER "SIVO"."NEWtrigger3EV_48H"
BEFORE INSERT
ON SIVO.EV_48H
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
declare
clef_var number(4,0);
ref_equip varchar2(40);
V_Nom_table varchar2(1000) ;
V_nom_seq Varchar2(2000) ;
stmt varchar2(200);
begin
SELECT clef_var
INTO :New.Clef_Var
FROM sivo.c_variable
WHERE Ref_Var= :new.Ref_Var;
-- Conversion des formats Date-Heure en DateHeure oracle
:New.EV_DATEAUTO := to_date(:New.EV_DATE || ' ' || :New.EV_HEURE, 'DD/MM/YY HH24:MI:SS');
stmt:='begin select clef_var into :New.Clef_Var From sivo.C_variable; end';
EXECUTE IMMEDIATE stmt using out clef_var;
IF clef_var is not null then
stmt :='begin select Ref_equip into :New.Ref_Equip FROM sivo.C_Equip WHERE Ref_var= :New.Ref_Var; end';
EXECUTE IMMEDIATE Stmt USING OUT Ref_Equip;
V_nom_table := 'EVV_'||Ref_Equip;
stmt :='insert into' ||V_nom_table || '(:New.Clef_Var, :New.Ev_DateAuto, :New.Ev_Valeur )';
EXECUTE IMMEDIATE stmt USING Ref_Equip;
ELSE
INSERT INTO SIVO.EV_48H_VAR_INCONNUES (REF_VAR, EV_DATE, EV_HEURE, EV_VALEUR)
VALUES ( :New.REF_VAR, :New.EV_DATE, :New.EV_HEURE, :New.EV_VALEUR);
end if;
END;
If someone can help me or put me on the right way. I don't know if I give all informations so tell me if I missed something.
Thanks
In your execute immediate you are missing the end of statement indicator colon after END
this should be
begin select clef_var into :New.Clef_Var From sivo.C_variable; end;
However there are other design choices that you should be aware of:
using execute immediate is handy but if you don't have to use it you shouldn't. The work can be done by a cursor or even a simple select statement if only one value will come back. In fact it appears you do the work twice. First you insert into the :new.clef_var then you do the same thing again with the execute immediate. Try commenting out the execute immediate.
by using execute immediate any errors are harder to track
using a trigger means the real time data source cannot end the transaction until the trigger completes. Why not run a scheduled job every minute to check for new data and process it? This breaks the transaction into two parts: data entry and data processing
is there any update of records that your process needs to capture?

Condition in SQL script

I've got an SQL-script executed by SQL*Plus, that needs to run with Oracle 10g and Oracle 11g.
That script gives grants on a package that does not exist before 11g:
GRANT EXECUTE ON sys.dbms_result_cache TO my_user;
I would like to avoid the exception on 10g, since I want to react to other exceptions in the script.
One way is to use Conditional Compilation and dbms_db_version:
BEGIN
$IF dbms_db_version.ver_le_10 $THEN NULL; $ELSE
EXECUTE IMMEDIATE 'GRANT EXECUTE ON sys.dbms_result_cache TO my_user';
$END
END;
/
Is there any other way, preferable without using PL/SQL?
Your question and one of the comments indicate that you want to avoid PL/SQL blocks and EXECUTE IMMEDIATE. I also assume that by "react to other exceptions" you mean abort execution of the script when an exception is encountered.
If so, I think the best you can do in pure SQL/SQL*Plus is to ignore the exception exit for the grant statement:
... first part of script (with exit on sqlerror in effect)
WHENEVER SQLERROR CONTINUE
GRANT EXECUTE ON sys.dbms_result_cache TO my_user;
WHENEVER SQLERROR EXIT SQL.SQLCODE
... remaining part of script
you could check if the object exists beforehand:
BEGIN
FOR cc IN (SELECT NULL
FROM all_objects
WHERE owner = 'SYS'
AND object_name = 'DBMS_RESULT_CACHE'
AND ROWNUM = 1) LOOP
EXECUTE IMMEDIATE 'GRANT EXECUTE ON sys.dbms_result_cache TO my_user';
END LOOP;
END;
You can simulate branching by writing SQL that generates SQL and spools it to a sql script. Then run the sql script:
define temp_file='somefile.sql'
set heading off
set feedback off
spool &&temp_file
SELECT 'GRANT EXECUTE ON sys.dbms_result_cache TO my_user;'
FROM all_objects
WHERE owner = 'SYS'
AND object_name = 'DBMS_RESULT_CACHE';
spool off
#&&temp_file
host rm &&temp_file
Thanks to #Vincent for the data dictionary query.

Execute Immediate within a stored procedure keeps giving insufficient priviliges error

Here is the definition of the stored procedure:
CREATE OR REPLACE PROCEDURE usp_dropTable(schema VARCHAR, tblToDrop VARCHAR) IS
BEGIN
DECLARE v_cnt NUMBER;
BEGIN
SELECT COUNT(*)
INTO v_cnt
FROM all_tables
WHERE owner = schema
AND table_name = tblToDrop;
IF v_cnt > 0 THEN
EXECUTE IMMEDIATE('DROP TABLE someschema.some_table PURGE');
END IF;
END;
END;
Here is the call:
CALL usp_dropTable('SOMESCHEMA', 'SOME_TABLE');
For some reason, I keep getting insufficient privileges error for the EXECUTE IMMEDIATE command. I looked online and found out that the insufficient privileges error usually means the oracle user account does not have privileges for the command used in the query that is passes, which in this case is DROP. However, I have drop privileges. I am really confused and I can't seem to find a solution that works for me.
Thanks to you in advance.
SOLUTION:
As Steve mentioned below, Oracle security model is weird in that it needs to know explicitly somewhere in the procedure what kind of privileges to use. The way to let Oracle know that is to use AUTHID keyword in the CREATE OR REPLACE statement. If you want the same level of privileges as the creator of the procedure, you use AUTHID DEFINER. If you want Oracle to use the privileges of the user currently running the stored procedure, you want to use AUTHID CURRENT_USER. The procedure declaration looks as follows:
CREATE OR REPLACE PROCEDURE usp_dropTable(schema VARCHAR, tblToDrop VARCHAR)
AUTHID CURRENT_USER IS
BEGIN
DECLARE v_cnt NUMBER;
BEGIN
SELECT COUNT(*)
INTO v_cnt
FROM all_tables
WHERE owner = schema
AND table_name = tblToDrop;
IF v_cnt > 0 THEN
EXECUTE IMMEDIATE('DROP TABLE someschema.some_table PURGE');
END IF;
END;
END;
Thank you everyone for responding. This was definitely very annoying problem to get to the solution.
Oracle's security model is such that when executing dynamic SQL using Execute Immediate (inside the context of a PL/SQL block or procedure), the user does not have privileges to objects or commands that are granted via role membership. Your user likely has "DBA" role or something similar. You must explicitly grant "drop table" permissions to this user. The same would apply if you were trying to select from tables in another schema (such as sys or system) - you would need to grant explicit SELECT privileges on that table to this user.
You should use this example with AUTHID CURRENT_USER :
CREATE OR REPLACE PROCEDURE Create_sequence_for_tab (VAR_TAB_NAME IN VARCHAR2)
AUTHID CURRENT_USER
IS
SEQ_NAME VARCHAR2 (100);
FINAL_QUERY VARCHAR2 (100);
COUNT_NUMBER NUMBER := 0;
cur_id NUMBER;
BEGIN
SEQ_NAME := 'SEQ_' || VAR_TAB_NAME;
SELECT COUNT (*)
INTO COUNT_NUMBER
FROM USER_SEQUENCES
WHERE SEQUENCE_NAME = SEQ_NAME;
DBMS_OUTPUT.PUT_LINE (SEQ_NAME || '>' || COUNT_NUMBER);
IF COUNT_NUMBER = 0
THEN
--DBMS_OUTPUT.PUT_LINE('DROP SEQUENCE ' || SEQ_NAME);
-- EXECUTE IMMEDIATE 'DROP SEQUENCE ' || SEQ_NAME;
-- ELSE
SELECT 'CREATE SEQUENCE COMPTABILITE.' || SEQ_NAME || ' START WITH ' || ROUND (DBMS_RANDOM.VALUE (100000000000, 999999999999), 0) || ' INCREMENT BY 1'
INTO FINAL_QUERY
FROM DUAL;
DBMS_OUTPUT.PUT_LINE (FINAL_QUERY);
cur_id := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.parse (cur_id, FINAL_QUERY, DBMS_SQL.v7);
DBMS_SQL.CLOSE_CURSOR (cur_id);
-- EXECUTE IMMEDIATE FINAL_QUERY;
END IF;
COMMIT;
END;
/
you could use "AUTHID CURRENT_USER" in body of your procedure definition for your requirements.
Alternatively you can grant the user DROP_ANY_TABLE privilege if need be and the procedure will run as is without the need for any alteration. Dangerous maybe but depends what you're doing :)