Skip to Main Content

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Al.

Asked: March 06, 2016 - 12:12 am UTC

Last updated: March 11, 2016 - 1:17 am UTC

Version: 11g

Viewed 1000+ times

You Asked

Hi Tom,

I'm creating a trigger for our environment and this is what is should do:

- Catch failed user login attempt and send an email to Database team to investigate if
failed_login attempts_count are >=10 and the attempts are made within <=10 minutes.

I have made this trigger and facing following difficulties.
a. Initially it worked if the failed_logon_attempts_count>=10 but it wasn't taking into consideration the 10 minutes period. This means even if 10 attempts are made spanning more than 10 minutes instead of resetting it would still send an email.
b. Today I may have done something that caused it to stop working for 10 attempts as well, this means no emails are sent even if the 10 attempts are made within 10 minutes.

I found that select into variable is not working. Can you please suggest if my logic is fine and if it is then why is it that it initially worked and then broke. If not can you please provide some guidance.

Here's my trigger code:

CREATE OR REPLACE TRIGGER "SYS"."FAILED_LOGON_NOTIFICATIONS"
AFTER SERVERERROR ON DATABASE

DECLARE

failed_username varchar2(30);
failed_login_attempts_count number;


CURSOR cur_dat (in_failed_username varchar2)
IS
SELECT to_char(dat.timestamp,'YYYY-MM-DD HH24:MI:SS') timestamp,
dat.username,
dat.RETURNCODE,
dat.SESSIONID,
dat.COMMENT_TEXT,
dat.USERHOST,
dat.TERMINAL,
dat.SES_ACTIONS
FROM( SELECT TIMESTAMP, username, RETURNCODE, SESSIONID, COMMENT_TEXT, USERHOST, TERMINAL, SES_ACTIONS
FROM sys.dba_audit_trail
) dat
WHERE TIMESTAMP BETWEEN SYSDATE -10/24/60 AND SYSDATE
AND RETURNCODE IN (1017,1004,1005,28000)
AND username = failed_username
ORDER BY TIMESTAMP DESC;

BEGIN

IF (IS_SERVERERROR(1017) or IS_SERVERERROR(1004) or IS_SERVERERROR(1005) or IS_SERVERERROR(28000))
THEN

SELECT USERNAME
INTO FAILED_USERNAME
FROM SYS.DBA_AUDIT_TRAIL
WHERE SESSIONID = SYS_CONTEXT('USERENV','SESSIONID');


SELECT COUNT(*) INTO failed_login_attempts_count
FROM (SELECT LCOUNT, TIMESTAMP
FROM USER$
INNER JOIN DBA_AUDIT_TRAIL
ON DBA_AUDIT_TRAIL.USERNAME = USER$.NAME
WHERE LCOUNT >=10
AND TIMESTAMP BETWEEN SYSDATE -10/24/60
AND SYSDATE AND RETURNCODE IN (1017,1004,1005,28000)
AND NAME = failed_username);


IF (failed_login_attempts_count >=10) -- When I change this to less <=10 it works, but that's not what I want. And in the email received the failed_login_attempts_count variable suggest that there are 0 attempts. This means the variable is not working.

THEN

SEND EMAIL TO DBA TEAM

END IF;
END IF;
END failed_logon_notifications;





and Connor said...

The timestamp is special in this case. Here's a variant on your trigger to just log the connection error.

SQL> CREATE OR REPLACE TRIGGER "SYS"."FAILED_LOGON_NOTIFICATIONS"
  2  AFTER SERVERERROR ON DATABASE
  3  DECLARE
  4    failed_username varchar2(30);
  5    procedure logger(m varchar2) is
  6      pragma autonomous_transaction;
  7    begin
  8      insert into sys.log_tab values (m); commit;
  9    end;
 10
 11  BEGIN
 12    IF (IS_SERVERERROR(1017) or IS_SERVERERROR(1004) or IS_SERVERERROR(1005) or IS_SERVERERROR(28000))
 13    THEN
 14      logger('in trigger');
 15
 16      SELECT USERNAME
 17      INTO FAILED_USERNAME
 18      FROM SYS.DBA_AUDIT_TRAIL
 19      WHERE SESSIONID = SYS_CONTEXT('USERENV','SESSIONID');
 20      logger('got FAILED_USERNAME='||FAILED_USERNAME);
 21
 22      for i in (
 23        select * from (
 24          SELECT "TIMESTAMP" ts
 25          FROM DBA_AUDIT_TRAIL
 26          WHERE RETURNCODE IN (1017,1004,1005,28000)
 27          AND   USERNAME = failed_username
 28          order by TIMESTAMP desc
 29        )
 30        where rownum < 10
 31      )
 32      loop
 33        logger('timestamp='||to_char(i.ts,'ddmmyyyy hh24miss'));
 34      end loop;
 35
 36    END IF;
 37  END;
 38  /

Trigger created.


Now I do failed login

SQL> conn demo/qww
ERROR:
ORA-01017: invalid username/password; logon denied
<code>

and now check the log

<code>
SQL> select to_char(sysdate,'ddmmyyyy hh24miss') from dual;

TO_CHAR(SYSDATE
---------------
06032016 134247

SQL> select * from sys.log_tab;

MSG
-------------------------------------
in trigger
got FAILED_USERNAME=DEMO
timestamp=06032016 054106

SQL> select * from (
  2    SELECT to_char("TIMESTAMP",'ddmmyyyy hh24miss') ts
  3    FROM DBA_AUDIT_TRAIL
  4    WHERE RETURNCODE IN (1017,1004,1005,28000)
  5    AND   USERNAME = 'DEMO'
  6    order by TIMESTAMP desc
  7  )
  8  where rownum < 10;

TS
---------------
06032016 134106


Notice the querying DBA_AUDIT_SESSION from a standard session gives me the time in my time zone, but from within the trigger, its GMT. You'll need to take that into account.

Hope this helps.

Rating

  (4 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

timestamp

Shahid, March 09, 2016 - 2:20 am UTC

Hi Connor,

Thanks you for you answer as it pinpointed me to the right direction. Not sure if another question arising from this can be asked or I have to open a new question?
Connor McDonald
March 09, 2016 - 5:45 am UTC

If its related, you can add it here


EXTENDED_TIMESTAMP

Al Khan, March 09, 2016 - 5:21 pm UTC

So after solving the time problem in my trigger the next issue is the daylight time. I have a query that is giving me the intended result as is for now, however when the daylight savings time start we are goign ahead one hour and this trigger will not work again because the time will be off by one hour again from systimetamp. I was under the impression from reading many other online posts that the EXTENDED_TIMESTAMP cloumn was made to handle the timezone/daylight issue? Maybe I understood it wrong. Is there a way that would allow me to handle daylight savings time automatically? I have tried many other online post and method exhaustively to no luck.


Query:

SELECT USERNAME, SYSTIMESTAMP, TO_CHAR(NEW_TIME (EXTENDED_TIMESTAMP -7/24,'MST', 'GMT'), 'DD-MON-YY HH24:MI:SS')
FROM DBA_AUDIT_TRAIL
WHERE EXTENDED_TIMESTAMP BETWEEN SYSTIMESTAMP -10/24/60 AND SYSTIMESTAMP
AND RETURNCODE IN (1017,1004,1005,28000)
AND USERNAME = failed_username
ORDER BY EXTENDED_TIMESTAMP DESC;


Result:

USERNAME SYSTIMESTAMP TO_CHAR(NEW_TIME(EXTENDED_T
--------- -------------------------------- ---------------------------
T1 09-MAR-16 10.04.17.873962 AM -07:00 09-MAR-16 10:03:58
T1 09-MAR-16 10.04.17.873962 AM -07:00 09-MAR-16 10:03:46
T1 09-MAR-16 10.04.17.873962 AM -07:00 09-MAR-16 10:03:36
T1 09-MAR-16 10.04.17.873962 AM -07:00 09-MAR-16 10:03:29
T1 09-MAR-16 10.04.17.873962 AM -07:00 09-MAR-16 10:03:23
Chris Saxon
March 10, 2016 - 2:47 am UTC

Well, you need to define what you are going to do for a start.

If the clock jumps forward 1 hour, then (by the wall clock definition) no-one has tried in the last 10mins.

Similarly, if the clock jumps back 1 hour, then are you planning on double-counting ? etc

It may well be the case that you just want to convert everything to UTC/GMT and work from there, but for those 2 hours of the entire year ... I'd be inclined not to bother.

EXTENDED_TIMESTAMP

Al Khan, March 10, 2016 - 12:28 am UTC

I thought I figured it out but not really,

When I run this query directly on the database I get the proper time returned by the EXTENDED_TIMESTAMP which is same as systimetamp of failed login attempt

SELECT USERNAME, to_char(SYSTIMESTAMP, 'DD-MON-YY HH24:MI:SS') AS TS, TO_CHAR(EXTENDED_TIMESTAMP, 'DD-MON-YYYY HH24:MI:SS TZH:TZM') AS E_TS
FROM DBA_AUDIT_TRAIL
WHERE EXTENDED_TIMESTAMP BETWEEN SYSTIMESTAMP -10/24/60 AND SYSTIMESTAMP
AND RETURNCODE IN (1017,1004,1005,28000)
AND USERNAME = 'T1'
ORDER BY EXTENDED_TIMESTAMP DESC;


USERNAME TS E_TS
------------------------------ --------------------------- ---------------------------------------------------------------------------
T1 09-MAR-16 17:23:18 09-MAR-16 05.23.13.771640 PM CANADA/MOUNTAIN
T1 09-MAR-16 17:23:18 09-MAR-16 05.23.10.202357 PM CANADA/MOUNTAIN
T1 09-MAR-16 17:23:18 09-MAR-16 05.22.59.113721 PM CANADA/MOUNTAIN


But via trigger the times does not match



USERNAME SYSTIMESTAMP EXTENDED_TIMESTAMP
------------------- ---------------------------------------------------------------------------
T1 09-MAR-16 05.23.10.261926 PM 05-MAR-16 03.55.41.890517 PM
T1 09-MAR-16 05.23.10.261926 PM 05-MAR-16 03.55.43.007400 PM
T1 09-MAR-16 05.23.10.261926 PM 05-MAR-16 03.55.44.661308 PM
T1 09-MAR-16 05.23.10.261926 PM 05-MAR-16 03.55.46.755995 PM
T1 09-MAR-16 05.23.10.261926 PM 05-MAR-16 03.55.51.296377 PM
T1 09-MAR-16 05.23.10.261926 PM 05-MAR-16 03.55.55.886834 PM

Again I thought the EXTENDED_TIMESTAMP column does not have to be provided hard coded offset value in order to take care of daylight savings.


EXTENDED_TIMESTAMP

Shahid Ali Khan, March 11, 2016 - 12:20 am UTC

Hi Connor,

I found a better way of dealing with daylight savings time. Since we know that timestamp/extended_timestamp column queried through the trigger will return GMT/UTC time then why not for the purpose of solving my problem bring the systimestamp column back to GMT/UTC and now both columns will return the same time. The SYS_EXTRACT_UTS(TimestampColumn) function will take this column to the GMT/UTC time.

Here's the query in my trigger that does this.

SELECT COUNT(*) INTO failed_login_attempts_count
FROM(SELECT USERNAME, SYS_EXTRACT_UTC(systimestamp), EXTENDED_TIMESTAMP
from DBA_AUDIT_TRAIL
where EXTENDED_TIMESTAMP between SYS_EXTRACT_UTC(systimestamp) -10/24/60 and SYS_EXTRACT_UTC(systimestamp)
and RETURNCODE in (1017,1004,1005,28000)
and USERNAME = failed_username);

Problem solved, thanks for your help.
Connor McDonald
March 11, 2016 - 1:17 am UTC

Glad we could help.

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library