create or replace package custom_apex_auth as procedure create_user p_username in varchar2, p_password in varchar2; function validate_user p_username in varchar2, p_password in var
Trang 1For this example, we’ll use DBMS_CRYPTO.RANDOMBYTES to generate a 16 byte key: SYSTEM@AOS> grant execute on dbms_crypto to sec_admin;
Grant succeeded.
SEC_ADMIN@AOS> select DBMS_CRYPTO.RANDOMBYTES(16) salt from dual;
SALT
231F8E440E65B5C180FA184F94F55B71
Now we’ll use following table to store usernames and passwords The user SEC_ADMIN will own this table and related packages
create table application_users(
id raw(16) default sys_guid(),
user_name varchar2(255),
verification raw(128),
constraint app_users_pk primary key (id),
constraint app_users_uq unique(user_name)
)
/
The following package will be used to create and authenticate users Note the use of
EXECUTE IMMEDIATE for any queries or DML against the APPLICATION_USERS table In the
event that someone does gain access to our table, he cannot simply query one of the dictionary views such as DBA_DEPENDENCIES to determine the package used to set the password This is certainly not a foolproof technique, but does make it more challenging to dissect the logic associated with password hashes
create or replace package custom_apex_auth
as
procedure create_user(
p_username in varchar2,
p_password in varchar2);
function validate_user(
p_username in varchar2,
p_password in varchar2)
return boolean;
end custom_apex_auth;
/
create or replace package body custom_apex_auth
as
key from dbms_crypto.randombytes
g_salt raw(256) := '231F8E440E65B5C180FA184F94F55B71';
function get_mac(
Trang 2return raw
is
begin
return dbms_crypto.mac(
src => utl_raw.cast_to_raw(p_password), typ => dbms_crypto.hmac_sh1,
key => utl_raw.cast_to_raw(g_salt));
end get_mac;
procedure create_user(
p_username in varchar2,
p_password in varchar2)
is
l_mac raw(128);
begin
l_mac := get_mac(p_password);
execute immediate 'insert into application_users
(user_name,verification) values (:a,:b)'
using upper(p_username),l_mac; end create_user;
function validate_user(
p_username in varchar2,
p_password in varchar2)
return boolean
is
l_mac raw(128);
l_user_name varchar2(255) := upper(p_username);
l_count pls_integer := 0;
begin
l_mac := get_mac(p_password);
execute immediate
'select count(*)
from application_users
where user_name = :username
and verification = :mac ' into l_count using l_user_ name,l_mac;
if l_count = 1 then
return true;
else
return false;
end if;
end validate_user;
end custom_apex_auth;
Trang 3Since we are storing the key inside the package, we must note that this code is accessible to anyone with a privileged account that can query data dictionary views such as DBA_SOURCE
To prevent this, we will use the PL/SQL “wrap” utility included with the Oracle Database This utility obfuscates the code so that it is still functional, yet is not readable by an attacker Here’s the procedure for wrapping this package:
1 Save the package body in a file named custom_apex_auth.pkb.
2 Copy this file to a computer that has the Oracle database installed You should check for
the existence of the wrap executable in $ORACLE_HOME/bin
3 Make sure $ORACLE_HOME/bin is in your path variable.
4 Execute the following from the command line, where iname is the name of the input file and oname is the name of the output file:
$ wrap iname=custom_apex_auth.pkb oname=custom_apex_auth.plb
If you open the output file in a text editor, you can see that the contents are completely obfuscated:
create or replace package body custom_auth wrapped
a000000
1
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
b
49b 2fa
tEQWVnLxdhO2QpYe8q3ImRMGA2UwgzsJMUiDZ47NCjoY+Mlxa55aWhbzjdSGbS0GLgMhQ95d CYA14bY3oT+dgofd882EY0pWQou5wW4T05JazzZ4CCtLIqTZc9wBsJtEI0aEcpuUSWtLBEL8 0Em/y0eLcJoG1+pl7ZBFucjL+pHyucbrlX3UpPAHubK+mMQs9VH5b2XoZlrgpcxN41C8YZMm 8r3Brr1O2MpAu0azbDgLxlMEnvrgUO3S1XxVTNIyUJVDvvPqiTsJ98/emfxqiET2+TteElAw 28UNX7ATU3dYGJaAeUfv4ll0IVSkggDUh9oyHRsBvemuZTaXyOfD8e/2L1gKGKFGq/E95qtx jA1FuNWpKxGjpsM20NTr5TqIMs13icQ2h5et11Rv+WfFROYv6X1EI3xLeJV/JIlLPpcAkWRk Bdd71Xj45pCgOrSp37AgdOWFnzqPYiR+QRNXwXabp3muOvMOJNk5A09KshfQXTWK1mzrw7dQ qN2IRmIXQBXLXNc0kA1QfkY3/iRNfrFqLvEvoc/puVufDYElGjtRnBIJYv4qURsHG2VvIxjI
Now create a user to test the code and verify that the password is not stored in clear text, and then verify that the function works as expected:
Trang 4custom_apex_auth.create_user('tyler','welcome');
end;
/
SQL> select user_name,verification from application_users;
USER_NAME VERIFICATION
-
-TYLER 02F4BB94F2C10F05F51E01B6E8A8A82928E243A8
SEC_ADMIN@AOS> set serveroutput on
declare
l_result boolean := false;
begin
l_result := custom_apex_auth.validate_user('tyler','welcome');
if l_result then
dbms_output.put_line('User Authenticated');
else
dbms_output.put_line('Authentication Failed');
end if;
end;
/
User Authenticated
PL/SQL procedure successfully completed.
declare
l_result boolean := false;
begin
l_result := custom_apex_auth.validate_user('tyler','hello');
if l_result then
dbms_output.put_line('User Authenticated');
else
dbms_output.put_line('Authentication Failed');
end if;
end;
/
Authentication Failed
PL/SQL procedure successfully completed.
Before testing this code in APEX, we need to grant execute on this package from the SEC_ ADMIN schema to the SEC_USER schema SEC_ADMIN will own the package, but our APEX application will parse as SEC_USER
SEC_ADMIN@AOS> grant execute on custom_apex_auth to sec_user;
Trang 5Now that all of the code is in place, the final step is to create a new authentication scheme in APEX that leverages this code To create a new authentication scheme in an application, navigate
to Shared Components | Security | Authentication Schemes Create a new authentication scheme,
select From Scratch, and name it Custom Table-Based Edit the newly created authentication
scheme and scroll down to the Login Processing section and enter the following code in the Authentication Function attribute (as shown in Figure 12-1):
return custom_apex_auth.validate_user
The default login page for a new application is page 101 The next two attributes assume that your login page is also 101 If it is not, be sure to substitute the correct value of your login page Select page 101 for the Session Not Valid attribute and make sure the Session Not Valid URL attribute is blank Set the Logout URL attribute to the following string:
wwv_flow_custom_auth_std.logout?p_this_flow=&APP_ID.&p_next_flow_page_sess=&APP_ ID.:101:&APP_SESSION.
Save this authentication scheme To activate this authentication scheme, click the Change Current tab on the Authentication Schemes page, and then select Custom Table-Based To test the
new authentication scheme, simply run the application and log in with a username of tyler and password of welcome—or any valid username and password that you created with the CUSTOM_
APEX_AUTH package
Authorization Schemes
APEX authorization schemes offer a powerful and flexible solution to allow or deny access selectively to any component of an application Their design encourages developers to define the logic for authorization at the application level and then apply that logic where it is needed The more this code is centralized and reused, the easier it is to review, audit, and change in the event that security requirements change
To define an authorization scheme, navigate to the Shared Components section of an application Authorization Schemes are listed in the Security section, just under Authentication Schemes A developer can create authorization schemes based on values of APEX items, SQL queries, or PL/SQL functions Once you create an authorization scheme and give it a unique name, this name will appear in the authorization scheme select list attribute of every APEX object that supports this feature
A partial list of APEX objects that support authorization schemes includes applications, pages, regions, report columns, list entries, and page items
FIGURE 12-1 Custom authentication scheme
Trang 6When an authorization scheme fails for a given user, the effect differs, depending on the type
of object to which it is applied For an application or page, APEX will return an error to any user that violates the authorization scheme All other objects, such as report columns, regions, items, and buttons, will simply not appear on the page for users that do not pass the authorization scheme This allows you to hide navigational elements or actions that a user cannot perform Note that simply hiding a list item or tab that links to an administrative page is not enough, however, as the user could simply enter the page in the URL The authorization scheme should also be applied to the page If a nefarious user tries to access a page for which she is not
authorized, the APEX will display the error message associated with the authorization scheme
In the following example, we create an Authorization scheme based on a PL/SQL function This function uses the APEX_LDAP package to query an LDAP directory and return the
Organizational Unit of a person Based on the value of this attribute, we conditionally allow access to certain parts of the application Two users are involved in this scenario: Kathy Evans (IT Security) and Robert Smith (Human Resources) Here is the code for the authorization scheme (also shown in Figure 12-2):
declare
l_attributes wwv_flow_global.vc_arr2;
l_attribute_values wwv_flow_global.vc_arr2;
begin
"ou" is the attribute we are interested in.
l_attributes(1) := 'ou';
apex_ldap.get_user_attributes(
p_username => :APP_USER, APEX User
p_pass => null,
p_auth_base => 'ou=people,dc=example,dc=com',
p_host => '127.0.0.1',
p_port => 389,
p_attributes => l_attributes,
p_attribute_values => l_attribute_values);
if l_attribute_values(1) = 'Human Resources' then
return true;
else
return false;
end if;
end;
Here’s how to implement this code as an authorization scheme:
1 Navigate to the Shared Components section of an application.
2 Select Authorization Schemes.
3 Create a new authorization scheme with the name In HR Org.
4 Under Scheme Type, select PL/SQL Function Body Returning a Boolean.
5 Enter the code in the preceding listing in the Expression 1 attribute.
6 Provide an error message.
Trang 7Now that we have created an authorization scheme named In HR Org, that name will appear
in the select list of the authorization scheme attribute of almost every APEX object Figure 12-3 shows this attribute for an APEX button Note that APEX will also include the opposite of our authorization scheme by including “(Not In HR Org),” as this a common requirement and eliminates the need to implement it in code
FIGURE 12-2 In HR Org authorization scheme
FIGURE 12-3 Authorization scheme attribute of a button
Trang 8Let’s use the report on the EMPLOYEES table as an example Figure 12-4 shows the same page
as it appears for two different users The user Robert Smith passes the authorization scheme, while user Kathy Evans does not The authorization scheme was applied to the Salary and edit link columns of the report, the Create button, and the Search text box As you can see, those elements simply do not appear for Kathy Evans
There are several scenarios in which reusing the logic of an authorization scheme is desirable, yet the declarative attribute is not present For example, let’s say we want to have sensitive items show up as read-only for anyone who is not an administrator This simply isn’t possible with the authorization scheme attribute of an item, as that attribute would completely hide the item for non-administrators, not simply show it as read-only If the logic for who is considered an administrator is already defined in an authorization scheme, we can leverage that logic by calling the APEX_UTIL.PUBLIC_CHECK_AUTHORIZATION API This API accepts the case-sensitive name
of an authorization scheme and returns a Boolean, true, if the current user passes the authorization scheme, and false if he or she fails the authorization scheme So, if we have an authorization scheme named IsAdmin, we can set the read-only attribute of one or more items to PL/SQL Function Body Returning a Boolean and enter the following code in the Expression 1 attribute: return apex_util.public_check_authorization('IsAdmin');
You can also use this API for certain circumstances in which you want to check more than one authorization scheme If you need to check more than one authorization scheme in multiple places, it might just be easier and more efficient to code an additional authorization scheme that encompasses the logic of several other schemes
FIGURE 12-4 Impact of authorization scheme for Robert and Kathy
Trang 9SQL Injection
Database-driven web applications are more prevalent than ever They are no longer relegated to the one-off internal systems of corporations, but have made their way to almost every mainstream web application on the Internet From photo-sharing sites; to web-based e-mail applications, online auctions, and store-fronts; to content management systems—all have a database behind the scenes New static HTML sites are now the exception, not the norm After all, why would anyone choose the management nightmare of a bunch of loosely coupled HTML, CSS, JavaScript, and image files when a content management system can separate the content from the formatting and move the ability of editing content from a few select technologists down to the people actually responsible for the content? A database brings so much power and flexibility to the Web, but it also introduces vulnerabilities
SQL injection has been around for several years, but few developers know what it is and even fewer know how to prevent it A SQL injection attack attempts to change the meaning of
a predefined SQL query in an application This type of attack can also change or add Data Manipulation Language (DML) statements, which are any inserts, updates, or deletes Data Definition Language (DDL) could also be injected
Most queries that are used inside an application consist of the SQL query and one or more parameters that can be changed at runtime For example, imagine a web page that lists all employees for a particular department As a user changes a select list on the web page, the page is submitted and the following query is executed:
select first_name,last_name,phone_number
from employees
where department_id = || X
The only thing that changes between page views is the value of X in the predicate The developer
of this application expects that only the value for DEPARTMENT_ID will ever be a number However,
a hacker might have very different plans for this predicate, such as returning all rows, or perhaps returning metadata about the database schema to plan an attack
The key to preventing SQL injection attacks is the use of bind variables If the structure or semantics of a query can change at runtime, then it is potentially vulnerable to SQL injection Most of the time, the vulnerability is introduced by concatenating variables within the body of
the query The example in the preceding paragraph shows the variable X concatenated with the
rest of the query The query cannot be parsed before this concatenation occurs, and therefore the concatenation can change the structure of the query every time it is run
For our purposes, we can simplify the Oracle SQL parser a bit and assume that it goes through three phases to run a SQL query: parse, bind, and execute In the parse phase, the SQL parser checks that the query is syntactically valid and that all the objects that it references are valid, and then it locks in the structure of the query During the bind phase, the actual values of bind variables are substituted for their placeholders in the query Again, the bind variables can change the value of the placeholder variables, but they cannot change the structure of the query since that was already established during the parse phase The final step is for the SQL parser to execute the query Queries that concatenate, not bind variables, essentially reverse the bind and parse phases The string is concatenated together, including the values of the variables, and then it is parsed This reversal
of phases allows an attacker to change the semantics of a query completely at runtime
Trang 10Example 1: The Wrong Way
To help you better understand the concept of SQL injection, let’s construct a procedure that is
vulnerable to SQL injection The following procedure takes in a parameter of p_last_name, then
outputs all employees that match the parameter (Note that I am using the “Q quote mechanism”
introduced in Oracle Database 10g R2 to make the examples easier to read.) The key is that the
string is enclosed in the following syntax: q’! some string!’ This allows strings to contain single
quotes without the need to escape those single quotes
create or replace procedure sql_injection(
p_last_name in varchar2)
is
type employee_record is table of employees%ROWTYPE;
emp_rec employee_record := employee_record();
x varchar2(32767);
begin
x := q'!select *
from employees
where last_name = '!'||p_last_name||q'!'!';
execute immediate x bulk collect into emp_rec;
for i in emp_rec.first emp_rec.last loop
dbms_output.put(emp_rec(i).last_name||' - ');
dbms_output.put_line(emp_rec(i).salary);
end loop;
dbms_output.put_line(emp_rec.count||' Rows Returned');
end;
/
At first glance, this procedure seems valid, and it would probably run just fine in a production environment Let’s take a look at the first two examples with this procedure They are semantically
the same, though the second procedure uses the q quote mechanism.
hr@aos> set serveroutput on
hr@aos> exec sql_injection('Grant');
Grant - 2600
Grant - 7000
2 Rows Returned
hr@aos> exec sql_injection(q'!Grant!');
Grant - 2600
Grant - 7000
2 Rows Returned
As you can see, the procedure displays both employees with the last name Grant
Now suppose we want to see all employees, even though the developer of this procedure never intended this functionality:
hr@aos> exec sql_injection(q'!Grant' or 1 = 1 !');
King - 24000