Turn off the #http #listener in #Oracle #STIG

Locking down a database (applying STIGs) you need to check to see if the listener is running http. If you don’t need the http service, turn it off. Turning off http will reduce the attack surface.

Step 1) Is http running?
[oracle@vbgeneric db_1]$ lsnrctl stat | grep HTTP
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=vbgeneric)(PORT=8081))(Presentation=HTTP)(Session=RAW))
[oracle@vbgeneric db_1]$

Step 2) Turn off http
RLOCKARD@orcl> select version from v$instance;
VERSION
-----------------
12.1.0.2.0

RLOCKARD@orcl12c> sho parameter dispatchers

NAME TYPE VALUE
———————————— ———– ——————————
dispatchers string (PROTOCOL=TCP)
max_dispatchers integer

RLOCKARD@orcl12c> exec dbms_xdb.sethttpport(0);
PL/SQL procedure successfully completed.

RLOCKARD@orcl12c> exit
Disconnected from Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 – 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options

SYS@orcl12c> sho parameter dispatchers

NAME TYPE VALUE
———————————— ———– ——————————
dispatchers string (PROTOCOL=TCP)
max_dispatchers integer
SYS@orcl12c>

[oracle@vbgeneric db_1]$ lsnrctl stop

LSNRCTL for Linux: Version 12.1.0.2.0 – Production on 15-SEP-2016 09:25:29

Copyright (c) 1991, 2014, Oracle. All rights reserved.

Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC1)))
The command completed successfully
[oracle@vbgeneric db_1]$ lsnrctl start

LSNRCTL for Linux: Version 12.1.0.2.0 – Production on 15-SEP-2016 09:25:34

Copyright (c) 1991, 2014, Oracle. All rights reserved.

Starting /u01/app/oracle/product/12.1.0.2/db_1/bin/tnslsnr: please wait…

TNSLSNR for Linux: Version 12.1.0.2.0 – Production
System parameter file is /u01/app/oracle/product/12.1.0.2/db_1/network/admin/listener.ora
Log messages written to /u01/app/oracle/diag/tnslsnr/vbgeneric/listener/alert/log.xml
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1)))
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=0.0.0.0)(PORT=1521)))
Notice it’s gone
Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC1)))
STATUS of the LISTENER
————————
Alias LISTENER
Version TNSLSNR for Linux: Version 12.1.0.2.0 – Production
Start Date 15-SEP-2016 09:25:34
Uptime 0 days 0 hr. 0 min. 0 sec
Trace Level off
Security ON: Local OS Authentication
SNMP OFF
Default Service orcl12c
Listener Parameter File /u01/app/oracle/product/12.1.0.2/db_1/network/admin/listener.ora
Listener Log File /u01/app/oracle/diag/tnslsnr/vbgeneric/listener/alert/log.xml
Listening Endpoints Summary…
(DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1)))
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=0.0.0.0)(PORT=1521)))
Services Summary…
Service “orcl12c” has 1 instance(s).
Instance “orcl12c”, status UNKNOWN, has 1 handler(s) for this service…
The command completed successfully

That was easy.

#infosec #Oracle #Migration #Encryption #2MTT

I have seen this twice in the past week. A customer requirement is to migrate their Oracle database to a new server and they want encryption implemented.The steps defined in the request is Migrate then Encrypt. This is backwards. You should setup your encryption then migrate your data into the new server. Why is this? Well if you move your data and then put it into encrypted tablespaces you are going to be chasing ghost data.

Here is a two minute tech tip I did for Oracle Technology Network explaining the problem.

Rating the international Air Carriers I use. @aeroflot @Icelandair and @British_Airways

13962508_10154938019085616_7088146218848019811_n

This is a departure from my usual database security post; hope you don’t mind my switching gears today.

Here is the ranking of the airlines that I currently use. Scores are 0 – 10 with ten being over the top great.

I spend way too much time on the road. Yes, I do enjoy it but let’s just say I would prefer to sleep in my own bead. I have become all too familiar with different airlines and airports. Understand, everything here is my personal opinion. I’m rating these airlines by customer service at the airport and customer service on the flight, seat comfort, boarding process, food, and pricing.

My first choice is always going to be fly myself. I purchased my 1948 Navion, N4281K about 25 years ago and fly it every chance I get.  81 Kelo is capable of 1,000 nm nonstop and still have twenty gallons of reserve fuel. She’s IFR certified, reliable and roomy. When flying her, I always arrive in comfort, style and at times very tired. I really need to get an autopilot installed. When weather or distance becomes an issue I let the professionals fly. I have lost too many friends too “I got to get there.” Remember takeoffs are optional, landings are mandatory.

13433073_10154797286945616_8319510841822513265_o

Category: International Travel

If I am going to be stuck in a metal tube hurtling through space at five hundred miles an hour  for fourteen hours, by golly I’m going to be comfortable.

Aeroflot:

Aeroflot1

Aeroflot is becoming my goto carrier for international travel.. Some of y’all are saying “what #1 for Aeroflot on international trips? I have even got that from friends in the airline industry. First off to dispel that issue. Aeroflot is flying new Boeings and new Airbus planes. When flying to Tallinn Estonia a couple years ago, Aeroflot had the best prices for the this trip. I took one for the team and was very pleasantly surprised at the high quality of their customer service. In November, I will be flying Aeroflot again to Sofia Bulgaria. Note: I have only flown Aeroflot in the Business Class cabin.

  • Customer Service at airport score 5

Checking in with Aeroflot is pretty standard; nothing stands out either great or poor. The business class lounge in Moscow is quite comfortable with professional attendants. The food is standard fare for a business class lounge, pretty average, not great but not bad either.   

  •  Customer Service on plane score 9.5

All I can say is WOW! This level of service disappeared from US carriers decades ago. I wish other carriers would take some notes from Aeroflot, they are doing a lot of things right. First, when you get on the plane you are greeted by white glove wearing Stewards and Stewardesses. After being seated, if you are wearing a jacket, it’s hung up and the Stewardess asks you “How shale I address you.” Paying attention to little details like this make me appreciate their service.

  • Seat Comfort score 7 

Business Class on Aeroflot international flights is the standard lay flat seat. They’re very comfortable and I have no problem sleeping on them. Yes I can completely stretch out my 6 foot 3 inch frame in their business class seat.

  •  Boarding Process score 7

It’s about standard, except the greeting you get when you get on the plane is much nicer than most any other airline.

  •  Food score 8

The food served was as close to gourmet as you can get on an airline. My last trip dinner was blackened sea bass and guacamole, and a great mimosa was served with my breakfast.

  • Pricing score 5

Pricing is competitive for business class travel; there are cheaper options for international business class travel.

I have not used Aeroflot’s site to book tickets. I normally start at http://flights.google.com and pick one of the sites like expedia to book. I’ve had issues in the past booking with a carrier who is not US Based, where my credit card get denied and then have to call up the bank to resolve it.

  • Tidbits


When connecting in Moscow make sure you have a paper copy of your boarding pass for your connection. My first trip through I did not have the paper copy of my boarding pass for my connection. The young lady at passport control did not speak any English but I could tell she was not pleased with me She was holding up my phone and kept telling me нет.. I had to go back and find an Aeroflot employee to print the paper copy to get through passport control to get my connection.

There are few things I would like to see. One is it would be great if Aeroflot offered more flights into and out of Washington DC and operated out of BWI, the other is needing a visa to get out of the international terminal. I realize there is nothing Aeroflot can do about the visa issue. Remember if you are flying Aeroflot international, you will be connecting in Moscow. The international terminal in Moscow is a depressing place to be. Fortunately I have been able to escape to the business class lounge.

Icelandair.

20160515_194605

I took Icelandair for the first time last May to Helsinki and really enjoyed the flight and the service. In the morning the flight attendant gave me a shot of carrot juice with ginger that I loved and to date I have not been able to duplicate it at home. Besides receiving a travel pack for Saga Class, you also get a noise canceling headset.

  • Customer Service at airport score 5

If you are flying Saga Class be sure to stop in the Saga Class lounge in Reykjavik. The food there is great and the seats are very comfortable; it’s my favorite lounge of all.

  • Customer Service on plane score 6

The flight attendants on Icelandair are professional and friendly. While flying home from Helsinki, one took the time to explain some of Iceland’s history and the belief in Elves. It’s the attention to little details that count.

  • Seat Comfort score   6

The Saga Class seats are the same seats you would expect on a domestic first class flight.

  • Boarding Process    score   1

So why would I rate a boarding process. Where the British have mastered the use of the queue, Icelandair takes the Viking approach, everyone storms the gate. Yes, at first I thought this is quite odd, then realized it’s just the way they do it. There is nothing that even remotely resembles a queue.

  • Food score 8

The food served on Icelandair is really quite good. I really don’t remember what I had to eat, only that I enjoyed it quite a bit.

  • Pricing score 7

The cost of flying Icelandair is quite competitive, but you need to be aware, the Saga Class seats are pretty much the same as premium economy on other airlines. So, when you shop for prices that is your point of comparison.

  • Tidbits

If you find yourself on a layover in Reykjavik make a point to go to the Blue Lagoon http://www.bluelagoon.com/. I had a two hour in water massage when I had my layover and it was outstanding. Now only if I could convince other airlines to offer massages during long layovers.

British Airways

BA

BA operates out of BWI, so when I traveling to London I will go ahead and fly BA. However I had a very bad experience with BA a couple of years ago when I took my mother to Helsinki with me. My mother needs a wheelchair; during our layover the BA contractor who takes care of the wheelchairs was very rude to my mother. This is a case of, one strike you’re out.

  • Customer Service at airport            score   5

All BA employees are professional and attentive. Note to BA, when I served in the US Navy I picked up an expression. One oh shit can destroy one thousand complements. Now that said, earlier this year I was flying from BWI to London and there was a blizzard moving into BWI the day before my flight. I contacted BA to change my flight and was told it would be $800 to change my flight. I than lodged a complaint on twitter, tagging BA. The social media team contacted me and move my flight no charge. Fixing an issue before it becomes an issue is important. We saw there was going to be a blizzard. I would have scored this 0 because of that one rude contractor however; on average BA is about average when it comes to customer service at the airport.

  • Customer Service on plane score 7

What can I say; British Airways flight crews are very British. On my last trip to London, I remember getting on the plane and the flight attendant offering me a glass of champagne, nice touch. I have flown BA in both Business Class and Premium Economy. There is a difference in the level of service, but I don’t have any complaints.

  • Seat Comfort           score   6

I’m 6’ 3” tall, in the business class section I can not completely stretch out when I turn the seat into a bed. The seats in premium economy are the same seats you would expect when flying domestic first class.

  • Boarding Process score 8

I believe the British invented queueing and they have it down to a science.

  • Food score   5

There is nothing spectacular about the food on BA; really it’s about average. I can’t think of anything that stands out either good or bad about their food.

  • Pricing          score   3

BA Business Class always seems to be a little more expensive than other options. I normally wind up flying premium economy when flying BA and sometimes upgrading my ticket at the airport.

You can use #sqlcl with #mkstore

I was struggling last week getting mkstore and sqlcl to work together. sqlcl is Oracle’s new command line interface. For more on sqlcl see http://www.oracle.com/technetwork/issue-archive/2015/15-sep/o55sql-dev-2692807.html. I have been using sqlcl almost exclusively for the past year and love it. I also have a lot of my connections in keystore to handle cron jobs along with a few other use cases.

To get sqlcl and keystore to work together is quite easy.

  • Add the wallet location and sqlnet.wallet_override=true to sqlnet.ora
    • WALLET_LOCATION = (SOURCE = (METHOD = FILE) (METHOD_DATA = (DIRECTORY =/u01/app/oracle/wallet)))
    • WALLET_OVERRIDE=true

01mkstore

  • Create a keystore
    • mkstore -wrl $ORACLE_BASE/wallet –create
    • enter the password and verify.
  • add the username/password@service to keystore
    • mkstore -wrl $ORACLE_BASE/wallet -createCredential localhost:1521/orcl rlockard mySecretPassword
    • enter the wallet password

Then connect

  • sql /@localhost:1521/orcl

02mkstore

Easy.

#phishing #infosec short post

Phishing has gotten more sophisticated over the years. Spelling and grammar has gotten better making phishing attempts more difficult to spot. There are some out there who did not get the memo and very easy to spot.

phishing

You still need to be diligent. If you are not sure it’s secure, don’t click any links and don’t open any attachments.

An enhancement I would love to see in Business class lounges. @aeroflot @icelandair @AmericanAir @KLM @airfrance @british_airways

It goes without saying I spend way too much time traveling and fortunately, most of the time I can fly myself in my 1948 Navion when the hop is less then 1,000 nautical miles.

Now quite frankly I love the travel, seeing different cities and countries, learning about different cultures and making new friends all over the world. However there is one thing that I really don’t enjoy. Multi-hour layovers (I think my record layover was over 11 hours in Moscow), they are a fact of life and to be honest, y’all have great lounges to make the wait more tolerable. There is one enhancement I believe a large part of your customer base will take advantage of.

Please give us a place to exercise. I’m not talking about a full blown gym, but a room with some exercise equipment where we can, well exercise during a layover. I would definitely take advantage of a gym. As another advantage y’all would be promoting good heath for your customers.

Thanks, –Rob

Upcoming speaking engagements two confirmed #oow16 #ecoug16 three waiting #bgoug, #rmoug and ???

The fall is filling out fast. I will be speaking at Oracle Open World 2016 on Holistic Database Security. Then speaking in November at the East Coast Oracle User Group on Holistic Database Security.

I currently have papers in to Bulgarian Oracle Users Group Autumn Conference November 11 – 13  on Holistic Database Security, Transparent Data Encryption and PL/SQL Secure Coding practices. I have heard many fine things about BGOUG conference. Everyone I have spoken to who’s been there can’t wait to go back.

I have also submitted to Rocky Mountain Oracle Users Group February 7 – 9 on Holistic Database Security and PL/SQL Secure Coding practices. RMOUG is the largest grass roots Oracle Users Group out there and every year they put on a wonderful conference.

Now, I’ve been told there will be a security conference coming in Rovaniemi Finland next year. Once I have more details, I’ll submit abstracts for that.

Four things a developer can do now to improve their applications #infosec posture.

Lets face it, we have deadlines to meet and millions of lines of code in production. I get it, I’ve been a working PL/SQL developer off and on for over 20 years. If we get into the habit of using some of the security features in the language along with some practices, we can improve the security of you code. So, lets get into it.

1) Use packages. Steve Feuerstein http://www.oracle.com/technetwork/issue-archive/index-087690.html has been saying for years to move those functions and procedures to packages and there is good security reasons to do that. If you have a SQL Injection bug in your application, I can get to ALL_SOURCE and read your code and if I can get to your code, I can find other exploits.

So we can read the code in Functions and Procedures.

1 SQL> select text from all_source where owner = user and name = 'PARSE_STRING'; 2 procedure parse_string(p_string varchar2) AS 3 CURSOR col_cur IS 4 select distinct (instr(p_string||',',',',1,level)) loc 5 FROM dual 6 CONNECT BY LEVEL <= LENGTH(REPLACE(p_string,','))+1 7 ORDER BY 1; 8 l_col varchar2(65); 9 BEGIN 10 for col_rec in col_cur 11 loop 12 l_col := substr(p_string, col_rec.loc, instr(p_string, col_rec.loc+1)); 13 dbms_output.put_line(l_col || 'col pos ' || col_rec.loc); 14 end loop; 15 end; 16 17 14 rows selected. 18 19 SQL> create user u2 identified by MY##56SecurePassword; 20 21 User created. 22 23 SQL> grant create session to u2; 24 25 Grant succeeded. 26 27 SQL> grant execute on parse_string to u2; 28 29 Grant succeeded. 30 31 SQL> conn u2@orcl 32 Enter password: 33 Connected. 34 SQL> select text from all_source where owner = 'RLOCKARD' 35 2 and name = 'PARSE_STRING'; 36 procedure parse_string(p_string varchar2) AS 37 CURSOR col_cur IS 38 select distinct (instr(p_string||',',',',1,level)) loc 39 FROM dual 40 CONNECT BY LEVEL <= LENGTH(REPLACE(p_string,','))+1 41 ORDER BY 1; 42 l_col varchar2(65); 43 BEGIN 44 for col_rec in col_cur 45 loop 46 l_col := substr(p_string, col_rec.loc, instr(p_string, col_rec.loc+1)); 47 dbms_output.put_line(l_col || 'col pos ' || col_rec.loc); 48 end loop; 49 end; 50 51 14 rows selected. 52 53 SQL> 54

Now when we put this into a package, the only thing I can extract from it is the package specification.

First lets put it into a package.

1 SQL> sho user 2 USER is "RLOCKARD" 3 SQL> create or replace package rlockard.utility AS 4 procedure parse_string(p_string varchar2); 5 end; 6 / 7 8 Package created. 9 10 SQL> create or replace package body rlockard.utility AS 11 12 procedure parse_string(p_string varchar2) IS 13 CURSOR col_cur IS 14 select distinct (instr(p_string||',',',',1,level)) loc 15 FROM dual 16 CONNECT BY LEVEL <= LENGTH(REPLACE(p_string,','))+1 17 ORDER BY 1; 18 l_col varchar2(65); 19 BEGIN 20 for col_rec in col_cur 21 loop 22 l_col := substr(p_string, col_rec.loc, instr(p_string, col_rec.loc+1)); 23 sys.dbms_output.put_line(l_col || 'col pos ' || col_rec.loc); 24 end loop; 25 end; 26 end; 27 / 28 29 Package body created. 30 31 SQL>

Now, lets test to see what we can see.

1 SQL> grant execute on utility to u2; 2 3 Grant succeeded. 4 5 SQL> conn u2@orcl 6 Enter password: 7 Connected. 8 SQL> select text from all_source where owner = 'RLOCKARD' 9 and name = 'UTILITY'; 10 2 11 TEXT 12 -------------------------------------------------------------------------------- 13 package utility AS 14 procedure parse_string(p_string varchar2); 15 end; 16

As we can see, now you only get the package specification. This is really more that I would like to get out, but it’s much better than getting all your source code.

2) Split up your packages into smaller packages based on function. I normally split them up by UTILITY, SENSITIVE and NON SENSITIVE. If there are functions / procedures against sensitive tables those will go into the sensitive packages. You can further break down you sensitive packages. ie: CUSTOMER_API_PKG that would be your interface into your customers table.

Here is a good post by Steve Feuerstein on breaking packages down and keeping them compatible with existing code. http://www.oracle.com/technetwork/issue-archive/2015/15-jan/o15plsql-2398996.html

3) Limit the execution rights to a package and what a user can do with a package.

3a) We have been granting execute to packages for decades now. Then Oracle 11g gave us Invoker and Definer rights. When you create a package and don’t specify invoker or definer rights, the package is created with definer rights as the default. That’s all well and good, but let’s think this through. If I execute a package with definer rights and that package updates the customers table, even thought I don’t have update on the customers table, the package will work.

1 SQL> create or replace package rlockard.cust_api AS 2 function update_customers_credit_limit(pID in number, pCredit in number) return number; 3 end; 4 / 5 Package created. 6 7 SQL> create or replace package body rlockard.cust_api AS 8 9 function update_customers_credit_limit(pID in number, pCredit in number) return number is 10 retVal number; 11 begin 12 update customers set credit = pCredit where id = pId; 13 return 1; 14 exception when no_data_found 15 then 16 retVal := helpdesk.utility.log_error(pPkg => $$PLSQL_UNIT, pLine => $$PLSQL_LINE, 17 pParm => 'pID = ' || to_char(iID) || 18 ' pAmount= ' || to_char(pCredit), 19 pErr => sqlcode); 20 return retVal * -1; -- we are flipping the sign to indacate it's an error to caller. 21 end; 22 end; 23 / 24 Package body created. 25 SQL> 26

I am going to grant execute to the user U2 that we create earlier and test this.

1 SQL> grant execute on rlockard.cust_api to u2; 2 3 Grant succeeded. 4 5 SQL> conn u2@orcl 6 Enter password: 7 Connected. 8 SQL> declare 9 2 x number; 10 3 begin 11 4 x:=rlockard.cust_api.update_customers_credit_limit(pId => 1770, pCredit => 1000000); 12 5 end; 13 6 / 14 15 PL/SQL procedure successfully completed. 16 17 SQL> select credit from rlockard.customers where id = 1770; 18 select credit from rlockard.customers where id = 1770 19 * 20 ERROR at line 1: 21 ORA-00942: table or view does not exist 22 23 24 SQL> conn rlockard@orcl 25 Enter password: 26 Connected. 27 SQL> select credit from rlockard.customers where id = 1770; 28 29 CREDIT 30 ---------- 31 1000000 32 33 SQL> 34

Did you expect that to happen?  How are we going to tighten this down. We are going to set the package to use invokers rights. By adding AUTHID CURRENT_USER to the package specification, the package executes with U2’s rights. U2 does not have any rights on the customers table, the package fails with ORA-00942: table or view does not exists.

1 SQL> create or replace package rlockard.cust_api 2 AUTHID CURRENT_USER 3 AS 4 function update_customers_credit_limit(pID in number, pCredit in number) return number; 5 end; 6 / 7 2 3 4 5 6 8 Package created. 9 10 11 SQL> conn u2@orcl 12 Enter password: 13 Connected. 14 SQL> declare 15 x number; 16 begin 17 x := rlockard.cust_api.update_customers_credit_limit(pId => 1770, pCredit => 2500000); 18 exception when others then 19 sys.dbms_output.put_line(sqlerrm); 20 end; 21 / 22 ORA-00942: table or view does not exist 23 24 SQL> conn rlockard@orcl 25 Enter password: 26 Connected. 27 SQL> select credit from customers where id = 1770; 28 29 CREDIT 30 ---------- 31 1000000 32 SQL> 33

3b) In Oracle 12c we were given the ability to grant roles to packages. (procedures and functions too, but you should be using packages) Now, when we have sensitive tables in another schema, we can create a role that a package needs and grant that role to a package.

1 CREATE ROLE update_customers; 2 3 grant update_customers to rlockard; 4 5 GRANT SELECT 6 ON customers 7 TO update_customers; 8 9 GRANT update_customers TO PACKAGE cust_api; 10 11 declare 12 x number; 13 begin 14 x := rlockard.cust_api.update_customers_credit_limit(pId => 1770, pCredit => 2500000); 15 exception when others then 16 sys.dbms_output.put_line(sqlerrm); 17 end; 18 /

3c) Oracle 12c also gave us the accessible by clause. This creates a white list of the packages that can call a package. This way you are narrowing down the ways a package can get called, creating a trusted path to your secure data. So here the public package can call the private package, but if anything else tries to call it a PLS-00904 error will be raised.

accessable_by

1 SQL> create or replace package public_package AS 2 procedure update_customers(pId in number, 3 pColumn in varchar2, 4 pValue in varchar2); 5 end; 6 / 7 8 Package created. 9 10 SQL> create or replace package body public_package as 11 procedure update_customers(pId in number, 12 pColumn in varchar2, 13 pValue in varchar2) IS 14 x number; -- we know it's a function that returs a number. 15 begin -- this is simplistic to demo accessable_by 16 if pColumn = 'CREDIT' then 17 x := rlockard.cust_api.update_customers_credit_limit(pId => pId, pCredit => pValue); 18 end if; 19 end; 20 end; 21 / 22 23 Package body created. 24 25 SQL> create or replace package rlockard.cust_api 26 accessible by (public_package) AS 27 function update_customers_credit_limit(pID in number, pCredit in number) return number; 28 end; 29 / 30 31 Package created. 32 33 SQL> 34 35 SQL> declare 36 x number; 37 begin 38 x := rlockard.cust_api.update_customers_credit_limit(pId => 1770, pCredit => 2500000); 39 exception when others then 40 sys.dbms_output.put_line(sqlerrm); 41 end; 42 / 43 x := rlockard.cust_api.update_customers_credit_limit(pId => 1770, pCredit => 2500000); 44 * 45 ERROR at line 4: 46 ORA-06550: line 4, column 8: 47 PLS-00904: insufficient privilege to access object CUST_API 48 ORA-06550: line 4, column 3: 49 PL/SQL: Statement ignored 50 51 52 SQL> begin 53 public_package.update_customers(pId => 1771, pColumn => 'CREDIT', pValue => '200'); 54 end; 55 / 56 57 PL/SQL procedure successfully completed. 58 59 SQL> 60

But when we call it from the package in the accessible by clause, then it works fine. Again, we are limiting the paths to get to the sensitive information.

4a) We are getting down to the meat of what every shop should be doing. Reviewing code. You should be looking for dynamic code that is concatenating variables together. This is a painfully bad piece of code with a major SQL Injection bug.

1 create or replace PROCEDURE UserLogin 2 (p_email logins.email%type DEFAULT NULL, 3 p_password logins.password%type DEFAULT NULL) 4 AS 5 6 STMT CONSTANT VARCHAR2(4000) := 7 'SELECT email 8 FROM logins 9 WHERE email = ''' || p_email || 10 ''' AND password = ''' || p_password || ''''; 11 12 l_result logins.email%type; 13 BEGIN 14 15 dbms_output.put_line ('SQL STMT: ' || STMT); 16 17 EXECUTE IMMEDIATE STMT INTO l_result; 18 19 dbms_output.put_line ('Logon succeeded.'); 20 21 EXCEPTION WHEN OTHERS THEN 22 null; -- OH NO HE DID NOT 23 END UserLogin;

How would I fix this. Well, lets’ change the dynamic SQL and put in some bind variables. We can still do a lot more with this code, but this fixes the SQL Injection bug and a couple other issues.

1 create or replace function UserLogin2 2 (p_email logins.email%TYPE DEFAULT NULL, 3 p_password logins.PASSWORD%TYPE DEFAULT NULL) 4 RETURN NUMBER AS 5 kount number; -- a dumb variable to hold a count 6 BEGIN 7 8 SELECT count(*) 9 INTO kount 10 FROM logins 11 WHERE email = p_email 12 and password = p_password; 13 14 IF kount = 1 THEN 15 sys.dbms_output.put_line ('Logon succeeded.'); 16 RETURN kount; 17 ELSE 18 return -1; 19 end if; 20 21 END UserLogin2;

Now you will find I love code reviews. Frequently we learn a way to do something and because it works, we continue doing it. Heck, I loved cursor for loops until I learned better in a code review. Code reviews should be approached as learning opportunities. You are going to learn a lot more tricks reading other peoples code and you may catch something that will improve the security of your system.

So in review the steps you can do now to improve the security posture of your applications are: Control the rights to executing code. Put everything in packages. Split up your packages. Do code reviews.

Four things a DBA can do now to improve their #infosec posture?

1) When we start talking about securing information, the first thing that always seems to come up is encryption. Everyone has heard about it, but some don’t really understand just what encryption is protecting. When we are discussing Transparent Data Encryption (TDE) we are discussing data at rest. The attack vectors we are protecting from is a bad actor gaining access to the physical hardware.

1a) Now, the easiest and fastest way to implement TDE is to encrypt tablespaces and move the sensitive data into the encrypted tablespace. You need to be careful here, just because you identified the tables that are sensitive, what about objects that are dependent on the table?  (Indexes, Materialized Views, etc).  Each of these sensitive objects need to be moved into encrypted tablespaces.

Find dependent objects.

1 set pagesize 1000 2 set linesize 132 3 col owner format a30 4 col name format a30 5 SELECT d.owner, 6 d.NAME, 7 s.tablespace_name, 8 t.ENCRYPTED 9 FROM dba_dependencies d, 10 dba_segments s, 11 dba_tablespaces t 12 WHERE d.owner = s.owner 13 AND d.NAME = s.segment_name 14 and s.tablespace_name = t.tablespace_name 15 and referenced_name IN ( 16 SELECT segment_name 17 FROM dba_segments 18 WHERE tablespace_name IN 19 (SELECT tablespace_name 20 FROM dba_tablespaces 21 WHERE tablespace_name = upper('&&tbs'))) 22 UNION 23 SELECT i.owner, 24 i.index_name, 25 i.tablespace_name, 26 dd.ENCRYPTED 27 FROM dba_indexes i, 28 dba_tablespaces dd 29 WHERE i.tablespace_name = dd.tablespace_name 30 AND table_name IN ( 31 SELECT segment_name 32 FROM dba_segments 33 WHERE tablespace_name IN 34 (SELECT tablespace_name 35 FROM dba_tablespaces 36 WHERE tablespace_name = upper('&&tbs')));

You can not change an unencrypted tablespace into an encrypted tablespace, so you are going to need to first create the encrypted tablespace.

1 CREATE TABLESPACE sensitive_dat 2 DATAFILE '/opt/oracle/oradata/DEV/datafile/sensitive_data01.dbf' size 1024M 3 ENCRYPTION USING 'AES256' DEFAULT STORAGE(ENCRYPT); 4 5 CREATE TABLESPACE sensitive_idx 6 DATAFILE '/opt/oracle/oradata/DEV/datafile/sensitive_idx01.dbf' size 1024M 7 ENCRYPTION USING 'AES256' DEFAULT STORAGE(ENCRYPT); 8

Now that we have an encrypted tablespace, we need to start moving all the sensitive data into it. It’s important to know, that to prevent ghost data you we are going to need to move everything out of the tablespace and into a new tablespace. I normally use alter table move, but you can also use dbms_redefination and create table as select.  Use the report from dependent objects to make sure you have everything out of the tablespace.  Once you have everything out, drop the tablespace then use a utility like shred to over right the data file(s) with random data. Once you have done that, you can safely delete the data file(s).

Here is a link to my demo on moving data to an encrypted tablespace. This demo assumes the base table is already in the encrypted tablespace, now we need to move indexes and materialized views.

https://www.youtube.com/watch?v=pZrdCZ09uiA

TDE also offers column encryption, the analysis required to properly implement column based encrypted is time consuming. So for now we are going to pass over column encryption.

1b) SQLNet encryption. Information that moves through the network is subject to various attacks including man in the middle, replay and modification attacks. With these data can be leaked, corrupted, or even replayed. So we use sqlnet encryption and integrity to protect our data from leaking, replays and modification. You are going to user net manager to setup. Make an encryption or integrity method either Accepted, Requested, Rejected or Required. You can read more on these in the Oracle Documentation.

https://docs.oracle.com/database/121/DBSEG/asoconfg.htm#DBSEG020

$ORACLE_HOME/bin/netmgr Open local –> profile then select network security and click on the encryption tab. Select the encryption algorithms you need and then enter 256 characters in the encryption seed block.

Oracle DB Developer VM_1

Select an integrity method. Remember MD5 has several weaknesses. SHA has become the defacto standard.

Oracle DB Developer VM

2) Audit your users and environment. I’ve have heard this one time and time again, “It’s not my job. It’s the auditors job.” The fact remains many breaches exists for weeks, months and even years before they are discovered. Than when a breach is discovered, the auditor request audit logs. We need to do better!. I get audit reports every morning and review them before I do anything else. So what do you want to audit?

2a) Audit login failures. Login failures can be a sign someone is trying to gain access to your system. If you start seeing login failures, investigate. Is the issue user training or is there something else going on.

2b) Audit logins from yesterday. Why you are going to be looking at os_username / username / userhost to see if people are logging in from multiple workstations. This could be an indicator of a username/password being shared. Another reason I do this audit is to check os_username / username. Is the user using their proper account. I have issues in the past where a user was using the application login to do their normal work. This audit showed this and allowed us to correct the situation.

2c) Audit logins for the past 31 days. This gives you a 30,000 foot picture on how often users are connecting and are then disconnecting at the end of the day.

1 set heading off 2 set pagesize 1000 3 set linesize 132 4 set serveroutput on 5 col object_name format a24 6 col object_type format a24 7 col doctype format a10 8 col userhost format a40 9 col os_username format a15 10 col username format a15 11 col terminal format a15 12 13 spool $HOME/session_audit.txt 14 15 select 'login failures' from dual; 16 17 select os_username, username, userhost, terminal, to_char(timestamp, 'dd-mon-rr hh24:mi') 18 from dba_audit_session 19 where returncode != 0 20 and trunc(timestamp) >= trunc(sysdate-1) 21 and username != 'DUMMY' 22 order by timestamp 23 / 24 25 select 'logins yesterday' from dual; 26 27 select os_username, username, userhost, count(*) 28 from dba_audit_session 29 where trunc(TIMESTAMP) >= trunc(sysdate-1) 30 and username != 'DUMMY' 31 and action_name != 'LOGOFF BY CLEANUP' 32 group by os_username, username, userhost 33 order by os_username, username 34 / 35 36 select 'logins last 31 days' from dual; 37 38 select os_username, username, userhost, count(*) 39 from dba_audit_session 40 where trunc(TIMESTAMP) >= trunc(sysdate-31) 41 and username != 'DUMMY' 42 and action_name != 'LOGOFF BY CLEANUP' 43 group by os_username, username, userhost 44 order by os_username, username 45 / 46

2d) Audit changes to any database objects. This is a simple query you can start with. You are checking objects based on last_ddl_time and created.

1 select 'changed / created objects last 24 hours' from dual; 2 3 select owner, object_type, object_name 4 from dba_objects 5 where (trunc(created) >= trunc(sysdate-1) 6 or trunc(last_ddl_time) >= trunc(sysdate-1)) 7 order by owner, object_type, object_name; 8

2e) Use a product like tripwire to check for any changes to ORACLE_HOME. You can also roll your own but getting a checksum of all the files in ORACLE_HOME, than doing the check everyday to see if a file has changed. (there are some files you will want to filter because they change in the normal course of operations)

3) Identify the sensitive data in your database. How can you know what to protect if you don’t know what is sensitive? When you identify what is sensitive, it’s easier to track that data through your enterprise to limit access to the data.

Now there is something most people know but don’t realize they know. You can have a simple piece of data, that by itself is not sensitive, but when combine it with other data that’s not sensitive and now you  have sensitive data. IE: I’m Robert, that by itself is not vary valuable. Combine that with my zip code that is not very sensitive and you have narrowed down the universe of Roberts’. Next add that I drive a Ford F150 and a BMW R1150RS, now you have uniquely identified me.

3a) Here is a Google spreadsheet with common sensitive column names.  Common Sensitive names

NOTE 1: this list of common names is not inclusive. If you like, feel free to send me other sensitive column names and I will add them to the sheet.

Note 2: I am working on putting this into an Oracle database that you can query locally. I’ll let you know when it’s available.

3b) If you have not already done so, you can reverse engineer you application scheme into Oracle SQL Developer Data Modeler. SQL Developer Data Modeler has the ability to mark and report columns that are sensitive.

Heli made a blog entry on how to mark and report on sensitive data in SQL Developer Data Modeler. Sensitive Data From Heli

4) Create trusted paths to your sensitive data. Or at the very least, limit the number of paths to get to your sensitive data. Now that you have a list of sensitive data, document where that data is getting accessed by. You can use unified_audit_trail to get hosts names and users accessing the data. Once you have validated how the data is getting accessed and from where and by who you can setup Virtual Private Database and redaction to limit the paths to get to the sensitive data.

Here is a simple example, if people who are authorized to access the data are in a specific subnet, then you can check the subnet and use the VPD policy to append where 1=2 onto the where clause to anyone querying the data from outside that subnet. You can also use authentication method in this check. Say the data is so sensitive you only want people to access it if they connected using RADIUS. If someone connected using anything else, again you would append where 1=2 onto the where clause to return nothing. This important thing to remember is, Your VPD policy can be anything you can code in PL/SQL.

Lets start this with setting up some support objects in the security schema.

Setup a table of ip_addresses and if the user is granted access to credit card number, social security number and if they user has customer access.

1 -- Create these objects under the security schema. 2 -- the table ip_addresses will be used in a login trigger for the context to populate. 3 -- the ccnbr_access defults to 'N' for no access. To grant access populate the ccnbr_access column 4 -- with 'Y'. ssn_access column default to 'N' for no access. To grant access to ssn populate the 5 -- ssn_access column with 'Y'. The column customer_access defaults to 'N' if this is 'N' then 6 -- where 1=2 will be appended to the where clause so the query returns nothing. If customers_access is 7 -- 'Y' then the stores table will be used to build the where clause for the customers table. 8 create table security.ip_addresses (id int generated by default as identity, 9 ip_address varchar2(16) not null, 10 ccnbr_access varchar2(1) default 'N' not null, 11 ssn_access varchar2(1) default 'N' not null, 12 customer_access varchar2(1) default 'N' not null); 13 14 -- create a unique index on ip_addresses. 15 create unique index ip_addresses_uidx on ip_addresses(ip_address); 16 -- we are going to insert a row that states localhost can access credit card 17 -- numbers and social security numbers. In this example, it would not be 18 -- a good idea on a policy level to grant a user both credit card number 19 -- and social security number. 20 insert into ip_addresses (ip_address, ccnbr_access, ssn_access, customer_access) values ('127.0.0.1', 'Y','Y','Y'); 21 22 commit; 23

We are going to create a couple of roles where to support CONTEXT, VPD and REDACTION.

1 -- the role ccnbr_access will be used by the redaction policy 2 create role ccnbr_access; 3 -- the ssn_access role will be used by the redaction policy 4 create role ssn_access; 5 -- the customers_access role will be used by the VPD policy. 6 create role customers_access; 7 -- note, app_user01 will not work here. The user will be connecting 8 -- with password authentication, therefore; for testing purposes you 9 -- can change the VPD code to authenticate with a password. Otherwise 10 -- you will need to create a cert for this user and install it. 11 create user app_user01 12 identified by My12SecurePassword; 13 14 create user app_user02 15 identified by My12SecurePassword; 16 17 grant ccnbr_access, customers_access to app_user01; 18 grant create session to app_user01, app_user02; 19

To start with Virtual Private Database you want to create a context. This will be used in a login trigger to get information about the user and their connection.

1 -- create a context 2 -- the customers_ctx context will have three names. 3 -- ccnbr, ssn and customers. 4 -- this relys on 1) the user connecting from the approate ip_address. 5 -- 2) the user having the correct roles. in order for the user to 6 -- access any of the customer informantion the user must have the 7 -- customer access role. If the user does not connect to the database 8 -- from an approved ip address and the user does not have the correct 9 -- role then where 1=2 will be appended onto the where clause. 10 -- if the user has the customer_access role and the user has the 11 -- ccnbr_access role, and the customer connects using ssl then the 12 -- user will be able to access ccnbr. If not, ccnbr will be redacted. 13 CREATE OR REPLACE CONTEXT customers_ctx USING cust_control_pkg; 14

Now, lets create a package to populate the customrs_ctx CONTEXT.

1 -- create the package to support the conext. 2 create or replace package cust_control_pkg as 3 -- in this example there is only one procedure we are 4 -- going to expose. Thats set_context. This procedure 5 -- will set all the context for customer security 6 procedure set_context; 7 end; 8 / 9 10 -- The package has three functions. 1) check the user has access to ccnbr, 11 -- 2) check the user has access to ssn 12 create or replace package body cust_control_pkg as 13 -- check to see if the ip address is allowed to access credit card numbers 14 -- and the user has the ccnbr_access role. 15 function has_ccnbr return boolean 16 is 17 cnt number; -- a variable to hold the count 18 begin 19 --the user must be connecting from an approved ip address and 20 -- have the ssn_access role and authenticate using SSL. 21 select count(*) 22 into cnt 23 from ip_addresses 24 where ip_address = sys_context('userenv','ip_address') 25 and sys_context('sys_session_roles','ccnbr_access') = 'TRUE' 26 and sys_context('userenv','AUTHENTICATION_METHOD') = 'RADIUS' 27 and ccnbr_access = 'Y'; 28 if cnt > 0 then 29 return true; 30 else 31 return false; 32 end if; 33 end has_ccnbr; 34 35 -- check to see if ip address can access ssn 36 function has_ssn return boolean 37 is 38 cnt number; -- to hold the count 39 begin 40 -- the user must be connecting from an approved ip address and 41 -- have the ssn_access role and authenticate using RADIUS. 42 -- for this demo, change RADIUS to PASSWORD 43 select count(*) 44 into cnt 45 from ip_addresses 46 where ip_address = sys_context('userenv','ip_address') 47 and sys_context('sys_session_roles','ssn_access') = 'TRUE' 48 and sys_context('userenv','AUTHENTICATION_METHOD') = 'PASSWORD' 49 and ssn_access = 'Y'; 50 51 if cnt > 0 then 52 return true; 53 else 54 return false; 55 end if; 56 end has_ssn; 57 58 function has_customer return boolean 59 is 60 cnt number; -- to hold the count 61 begin 62 -- the user must be connecting from an approved ip address and 63 -- have the ssn_access role and authenticate using RADIUS. 64 -- for this demo, change RADIUS to PASSWORD 65 select count(*) 66 into cnt 67 from ip_addresses 68 where ip_address = sys_context('userenv','ip_address') 69 and sys_context('sys_session_roles','customers') = 'TRUE' 70 and sys_context('userenv','AUTHENTICATION_METHOD') = 'PASSWORD' 71 and customer_access = 'Y'; 72 73 if cnt > 0 then 74 return true; 75 else 76 return false; 77 end if; 78 end has_customer; 79 80 procedure set_context is 81 begin 82 if has_ccnbr then 83 sys.dbms_session.set_context('customers_ctx', 'ccnbr', 'Y'); 84 else 85 sys.dbms_session.set_context('customers_ctx', 'ccnbr', 'N'); 86 end if; 87 if has_ssn then 88 sys.dbms_session.set_context('customers_ctx', 'ssn', 'Y'); 89 else 90 sys.dbms_session.set_context('customers_ctx', 'ssn', 'N'); 91 end if; 92 if has_customer then 93 sys.dbms_session.set_context('customers_ctx', 'customers', 'Y'); 94 else 95 sys.dbms_session.set_context('customers_ctx', 'customers', 'N'); 96 end if; 97 end set_context; 98 end; 99 / 100

Now we are going to create a login trigger to populate the context when to user connects.

1 -- create the logon trigger to populate the context 2 CREATE OR REPLACE EDITIONABLE TRIGGER "SECURITY"."SET_VPD_CTX_TRIG" AFTER LOGON ON DATABASE 3 BEGIN 4 -- setup the context for access to customers 5 security.cust_control_pkg.set_context; 6 -- setup other context for sensitive information 7 EXCEPTION when others then 8 -- we need a way to trap the error. If the logon trigger is broken 9 -- then users will not be able to connect 10 utility.pkg_log.log_error('Logon trigger raised exception ' || sqlerrm); 11 END; 12 / 13 ALTER TRIGGER "SECURITY"."SET_VPD_CTX_TRIG" ENABLE; 14

Now we are going to create a Virtual Private Database policy on the customers table.

1 -- create a virtual private database policy. 2 BEGIN 3 DBMS_RLS.ADD_POLICY ( 4 object_schema => 'rlockard', 5 object_name => 'customers', 6 policy_name => 'cust_policy', 7 function_schema => 'security', 8 policy_function => 'cust_vpd', 9 statement_types => 'select, insert, update, delete' 10 ); 11 END; 12 / 13

 

And a function that returns the where clause for the policy.

1 -- create the function to append to the where clause 2 CREATE OR REPLACE FUNCTION cust_vpd (object_schema IN VARCHAR2,object_name VARCHAR2) 3 RETURN VARCHAR2 IS 4 return_val VARCHAR2(4000); 5 BEGIN 6 7 IF sys_context('customers_ctx','customers') = 'N' 8 THEN 9 return_val := '1=2'; -- don't return anything 10 ELSE 11 return_val := '1=1'; -- return everything, we can make this 12 -- a lot more fisicated, but for this 13 -- purpose we will stick to where 1=1 14 END IF; 15 return return_val; 16 END; 17 / 18

To tighten this down a bit more lets add redaction to credit card number and social security number.

1 -- create the redaction policy for SSN and CCNBR 2 -- the expression must evaluate to true for the column to be 3 -- redacted and you can only use sys_context in the expression. 4 declare 5 begin 6 dbms_redact.add_policy ( 7 object_schema => 'RLOCKARD', 8 object_name => 'CUSTOMERS', 9 policy_name => 'SENSITIVE_CUST_DATA', 10 expression => 'sys_context(''customers_ctx'', ''SSN'') = ''N''', 11 column_name => 'SSN', 12 function_type => dbms_redact.full 13 ); 14 end; 15 / 16 17 -- alter the policy to add credit card number 18 BEGIN 19 DBMS_REDACT.ALTER_POLICY ( 20 object_schema => 'RLOCKARD', 21 object_name => 'CUSTOMERS', 22 policy_name => 'SENSITIVE_CUST_DATA', 23 column_name => 'CCNBR', 24 action => DBMS_REDACT.ADD_COLUMN, 25 function_type => DBMS_REDACT.PARTIAL, 26 function_parameters => DBMS_REDACT.REDACT_CCN16_F12, 27 expression => 'sys_context(''customers_ctx'', ''CCNBR'') = ''N''' 28 ); 29 END; 30 / 31

 

Trusted paths don’t only include using Virtual Private Databases and Redaction. There are a number of ways to narrow down the paths to get to sensitive data on the developers side of the house. I will cover those in my next blog post.

So in summary, your four steps are Encrypt, Audit, Find your sensitive data and Limit the access paths to your sensitive data by creating trusted paths.